原文链接

pywinauto

(仅作为个人笔记,如有雷同,请联系删除。。)

== Pywinauto
==是基于Python开发的,用于自动化测试的脚本模块,主要操作于Windows标准图形界面。它可以允许你很容易的发送鼠标、键盘动作给Windows的对话框和控件。

参考:https://www.cnblogs.com/xp1315458571/p/13892205.html

1、安装方式:

  1. 命令行安装:pip install pywinauto,简单,方便,直接。
  2. 手动安装:【解压缩后执行 python setup.py install】
  3. pyWin32:python调用windows api的库。https://sourceforge.net/projects/pywin32/files/pywin32/Build 220/
  4. comtypes: COM接口的调度。https://github.com/enthought/comtypes/releases
  5. six:用来兼容Python2和Python3的库。https://pypi.org/project/six/
  6. Pillow:可选,用来做屏幕截图的。https://pypi.org/project/Pillow/2.7.0/
  7. Pywinauto:PC端自动化工具。https://github.com/pywinauto/pywinauto/releases
  8. 检查安装成功:使用python解释器运行以下代码,启动windows自带的记事本,如果正常启动不报错,则安装成功。
    from pywinauto.application import Application
    app = Application(backend="uia").start("notepad.exe")
    pywinauto - 图1

2、backend选择:

安装完成后,要先确定哪种==可访问性技术(backend)==可以用于我们的应用程序。在windows上受支持的有两种:

  1. Win32 API (backend= "win32") ,默认的backend
  2. MS UI Automation (backend="uia")

注: 可以借助于GUI对象检查工具来确定程序到底适用于那种backend 。eg:如果使用 inspect
的uia模式,可见的控件和属性更多的话,backend可选uia,反之,backend可选win32。

  1. 常用的检查工具:
  2. 1. Inspect(定位元素工具(uia))
  3. 2. Spy++ (定位元素工具(win32))
  4. 3. UI Spy (定位元素工具)
  5. 4. Swapy(可简单生成pywinauto代码)

3、元素定位工具 inspect:

  1. 下载:https://github.com/blackrosezy/gui-inspect-tool,或 https://www.pconlife.com/download/otherfile/128053/5f2da0a792d68ff9e9bf33a60613fb57/,或
    https://pan.baidu.com/s/1LHvbcP5NKqSHC7FLSpiTFQ,提取码:p4hm

  2. 使用:将inspect左上角的下拉列表中切换到==“UI Automation”,然后鼠标点一下需要测试的程序窗体==,右侧就会显示相关信息,如图所示,说明backend为uia。

注意:程序里面的任意一个部位其实都是控件,在inspect的控件树中都可以找到,是一层一层分级别的,可以一个个点开所有控件。

pywinauto - 图2

4、启动并创建一个实例对象:connect 或 start

  1. from pywinauto.application import Application
  1. 启动start(self, cmd_line, timeout=app_start_timeout),timeout为超时参数,默认为5s,如果启动时间较长可设置 timeout。
    举例:
  1. ").start(r"E:\Office\Office14\EXCEL.exe") # 启动Excel
  2. app = Application(backend = "uia").start(r'C:\Program Files (x86)\Tencent\WeChat\WeChat.exe') # 启动微信
  1. 连接connect()用于连接已经启动的程序。连接一个已经运行的程序有以下几种方法:
    (1). 使用进程号:进程号在 任务管理器-详细信息 可以查看,项目中一般根据进程名称自动获取
    (2). 使用handle:应用程序的窗口句柄
    (3). path:进程的执行路径。GetModuleFileNameEx 模块会查找进程的每一个路径并与我们传入的路径去做比较
    (4). 参数组合:定位元素的参数,传递给pywinauto.findwindows.find_elements()这个函数
  1. app = Application().connect(handle=0x010f0c) # 句柄
  2. app = Application().connect(path="D:\Office14\EXCEL.exe") # path
  3. app = Application().connect(title_re=".*Notepad", class_name=“Notepad”) # 参数组合

注意: 应用程序必须先准备就绪
,才能使用connect(),当应用程序start()后没有超时和重连的机制。在pywinauto外再启动应用程序,需要sleep,等程序start。

5、Application对象app的常用方法:

  1. app.top_window() # 返回应用程序当前顶部窗口,是WindowSpecification对象,可以继续使用对象的方法往下继续查找控件
  2. # eg:如:app.top_window().child_window(title='地址和搜索栏', control_type='Edit')
  3. app.window(**kwargs) # 根据筛选条件,返回一个窗口, 是WindowSpecification对象,可以继续适用对象的方法往下继续查找控件
  4. # eg: 微信主界面 app.window(class_name='WeChatMainWndForPC')
  5. app.windows(**kwargs) # 根据筛选条件返回一个窗口列表,无条件默认全部,列表项为wrapped(装饰器)对象,可以使用wrapped对象的方法,注意不是WindowSpecification对象
  6. # eg:[<uiawrapper.UIAWrapper - '李渝的早报 - Google Chrome', Pane, -2064264099699444098>]
  7. app.kill(soft=False) # 强制关闭
  8. app.cpu_usage() # 返回指定秒数期间的CPU使用率百分比
  9. app.wait_cpu_usage_lower(threshold=2.5, timeout=None, usage_interval=None) # 等待进程CPU使用率百分比小于指定的阈值threshold
  10. app.is64bit() # 如果操作的进程是64-bit,返回True

6、操作控件的步骤:

  1. 实例化要操作的进程,得到Application对象;
    eg:app=Application().connect(process=8948)

  2. 选择窗口 :app.window(‘一个或多个筛选条件’), 得到的窗口是WindowSpecification对象;

  3. 基于WindowSpecification对象使用其方法再往下查找,定位到具体的控件;

  4. 使用控件的方法属性执行需要的操作。

7、控件的定位和可用方法:

  1. 层级查找控件的方法 :定位控件
  1. window(**kwargs) # 用于窗口的查找
  2. child_window(**kwargs) # 可以无视层级的找后代中某个符合条件的元素===>【最常用】
  3. parent() # 返回此元素的父元素,没有参数
  4. children(**kwargs) # 返回符合条件的子元素列表,支持索引,是BaseWrapper对象(或子类)
  5. iter_children(**kwargs) # 返回子元素的迭代器,是BaseWrapper对象(或子类)
  6. descendants(**kwargs) # 返回符合条件的所有后代元素列表,是BaseWrapper对象(或子类)
  7. iter_children(**kwargs) # 符合条件后代元素迭代器,是BaseWrapper对象(或子类)---> 存疑,是iter_descendants?
  1. kwargs筛选条件
  1. class_name=None, # 类名
  2. class_name_re=None, # 正则匹配类名
  3. title=None, # 控件的标题文字,对应inspect中Name字段
  4. title_re=None, # 正则匹配文字
  5. control_type=None, # 控件类型,inspect界面LocalizedControlType字段的英文名
  6. best_match=None, # 模糊匹配类似的title
  7. auto_id=None, # inspect界面AutomationId字段,但是很多控件没有这个属性
  8. # 不常用
  9. parent=None,
  10. process=None,# 这个基本不用,每次启动进程都会变化
  11. top_level_only=True,
  12. visible_only=True,
  13. enabled_only=False,
  14. handle=None,
  15. ctrl_index=None,
  16. found_index=None,
  17. predicate_func=None,
  18. active_only=False,
  19. control_id=None,
  20. framework_id=None,
  21. backend=None,
  1. 控件可用的方法属性
  1. dlg.close() # 关闭界面
  2. dlg.minimize() # 最小化界面
  3. dlg.maximize() # 最大化界面
  4. dlg.restore() # 将窗口恢复为正常大小,比如最小化的让他正常显示在桌面
  5. dlg.get_show_state() # 正常0,最大化1,最小化2
  6. dlg.menu_select() # 菜单栏,eg:app.window.menu_select(Edit -> Replace)
  7. dlg.exists(timeout=None, retry_interval=None) # 判断是否存在
  8. #timeout:等待时间,一般默认5s
  9. #retry_interval:timeout内重试时间
  10. dlg.wait(wait_for, timeout=None, retry_interval=None) # 等待窗口处于特定状态
  11. dlg.wait_not(wait_for_not, timeout=None, retry_interval=None) # 等待窗口不处于特定状态,即等待消失
  12. # wait_for/wait_for_not:
  13. # * 'exists' means that the window is a valid handle
  14. # * 'visible' means that the window is not hidden
  15. # * 'enabled' means that the window is not disabled
  16. # * 'ready' means that the window is visible and enabled
  17. # * 'active' means that the window is active
  18. # timeout:等待多久
  19. # retry_interval:timeout内重试时间
  20. # eg: dlg.wait('ready')
  21. # 鼠标键盘操作,只列举了常用形式,他们有很多默认参数但不常用,可以在源码中查看
  22. ctrl.click_input() # 最常用的点击方法,一切点击操作的基本方法(底层调用只是参数不同),左键单击,使用时一般都使用默认不需要带参数
  23. ctrl.right_click_input() # 鼠标右键单击
  24. ctrl.type_keys(keys, pause = None, with_spaces = False,) # 键盘输入,底层还是调用keyboard.send_keys
  25. # keys:要输入的文字内容
  26. # pause:每输入一个字符后等待时间,默认0.01就行
  27. # with_spaces:是否保留keys中的所有空格,默认去除0
  28. ctrl.double_click_input(button ="left", coords = (None, None)) # 左键双击
  29. ctrl.press_mouse_input(coords = (None, None)) # 指定坐标按下左键,不传坐标默认左上角
  30. ctrl.release_mouse_input(coords = (None, None)) # 指定坐标释放左键,不传坐标默认左上角
  31. ctrl.move_mouse_input(coords=(0, 0)) # 将鼠标移动到指定坐标,不传坐标默认左上角
  32. ctrl.drag_mouse_input(dst=(0, 0)) # 将ctrl拖动到dst,是press-move-release操作集合
  33. # 控件的常用属性
  34. ctrl.children_texts() # 所有子控件的文字列表,对应inspect中Name字段
  35. ctrl.window_text() # 控件的标题文字,对应inspect中Name字段
  36. # ctrl.element_info.name
  37. ctrl.class_name() # 控件的类名,对应inspect中ClassName字段,有些控件没有类名
  38. # ctrl.element_info.class_name
  39. ctrl.element_info.control_type # 控件类型,inspect界面LocalizedControlType字段的英文名
  40. ctrl.is_child(parent) # ctrl是否是parent的子控件
  41. ctrl.legacy_properties().get('Value') # 可以获取inspect界面LegacyIAccessible开头的一系列字段,在源码uiawraper.py中找到了这个方法,非常有用
  42. # 控件常用操作
  43. ctrl.draw_outline(colour='green') # 空间外围画框,便于查看,支持'red', 'green', 'blue'
  44. ctrl.print_control_identifiers(depth=None, filename=None) # 以树形结构打印其包含的元素,详见打印元素
  45. # depth:打印的深度,缺省时打印最大深度。
  46. # filename:将返回的标识存成文件(生成的文件与当前运行的脚本在同一个路径下)
  47. ctrl.scroll(direction, amount, count=1,) # 滚动
  48. # direction :"up", "down", "left", "right"
  49. # amount:"line" or "page"
  50. # count:int 滚动次数
  51. ctrl.capture_as_image() # 返回控件的 PIL image对象,可继续使用其方法如下:
  52. # eg: ctrl.capture_as_image().save(img_path)
  53. ret = ctrl.rectangle() # 控件上下左右坐标,(L430, T177, R1490, B941),可输出上下左右
  54. # eg: ret.top=177
  55. # ret.bottom=941
  56. # ret.left=430
  57. # ret.right=1490

8、实战:

  1. 对话框dialog选择 :以微信主界面窗口为例:
    pywinauto - 图3
  1. # 这个最好用
  2. dlg1 = app.window(class_name='WeChatMainWndForPC') # 是WindowSpecification对象
  3. # 下面几种方法速度慢,不指名道姓容易出错
  4. dlg2 = app.Dialog # eg:dlg2 = app.微信
  5. dlg3 = app['Dialog'] # eg: dlg3 = app['微信']
  1. 打印元素 :拿到控件后,可以将该控件下的所有子控件及其属性以树形结构打印出来
  1. win_main_Dialog = app.window(class_name='WeChatMainWndForPC')
  2. # 判断是否为dialog,一个微信是一个dialog,就是窗口
  3. print(win_main_Dialog.is_dialog)
  4. # 给控件画个红色框便于看出是哪个
  5. win_main_Dialog.draw_outline(color = 'red')
  6. # 打印当前窗口的所有controller(控件和属性)
  7. win_main_Dialog.print_control_identifiers(depth=None, filename=None)
  8. # depth:打印的深度,缺省时打印最大深度
  9. # filename:将返回的标识存成文件(生成的文件与当前运行的脚本在同一个路径下)
  10. # eg:dlg. print_control_identifiers(filename =’a.txt’)
  11. # 源码内部函数名链式赋值了,都能用,一样的
  12. # print_ctrl_ids = dump_tree = print_control_identifiers

注:打印出来的文档树就是inspect中的控件树完全展开的样子,都是有层级的,和微信程序中的各个元素是一一对应的:
pywinauto - 图4

  1. 常用查找方法
  1. 主窗口
  2. chat_list = win_main_Dialog.child_window(control_type='List', title='会话') # 主窗口下的某个窗口,不管层级的找
  3. first = chat_list.items()[0] # 第一个聊天项,列表支持items(),支持循环,支持索引
  4. # 详情页修改备注操作 parent()和children()都是只往上或往下查找一个层级,所有满足的放进列表
  5. details_page = win_main_Dialog.child_window(class_name='ContactProfileWnd') # 窗口下的某个窗口
  6. we_id = details_page.child_window(title="微信号:", control_type="Text").parent().children()[1].window_text() # 窗口的爸爸的第二个儿子的文字
  7. alia = details_page.child_window(title="微信号:", control_type="Text").parent().parent().children()[0].children()[0].window_text()
  8. edit_btn = details_page.child_window(title="备 注", control_type="Text").parent().children()[1]
  9. edit_btn.click_input()
  10. btn_modify_name_edit = edit_btn
  11. # 先ctrl+a选中所有然后再type_keys替换
  12. btn_modify_name_edit.type_keys('^a').type_keys('备注名字', with_spaces=True)
  13. # descendants查找所有后代中满足的,不管层级,所有满足的放进列表
  14. btns_list = win_main_Dialog.child_window(control_type='ToolBar').parent().descendants(control_type='Button')
  15. btns_list[0].click_input()
  16. dialog.child_window(title="文件名(N):", auto_id="1148", control_type="Edit")
  1. 快速定位 :可以先定位某个页面,打印出页面结构,然后基于页面快速定位
    pywinauto - 图5
  1. def we_name(self):
  2. # todo+++++++++++++++++++++++++++++++++++++
  3. try:
  4. self._popup = wechat.win_main.child_window(class_name='ContactProfileWnd')
  5. self._popup.wait('visible')
  6. self._popup.print_control_identifiers(depth=None, filename=None)
  7. print(self._popup.Edit.window_text()) # www.pu🤗
  8. print(self._popup.Edit0.window_text()) # www.pu🤗
  9. print(self._popup.Edit1.window_text()) # www.pu🤗
  10. print(self._popup.Edit2.window_text()) # qwer1315458571
  11. print(self._popup.child_window(best_match='微信号:Edit').window_text()) # qwer1315458571
  12. print(self._popup.child_window(best_match='Edit2').window_text()) # qwer1315458571
  13. return self._popup.Edit.window_text()
  14. # return self._popup.child_window(title="微信号:", control_type="Text").parent().parent().children()[0].children()[0].window_text()
  15. except:
  16. return None

9、控件自带的一些方法:

  1. 点击和输入
  1. edit_btn.double_click_input # 双击
  2. edit_btn.right_click_input # 右击
  3. ...
  4. edit_btn.type_keys('^a').type_keys('备注名字', with_spaces=True) # ctrl+a全选,再输入替换
  5. # 常用快捷键:出下面这些外,其他都没变
  6. SHIFT +
  7. CTRL ^
  8. ALT %
  9. 空格键 {SPACE}
  10. BACKSPACE {BACKSPACE}、{BS} or {BKSP}
  11. DEL or DELETE {DELETE} or {DEL}
  12. ENTER {ENTER} or ~
  13. INS or INSERT {INSERT} or {INS}
  14. + {ADD}
  15. - {SUBTRACT}
  16. * {MULTIPLY}
  17. / {DIVIDE}
  1. 对控件截图并保存
  1. rol_type='Image')
  2. if ctrl_qrcode.exists():
  3. # capture_as_image()方法,返回控件的其实是 PIL image对象,可用该方法的属性方法,比如save
  4. ctrl_qrcode.capture_as_image().save(img_path) # 截图并保存
  1. 窗口的等待 :窗口加载需要时间,但又不能一直sleep,所以就需要等待,等待窗口出现或者等待窗口关闭
  1. save_dialog.wait('ready',timeout=2)
  2. save_dialog.close()
  3. save_dialog.wait_not('visible')
  4. # 'exists':窗口是有效的句柄
  5. # 'visible':窗口未隐藏,常用
  6. # 'enabled':未禁用窗口
  7. # 'ready':窗口可见并启用,常用
  8. # 'active':窗口处于活动状态
  1. 窗口存在和关闭
  1. if self.chatwnd.exists(): # 检验存在
  2. self.chatwnd.close() # 关闭窗口
  1. 其他
  1. dlg = app.top_window()
  2. # 取某些属性值
  3. print(dlg.class_name()) #'WeChatMainWndForPC'
  4. # 滚动,常用于页面的滚动,比如好友列表、聊天列表、消息界面
  5. chat_list.scroll(direction='up', amount='page')

10、鼠标和键盘操作:

  1. 鼠标的常见操作
  1. mouse.move(coords=(x, y)) # 移动鼠标
  2. mouse.click(button='left', coords=(40, 40)) # 指定位置,鼠标左击
  3. mouse.double_click(button='left', coords=(140, 40)) # 鼠标双击
  4. mouse.press(button='left', coords=(140, 40)) # 将属性移动到(140,40)坐标处按下
  5. mouse.release(button='left', coords=(300, 40)) # 将鼠标移动到(300,40)坐标处释放
  6. mouse.right_click(coords=(400, 400)) # 右键单击指定坐标
  7. mouse.wheel_click(coords=(400, 400)) # 鼠标中键[滚轮]单击指定坐标(很少用的到)
  8. mouse.scroll(coords=(1200,300),wheel_dist=-3) # 滚动鼠标 wheel_dist指定鼠标滚轮滑动,正数往上,负数往下
  9. check_by_click() # 通过click()方法勾选checkbox
  10. uncheck_by_click() # 通过click()方法取消勾选checkbox
  11. get_check_state() # 返回checkbox的勾选状态(0没勾选,1勾选,2不定)
  12. is_checked() # 勾选返回true,未勾选返回false,不定返回None
  13. check() # 勾选checkbox
  14. uncheck() # 不勾选checkbox
  15. invoke() # 点击(uia mode)
  16. toggle () # 勾选checkbox(uia mode)
  17. # 举例:以控件中心为起点,滚动
  18. def mouse_scroll(control, distance):
  19. rect = control.rectangle()
  20. cx = int((rect.left+rect.right)/2)
  21. cy = int((rect.top + rect.bottom)/2)
  22. mouse.scroll(coords=(cx, cy), wheel_dist=distance)
  23. mouse_scroll(control=win_main_Dialog.child_window(control_type='List', title='联系人'), distance=-5)
  1. 键盘操作 :和控件自己的type_keys方法效果一样,但是更快,那个是从前到后啪啪啪的输入,这个是一下就出来了那种,在发送文件和图片的时候可以使用键盘模块,复制粘贴,比啪啪啪输入路径再发送速度快多了,并且该模块可以适配很多表情等特殊符号
  1. import io
  2. for line in io.StringIO(msg):
  3. keyboard.write(line.strip()) #
  4. keyboard.send('ctrl+enter')
  5. keyboard.write(chat_name)
  6. keyboard.send('enter')
  7. keyboard.send('ctrl+v')