Windows GUI程序自动化之pywinauto

一. pywinauto知识点总结

官方英文版文档网址:https://pywinauto.readthedocs.io/en/latest/index.htmlhtml

1.1 pywinauto的安装与配置

<1>相关库文件的下载地址java

 pywinauto最新版本下载地址: https://github.com/pywinauto/pywinauto/releasespython

 pywin32下载地址:https://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/c++

 comtypes下载地址:https://github.com/enthought/comtypes/releasesgit

 six下载地址:https://pypi.org/project/six/ github

 Pillow下载地址:https://pypi.org/project/Pillow/2.7.0/shell

<2>安装相关库windows

法1.直接经过命令行安装api

 (安装pywin32,直接安装比在官网下载速度慢,可是若是你们python3和python2同时安装了,那么直接下载再安装,会报错:找不到符合要求的python版本)微信

 安装pywin32,E:\soft\python3.6\Scripts>pip3 install pywin32

法2.经过setup.py文件安装(安装包放中文路径下,可能安装出错)

 如安装comtypes, (若是是unpack的下载包,)安装方法,进入下载的安装包路径,用命令行python3 setup.py install安装

1.2 pywinauto的基本操做

<1>一个小练习快速上手 pywinauto基本操做

#_*_coding=utf-8_*_
import pywinauto
from pywinauto.mouse import *
from pywinauto.keyboard import *
import time
#1.运行记事本程序
app = pywinauto.Application().start('notepad.exe')
#2.窗体选择
title_notepad = u'无标题-记事本'
#3.选择一个菜单项
app[title_notepad].menu_select('帮助->关于记事本')
time.sleep(3)
#4.点击新弹出窗体的肯定按钮
out_note=u'关于记事本'
button_name_ok='肯定'
app[out_note][button_name_ok].click()
#5.查看一个窗体含有的控件,子窗体,菜单
print(app[title_notepad].print_control_identifiers())
#-------------------无标题记事本的含有的控件,子窗体,菜单-----------------
# Control Identifiers:
#
# Notepad - '无标题 - 记事本'    (L8, T439, R892, B815)
# ['无标题 - 记事本Notepad', 'Notepad', '无标题 - 记事本']
# child_window(title="无标题 - 记事本", class_name="Notepad")
#    |
#    | Edit - ''    (L16, T490, R884, B807)
#    | ['无标题 - 记事本Edit', 'Edit']
#    | child_window(class_name="Edit")
#    |
#    | StatusBar - ''    (L16, T785, R884, B807)
#    | ['StatusBar', '无标题 - 记事本StatusBar', 'StatusBar   第 1 行,第 1 列']
#    | child_window(class_name="msctls_statusbar32")
# None

#6.在记事本中输入一些文本
#[tips-> ctrl+点击鼠标左键快速查看被调用函数]
app.title_notepad.Edit.type_keys('pywinauto works!\n',with_spaces=True,with_newlines=True)
app.title_notepad.Edit.type_keys('hello word !\n',with_spaces=True,with_newlines=True)
#7.选择编辑菜单->编辑时间/日期
# app[title_notepad].menu_select('编辑->时间/日期(&d)')
#8.链接已运行程序
#如链接微信 借助spy++找到运行程序的handle
app1=pywinauto.Application(backend='uia').connect(handle=0x00320830)
#9.查看运行窗口窗体名称
print(app1.window())
print(app1['Dialog'].print_control_identifiers())
# Dialog - '微信'    (L968, T269, R1678, B903)
# ['微信Dialog', 'Dialog', '微信']
# child_window(title="微信", control_type="Window")
#    |
#    | Pane - 'ChatContactMenu'    (L-10000, T-10000, R-9999, B-9999)
#    | ['ChatContactMenu', 'ChatContactMenuPane', 'Pane', 'Pane0', 'Pane1']
#    | child_window(title="ChatContactMenu", control_type="Pane")
#    |    |
#    |    | Pane - ''    (L-10019, T-10019, R-9980, B-9980)
#    |    | ['', 'Pane2', '0', '1']
#    |
#    | Pane - ''    (L948, T249, R1698, B923)
#    | ['2', 'Pane3']
# None
#10.经过路径去打开一个已有程序
#11.鼠标控制
x=0
y=0
for i in range(20):
    step_x = i*8
    step_y = i*5
    move(coords=(step_x,step_y ))
    time.sleep(1)

#12.键盘控制
#键盘对应的ascii http://www.baike.com/wiki/ASCII
#发送键盘指令,打开命令行,输入一条命令for /l %i in (1,1,100) do tree
SendKeys('{VK_LWIN}')
SendKeys('cmd')
SendKeys('{VK_RETURN}')
time.sleep(3)
SendKeys('for /L +5i in +9 1,1,100+0 do tree {VK_RETURN}',with_spaces=True)
View Code

1.3 pywinauto的使用

1.3.1 pywinauto支持的windows应用  

<1>Win32 API (backend="win32")(程序默认) :支持MFC、VB六、VCL、以及一些使用winforms的老应用 
<2>MS UI Automation (backend="uia"):支持WinForms, WPF, Store apps, Qt5, browsers
 
<3>不支持Java AWT/Swing, GTK+, Tkinter.

1.3.2 判断程序的backend

法1:使用工具spy++ 
若是是GUI的程序,用spy++这个微软的小工具来看,从类名前缀能看出是什么编写的程序。
a、afx__开头的:mfc写的; 
b、t_开头的:通常是delphi,少部分是c++builder;好比主窗体通常是tMainForm;
 
c、thunder_开头的:通常是VB6写的;
 
d、windows__开发头的,通常都是.net写的;
 
e、awt__或者swing__开头的,通常都是java写的;
 
f、其余的直接以win32api gui控件开头的,通常都是c++或者VC++写的。
 
法2:使用工具inspect
点击inspect左上角的下拉列表,切换到“UI Automation”,而后鼠标点一下你须要测试的程序窗体,inspect就会显示相关信息。  inspect中显示了程序的有关信息,说明backend为uia,inspect中显示拒绝访问,说明程序的backend应该是win32

1.3.3 自动化入口

这里主要是限制自动化控制进程的范围。如一个程序有多个实例,自动化控制一个实例,而保证其余实例(进程)不受影响。主要有两种对象能够创建这种入口点Application() ,Desktop()。 Application的做用范围是一个进程,如通常的桌面应用程序都为此类。 Desktop的做用范围能够跨进程。主要用于像win10的计算器这样包含多个进程的程序。这种目前比较少见。
<1>经过对象Application()运行一个现有的程序

app = Application().start(r"c:\path\to\your\application -a -n -y --arguments")
from pywinauto.application import Application
app = Application(backend="uia").start('notepad.exe')
# describe the window inside Notepad.exe process,注意若是窗体名称是中文,不能直接app.窗体名选择窗体,以下面不能直接使用app.untitled_notepad ,这样会报错
untitled_notepad = u'无标题-记事本'
dlg_spec = app[untitled_notepad]
# wait till the window is really open
actionable_dlg = dlg_spec.wait('visible')
经过Application()对象运行记事本程序

<2>经过对象Application()链接到一个已经运行的程序

#注意用connect方法关联应用程序时,应用程序必须是处于运行状态,能够经过程序的path,process,handle参数或组合参数链接程序
from pywinauto.application import Application
app = Application()
#1.经过应用程序路径
#app.connect(path = r"C:\Windows\System32\notepad.exe")
#2.经过进程pid
#app.connect(process = 2341)
#3.经过窗口句柄
#app.connect(handle = 0x010f0c)
#4.经过组合参数
#app = Application().connect(title_re=".*Notepad", class_name="Notepad")
经过connect链接到一个已运行程序

<3>经过对象Desktop()运行一个程序

from subprocess import Popen
from pywinauto import Desktop

Popen('calc.exe', shell=True)
dlg = Desktop(backend="uia").Calculator
dlg.wait('visible')
经过Desktop()对象运行计算器

1.3.4  肯定想操做的窗体

<1> 肯定想操做的窗体

#建立一个 WindowSpecification实例
app=pywinauto.Application(backend='uia').start('notepad.exe')
#1.经过窗体名肯定窗体
window_title=['无标题-记事本']
print(app[window_title])
#<pywinauto.application.WindowSpecification object at 0x000001FD1B492400>
dlg_spec = app.window(title='无标题-记事本')
print(dlg_spec)
#2.wrapper_object()方法返回实际存在的窗口/控件,或者引起ElementNotFoundError.
app[window_title].wrapper_object()
#uiawrapper.UIAWrapper - '无标题 - 记事本', Dialog
#3. 经过多层次的描述指定一个窗口,如(a),或使用组合参数指定一个窗体如(b),下面两条语句肯定的是同一个窗体
(a)app.window(title_re='.* - 记事本$').window(class_name='edit')
(b)app.window(title_re='.* - 记事本$',class_name='edit')
#4.pywinauto.findwindows.find_elements()返回全部的已运行程序的win32_element_info.HwndElementInfo
View Code

 注意Unicode编码的字符和特殊符号使用时以相似字典的方式进行访问,好比中文字符

app['无标题-记事本']
# is the same as
app.window(best_match='无标题-记事本')
View Code

 dlg = app.top_window()该方法返回应用程序最顶层的窗口

import time
# app = application.Application().start("Notepad.exe")
# winTitle = u'无标题-记事本'
# app[winTitle].draw_outline()
# time.sleep(3)
# app[winTitle].menu_select('编辑->替换(&R)...')
# childWindow='替换'
# cancel_botton='取消'
# app[childWindow].print_control_identifiers()
# time.sleep(3)
# app[childWindow][cancel_botton].click()
# app[winTitle].Edit.type_keys("Hi from Python interactive prompt %s" % str(dir()), with_spaces = True)
# app[winTitle].menu_select('文件->退出')
# childWindow1='记事本'
# app[childWindow1].Button2.click()
#------------------------------------------------------------
app = Application().start('notepad.exe')
time.sleep(1)
app[' 无标题 - 记事本 '].menu_select("编辑(&E) -> 替换(&R)..")
time.sleep(1)
app['替换'].取消.click()
# 没有with_spaces 参数空格将不会被键入。请参阅SendKeys的这个方法的文档,由于它是SendKeys周围的薄包装。
app[' 无标题 - 记事本 '].Edit.type_keys("Hi from Python interactive prompt %s" % str(dir()), with_spaces = True)

app[' 无标题 - 记事本 '].menu_select('文件(&F) -> 退出(&X)')

# 在这时候不清楚“不保存”的按钮名就对app['记事本'] 使用print_control_identifiers()
app['记事本'].Button2.click()
一个小练习

1.3.5 在窗体中指定控件

 对于常见的窗口程序,须要操做的控件有输入框(Edit)、按钮(Button)、复选框(CheckBox)、单选框(RadioButton)、下拉列表(ComboBox)。有不少方法能够指定这些控件,最简单的是:

app.dlg.control

app['dlg']['control']

 对于非英文的环境,须要传递unicode字符,则有

app[u'your dlg title'][u'your control title']

代码根据以下内容为每一个控件创建多个标识符:

 a.标题   b.相关类    c.标题+相关类

若是控件的标题文本为空(去除非字符字符后),这些标题文件就不能被使用。于是,咱们会去寻找最接近文本标题的控件,并附加其相关类,因此此时列表为:

 a.相关类         b.最接近的文本+相关类

一旦对话框中因此控件建立了一组标识符,咱们就能区别他们。

方法 WindowsSpecification.print_control_identifiers()返回窗体内因此控件及其标识符列表,注意,此方法打印的标识符已经过使标识符惟一的进程运行。若是窗体内有两个编辑框,他们都会在其中列出。实际中,第一个编辑框被称为“Edit”, “Edit0”, “Edit1”,第二个应该被称为‘Edit2’

1.3.6 如何在非英文环境中使用pywinauto

 由于Python不支持代码中的Unicode标识符,因此此时不能使用属性访问方式引用控件,您将不得不使用类字典的方式进行访问,或者使用Windows()方法进行显式调用。

将原调用方式app.dialog_ident.control_ident.click()改成app['dialog_ident']['control_ident'].click(),或使用app.window(title_re="NonAsciiCharacters").window(title="MoreNonAsciiCharacters").click()

1.3.7 如何处理不按照预期进行响应的控件(例如自主绘制的控件)

 一些控件不按照预期的方式响应事件。例如,若是你查看任何HLP文件,而后转到索引选项卡(单击‘搜索’按钮),你将会看见一个列表框。运行 Spy++ 或者 inspector 工具查看控件,你会发现它确实是一个列表框,但它是自主绘制的列表框,这意味着开发人员已经告诉windows,他们覆盖了项目的显示方式。在这种状况下,这样的一些字符没法被检索。

 若是存在不按照预期进行响应的控件,那么,这将会带来什么问题呢?

app.HelpTopics.ListBox.texts() # 1

app.HelpTopics.ListBox.select("ItemInList") # 2

此时运行语句1,将返回空字符串列表,这意味着pywinauto没法获取列表框中的字符串。

此时运行语句2,将返回IndexError。

 下面的解决方法将对该类控件起做用

app.HelpTopics.ListBox.select(1),这将选择ListBox中的第二项,由于它不是经过字符串查找方法操做控件,所以它能工做正常。

不幸的是,这种方法不是任什么时候候都有效。开发人员可使控件不响应标准事件,如SELECT,在这种状况下,选择列表框中的项的惟一方法是使用TypeKeys的键盘仿真

此时选择列表框第三个选项的方法为:

app.Helptopics.ListBox1.type_keys("{HOME}{DOWN 2}{ENTER}")
  • {HOME} 确保第一个选项被突出显示

  • {DOWN 2} 而后将高亮点向下移动两项

  • {ENTER} 选择突出显示的项目

若是你的应用程序普遍的使用相似的控件类型,那么你能够经过ListBox派生一个新类来简化使用。

1.3.8 等待长时间操做的方法

  GUI应用程序行为一般是不稳定的,你的脚本须要等待直到出现新窗口或现有窗口被关闭/隐藏。Pywinauto能够隐式地等待对话框初始化(默认超时参数 timeout)。有几种方法可使你的代码更容易更可靠。

<1>法1  wait_cpu_usage_lower (new in pywinauto 0.5.2, renamed in 0.6.0)

 这种方法对于容许在另外一个线程中进行延迟初始化的多线程接口很是有用,因为GUI是响应性的,而且他全部的控件已经存在并可用。因此等待一个窗口的存在/状态是无用的,在这种状况下,整个过程的CPU使用量指示任务计算还没有结束。

使用实例:app.wait_cpu_usage_lower(threshold=5) # wait until CPU usage is lower than 5%。

<2>法2 WindowSpecification方法,全部控件均可以使用

  • wait
  • wait_not

 一个WindowSpecification对象不必定与现有的窗口/控件有关。这只是一个描述,即搜索窗口的几个条件。wait方法(若是没有引起任何异常)能够保证目标控件存在,甚至可见,启用或活动。

<3> 法三:timings功能模块

  对任何代码都有用的低级方法

  • wait_until
  • wait_until_passes

 装饰器pywinauto.timings.always_wait_until()pywinauto.timings.always_wait_until_passes()也能够被每一个函数调用,进行时间控制。

# call ensure_text_changed(ctrl) every 2 sec until it's passed or timeout (4 sec) is expired

@always_wait_until_passes(4, 2)
def ensure_text_changed(ctrl):
    if previous_text == ctrl.window_text():
        raise ValueError('The ctrl text remains the same while change is expected')
View Code

 二 .  pywinauto控件的经常使用函数

下面的函数适用于全部控件

capture_as_image
click
click_input
close
close_click
debug_message
double_click
double_click_input
drag_mouse
draw_outline
get_focus
get_show_state
maximize
menu_select
minimize
move_mouse
move_window
notify_menu_select
notify_parent
press_mouse
press_mouse_input
release_mouse
release_mouse_input
restore
right_click
right_click_input
send_message
send_message_timeout
set_focus
set_window_text
type_keys
Children
Class
ClientRect
ClientRects
ContextHelpID
ControlID
ExStyle
Font
Fonts
FriendlyClassName
GetProperties
HasExStyle
HasStyle
IsChild
IsDialog
IsEnabled
IsUnicode
IsVisible
Menu
MenuItem
MenuItems
Owner
Parent
PopupWindow
ProcessID
Rectangle
Style
Texts
TopLevelParent
UserData
VerifyActionable
VerifyEnabled
VerifyVisible
WindowText
View Code

 按钮,复选框,单选按钮,分组框

ButtonWrapper.Check
ButtonWrapper.GetCheckState
ButtonWrapper.SetCheckIndeterminate
ButtonWrapper.UnCheck
View Code

 组合框

ComboBoxWrapper.DroppedRect
ComboBoxWrapper.ItemCount
ComboBoxWrapper.ItemData
ComboBoxWrapper.ItemTexts
ComboBoxWrapper.Select
ComboBoxWrapper.SelectedIndex
View Code

 对话框

DialogWrapper.ClientAreaRect
DialogWrapper.RunTests
DialogWrapper.WriteToXML
View Code

编辑框

EditWrapper.GetLine
EditWrapper.LineCount
EditWrapper.LineLength
EditWrapper.Select
EditWrapper.SelectionIndices
EditWrapper.SetEditText
EditWrapper.set_window_text
EditWrapper.TextBlock
View Code

HeaderWrapper.GetColumnRectangle
HeaderWrapper.GetColumnText
HeaderWrapper.ItemCount
View Code

列表框

ListBoxWrapper.GetItemFocus
ListBoxWrapper.ItemCount
ListBoxWrapper.ItemData
ListBoxWrapper.ItemTexts
ListBoxWrapper.Select
ListBoxWrapper.SelectedIndices
ListBoxWrapper.SetItemFocus
View Code

列表视图

ListViewWrapper.ColumnCount
ListViewWrapper.Columns
ListViewWrapper.ColumnWidths
ListViewWrapper.GetColumn
ListViewWrapper.GetHeaderControl
ListViewWrapper.GetItem
ListViewWrapper.GetSelectedCount
ListViewWrapper.IsChecked
ListViewWrapper.IsFocused
ListViewWrapper.IsSelected
ListViewWrapper.ItemCount
ListViewWrapper.Items
ListViewWrapper.Select
ListViewWrapper.Deselect
ListViewWrapper.UnCheck
View Code

状态栏

StatusBarWrapper.BorderWidths
StatusBarWrapper.GetPartRect
StatusBarWrapper.GetPartText
StatusBarWrapper.PartCount
StatusBarWrapper.PartRightEdges
View Code

选项卡控件

TabControlWrapper.GetSelectedTab
TabControlWrapper.GetTabRect
TabControlWrapper.GetTabState
TabControlWrapper.GetTabText
TabControlWrapper.RowCount
TabControlWrapper.Select
TabControlWrapper.TabCount
TabControlWrapper.TabStates
View Code

工具栏

ToolbarWrapper.Button
ToolbarWrapper.ButtonCount
ToolbarWrapper.GetButton
ToolbarWrapper.GetButtonRect
ToolbarWrapper.GetToolTipsControl
ToolbarWrapper.PressButton
View Code

ToolbarButton (returned by Button())

ToolbarButton.Rectangle
ToolbarButton.Style
ToolbarButton.click_input
ToolbarButton.Click
ToolbarButton.IsCheckable
ToolbarButton.IsChecked
ToolbarButton.IsEnabled
ToolbarButton.IsPressable
ToolbarButton.IsPressed
ToolbarButton.State
View Code

 三 . pywinauto.py与automation.py的搭配使用

当UI界面的控件、控件的值或状态没法用pywinauto进行识别时,能够用开源库automation识别

运行待测程序,并准备好待测界面,运行cmd,cd到automation工具的目录,输入python3 automation.py -t3回车,而后3秒内切换到待测界面,cmd窗口中就显示了当前待测窗口中的控件信息,依据控件信息便能方便的找到相应控件,进行相应操做

 

 

 

 

 

>>>>>>待续 

相关文章
相关标签/搜索