かつて、GUIテストを自動化するツールを見つける過程で、興味深いpythonauto pythonパッケージに出会いました。 また、ネイティブコントロールと部分的にWindowsフォームのみをサポートしますが、タスクには非常に適しています。
pywinautoの歴史は1998年頃に遡り、Mark McMahonはGUIオートメーションのニーズに合わせてCでユーティリティを作成し(2年かかりました)、その後2005年に3か月でPythonで書き直しました。 。 pythonの力は、その栄光のすべてに現れました。pywinautoインターフェースは、シンプルで表現力豊かであることが判明しました。 このツールは2006年から2010年まで積極的に開発されました。
その間、世界は変化していました。 私たちのチームは64ビットバイナリに切り替え、pywinautoクローンは64ビットPythonで動作しました。 メインブランチでは、プロジェクトは4年間開発されず、古くなっています。 2015年、マークの同意を得て、彼はプロジェクトに新しい命を吹き込むことができました。 現在、pywinautoは公式にgithub上に存在し、主にairelil 同志のおかげで、AppVeyor CIサーバーで単体テストが実行されます 。
現時点では、0.5.xラインの3つの新しいリリース(最後の-0.5.2)をリリースしました。 0.4.2と比較した大幅な改善:
- 64ビットアプリケーションとx64 pythonのサポート(ただし、32ビットバイナリには32ビットPythonが必要です)。
- Python 3のサポート。
- PyPIパッケージの問題を修正しました。
- py2exeなどでpywinautoの友達を作ることができました。
- 多くのコントロール、特にツールバー、ツリービュー、リストビューのサポートが改善されました。
-
pywinauto.actionlogger.enable()
をpywinauto.actionlogger.enable()
、ほとんどのアクションのログを有効にできます。 - 一連のマイナーな改善とバグ修正。
過去4年間、私たちのチームはpywinautoを使用して、複雑なグラフィカルカスタムコントロールを含む内部ソフトウェアをテストすることに成功しました。 HwndWrapper.SendMessageメソッドとRemoteMemoryBlockクラスを使用して、独自のラッパーを持っています(また、途中で改善されました)。 しかし、これは別の分析のトピックです。 pywinautoのカスタムコントロールのオープンな例を見たことはありません。
とりあえず、特定の例を使用してpywinautoの機能のいくつかを見てみましょう。
自動化の例のインストール/アンインストール
多くの場合、100,500台のテストマシンでソフトウェアのインストール/削除を自動化するタスクがあります。 7zipの例(デモの例!)を使用してこれを行う方法を示します。 64ビットインストーラーはwww.7-zip.orgから事前にダウンロードされており、たとえばスクリプトと同じフォルダーにあると想定されています。 テストマシンでは、ユーザーアカウント制御(UAC)はレベル0(通常はセキュリティを危険にさらさない分離されたサブネット)に無効化されます。
設置
これはインストールスクリプトinstall_7zip.pyです (リンクは更新されたバージョンです):
from __future__ import print_function # for py2/py3 compaibility import sys, os # assume the installer is placed in the same folder as the script os.chdir(os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]))) import pywinauto app = pywinauto.Application().Start(r'msiexec.exe /i 7z920-x64.msi') Wizard = app['7-Zip 9.20 (x64 edition) Setup'] Wizard.NextButton.Click() Wizard['I &accept the terms in the License Agreement'].Wait('enabled').CheckByClick() Wizard.NextButton.Click() Wizard['Custom Setup'].Wait('enabled') Wizard.NextButton.Click() Wizard.Install.Click() Wizard.Finish.Wait('enabled', timeout=30) Wizard.Finish.Click() Wizard.WaitNot('visible') if os.path.exists(r"C:\Program Files\7-Zip\7zFM.exe"): print('OK') else: print('FAIL')
インストールでは、すべてが非常に簡単ですが、いくつかの明らかでない点があります。 チェックボックスがライセンスに同意するようにするには、多くのチェックボックスがWM_CHECKメッセージを処理せず、実際のクリックにのみ応答するため、pywinauto 0.5.0で登場したCheckByClick()メソッドを使用します。
インストールプロセス自体の長い待機は、明示的なパラメータタイムアウト= 30(秒単位)を指定したWait()メソッドによって提供されます。 つまり、
Wizard.Finish
オブジェクト
Wizard.Finish
はボタンの単なる説明であり、Wait()メソッドまたは他のメソッドが呼び出されるまで実際のボタンに接続されません。 厳密に言うと、
Wizard.Finish.Click()
呼び出すことは、
Wizard.Finish.WrapperObject().Click()
(通常は暗黙的に行われます)を
Wizard.Finish.Wait('enabled').Click()
と同等であり、
Wizard.Finish.Wait('enabled').Click()
とほぼ同等です。 1行で記述できますが、Wait()メソッドの重要性を強調する価値がある場合があります。
削除する
uninstall_7zip.pyを削除するスクリプトは、「プログラムのアンインストール」セクションのコントロールパネルに移動する必要があるため、もう少し複雑です。 必要に応じて、explorer.exeを使用して、他のタスクを自動化できます。
from __future__ import print_function import pywinauto pywinauto.Application().Start(r'explorer.exe') explorer = pywinauto.Application().Connect(path='explorer.exe') # Go to "Control Panel -> Programs and Features" NewWindow = explorer.Window_(top_level_only=True, active_only=True, class_name='CabinetWClass') try: NewWindow.AddressBandRoot.ClickInput() NewWindow.TypeKeys(r'Control Panel\Programs\Programs and Features{ENTER}', with_spaces=True, set_foreground=False) ProgramsAndFeatures = explorer.Window_(top_level_only=True, active_only=True, title='Programs and Features', class_name='CabinetWClass') # wait while list of programs is loading explorer.WaitCPUUsageLower(threshold=5) item_7z = ProgramsAndFeatures.FolderView.GetItem('7-Zip 9.20 (x64 edition)') item_7z.EnsureVisible() item_7z.ClickInput(button='right', where='icon') explorer.PopupMenu.MenuItem('Uninstall').Click() Confirmation = explorer.Window_(title='Programs and Features', class_name='#32770', active_only=True) if Confirmation.Exists(): Confirmation.Yes.ClickInput() Confirmation.WaitNot('visible') WindowsInstaller = explorer.Window_(title='Windows Installer', class_name='#32770', active_only=True) if WindowsInstaller.Exists(): WindowsInstaller.WaitNot('visible', timeout=20) SevenZipInstaller = explorer.Window_(title='7-Zip 9.20 (x64 edition)', class_name='#32770', active_only=True) if SevenZipInstaller.Exists(): SevenZipInstaller.WaitNot('visible', timeout=20) if '7-Zip 9.20 (x64 edition)' not in ProgramsAndFeatures.FolderView.Texts(): print('OK') finally: NewWindow.Close()
ここにはいくつかの重要なポイントがあります。
explorer.exeを起動すると、explorer.exe(ワーカー)が既に実行されていることを確認する短期起動プロセス(ランチャー)が作成されます。 このような「ランチャー->ワーカー」の束がときどき見つかります。 したがって、connect()メソッドを使用してexplorer.exeワークフローに個別に接続します。
アドレスバー(
AddressBandRoot
)をクリックすると、いわゆるインプレース編集ボックスが表示されます(入力時のみ)。
TypeKeys()
メソッドを呼び出す場合、
set_foreground=False
パラメーター(0.5.0で表示)を指定する必要があります。指定しない場合、インプレース編集ボックスはレーダーから消えます。 すべてのインプレースコントロールについて、このパラメーターをFalseに設定することをお勧めします。
さらに、プログラムのリストは長時間初期化されますが、ListViewコントロール自体は使用可能であり、
ProgramsAndFeatures.FolderView.Wait('enabled')
への単純な呼び出しは、それが既に完全に初期化されていることを保証しません。 遅延(遅延)初期化は別のスレッドにあるため、explorer.exeプロセス全体のCPUアクティビティを監視する必要があります。 このため、pywinauto 0.5.2は、CPU負荷の割合を返す
WaitCPUUsageLower()
と、CPU負荷がしきい値(デフォルトでは2.5%)を下回るまで
WaitCPUUsageLower()
する
CPUUsage()
2つのメソッドを実装します。 これらのメソッドを実装するというアイデアは、JOHN_16という同志の記事を示唆しました。 「CPUのロードプロセスの完了を監視します 。 」
ところで、
item_7z.EnsureVisible()
呼び出すと、リストが魔法のようにスクロールされ、
item_7z.EnsureVisible()
のアイテムが表示されます。 スクロールバーを使用した特別な作業は必要ありません。
WaitおよびWaitNotの呼び出しは、特定のウィンドウの開閉を比較的長い時間(デフォルトよりも長い時間)待つ必要があることを意味します。 ただし、一部のWaitNot呼び出しは、制御のためだけに挿入されます。 これは良い習慣です。
「結局のところ、人生、それはよりシンプルであり、より難しい...」
もちろん、これはほんの一例です。 7zipの場合、すべてがはるかに簡単に解決されます。 cmd.exeを管理者として起動し、簡単な行を実行します(UACのどのレベルでも動作します)。
wmic product where name="7-Zip 9.20 (x64 edition)" call uninstall
もちろん、インストーラーの動物園は.msiパッケージに限定されず、自動化タスクの範囲は非常に広いです。
最もよく尋ねられるもの
主な質問がPython 3および64ビットに関するものだった前に、WPFおよびUI Automation APIをサポートする他の多くの非ネイティブアプリケーションのサポートがアジェンダにあります。 この方向に進展があります。 さまざまなバックエンドをpywinautoインターフェースに適合させるための助けを歓迎します。