自動化工具之三: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:\python\auto_tools\Terminal_部標_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:\python\auto_tools\Terminal_部標_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:\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()

 

 

有三點需要說明的地方:

1、 “打開”對話框中只有一個輸入框,所以,使用Edit就可以綁定文件名的編輯框。

2、 編輯框是可編輯的,也就是說我們可以使用type_keys()函數模擬鍵盤輸入文件路徑,但是需要顯式指明字符串中含有空格,不然空格會被忽略掉。而且需要先清空編輯框的內容。

dlg_open.Edit.type_keys(r'E:\test 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)\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_()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM