コードもREADMEもGitHub Copilotに書かせているので、割と適当です。とりあえずやりたいことができそうなので、気が向くと開発が続行します。
やりたいこと
例えばこういうGUIがある。「スキャン画面を開く」ボタンを押すことを自動化したい。さてどうしよう?
言語はとりあえずPythonを使いたいものとする。
先行研究
1. PyAutoGUI
いちいちコードは示さないが、そのボタンのスクショを撮って、画像を保存しておき、今現在のデスクトップのスクショからパターンマッチでボタンを見つけ出して、マウスを移動してクリックする、という古典的なRPA手法がある。
PyAutoGUIならPyScreezeとOpenCVでいい感じにやってくれるとは思うけど、画像のスクショを保存しておく必要がある上、たまたまボタンが隠れたり、ボタンのスケールが変わったりした場合などに対するロバストネスが低すぎる。作業コスパが悪い。なし。
2. uiautomation
そこで、UI AutomationというゴリゴリのWindowsプログラミングフレームワークに乗っかることを検討しなければならない。
さいわい、とても有益なPythonでの先行事例を見つけることができた。特にダンプのところを見ればわかるとおり、UIは完全に木構造であることが見て取れる。
なら、木構造そのままダンプできるXMLっていう素晴らしいマークアップ形式がありますやんね?
やってみたこと
とりあえずHTMLでいい感じに見やすくダンプしてみよう!
とりあえず先人の知恵をもとにダンプを見やすくHTMLにマークアップしてもらいますか。
GeminiやGitHub Copilotなどにいい感じにコードを書いてもらって、こんな感じになりました。
たとえば、「ファイル形式」のコンボボックスを操作したいときは、 TextControl(Name="ファイル形式")
をまず探して、その兄弟の直近の ConboBoxControl
を探さないといけないわけです。
これを GetParentControl
とか GetNextSiblingControl
とかで頑張るのはちょっと嫌です。
UIをXPathで検索したいな…、じゃあUI構造をXMLに起こせばいいじゃん。
そこで、このUIの木構造をXMLで表現してしまえば、XPathで検索することもできるだろうよ、ってことになるわけです。
/WindowControl[@name="ScanSnap Home"]//ComboBoxControl[preceding-sibling::TextControl/@name="ファイル形式"]
みたいなXPathが使えるようになると嬉しいわけですね。
XMLで表現するっていったって、実際にやることはlxml.etreeを組み立てるだけです。任意の要素から実際のコントロールにアクセスできるようUUIDを振って、dictで管理しておけばいいわけです。
というわけで、面倒な実装はGitHub Copilotに丸投げして、こんな感じの操作ができるようになりました。
from uitree import UITree
ui_tree = UITree(depth=1)
window = ui_tree.xpath('.//WindowControl[@name="ScanSnap Home"]')
window_tree = UITree(window[0])
# スキャン画面を開く ボタンを押す。
window_tree.xpath(".//ButtonControl[./TextControl/@name='スキャン画面を開く']")[0].control.Click()
# 操作対象ウインドウを切り替え
ui_tree = UITree(depth=1)
window = ui_tree.xpath('.//WindowControl[@name="ScanSnap Home - スキャン"]')
window_tree = UITree(window[0])
# ScanSnap Homeプロファイルを選択
item = window_tree.xpath('.//ListItemControl[@name="Pfu.ScanSnap.Home.UI.Sub.ViewModels.ProfileItemViewModel" and .//TextControl/@name="ScanSnap Home"]')
item[0].control.Click()
# 右クリックメニューを開く
target = item[0].control
target.SetFocus()
target.RightClick()
#window_tree.refresh()
#print(window_tree.dumpxml(), file=open('dump.xml', 'w', encoding='utf-8'))
もっと色々やってみたいところですが、気が向いたら。
っていうか、一晩明けて
GitHub Copilotに丸投げしたのでコードがひっどい。誰か直してほしい。私はUIAに早速飽きたのでモチベなし。