自动化工具之三:pywinauto

Python自动化工具pywinauto

一、pywinauto的安装

(1)安装命令

pip install -U pywinauto/pip3 install -U pywinauto

(2)验证是否安装成功

from pywinauto.application import Application

二、pywinauto的使用

  1. 判断程序的backend

1.1介绍程序的backend

首先要判断程序是用什么语言写的?在实例化会有区别,主要是判断程序的backend?

程序的backend大致有两种:

(1)Win32 API(backend=“win32”)

(2)MS UI Automation(backend=“uia”)

1.2如何判断程序的backend?

推荐使用spy++和inspect来检查(spy++和inspect工具的下载地址:https://github.com/blackrosezy/gui-inspect-tool);

1.3如何使用inspect来判断backend的类别

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

自动化工具之三:pywinauto

如果inspect中显示拒绝访问,说明该程序的backend应该是win32;

  1. 确定自动化入口点

这里主要是限制自动化控制进程的范围。如一个程序有多个实例,自动化控制一个实例,而保证其他实例(进程)不受影响。

主要有两种对象可以建立这种入口点——

-->Application()

-->Desktop()

Application的作用范围是一个进程,如一般的桌面应用程序都为此类。

Desktop的作用范围可以跨进程。主要用于像win10的计算器这样包含多个进程的程序。这种目前比较少见。使用方法见:https://pywinauto.readthedocs.io/en/latest/getting_started.html#entry-points-for-automation

  1. 连接到进程

建立好入口后,需要连接进程;

3.1连接进程的方法

(1)使用Application对象的start()方法

start(self, cmd_line, timeout=app_start_timeout) # instance method:

cmd_line参数就是你使用命令行启动程序的命令语句(程序路径)

例如:

app = Application().start(r"F:\python\auto_tools\Terminal_部标_2014-01-12-11.exe")

(2)使用Application对象的connect()方法

-->使用进程ID (PID)进行绑定;

app = Application().connect(process=15860)

进程的PID可以在任务管理器中查看;

自动化工具之三:pywinauto

-->使用窗口句柄绑定

app = Application().connect(handle=0x00030F9A)

窗口句柄可以在Spy++中查看 :

自动化工具之三:pywinauto

-->使用程序路径绑定

app=Application().connect(path=r"F:\python\auto_tools\Terminal_部标_2014-01-12-11.exe")

-->使用标题、类型等匹配

app = Application().connect(title_re="Terminal_部标_2014-01-12.*", class_name="#32770 (对话框)")
  1. 选择菜单项

三、使用pywinauto操作窗口

1.启动程序

(1)application.Application().start('路径+程序名/程序名')

app = application.Application().start('notepad.exe')

例子:

myapp = Application().start("notepad")

myapp.__setattr__("name","notepad")

错误用法:

from pywinauto import application

app = application.Application.start('notepad.exe')

原因:因为start方法必须是针对应用实例的方法, 忘记了实例化操作, ()符号;

(2)MenuSelect方法自动检索Notepad上的菜单选项

(3)decode(‘gb2312’)方法,python3使用encode('gb2312')方法是把中文强制转换为unicode编码,对于非英文的操作系统都是要转换的;

例如:点击“帮助->关于记事本”操作;

app.Notepad.MenuSelect('帮助->关于记事本'.decode('gb2312'))

附注:抛出异常

① AttributeError: 'str' object has no attribute 'decode'

主要原因:Python2和Python3在字符串编码上的区别(必须将字节字符串解码后才能),python在bytes和str两种类型转换,所需要的函数依次是encode(),decode()

解决方案:str通过encode()方法可以编码为指定的bytes

反过来,当从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法。反之,则使用encode()方法即可!正确用法如下:

app.Notepad.MenuSelect('帮助->关于记事本'.encode('gb2312').decode('gb2312'))

例如,对一个写字板app应用中的窗口,在英文操作系统中,其标题是“untitled - Notepad”

可以使用以下两种方式调用该窗体

app.Untitled

app.Notepad

对于关于窗口,其标题是“About Notepad”

可以使用以下名称调用该窗体

app.AboutNotepad

这里的app是你刚才实例的对象,Notepad是类名;

  1. 查找/调用窗口

通过工具spy++lite查看窗口的类名和标题文字...

这里先介绍官方文档的两种方法:

(1)通过top_dlg = app.top_window_() 来获取最上面的window(不推荐,如果有新进程,就会得到错误对象)

(2)通过find_dlg = app.window_(title_re = ‘ ’, class_name = ‘ ’) 方法获得,title_re和 class_name这两个可以单独使用也可以一块使用,因为有时没有标题文本,也有时一个窗口类名有多个对象;

例如:

“Edit”有时当一个对话框中有多个输入框时会有多个Edit类名,对于“关于记事本”我们可以通过以下代码获得-->

about_dlg = app.window_(title_re = u"关于", class_name = "#32770")

中文要进行unicode编码,这里也可以通过decode(‘gb2312’)方法实现,也可以这样(u”关于”)

这里通过print一下about_dlg可以确定我们得到的是一个什么对象?

例如:

<pywinauto.application.WindowSpecification object at 0x01F0A530>

说明我们得到的是一个application.WindowSpecification对象

(3)通过dlg_spec = app.window(title='')

或者app.window(title_re=’’)

  1. 定位对话框

例如:我要定位“关于记事本”的对话框

① 第一种方法

about_dlg= app.window_(title_re="关于",class_name="#32770")  # 这里可以进行正则匹配title

app.window_(title_re='关于“记事本”').window_(title_re='确定').Click()

② 第二种方法

ABOUT= '关于“记事本”'

OK= '确定'

# about_dlg[OK].Click()

# app[ABOUT][OK].Click()

app['关于“记事本”']['确定'].Click()
  1. 在窗口上找到某个按钮

4.1 例如:在”关于记事本”窗口上找到“确定”按钮(button)

① 第一种方法

(1)打印当前窗口的所有controller(控件和属性)

about_dlg.print_control_identifiers()

注意事项:

窗口的控件和属性打印失败的原因,有可能是因为程序启动的时候,但是窗口并没有马上出现,可以给一个合适的休眠时间即可。

例如:

自动化工具之三:pywinauto

在pywinauto中,对话框下面的是controller,button,checkbox,textbox等都是controller。

static,SysLink,button等是它类型,后面接的是title,都是unicode的,这里面就有没有title的controller,再后面的(L,T,R,B)是这个控件的位置,分别对应着左上右下;

例如:在”关于记事本”窗口上找到“确定”按钮;

可以通过app.window_()方法,传入的参数可以是title,也可以是class_name,所以我说这两个值相当重要,一直在用,这里的title支持正则表达式,非常方便,在app上先找到about_dlg,然后再about_dlg上找确定button,

app.window_(title_re = u'关于“记事本”').window_(title_re = u'确定'),然后通过Click()方法来单击这个button;

② 第二种方法(非英文系统下)

OK = u'确定'

about_dlg[OK].Click()

这种方法需要在about_dlg下找到u’确定’

③ 第三种方法(不需要找about_dlg)

app[u'关于“记事本”'][u'确定'].Click()

  1. 特殊符号快捷键,对应码表

SHIFT

+

CTRL

^

ALT

%

空格键

{SPACE}

BACKSPACE

{BACKSPACE}、{BS} or {BKSP}

BREAK

{BREAK}

CAPS LOCK

{CAPSLOCK}

DEL or DELETE

{DELETE} or {DEL}

DOWN ARROW

{DOWN}

END

{END}

ENTER

{ENTER} or ~

ESC

{ESC}

HELP

{HELP}

HOME

{HOME}

INS or INSERT

{INSERT} or {INS}

LEFT ARROW

{LEFT}

NUM LOCK

{NUMLOCK}

PAGE DOWN

{PGDN}

PAGE UP

{PGUP}

PRINT SCREEN

{PRTSC}

RIGHT ARROW

{RIGHT}

SCROLL LOCK

{SCROLLLOCK}

TAB

{TAB}

UP ARROW

{UP}

+

{ADD}

-

{SUBTRACT}

*

{MULTIPLY}

/

{DIVIDE}

(1)使用type_keys()函数,这里需要的快捷键是Alt+T+P:

例如:

dlg_spec = app.window(title='屏幕录像专家 V2017') dlg_spec.type_keys('%TP')

(2)使用type_keys()函数,光标跳到最后

app[u"Terminal_部标_2014-01-12"]['Edit'].type_keys('{END}')

(3)使用type_keys()函数,删除输入框的内容

app[u"Terminal_部标_2014-01-12"]['Edit'].type_keys('{BACKSPACE}' * 20)

这里输入20个回退键

(4)使用TypeKeys()函数,输入文本,不会清空原来的数据

例如:app["标题"]['类名'].TypeKeys(u"119.23.142.92")

app[u"Terminal_部标_2014-01-12"]['Edit'].TypeKeys(u"119.23.142.92")

注意:如果输入框内初始有文本,需要将光标移到最后,然后进行删除操作,最后才能输入文本;

(5)app['Cy']['Edit3'].SetEditText('bbbb')

(6)#app['Cy']['Edit3'].SetText('bbbb')

(7)app['Cy']['Edit4'].set_edit_text('你好')

#(4)(5)(7)3种输入值,与.TypeKeys区别在于,这个如果文本框禁止输入也可强制输入

a=app['Cy']['Edit1'].WindowText()#获取值

b=app['Cy']['Edit3'].texts()#获取值,返回一个数组

c=app['Cy']['Edit4'].text_block()#获取值

6.操作窗口控件

常见的窗口程序的控件:输入框(Edit)、按钮(Button)、复选框(CheckBox)、单选框(RadioButton)、下拉列表(ComboBox).

关于各个控件的函数方法官网:https://pywinauto.readthedocs.io/en/latest/controls_overview.html

6.1 结合程序,控件的用法

大致用法分两步:

● 找到控件

● 操作控件

接下来,如何让程序找到控件?

(1)如何匹配控件

① 第一种方法

最简单的通过空间特征进行匹配(窗体也可以看成大控件),匹配窗口的方法除了前面提到的window()方法,还可以通过中括号加窗口名,例如:

dlg_spec = app.window(title=r'EXE/EXE 转 MP4')

dlg_spec = app[r'EXE/EXE 转 MP4']

除了title,还可以使用class或者title+class或者相近的text和类来匹配控件,

② 第二种方法

如果我们知道了程序的层次结构,然后类似寻到DOM元素一样一层一层的匹配;

那么我们要如何找到层次结构呢?

pywinauto提供了print_control_identifiers()函数来显示该窗体下所有控件的结构。

得到文件名后面的编辑框的属性为:

Edit - '' (L259, T88, R429, B107)

['EXE/EXE 转 MP4Edit', 'Edit3']

所以我们可以通过控件的text或者title来查找控件。如:

edit = dlg_spec[''] # 1 

edit = dlg_spec['Edit2'] # 2 

edit = dlg_spec.Edit2 #

注意,对于输入控件Edit,一般不建议使用text内容绑定,因为Edit的text内容会发生变化。另外,绑定的控件也可能不唯一。对于title,我这里可能理解不够,属性显示的是Edit3,但实际上绑定的时候用的却是Edit2,也就是数字要减一

(2)如何操作控件

对于Edit控件,要么就是向里面写内容,要么就是读里面的内容。

参考地址:

https://blog.csdn.net/shawpan/article/details/78170200

①  转换文件

例如:我们要向Edit3写入要转换文件的路径(r’E:\test test .exe’),这里的文件名在中间又加了空格;

edit.set_text(r'E:\test test .exe') # 1

edit.type_keys(r'E:\test test .exe',with_spaces = True) # 2

上述代码第一种方法是直接设置edit的text,而第二种是在里面模拟键盘输入(如果字符串中没有空格,可以省略后面的参数)

注意:使用第二种方法输入并没有什么效果,因为该编辑框设置了禁止输入(自己手动敲键盘,发现编辑框没有反应)

②  点击对话框的按钮

--> 第一种方法

about_dlg= app.window_(title_re="关于",class_name="#32770")  # 这里可以进行正则匹配title

app.window_(title_re='关于“记事本”').window_(title_re='确定').Click()

使用格式:

app.window_(title_re='窗体标题').window_(title_re='Button名').Click()

--> 第二种方法

ABOUT= '关于“记事本”'

OK= '确定'

# about_dlg[OK].Click()

# app[ABOUT][OK].Click()

app['关于“记事本”']['确定'].Click()

使用格式:app['控件标题']['Button'].Click()

--> 第三种方法

dlg_spec = app[r'EXE/EXE 转 MP4']

dlg_spec.Button0.click()

使用格式:匹配控件方法.类名.click()

--> 第四种方法

dlg_spec = app[r'EXE/EXE 转 MP4']

dlg_spec['浏览'].click()

使用格式:匹配控件方法 + ['Button名'] + . + click()

举个例子:

手动操作的步骤: 先在查找范围后的ComboBox中找到要转换的源程序所在的文件夹,中间的list中就会出现该文件中的所有文件和文件夹,选中源文件,文件名后面的Edit里面就会显示文件名,最后点击打开按钮完成选择。

如果按照手动操作的步骤进行模拟,脚本编写起来会比较复杂,这里我们使用了一个trick。直接在文件名的编辑框中输入源文件的绝对路径,然后点击打开按钮完成选择。

dlg_open = app['打开']

dlg_open.Edit.set_text(r'E:\test test .exe')

dlg_open['打开'].click()

自动化工具之三:pywinauto

有三点需要说明的地方:

1、 “打开”对话框中只有一个输入框,所以,使用Edit就可以绑定文件名的编辑框。

2、 编辑框是可编辑的,也就是说我们可以使用type_keys()函数模拟键盘输入文件路径,但是需要显式指明字符串中含有空格,不然空格会被忽略掉。而且需要先清空编辑框的内容。

dlg_open.Edit.type_keys(r'E:\test test .exe')

图中为没有事先清理编辑框和没有显式指明含有空格的结果。

3、 若文件绝对路径输入错误,点击“打开”按钮会先跳到路径中最后一个文件夹,然后再点击“打开”,提示找不到文件的错误。

自动化工具之三:pywinauto

③  勾选选项操作

自动化工具之三:pywinauto

现在变成了5帧/秒,建议扩频后不超过15帧/秒,也就是扩频3倍即可,默认参数也是3,所以只需要勾选上“自动扩频”即可。

dlg_spec.CheckBox0.check()

④  处理对话框的确认按钮

自动化工具之三:pywinauto

我们需要做的就是找到这个对话框,点击里面的OK按钮;

app['屏幕录像专家'].Ok.click()

⑤  listview如何进行双击

使用DoubleClick()方法

⑥  鼠标右击

使用RightClick()方法

⑦  处理下拉列表

使用select()方法

自动化工具之三:pywinauto

终端类型选择“A08”,代码如下:

自动化工具之三:pywinauto

⑧  按、移动、释放鼠标

Control.PressMouse/MoveMouse/ReleaseMouse()
  1. 等待窗口出现

实际操作中,我们不知道它什么时候会处理完?如上图的OK按钮只有处理完后弹窗后才能点击;

(1)等待法。首先预估一个所需的最长时间,保证此时已经处理完并弹窗,然后让程序等待这么长时间后在点击OK按钮;

import time

...

time.sleep(100)

app['屏幕录像专家'].Ok.click()

(2)查询法。

写个循环,一直查询是否存在“屏幕录像专家”弹窗,若存在,则退出循环。

...

自动化工具之三:pywinauto

注意,在查询的时候,最好不要用app[‘屏幕录像专家’].exists()。这个匹配不精准,如下图中的最后一个句柄。这个句柄在开启程序后就一直存在,且于我们要找的对话框title一样,所以我们在查找的时候需要加上class_name。

自动化工具之三:pywinauto

(3)查询等待法。

查询有个缺点就是如果一直没出现,就会一直等待。所以我们最好设置一个等待时间限。 
使用模块中自带的wait函数就可以实现该功能了;

官网地址:

https://pywinauto.readthedocs.io/en/latest/wait_long_operations.html

  1. 举个例子code

(1)批量转换exe视频;

# -*- coding: utf-8 -*-
"""
Created on Wed Oct 4 16:52:13 2017 @author: x
"""
from pywinauto.application import Application program_path = r"D:\Program Files (x86)\tlxsoft\屏幕录像专家 共享版 V2017\屏录专家.exe"
app = Application().start(program_path)
dlg_spec = app.window(title_re='屏幕录像专家.*',class_name='TMainForm')
#dlg_spec.type_keys('%TP')
dlg_spec.menu_select(r"转换工具->EXE/LXE转成MP4")
dlg_spec = app[r'EXE/EXE 转 MP4']
dlg_spec.print_control_identifiers() # 打印该窗体下的所有控件结构 for line in open('ToBeConvert.txt'):
filename = line.strip() # 去掉读取每一行时最后带着的空格和回车符
dlg_spec.Button3.click() # 点击“浏览”按钮
dlg_open = app.window(title=r'打开') # 获取“打开”对话框句柄
dlg_open.Edit.type_keys(filename,with_spaces = True)
# dlg_open.Edit.set_text(filename) # 将文件绝对路径写入编辑框中
dlg_open.Button0.click() # 点击“打开”按钮
dlg_open.wait_not('visible') dlg_spec.CheckBox0.check() # 勾选自动扩帧
dlg_spec.Button0.click() # 点击“转换” app['另存为'].Button0.click() # 点击“另存为”对话框的“保存”按钮 app.window(title=r'屏幕录像专家',class_name='TMessageForm').Wait('enabled',timeout=300) # 等待转换结束
app.window(title=r'屏幕录像专家',class_name='TMessageForm').Ok.click() # 关闭转换完成后弹出的对话框

附注:

-1- 根据屏幕录像专家程序的安装位置修改变量‘program_path’的值。

-2- 在当前目录新建一个 ToBeConvert.txt 文档,每行写上一个带转换的源文件,目标文件目录默认与源文件相同。

E:\test test .exe

E:\test test1.exe

  1. 注意事项

在MenuSelect函数中不支持正则,完全是全文匹配,如我输入:

dig = app.Notepad.MenuSelect("编辑->替换".decode('gb2312')) 是找不到对象的

必须要:

dig = app.Notepad.MenuSelect("编辑(E)->替换(R)".decode('gb2312')) 

得把(R) (E)写上才行,但是奇怪的是上面的“帮助->关于记事本”就不用输入;

(1)例1:

#! /usr/bin/env python
#coding=gbk
import time
from pywinauto import application
app = application.Application().start('notepad.exe')
app.Notepad.MenuSelect('帮助->关于记事本'.encode('gb2312').decode('gb2312'))
time.sleep(0.5) #这里有两种方法可以进行定位“关于记事本”的对话框
#top_dlg = app.top_window_() 不推荐这种方式,因为可能得到的并不是你想要的
about_dlg = app.window_(title_re = u"关于", class_name = "#32770")#这里可以进行正则匹配title
#about_dlg.print_control_identifiers()
app.window_(title_re = u'关于“记事本”').window_(title_re = u'确定').Click()
app.Notepad.MenuSelect('帮助->关于记事本'.encode('gb2312').decode('gb2312'))
time.sleep(0.5) #停0.5s 否则你都看不出来它是否弹出来了!
ABOUT = u'关于“记事本”'
OK = u'确定'
#about_dlg[OK].Click()
#app[ABOUT][OK].Click()
app[u'关于“记事本”'][u'确定'].Click() app.Notepad.TypeKeys(u"杨彦星")
dig = app.Notepad.MenuSelect("编辑(E)->替换(R)".encode('gb2312').decode('gb2312'))
Replace = u'替换'
Cancle = u'取消'
time.sleep(0.5)
app[Replace][Cancle].Click()
dialogs = app.windows_()

(2)例2:

import time
from pywinauto import application
app = application.Application().start('notepad.exe')
app.Notepad.MenuSelect('帮助->关于记事本')
time.sleep(1)
OK='确定'
app['关于“记事本”']['确定'].Click() #或者app['关于“记事本”'][OK].Click()
time.sleep(1)
app.notepad.TypeKeys("输入测试文本")
time.sleep(2)
dig = app.Notepad.MenuSelect("编辑(E)->替换(R)")
time.sleep(1)
Replace='替换'
Cancel='取消'
app[Replace][Cancel].Click() #要不然就写成app['替换']['取消'].Click()
time.sleep(1)
dialogs = app.windows_()
上一篇:windows下安装Python2和Python3共存


下一篇:C#-UWP App发行版本中的System.TimeZoneNotFoundException