LinuxでGUIアプリの操作を自動化してテストする

#linux #x11 #test #gui

デスクトップアプリを「外側から」操作してテストする話。アプリ内部のテストフレームワーク(GPUIアプリのテストのようなもの)では検証できない領域——本当にウィンドウが出るか、ウィンドウマネージャとの連携、IME、クリップボードの実挙動——を確かめるために使う。

実際に手を動かした記録はX11 GUI自動操作の実験にある。

土台: XTEST拡張

X11でのGUI自動化はほぼすべてXTEST拡張の上に載っている。Xサーバーに対して「本物の入力デバイスから来たかのように」イベントを注入する仕組みで、XSendEventで特定ウィンドウにイベントを投げるのとは意味が違う。

XTEST XSendEvent
注入先 サーバーの入力パイプラインの根元(グローバル) 指定したウィンドウ
宛先の決まり方 現在のキーボードフォーカス/ポインタ位置に従う 明示指定
アプリからの見え方 本物の入力と区別できない send_eventフラグが立つ

xdotoolxte(xautomation)、PyAutoGUI、Rustのenigoはどれも中身はXTEST。XTESTはグローバルなので、入力前に必ずフォーカスを固定するのが鉄則であり、そのために専用のXサーバー(Xvfb)を立てるのが定番になる。

xdotool type --window <id>のように宛先ウィンドウを明示するとXSendEventが使われるが、これで非フォーカスのウィンドウを裏から操作できるわけではない。GTK4で試した限り、ツールキット側の内部フォーカス状態を見て捨てられる(実験参照)。挙動はツールキット依存なので、素直にフォーカスを移してからXTESTで叩くほうがよい。

3つのアプローチ

代表ツール 操作の指定方法 弱点
座標ベース xdotool, xte, PyAutoGUI, enigo 「(320,150)をクリック」 レイアウト変更で即死
アクセシビリティツリー dogtail, selenium-webdriver-at-spi, X11::GUITest 「"保存"という名前のボタンを押す」 一番堅牢だが対応アプリが要る
画像マッチング openQA(os-autoinst)のneedle, SikuliX, OpenCV 「この画像が写っている場所を押す」 テーマ・フォントで壊れる

AT-SPI2(アクセシビリティ)が使えるなら最優先。GTK / Qt / Electronは対応しているので、dogtailからapp.child('保存', roleName='push button').click()のように意味レベルで書ける。ツリーの中身はaccerciserで覗きながら開発する。KDE陣営のselenium-webdriver-at-spiは、これをWebDriverプロトコルに被せてSeleniumの各言語バインディングから叩けるようにしたもの。Playwrightがブラウザに対してやっていることの、デスクトップ版に相当する。

逆にGPUIのような「全部自前で描画する」フレームワークはAT-SPIツリーを出さない(あるいは非常に薄い)ので、外側からは座標か画像でしか掴めない。ここがGUI自動化の分かれ目になる。

ヘッドレス実行

一番簡単なのはxvfb-run

xvfb-run -a -s "-screen 0 1280x800x24" cargo test --test e2e

ただしウィンドウマネージャがいないと、xdotool windowactivate(EWMHの_NET_ACTIVE_WINDOW経由)が使えない。実アプリのテストでは軽量WMを一緒に上げる。

#!/bin/sh
export DISPLAY=:99
Xvfb :99 -screen 0 1280x800x24 -nolisten tcp &
i3 -c /dev/null &     # or fluxbox / openbox
sleep 1

./target/debug/myapp &
WID=$(xdotool search --sync --name '^MyApp$' | head -1)
xdotool windowactivate --sync "$WID"
xdotool key --clearmodifiers ctrl+n
xdotool type --delay 20 'hello'
import -window "$WID" /tmp/shot.png
  • xdotool search --sync — ウィンドウが出現するまでブロックする。sleepを撒くより遥かに安定する(flaky test対策)。同様に--syncwindowactivate/windowfocusにも付けられる。
  • --clearmodifiers — 前の操作で押しっぱなしになった修飾キーを解除してから叩く。
  • WMを上げない場合はwindowactivateではなくwindowfocusXSetInputFocusを直接呼ぶ)を使えば単一ウィンドウなら足りる。
  • デバッグ時はXvfbの代わりにXephyr :99 -screen 1280x800を使うと、入れ子のXサーバーの中身を目で見ながら開発できる。CIではXvfb、手元ではXephyr、という使い分け。
  • 録画はx11vnc+VNCクライアント、またはffmpeg -f x11grab -i :99 out.mp4

スクリーンショットによる検証

import -window "$WID" actual.png
compare -metric AE -fuzz 2% actual.png expected.png diff.png

同一マシン・同一Xvfb設定であれば、別プロセスとして起動し直しても描画は1ピクセルも変わらない(実験でAE=0を確認)。つまりスナップショットテスト自体は成立する。壊れるのは環境をまたいだときで、フォント・DPI・アンチエイリアス・テーマのどれかが違えば即座に差分が出る。CIでやるなら、

  • テスト専用のFONTCONFIG_FILEでフォントを固定する
  • Xvfbの解像度・色深度を固定する
  • 画面全体ではなくopenQAのneedleのように小領域だけをあいまい一致で照合する

くらいまで揃える必要がある。

Waylandでは

XTESTに相当する統一手段がない。xdotool作者のJordan Sissel自身が、コンポジタごとに手段がバラバラな現状について「進め方に途方に暮れている」と書いている。

  • libei + XDG RemoteDesktop portal — 標準路線。portal 1.17(2023年半ば)で統合され、1.21でセッション永続化(毎回の許可ダイアログを省く仕組み)が入った。ただしGNOME/KDEでは初回に許可ポップアップが出る。
  • wlroots系virtual-keyboard-unstable-v1 / virtual-pointerプロトコル。wtypewdotoolなど。
  • KDEkdotool。GNOMEでは動かない。
  • ydotool/dev/uinputに直接書くのでコンポジタ非依存だが、権限(rootかuinputグループ)が要り、フォーカスやパーミッションの概念を無視して盲目的に叩く。
  • CI用のヘッドレスWaylandはswayWLR_BACKEND=headless、またはweston --backend=headless

Rustアプリの場合

入力注入はenigoクレートがX11 / Wayland / libeiをカバーする(Waylandとlibeiはexperimentalでフィーチャフラグの下にある)。X11ではlibxdo-devが要る。

ただしGPUIアプリなら、まずGPUI内蔵のテスト機構で内側からテストするほうが速くて安定する。外側のX11自動化は、内側では検証できないものに絞るのが現実的な線引き。

出典

作成日時: 2026-09-05 09:40 / 更新日時: 2026-09-05 09:40