桌面 UI 的自动化现在已经变成的冷门技术,相比于移动与 Web 自动化来说,桌面应用的控件定位的有其历史复杂性,又有很多古老非标准控制或自定义控件,这些都为桌面程序的自动化增加了难度。

桌面的开源自动化测试框架一直不多,以前比较流行的 White Framework 在近几年也几乎停止了更新。

最近由于业务需要重拾了 White 框架,发现各种难用,定位与调试难度都比较大,有些控件在 White 中死活识别不能,或出现一些 BUG 导致识别失败,只好通过 UIAutomation 重写,非常麻烦。

一个偶然的机会,在 GIt 上看见了这个项目,Python-UIAutomation-for-Windows,基于 Python 封装的 UIAutomation 实现,试用了一下感觉还是不错的,作者更新也非常及时,这里推荐给大家。作者是个中国人哦。

https://github.com/yinkaisheng/Python-UIAutomation-for-Windows

它的核心库就是 automation.py,里面封装了基于 UIAutomation 的实现,理论上可以支持所有.Net 框架所编写的程序,作者声称可以支持 MFC, Windows Form, WPF, Modern UI(Metro UI), Qt and Firefox。

语法结构非常简单,这里贴一段示例吧,操作 QQ 登录界面的一段代码:

# -*- 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()

又比如自动化完成批量重命名的工作:

有兴趣的可以去试一下。


↙↙↙阅读原文可查看相关链接,并与作者交流