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窗口中就顯示了當前待測窗口中的控件信息,依據控件信息便能方便的找到相應控件,進行相應操做

 

 

 

 

 

>>>>>>待續 

相關文章
相關標籤/搜索