• 自动化工具之三: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。 

     

    如果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:pythonauto_toolsTerminal_部标_2014-01-12-11.exe")

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

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

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

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

    -->使用窗口句柄绑定

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

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

     

    -->使用程序路径绑定

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

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

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

    三、使用pywinauto操作窗口

    1.启动程序

    1application.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方法必须是针对应用实例的方法, 忘记了实例化操作, ()符号;

    2MenuSelect方法自动检索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中,对话框下面的是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 # 3

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

    (2)如何操作控件

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

    参考地址:

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

    ①  转换文件

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

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

    edit.type_keys(r'E: est 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:	est test .exe')
    
    dlg_open['打开'].click()

     

    有三点需要说明的地方:

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

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

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

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

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

     

    ③  勾选选项操作

     

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

    dlg_spec.CheckBox0.check()

    ④  处理对话框的确认按钮

     

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

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

    ⑤  listview如何进行双击

    使用DoubleClick()方法

    ⑥  鼠标右击

    使用RightClick()方法

    ⑦  处理下拉列表

    使用select()方法

     

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

     

    ⑧  按、移动、释放鼠标

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

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

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

    import time
    
    ...
    
    time.sleep(100)
    
    app['屏幕录像专家'].Ok.click()

    (2)查询法。

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

    ...

     

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

     

    (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)	lxsoft屏幕录像专家 共享版 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: est test .exe 

    E: est 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_()
  • 相关阅读:
    iOS-Core-Animation-Advanced-Techniques(一)
    vue 路由
    Vue 生产环境部署
    vue 单文件组件
    vue 插件
    Vue 混合
    vue 自定义指令
    vue render函数 函数组件化
    vue render函数
    vue 过渡状态
  • 原文地址:https://www.cnblogs.com/yfacesclub/p/10113003.html
Copyright © 2020-2023  润新知