対象読者
本稿ではSikuliXの概要や基本的な記法にはあまり触れません。
SikuliXを一通り使った経験のある方向けの記事です。
はじめに
本稿では、SikuliXを用いて業務を自動化する過程で蓄積したノウハウを共有します。工数が初期の想定から倍以上かかってしまった反省を踏まえ、同様の事態を未然に防ぐための備忘録でもあります。
対象ソフトウェアの情報
- Sikulixのバージョン : 2.0.5
- Javaのバージョン:11.0.22+7-LTS
なぜ工数は倍以上にも膨らんだのか
回答: 画像依存のコーディングだったから。
Sikulixのコードを本番環境に移行した際、解像度を合わせていたにもかかわらず、 テスト環境でキャプチャした画像が認識されず正常に動作していなかったのです。
画像認識がSikuliXの強みである一方、ほんの少しの違いでも動作不具合を引き起こすことがあります。
では、どのように対策すべきだったのか――次のページをご覧ください。
結論: 画像は極力使わない。
というわけで画像依存のコードをやめましょう。
以下にTipsを載せておきます。
① wait のやり方
画像を使わないということはwait使えないやんと思ったそこのあなた。安心してください、使えますよ!
NGパターン
wait("image.png", 10) //画像出現を最大10秒待機
#次の処理
ただし、画像は認識されないものとして扱いましょう。
このままでは FindFailed の文字列と一生付き合うことになります。
OKパターン
try:
wait("image.png", 10) //画像出現を最大10秒待機
except:
sleep(10)
#次の処理
画像は認識してくれない前提で書く必要があります。
try-catch文で画像があるのに認識されていない場合でも次の処理が動くようにしてください。
② 非アクティブ対策のやり方
さて、画像を極力使わないということはClick関数も極力さけるということです。しかしながら画面の遷移などでウィンドウが非アクティブになり、期待した動作が起こせない場合もあります。その時は、Regionボタンを使って画面のクリックをさせてください。以下は例です。
#画面の相対位置をクリック
Region(574,643,460,330).click()
※Regionの使い方については他記事を参考にしてください。
③ batでの他アプリの呼び方
最新バージョンではsubprocess.Popenで実行できますが、それができなかった際の代替案になります。subprocessが実行できる場合は無視してください。
他のアプリを起動したい場合はバッチファイルによる起動を試してみてください。
まずバッチファイル(test.bat)にアプリを起動するコマンドを入力します
start "" "C:\XXXX.exe"
※コマンドプロンプトの窓を表示させないためにこの記述にしています。2つ目の引数は実際のパスに置き換えてください。
次にSikulixのコード側で以下のように記述してください。
# ① バッチファイルのフルパスを指定
bat_path = r'C:\test.bat' # 実際のバッチファイル名に変更
#bat_path = 'C:\\test.bat' でも可
# ② バッチファイルを実行
os.system(bat_path)
アプリが起動できたら行いたい処理を行って、最後に閉じてください
#③ 処理の実行
# ④ Alt+F4 で閉じる
type(Key.F4,Key.ALT)
④ 確実に入力させるやり方
タイミング等の問題でテキストボックスに入力させたい文字が入力できない場合もあります。その時は以下の関数を作り呼び出せるようにするのが得策です。ここでは仮にForceInput関数と名付けておきます。
# -*- coding: utf-8 -*-
from sikuli import *
def ForceInput(input_text):
#リトライ数を5にしています。任意の数に変更してください。
max_retries = 5
for attempt in range(1, max_retries + 1):
# 0. クリップボードをクリア
Env.setClipboard("")
sleep(1)
# 1. 既存のテキストを全選択して削除
type("a", Key.CTRL); sleep(0.5)
type(Key.DELETE); sleep(0.5)
# 2. テキストを入力
type(input_text); sleep(1)
# 3. 入力結果を全選択してコピー
type("a", Key.CTRL); sleep(0.5)
type("c", Key.CTRL); sleep(0.5)
# 4. クリップボードから取得して確認
typed_text = Env.getClipboard()
if typed_text == input_text:
print("True")
return
else:
print("False")
sleep(1)
この関数を入力する部分にカーソルが当たった時に呼び出すとエラーになりづらいです。
しかし、Ctrl+Aが使えない場合もあるのでその時は以下の関数を使ってください。
# -*- coding: utf-8 -*-
from sikuli import *
from java.awt import Toolkit
from java.awt.event import KeyEvent
def ForceInput(input_text):
#リトライ数を5にしています。任意の数に変更してください。
max_retries = 5
for attempt in range(1, max_retries + 1):
# 0. NumLock を強制的に OFF に
Toolkit.getDefaultToolkit().setLockingKeyState(KeyEvent.VK_NUM_LOCK, False)
sleep(0.2)
# 1. END → Shift+HOME で全選択 → Backspace で削除
type(Key.END); sleep(0.2)
type(Key.HOME, Key.SHIFT); sleep(0.2)
type(Key.BACKSPACE); sleep(0.2)
# 2. 引数の文字列を入力
type(input_text); sleep(0.5)
# 3. END → Shift+HOME で全選択 → Ctrl+C でコピー
type(Key.END); sleep(0.2)
type(Key.HOME, Key.SHIFT); sleep(0.2)
type("C", Key.CTRL); sleep(0.2)
# 4. クリップボードから取得して確認
typed_text = Env.getClipboard()
if typed_text == input_text:
print("True")
return
else:
print("False")
sleep(1)
番外編
以下のテクニックは直接の原因解決ではありませんでしたが、役に立つノウハウだったので残しておきます。
1: Sikulixもbatで呼び出すべし
開発の際はGUI操作でSikulixを起動すると思いますが、その際もバッチファイルで呼び出すようにしてください。
@echo off
rem 管理者権限で必ず実行
whoami /priv | find "SeDebugPrivilege" > nul
if %errorlevel% neq 0 (
@powershell start-process %~0 -verb runas
exit
)
rem 実際のパスを入力
start "" "C:\sikulix\sikulixide-2.0.5.jar"
親のコードを決め、親コードからimport文を使って子のコードを呼び出すようにしたら管理しやすくておすすめです。
完成したら直接バッチで親コードをよびだしてください。
2:プリンターを規定するべし
印刷をするテストを自動化したいときもあると思いますが、環境が変わればプリンターの構成も変わっているはずです。そういったときにバッチファイルであらかじめ使いたいプリンターを規定にする必要があります。たとえばMicrosoft to PDF を規定値にしたい場合以下のように記述します
@echo off
rundll32 printui.dll,PrintUIEntry /y /n "Microsoft Print to PDF"
3:エラーをキャッチすべし
各子コードでは次のようにエラー処理を組み込みましょう:
-
try-exceptで例外を捕捉し、エラー発生時に「フラグファイル」を生成しておく -
各コードを実行する前にフラグファイルの有無をチェックし、存在すればスキップする
こうすることで、途中のエラーが全体を止める設計になり、失敗箇所の特定やリカバリーが容易になります。
4:RDPを活用すべし
仮想環境を使用している際は念のため、RemoteDesktop接続で入るようにしてください。Hyper-Vなどの仮想環境では解像度が勝手に変わる現象が発生するようです。(PCが勝手にスリープしないようにしてください。)
さいごに
初めて記事を投稿するのでいろいろ大変でしたが、なんとか終わってよかったと思います。
内容について間違えていた場合コメントなどで教えてください。
ここまで読んでいただき本当にありがとうございました。