首先,大家可以看下这个链接 Windows GUI自动化测试技术的比较和展望 。 这篇文章介绍了Windows中GUI自动化的三种技术:Windows API, MSAA - Microsoft Active Accessibility, UIAutomation 用脚本语言AutoIT实现自动化就是第一种技术Windows API, 查找窗口句柄实现的。 用工具Spy++查看程序,如果Spy++能识别程序窗口中的控件就能用这种技术。 python中也有一个UI自动化测试模块pywinauto,也是用这种技术实现的(补充:pywinauto后来也支持UIAutomation了,本人写这个库时还不支持)。 但Windows API实现的自动化不支持WPF程序、Windows 8中的Metro程序。 用UIAutomation实现的自动化支持微软提供的各种界面开发框架,如MFC, Windows Forms, WPF, Metro App。也部分支持Qt。 该技术的详细介绍可以参考CodeMagazine上的一篇文章 Creating UI Automation Client Applications官方文档 msdn: UI Automation Client Programmer's Guide 我就是根据这个用Python和C++对UIAutomation做了一层封装,方便我自己的使用,可以快速开发自动化脚本。 UIAutomation支持平台包括Windows XP(SP3),Windows Vista, Windows 7, Windows 8、8.1、10。
安装使用uiautomation,支持Python2,Python3,x86,x64。 运行pip install uiautomation,安装后在c:\python36\scripts目录里会有automation.py,在cmd里运行automation.py -h。 或者在github上下载源码https://github.com/yinkaisheng/Python-UIAutomation-for-Windows,在cmd里运行源码根目录里的automation.py -h。 运行源码demos目录里的操作计算器的脚本 demos\automation_calculator.py看下运行效果。
下面通过QQ2013做下实验(spy++获取不到QQ窗口内的控件,可以对比一下): 然后运行最新版QQ2013, 先保持在qq登录界面 运行cmd,cd到工具的目录,输入automation.py -t3回车,然后3秒内切换到qq的登录界面 cmd窗口中就显示了qq窗口中的控件信息 运行automation.py遍历控件时,支持下列参数 -t int value, 延迟时间time秒,延迟指定秒数再遍历控件, -a, 获取光标下控件及其所有祖先(ancestor)控件 -n, 显示控件的完整name, 如果不指定,只显示前30个字符 automation.py –c –t3, 3秒后遍历鼠标光标下面的控件信息 automation.py –c –t3 -d-2, 3秒后遍历鼠标光标下面的控件向上两层的父控件 下面是在Windows 8中运行automation.py –r –d1 –t0的例子, 如下图
在UIAutomation中控件树的根部是 桌面Desktop, 上面的命令输入了了 -r(root)参数,就从根部枚举窗口,参数-d1,只枚举桌面的第一层子控件。 在Windows 8中,如果要查看Metro App的控件信息,必须当前窗口要在Metro界面才能枚举,如果Metro App被切换到后台,则获取不到它的控件。 先运行automation.py -t5, 在5秒内切换到Metro App, 等待几秒钟,查看cmd,就能看到Metro App的控件信息。 automation模块同时会把显示的信息写入到文件@AutomaitonLog.txt,方便查看。 还有,在Windows 7及更高版本系统中,最好以管理员来运行cmd,再调用python,我发现有些程序用普通权限获取不到全部控件信息。
登录QQ2013,再一次枚举它的窗口,如图
另一个操作QQ群导出所有群成员详细信息的例子 : 使用python UIAutomation从QQ2017(v8.9)群界面获取所有群成员详细资料
下面介绍下用uiautomaton模块自动化操作系统记事本notepad.exe的一个例子, 先用automaiton.py遍历记事本窗口,知道它的控件树结构,再开始写代码 python2.7代码如下(在代码demos目录automation_notepad_py2.py, automation_notepad_py3.py):
首先用subprocess.Popen('notepad') 调用notepad 先写查找notepad窗口的代码了 #查找notepad, 如果name有中文,要使用Unicode window = WindowControl(searchDepth = 1, ClassName = 'Notepad', SubName = u'记事本') searchDepth = 1, 表示只查找树的的第一层子控件,不会遍历整个树查找,所以能很快找到notepad的窗口。
修改EditControl内容并在当前文字后面模拟打字
# -*- coding:utf-8 -*- from automation import * time.sleep(2) #查找qq窗口,searchDepth = 1,设置查找深度为1,查找Desktop的第一层子窗口就能很快找到QQ qqWindow = WindowControl(searchDepth = 1, ClassName = 'TXGuiFoundation', Name = 'QQ2013') if not qqWindow.Exists(): shellTray = Control(searchDepth = 1, ClassName = 'Shell_TrayWnd') qqTray = ButtonControl(searchFromControl = shellTray, Name = 'QQ') if qqTray.Exists(): qqTray.Click() time.sleep(1) if not qqWindow.Exists(): Logger.WriteLine(u'Can not find QQ window, please put it in front first!' % edit.CurrentValue(), ConsoleColor.Red) #查找QQ账号Edit,设置searchFromControl = qqWindow,从qqWindow开始查找子控件 #foundIndex = 1,表示查找第一个符合要求的控件,子窗口中的第一个Edit edit = EditControl(searchFromControl = qqWindow, foundIndex = 1) edit.Click() Win32API.SendKeys('{Ctrl}A') Logger.Write('Current QQ is ') #获取edit内容 Logger.WriteLine(u'%s' % edit.CurrentValue(), ConsoleColor.DarkGreen) time.sleep(1) #查找第二个Edit,即密码Edit edit = EditControl(searchFromControl = qqWindow, foundIndex = 2) edit.Click() Logger.Write('Current QQ password is ') #获取password内容 Logger.WriteLine('%s' % edit.CurrentValue(), ConsoleColor.DarkGreen) Logger.WriteLine('Only get stars. password can not be got', ConsoleColor.DarkGreen) time.sleep(1) Logger.WriteLine('Now let\'s show the buttons of QQ') time.sleep(2) #遍历QQ窗口内的所有Button buttonIndex = 1 button = ButtonControl(searchFromControl = qqWindow, foundIndex = buttonIndex) while button.Exists(): l, t, r, b = button.BoundingRectangle Logger.WriteLine('button %d, position (%d,%d,%d,%d)' % (buttonIndex, l, t, r, b)) button.MoveCursorToMyCenter() time.sleep(1) buttonIndex += 1 button = ButtonControl(searchFromControl = qqWindow, foundIndex = buttonIndex) Logger.WriteLine('\r\nLook, the last button\'s position are all 0, it may be invisible.', ConsoleColor.Yellow) button = ButtonControl(searchFromControl = qqWindow, foundIndex = 4) button.Click() menu = Control(searchDepth = 1, ControlType = ControlType.MenuControl, Name = u'TXMenuWindow') if (menu.Exists()): menuItem = MenuItemControl(searchFromControl = menu, Name = u'隐身') menuItem.Click() time.sleep(1) button = ButtonControl(searchFromControl = qqWindow, foundIndex = 8) button.Click() time.sleep(1) button = ButtonControl(searchFromControl = qqWindow, Name = u'取消') button.Click() Windows 8 中自动化操作系统Metro日历的一段代码 from automation import * def main(): RunMetroApp(u'日历') canlendarWindow = WindowControl(ClassName = MetroWindowClassName, Name = u'日历') t = time.localtime() day = t.tm_mday text1 = TextControl(searchFromControl = canlendarWindow, foundIndex = 1, Name = str(day)) text2 = TextControl(searchFromControl = canlendarWindow, foundIndex = 2, Name = str(day)) if text2.Exists(1) and text2.BoundingRectangle[0] > 0: text2.Click() else: text1.Click() location = EditControl(searchFromControl = canlendarWindow, AutomationId = 'LocationTextbox') location.Click() Win32API.SendKeys(u'南京') title = EditControl(searchFromControl = canlendarWindow, AutomationId = 'EventTitleTextbox') title.Click() Win32API.SendKeys('Hi') content = EditControl(searchFromControl = canlendarWindow, AutomationId = 'NotesTextboxContent') content.Click() Win32API.SendKeys('Nice to meet you!', 0.2) canlendarWindow.MetroClose() ShowDesktop() if __name__ == '__main__': main()
IRawElementProviderSimple就是UI Automation Provider,包含了控件的各种信息,如Name,ClassName,ContorlType,坐标... 所以如果你发现UIAutomation不能识别一些程序内的控件或部分不支持,这并不是UIAutomation的问题,
源代码下载,最新代码支持py2, py3, x86, x64 https://github.com/yinkaisheng/Python-Automation-for-Windows
。 |
|