
バージョン0.4.7では、コードジェネレーターが完全に再設計されています。 主な機能、およびUIの自動テスト用のスクリプトを迅速かつ簡単に作成する方法の例は、catの下を参照してください。
説明
SWAPYは、ウィンドウの階層を表示し、pywinautoライブラリのUIオートメーションコードを生成するためのグラフィカルユーティリティです。

名前自体は、アプリケーションの主要な考え方であるPYthonのSimple Windows Automationを反映した頭字語です。 このユーティリティは、PyInstallerを使用してコンパイルされた完全なexeファイルです。 SWAPYでは、自動化とコード生成のための追加設定は必要ありません。 もちろん、コードをさらに使用するには、少なくともPythonとpywinautoをインストールする必要があります。 しかし、機能をテストするために、そして最も重要なことは、そのような束がアプリケーションの自動化に適しているかどうかを確認するために、SWAPYは完全に自給自足です。
このユーティリティには、次の3つの主要コンポーネントが含まれています。
- オブジェクトのツリー
- 選択したオブジェクトのプロパティテーブル
- コードフィールド
スクリプトを作成するには、すべてのコントロールのツリーで要素を見つけてから、クリックなどのアクションを呼び出す必要があります。 この場合、アクション自体がオブジェクトに対して実行され、コードのあるフィールドが更新されます。
以前は、コードジェネレーターにはほとんど注意が払われていませんでした。 多くの場合、要素を検索し、そのパラメーターを表示する機能が使用されました。 コードジェネレーターのすべての修正と機能は、残差の原則に従って追加されました。 その結果、機能するコードを取得するために、ユーザー側で一定の努力が必要でした-すべての祖先を順番に初期化する必要がありました。
新しいコードジェネレーターには、以前の欠陥が基本的にありません。
開発履歴
2011年初頭、オートメーションQAエンジニアの立場で、彼はUIオートメーションのライブラリpywinautoを発見しました。 ライブラリ「Old new pywinauto」でライブラリ自体の歴史について学ぶことができます。 当時、実際にはサポートされていませんでした。 ただし、Pywinautoは競合他社をすべて破り、中程度のGUIの複雑さを持つ多数の製品をテストするために選ばれました。
このオプションが選択の主な利点であることに注意してください。
- ツール価格。 Pywinautoは無料で、GNU LGPL v.2.1ライセンスの下で配布されます
- これはPythonライブラリです。 すべての機能、ライブラリなどを備えています
- 環境の簡単な準備。 Python + pywinautoをインストールして仮想マシンをテスト用に準備する方が、たとえばTestCompleteのようなモンスターをインストールするよりもはるかに簡単です。 これは、継続的インテグレーションを使用する場合に非常に重要です。
すぐに、1つの欠点が発見されました。必要な要素を検索し、そのプロパティを分析するのに多くの時間が無駄になりました。 要素のツリーとそのパラメーターを表示するためのグラフィカルユーティリティが本当に不足していました。 グラフィカルインターフェイスを自動化するライブラリは、グラフィカルインターフェイスを備えていると便利です。
この不正を修正することが決定されました。

翌年にわたって、エラーは徐々に修正され、重要でないものが追加されました。 そして、私は自分自身が使用していないユーティリティをサポートすることに仕事と関心を変えました。
SWAPYは2015年9月にpywinautoのメンバーが自分たちに電話をかけたときに2番目の風を受けました。
それ以来、彼は再びユーティリティを積極的に開発し始めました。 主な改善点は、新しいコードジェネレーターです。
主な機能の1つとしてのコード生成機能に対する姿勢を再考しました。 コードジェネレーターを使用して、開発者にライブラリの追加機能を紹介し、経験豊富な開発者でさえルーチンから救うことができます。
新機能
- コードジェネレーターが正常に動作するようになりました。 これは、作業ツリーを取得するためにオブジェクトツリー内のすべての祖先をクリックする必要がないことを意味します。 必要な要素を見つけてアクションを実行するだけで、コードは自動的に構築されてインポートされます。 新しいバージョンでワンクリック:
from pywinauto.application import Application app = Application().Start(cmd_line=u'"C:\\Program Files (x86)\\Notepad++\\notepad++.exe" ') notepad = app[u'Notepad++'] notepad.Wait('ready') systabcontrol = notepad.Tab systabcontrol.Select(u'new 1') app.Kill_()
古いバージョンでは、同様のアクションにより次の結果が得られます。
import pywinauto pwa_app = pywinauto.application.Application() ctrl.Select(0)
明らかに、そのようなコードは機能しません。 - わかりやすい変数名。 同意して、
systabcontrol
ctrl
よりもずっと明確です。 名前は、コントロールのクラス名に基づいて、またはアクセスのための最短名(pywinautoから)に基づいて形成されます。 これらの両方のケースが失敗した場合にのみ、フェースレスcontrol
が使用されます。 - 同じ変数名を制御します。 同じ名前の異なるコントロールを使用する必要がある場合、SWAPYはそれらが一意のままであることを確認します。
button = calcframe.Button19 button.Click() button2 = calcframe.Button20 button2.Click()
これは次の段落に関連します。 - 再利用。 通常、アクションは2行で構成されます。 前者では、コントロールへのアクセスが初期化され、後者ではアクション自体が初期化されます。 そのため、ある時点で、既に初期化されているコントロールでアクションを繰り返す必要がある場合、アクションコードのみが追加されます。
button = calcframe.Button19 button.Click() button2 = calcframe.Button20 button2.Click() button.Click() # Click Button19
最後のコマンドをキャンセルします。 たとえば、実験が失敗した後などに、最後のコマンドを削除する必要があることがよくあります。 これは、エディターのコンテキストメニューから実行できます。 この場合、消失した変数の名前は解放され、次回使用されます。 任意の数のステップをキャンセルできます。 最後のコマンドをキャンセルするとコードがクリアされるだけで、アプリケーションのアクションはキャンセルされないことを理解する必要があります。
すべてのコードを一度にクリアすることもできます。また、すべてのコードをファイルに保存することもできます。
その場でコードを変更します。 現時点では、この機能はトップレベルウィンドウで
app = Application().Start(cmd_line=...
とapp = Application().Connect(title=...
を切り替えますapp = Application().Start(cmd_line=...
app = Application().Connect(title=...
ほとんどの場合、Start
十分ですが、必要でない場合は切り替えapp = Application().Connect(title=...
アプリケーションを開始するには、オブジェクト名のコンテキストメニューでウィンドウ名をクリックしてApplication.Connect
を選択する必要があります。エディターのコードが更新され、Application().Start
メソッドへのバインディングが消えApplication().Start
コマンドcalcframe.Wait('ready')
atapp.Kill_()
andapp.Kill_()
最後に。
アプリケーションを開始するサンプルコード。
from pywinauto.application import Application app = Application().Start(cmd_line=u'"C:\\Program Files (x86)\\Notepad++\\notepad++.exe" ') notepad = app[u'Notepad++'] notepad.Wait('ready') app.Kill_()
すでに実行中のアプリケーションに接続します。
from pywinauto.application import Application app = Application().Connect(title=u'new 1 - Notepad++', class_name='Notepad++') notepad = app[u'Notepad++']
使用例
次に、テストを自動化するためのスクリプトを作成してみましょう。 私は十分な人生の例を選択し、同時にコードジェネレーターの新しい機能を実証しようとしました。
ライセンステキスト
このテストでは、[About]ダイアログにライセンステキストが表示されることを確認します。 同時に、新しいウィンドウが古いアプリケーションに属し、不要な呼び出し
app = Application().Start(...)
作成しないことをSWAPYが理解していることを確認してください。
ライセンステキストの確認
最終的なテストコードは次のようになります。
ご覧のとおり、最小限のネイティブコード。
- Notepad ++を手動で起動します。
- SWAPY要素のツリーで必要なメニュー項目を見つけてクリックします。
- 要素ツリーを更新して新しく開いたウィンドウを表示するには、ツリー内の
root
要素に選択を置く必要があります。 この場合、すべての子要素が更新されます。 - 名前が通常の方法(
window.Texts()
)で決定されていないため、このSWAPY自体がウィンドウのハンドルから名前を形成しました。 - ダイアログの階層についてで、ライセンステキストを見つけてクリックします。
次の行のみが追加されました。
window = app.Dialog edit = window.Edit2 edit.Click() #
つまり SWAPYは既存のapp
変数を使用しました。 このテスト用に自動生成されたコードで、完了です。 Notepad ++はテスト後に起動および終了することに注意してくださいapp.Kill_()
最後の行がこれを担当します。
最終的なテストコードは次のようになります。
from pywinauto.application import Application expected_text = “...” app = Application().Start(cmd_line=u'"C:\\Program Files (x86)\\Notepad++\\notepad++.exe" ') notepad = app[u'Notepad++'] notepad.Wait('ready') menu_item = notepad.MenuItem(u'&?->\u041e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0435...\tF1') menu_item.Click() window = app.Dialog edit = window.Edit2 actual_text = edit.Texts() app.Kill_() assertEqual(expected_text, actual_text)
ご覧のとおり、最小限のネイティブコード。
タブ順
移動するタブを確認しましょう。 コードを生成する際に故意にミスを犯し、SWAPYがそれを削除する方法を確認します。
タブ順序の変更を確認する
テストは次のようになります。
ここでは、少しのドキュメントを読んで、生成されたコードを少し操作する必要がありました。
- Notepad ++を手動で起動します。
- 追加の2つのタブを開きます。 要素ツリーで必要な
ToolBar
を見つけ、インデックス0のボタンに対してClick
アクションを実行します。その結果、コードが表示され、1つの新しいタブが開きます。
別のタブが必要です。もう一度アクションを繰り返してください。 ボタンテキストは使用できないため、インデックスによるアドレス指定が使用されます。 気づかずに、インデックス1のボタンを誤ってクリックしました。
追加されたコード:
toolbar_button2 = toolbarwindow.Button(1) toolbar_button2.Click()
修正する必要があります。 繰り返しを繰り返さないために、SWAPYでは最後のコマンドを元に戻すことができます(少なくともすべてのコードを連続して元に戻すことができます)。
Clear last command
をClear last command
すると、最後のコマンド(強調表示されたフラグメント)が元に戻ります-必要なだけです。 コードを完全にクリアするには、Clear the code
コマンドがあります。 職場での事故を防ぐため、完全なクリーニングは確認ダイアログの後ろに隠れています。
ここですべてを正しく行い、インデックス0のボタンをクリックします。
コードが追加されます:
toolbar_button.Click()
SWAPYは、すでにtoolbar_button = toolbarwindow.Button(0)
あり、2回目のクリックで初期化する必要がないことを覚えています。
- ドラッグアンドドロップでは、
toolbarwindow.DragMouseInput
メソッドを使用します。 使用法の詳細は、 ドキュメントに記載されています 。
タブ座標はsystabcontrol.GetTabRect(0).mid_point()
を使用して決定できます
テストは次のようになります。
# automatically generated by SWAPY from pywinauto.application import Application app = Application().Start(cmd_line=u'"C:\\Program Files (x86)\\' u'Notepad++\\notepad++.exe" ') notepad = app[u'Notepad++'] notepad.Wait('ready') systabcontrol = notepad.Tab assertEqual([u'Tab', u'new 1'], systabcontrol.Texts()) toolbarwindow = notepad[u'3'] toolbar_button = toolbarwindow.Button(0) toolbar_button.Click() toolbar_button.Click() assertEqual([u'Tab', u'new 1', u'new 2', u'new 3'], systabcontrol.Texts()) systabcontrol.DragMouseInput( press_coords=systabcontrol.GetTabRect(0).mid_point(), release_coords=systabcontrol.GetTabRect(2).mid_point()) assertEqual([u'Tab', u'new 2', u'new 3', u'new 1'], systabcontrol.Texts()) app.Kill_()
ここでは、少しのドキュメントを読んで、生成されたコードを少し操作する必要がありました。
テキストを挿入して保存する
テストでは、テキストをコピーして貼り付けてから保存する必要があります。 タスクを複雑にしましょう-Notepad ++は既に実行され、最小化(最小化)されており、標準のメモ帳(コピーの実行元)はまだ起動されていません。
複数のウィンドウで作業する
すべての主要なアクションがありますが、現在はCTRL + C、CTRL + Vコマンドの送信、および実際のテストを取得するためのチェックを追加します。
コマンドを送信するには、組み込みのTypeKeysメソッドを使用します。
以下に全文を示します。
- テストアプリケーションを準備します。 Notepad ++を実行して折りたたみ、テストファイル= "notepad check.txt"で通常のメモ帳を実行します。
- オブジェクトのツリーでノートブックを見つけ、エディターのコンテンツをクリックします。
メモ帳は元の引数で起動されることに注意してください。 - これで、メモ帳++とそのテキストボックスが見つかりました。 最初に展開することを忘れないでください(復元)。
すべてが計画通りに進みますが、突然、タスクの条件に従って、Notepad ++が既に実行されていて、コードがそれを実行しようとすることを思い出しました。
SWAPYはデフォルトでapp = Application().Start ... app.Kill_()
束を生成します。 ただし、この場合、Notepad ++を再度実行する必要はありません。
新しいコードジェネレーターを使用すると、「アプローチ」を変更してコードを生成できます。これは事後的に行うこともできます。 -
Application().Start
を変更するには、Application().Start
Application().Connect
ますApplication().Connect
するには、メモ帳++アプリケーションウィンドウのコンテキストメニューを呼び出し、Application().Connect
を選択する必要がありApplication().Connect
。
- テキストをコピーして貼り付けます。後で発行しますが、ここでテキストを保存する必要があるとします。
- [名前を付けて保存]ウィンドウが開きました。表示するには、要素ツリーを更新する必要があります。 これを行うには、ツリーのルート要素を選択します。 ツリーを更新した後、保存するファイルの名前のフィールドをクリックして(後で変更するため)、保存するボタンをクリックします。
すべての主要なアクションがありますが、現在はCTRL + C、CTRL + Vコマンドの送信、および実際のテストを取得するためのチェックを追加します。
コマンドを送信するには、組み込みのTypeKeysメソッドを使用します。
以下に全文を示します。
# automatically generated by SWAPY from pywinauto.application import Application import time import os SAVE_PATH = r"Notepad_default_path" app = Application().Start(cmd_line=u'"C:\\Windows\\system32\\notepad.exe" check.txt') notepad = app.Notepad notepad.Wait('ready') edit = notepad.Edit edit.TypeKeys("^a^c") # Copy all the text app2 = Application().Connect(title=u'new 1 - Notepad++', class_name='Notepad++') notepad2 = app2[u'Notepad++'] notepad2.Restore() scintilla = notepad2[u'1'] scintilla.TypeKeys("^a^v") # Paste the text #Save a file menu_item = notepad2.MenuItem(u'&\u0424\u0430\u0439\u043b->\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u0430\u043a...\tCtrl+Alt+S') menu_item.Click() window = app2.Dialog edit2 = window.Edit filename = "checked_at_%s" % time.time() # Compose a filename edit2.TypeKeys(filename) button = window.Button button.Click() with open(os.path.join(SAVE_PATH, filename)) as f: assertEqual(“expected_text”, f.read()) app.Kill_()
それはさらに良いですか?
もちろん-はい!
説明した例でも、
Click()
を実行
Click()
てから、テキストを受信するように手動で変更する必要がありました
Texts()
。 または、手動で
TypeKeys
を追加し
TypeKeys
。 将来のリリースでは、コンテキストメニューに項目を追加することで、このような人気のあるアクティビティをまだ簡素化していません。
アイテムにアクセスするための形式を制御することはまだできません。 Pywinautoでは、属性
window.Edit
を使用して要素にアクセスできます。これが不可能な場合(Python変数の名前が無効)、
__getitem__
window[u'0']
ます。
SWAPYは、アクセスのための最短の名前を見つけ、それを属性として使用しようとします。 動作しない場合は、
__getitem__
ます。 これまでのアイデアは最も簡単です-短いコードを取得します。
しかし、たとえば、「Tab Order」のテストでは、そのような行
toolbarwindow = notepad[u'3']
ます。 すべてが機能し、すべてがOKです。 しかし、想像してみてください。しばらくしてこのテストを開いたところ、そのような魔法の数字があります。 トリプルの代わりに、
Toolbar
があり
Toolbar
-最も理解しやすく、最短の名前ではありません。 計画では、ユーザーに名前(「Name!Name、sister!」)を選択する機会を与えます。
また、オブジェクトのツリーを手動で更新する必要があります。 自動更新は明らかに利便性を追加します。
便利なリンク
PS
新しいコードジェネレーターの機能の議論に積極的に参加してくれた-v-ryabovとairelilの仲間に、感謝します。