MFC Edit Control唯一标识问题:UI测试中如何定位编辑控件
解决MFC Edit控件在Appium UI测试中无法唯一定位的问题
方案1:测试端通过Windows API直接获取MFC控件资源ID
每个MFC控件的资源ID在程序中是唯一的,你可以在Appium测试代码里通过P/Invoke调用Windows原生API来获取目标Edit控件的ID,步骤如下:
- 在C#测试项目中添加P/Invoke声明:
using System.Runtime.InteropServices; public static class Win32Api { [DllImport("user32.dll", SetLastError = true)] public static extern int GetDlgCtrlID(IntPtr hWnd); [DllImport("user32.dll", SetLastError = true)] public static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern int GetClassName(IntPtr hWnd, char[] lpClassName, int nMaxCount); public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); }
- 枚举父窗口下的子控件,找到目标Edit控件并获取其ID:
// 从Appium获取父窗口的原生句柄 string parentHandleStr = driver.FindElement(By.Id("ParentWindow")).GetAttribute("NativeWindowHandle"); IntPtr parentHandle = new IntPtr(long.Parse(parentHandleStr)); IntPtr targetEditHandle = IntPtr.Zero; Win32Api.EnumChildWindows(parentHandle, (hWnd, lParam) => { char[] classNameBuffer = new char[256]; Win32Api.GetClassName(hWnd, classNameBuffer, classNameBuffer.Length); string className = new string(classNameBuffer).Trim('\0'); // 筛选Edit类控件 if (className == "Edit") { int ctrlId = Win32Api.GetDlgCtrlID(hWnd); // 匹配目标控件的资源ID if (ctrlId == 1001) // 替换为你的Edit控件资源ID { targetEditHandle = hWnd; return false; // 停止枚举 } } return true; }, IntPtr.Zero); // 通过句柄定位控件并操作 var editElement = driver.FindElement(By.Name(new System.Windows.WindowInteropHelper(new System.Windows.Window { Handle = targetEditHandle }).WindowText));
方案2:子类化MFC Edit控件,自定义辅助功能属性
既然MFC自带的put_accName是存根实现,你可以子类化CEdit控件,自行实现IAccessible接口的名称属性:
- 创建自定义Edit控件类:
class CMyEdit : public CEdit { protected: DECLARE_MESSAGE_MAP() virtual HRESULT GetAccessibleObject(IAccessible** ppAcc) override; }; BEGIN_MESSAGE_MAP(CMyEdit, CEdit) END_MESSAGE_MAP() HRESULT CMyEdit::GetAccessibleObject(IAccessible** ppAcc) { if (!ppAcc) return E_POINTER; *ppAcc = nullptr; // 创建自定义IAccessible实现类实例 CComObject<CMyAccessible>* pAccObj = nullptr; CComObject<CMyAccessible>::CreateInstance(&pAccObj); if (pAccObj) { pAccObj->AddRef(); // 用控件资源ID生成唯一名称 CString uniqueName; uniqueName.Format(_T("EditCtrl_%d"), GetDlgCtrlID()); pAccObj->SetAccessibleName(uniqueName); *ppAcc = pAccObj; return S_OK; } return E_FAIL; }
- 实现自定义IAccessible类:
class CMyAccessible : public CComObjectRootEx<CComSingleThreadModel>, public IAccessible { private: CString m_strAccName; public: BEGIN_COM_MAP(CMyAccessible) COM_INTERFACE_ENTRY(IAccessible) END_COM_MAP() void SetAccessibleName(const CString& strName) { m_strAccName = strName; } STDMETHOD(get_accName)(VARIANT varChild, BSTR* pszName) { if (!pszName) return E_POINTER; *pszName = m_strAccName.AllocSysString(); return S_OK; } // 其他IAccessible方法返回默认实现 STDMETHOD(get_accValue)(VARIANT varChild, BSTR* pszValue) { return E_NOTIMPL; } STDMETHOD(get_accDescription)(VARIANT varChild, BSTR* pszDescription) { return E_NOTIMPL; } // 其余接口方法省略,按需实现 };
- 在资源编辑器中,将目标Edit控件的类修改为
CMyEdit,测试工具即可通过accName属性(格式为EditCtrl_XXX)唯一定位控件。
方案3:给Edit控件添加唯一窗口扩展属性
在MFC代码中给Edit控件设置自定义窗口属性,测试端通过API读取该属性作为标识:
- MFC端设置属性:
// 在控件初始化完成后(比如OnInitDialog中) CEdit* pTargetEdit = (CEdit*)GetDlgItem(IDC_TARGET_EDIT); if (pTargetEdit) { CString uniqueId; uniqueId.Format(_T("TestEdit_%d"), IDC_TARGET_EDIT); // 设置自定义属性 ::SetProp(pTargetEdit->GetSafeHwnd(), _T("TestUniqueId"), (HANDLE)uniqueId.GetBuffer()); uniqueId.ReleaseBuffer(); }
- 测试端读取属性:
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr GetProp(IntPtr hWnd, string lpString); // 读取目标控件的自定义属性 IntPtr propPtr = Win32Api.GetProp(targetEditHandle, "TestUniqueId"); string uniqueId = Marshal.PtrToStringUni(propPtr); // 基于uniqueId完成控件定位或验证
内容的提问来源于stack exchange,提问作者Sergey Alikin
相关产品推荐
相关产品推荐

