pythonUIA实例
⾸先,⼤家可以看下这个链接。
这篇⽂章介绍了Windows中GUI⾃动化的三种技术:Windows API, MSAA - Microsoft Active Accessibility, UIAutomation
⽤脚本语⾔AutoIT实现⾃动化就是第⼀种技术Windows API, 查窗⼝句柄实现的。
⽤⼯具Spy++查看程序,如果Spy++能识别程序窗⼝中的控件就能⽤这种技术。
python中也有⼀个UI⾃动化测试模块pywinauto,也是⽤这种技术实现的。
但Windows API实现的⾃动化不⽀持WPF程序、Windows 8中的Metro程序。
⽤UIAutomation实现的⾃动化⽀持微软提供的各种界⾯开发框架,如MFC, Windows Forms, WPF, Metro App。也部分⽀持Qt。
该技术的详细介绍可以参考CodeMagazine上的⼀篇⽂章
官⽅⽂档 msdn:
我就是根据这个⽤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。
运⾏源码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秒,延迟指定秒数再遍历控件,
-r, 从树的根部(root: Desktop)遍历,如果不指定,从当前窗⼝遍历
-d, int Value, 遍历控件树的的深度depth,如果不指定,遍历整个树,和-c⼀起使⽤时,可以为负值
-f, 遍历焦点focus控件,如果不指定,从当前窗⼝遍历
-c, 遍历光标下的控件,如果不指定,从当前窗⼝遍历,如果同时指定-d, -d可以为负值,⽐如-d-2表⽰从光标下控件向上到两层⽗控件,遍历这个⽗控件-a, 获取光标下控件及其所有祖先(ancestor)控件
-n, 显⽰控件的完整name, 如果不指定,只显⽰前30个字符
-m, 显⽰控件更多more属性,默认只显⽰控件的⼏个属性
例⼦:
automation.py –t3, 3秒后遍历当前窗⼝所有控件
automation.py –d2 –t3, 3秒后遍历当前窗⼝前三层控件
automation.py –r –d1 –t0 -n, 0秒后遍历root的第⼀层⼦控件,并显⽰控件完整名称
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模块同时会把显⽰的信息写⼊到⽂件@,⽅便查看。
还有,在Windows 7及更⾼版本系统中,最好以管理员来运⾏cmd,再调⽤python,我发现有些程序⽤普通权限获取不到全部控件信息。
登录QQ2013,再⼀次枚举它的窗⼝,如图
另⼀个操作QQ导出所有成员详细信息的例⼦:
下⾯介绍下⽤uiautomaton模块⾃动化操作系统记事本的⼀个例⼦,
先⽤automaiton.py遍历记事本窗⼝,知道它的控件树结构,再开始写代码
python2.7代码如下(在代码demos⽬录automation_notepad_py2.py, automation_notepad_py3.py):1
2
3
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58#!python2
# -*- coding: utf-8 -*-
import os, sys, time
import subprocess
import uiautomation as automationpython怎么读取桌面上的文件
def testNotepadCN():
consoleWindow =automation.GetConsoleWindow()
consoleWindow.SetActive()
automation.Logger.ColorfulWriteLine('\nI will open <Color=Green>Notepad</Color> and <Color=Yellow>automate</Color> it. Please wait for a while.')    time.sleep(2)
automation.ShowDesktop()
#打开notepad
subprocess.Popen('notepad')
#查notepad,如果name有中⽂,python2中要使⽤Unicode
window =automation.WindowControl(searchDepth =1, ClassName ='Notepad', RegexName =u'.* - 记事本')
#可以判断window是否存在,如果不判断,不到window的话会抛出异常
#if window.Exists(maxSearchSeconds = 3):
if automation.WaitForExist(window, 3):
automation.Logger.WriteLine("Notepad exists now")
else:
automation.Logger.WriteLine("Notepad does not exist after 3 seconds", automation.ConsoleColor.Yellow)
screenWidth, screenHeight =automation.Win32API.GetScreenSize()
window.MoveWindow(screenWidth //4, screenHeight //4, screenWidth //2, screenHeight //2)
window.SetActive()
#从window查edit
edit =window.EditControl()
edit.Click(waitTime =0)
#python2中要使⽤Unicode, 模拟按键
edit.SetValue(u'hi你好')
edit.SendKeys(u'{Ctrl}{End}{Enter}下⾯开始演⽰{! 4}{ENTER}', 0.2, 0)
edit.SendKeys('{Enter 3}0123456789{Enter}', waitTime =0)
edit.SendKeys('ABCDEFGHIJKLMNOPQRSTUVWXYZ{ENTER}', waitTime =0)
edit.SendKeys('abcdefghijklmnopqrstuvwxyz{ENTER}', waitTime =0)
edit.SendKeys('`~!@#$%^&*()-_=+{ENTER}', waitTime =0)
edit.SendKeys('[]{{}{}}\\|;:\'\",<.>/?{ENTER}', waitTime =0)
edit.SendKeys(u'™®①②③④⑤⑥⑦⑧⑨⑩§№☆★○●◎◇◆□℃‰€■△▲※→←↑↓〓¤°#&@\^_―♂♀{ENTER}{CTRL}a')
window.CaptureToImage('Notepad.png')
edit.SendKeys('Image Notepad.png was captured, you will see it later.', 0.05)
#查菜单
window.MenuItemControl(Name =u'格式(O)').Click()
window.MenuItemControl(Name =u'字体(F)...').Click()
windowFont =window.WindowControl(Name =u'字体')
windowFont.ComboBoxControl(AutomationId ='1140').Select(u'中⽂ GB2312')
windowFont.ButtonControl(Name =u'确定').Click()
window.Close()
if automation.WaitForDisappear(window, 3):
automation.Logger.WriteLine("Notepad closed")
else:
automation.Logger.WriteLine("Notepad still exists after 3 seconds", automation.ConsoleColor.Yellow)
# buttonNotSave = ButtonControl(searchFromControl = window, SubName = u'不保存')
# buttonNotSave.Click()
# or send alt+n to not save and quit
# automation.SendKeys('{Alt}n')
# 使⽤另⼀种查⽅法
buttonNotSave =automation.FindControl(window,
lambda control, depth: control.ControlType ==automation.ControlType.ButtonControl and u'不保存'in control.Name)
buttonNotSave.Click()
subprocess.Popen('Notepad.png', shell =True)
time.sleep(2)
59 60 61 62 63 64    consoleWindow.SetActive()
automation.Logger.WriteLine('script exits', automation.ConsoleColor.Cyan)    time.sleep(2)
⾸先⽤subprocess.Popen('notepad') 调⽤notepad
先写查notepad窗⼝的代码了
#查notepad,如果name有中⽂,要使⽤Unicode
window = WindowControl(searchDepth = 1, ClassName = 'Notepad', SubName = u'记事本')
searchDepth = 1,表⽰只查树的的第⼀层⼦控件,不会遍历整个树查,所以能很快到notepad的窗⼝。
查控件可以指定如下参数 ClassName, WindowControl, AutomationId, Name , SubName,foundIndex,前四个都是cmd⾥显⽰的内容,SubName可以指定如果Name中含有SubName这个字符串,也算查成功。
foundIndex表⽰第⼏个符合查条件的控件,不指定的话默认是1。
然后再查EditControl
1 2#从window查edit
edit =window.EditControl()
 修改EditControl内容并在当前⽂字后⾯模拟打字 
1 2 3#python2中要使⽤Unicode, 模拟按键
edit.SetValue(u'hi你好')
edit.SendKeys(u'{Ctrl}{End}{Enter}下⾯开始演⽰{! 4}{ENTER}', 0.2, 0)
另⼀个例⼦,操作QQ登录界⾯的⼀段代码,这⾥没有写调⽤的代码,先⼿动启动qq2013,保持在登录界⾯,
# -*- 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()
测试Firefox:
Windows版Firefox(Version<=56)是⽤DirectUI实现的,只能看到⼀个窗⼝句柄,但是⽤UIAutomation就能看到⽹页⾥所有元素控件。但最新版Firefox57版本采⽤了新的Rust内核,不⽀持UIAutomation了。现在是2018年,我⽤最新版的Firefox60测试发现该版本⽀持UIAutomation了。
UIAutomation的⼯作原理是:
当你⽤UIAutomation操作程序时,UIAutomation会给程序发送消息,
如果程序处理WM_GETOBJECT消息,实现,并调⽤函数
此程序就⽀持UIAutomation。
IRawElementProviderSimple就是UI Automation Provider,包含了控件的各种信息,如Name,ClassName,ContorlType,坐标...
UIAutomation根据程序返回的IRawElementProviderSimple,就能遍历程序的控件,得到控件各种属性,进⾏⾃动化操作。
所以如果你发现UIAutomation不能识别⼀些程序内的控件或部分不⽀持,这并不是UIAutomation的问题,
是程序作者没有处理WM_GETOBJECT或没有实现UIAutomation Provider,或者故意不想⽀持UIAutomation。
很多DirectUI程序都没有实现UIAutomation Provider,所以不⽀持⾃动化,要想⽀持⾃动化,必须程序作者修改源码⽀持。
源代码下载,最新代码⽀持py2, py3, x86, x64

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。