Windows Desktop E2e
E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation.
- Skill ID
- affaan-m/ecc/windows-desktop-e2e
- Publisher
- affaan-m
- Repository
- ecc
- Installs
- 0
- Files
- 1
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
affaan-m/ecc/windows-desktop-e2eInstalls these files- SKILL.md
What this skill tells the agent
Windows デスクトップ E2E テスト
pywinauto と Windows UI Automation(UIA)を使用したWindowsネイティブデスクトップアプリケーションのエンドツーエンドテスト。WPF、WinForms、Win32/MFC、Qt(5.x / 6.x)をカバーし、Qt固有のガイダンスは専用セクションとして提供します。
アクティベートするタイミング
- Windowsネイティブデスクトップアプリケーションのエンドツーエンドテストを書くまたは実行するとき
- デスクトップGUIテストスイートをゼロから設定するとき
- 不安定または失敗するデスクトップオートメーションテストを診断するとき
- 既存のアプリにテスタビリティ(AutomationId、アクセシブルな名前)を追加するとき
- デスクトップエンドツーエンドをCI/CDパイプライン(GitHub Actions
windows-latest)に統合するとき
使用しないタイミング
- Webアプリケーション →
e2e-testingスキル(Playwright)を使用する - Electron / CEF / WebView2 アプリ → HTMLレイヤーにはUIAではなくブラウザオートメーションが必要
- モバイルアプリ → プラットフォーム固有のツールを使用する(UIAutomator、XCUITest)
- 実行中のGUIを必要としない純粋なユニットまたは統合テスト
コアコンセプト
すべてのWindowsデスクトップオートメーションはUI Automation(UIA)に依存します。これはWindowsに組み込まれたアクセシビリティAPIです。サポートされているすべてのフレームワークは、読み取りおよび操作可能なプロパティを持つUIA要素のツリーを公開します:
テスト(Python)
└── pywinauto(UIAバックエンド)
└── Windows UI Automation API ← Windowsに組み込み、フレームワーク非依存
└── アプリのUIAプロバイダー ← 各フレームワークが独自に実装
└── 実行中の .exeフレームワーク別UIA品質:
| フレームワーク | AutomationId | 信頼性 | 注記 |
|---|---|---|---|
| WPF | 5/5 | 優秀 | x:Name が直接AutomationIdにマッピング |
| WinForms | 4/5 | 良好 | AccessibleName = AutomationId |
| UWP / WinUI 3 | 5/5 | 優秀 | Microsoftの完全サポート |
| Qt 6.x | 5/5 | 優秀 | アクセシビリティがデフォルトで有効;クラス名が Qt6* に変更 |
| Qt 5.15+ | 4/5 | 良好 | Accessibilityモジュールが改善 |
| Qt 5.7–5.14 | 3/5 | 普通 | QT_ACCESSIBILITY=1 が必要;objectNameは手動設定 |
| Win32 / MFC | 3/5 | 普通 | コントロールIDにアクセス可能;テキストマッチングが一般的 |
セットアップと前提条件
# Python 3.8+、Windowsのみ
pip install pywinauto pytest pytest-html Pillow pytest-timeout
# オプション:画面録画
# ffmpegをインストールしてPATHに追加:https://ffmpeg.org/download.htmlUIAが到達可能か確認:
from pywinauto import Desktop
Desktop(backend="uia").windows() # すべてのトップレベルウィンドウを一覧表示Accessibility Insights for Windowsをインストールしてください(Microsoft提供、無料)— テストを書く前にUIA要素ツリーを検査するためのDevTools相当のツールです。
テスタビリティのセットアップ(フレームワーク別)
テストを書く前に全てのインタラクティブなコントロールに安定したAutomationIdを設定することが最も効果的です。
WPF
<!-- XAML: x:Name が自動的にAutomationIdになる -->
<TextBox x:Name="usernameInput" />
<PasswordBox x:Name="passwordInput" />
<Button x:Name="btnLogin" Content="Login" />
<TextBlock x:Name="lblError" />WinForms
// デザイナーまたはコードで設定
usernameInput.AccessibleName = "usernameInput";
passwordInput.AccessibleName = "passwordInput";
btnLogin.AccessibleName = "btnLogin";
lblError.AccessibleName = "lblError";Win32 / MFC
// .rcファイルのコントロールリソースIDがAutomationId文字列として公開される
// IDC_EDIT_USERNAME -> AutomationId "1001"
// 名前にはSetWindowTextを優先;より豊かなサポートにはIAccessibleを追加するQt — 以下の専用セクションを参照
ページオブジェクトモデル
tests/
├── conftest.py # アプリ起動フィクスチャ、失敗時スクリーンショット
├── pytest.ini
├── config.py
├── pages/
│ ├── __init__.py # インポートに必須
│ ├── base_page.py # ロケーター、ウェイト、スクリーンショットヘルパー
│ ├── login_page.py
│ └── main_page.py
├── tests/
│ ├── __init__.py
│ ├── test_login.py
│ └── test_main_flow.py
└── artifacts/ # スクリーンショット、動画、ログbase_page.py
import os, time
from pywinauto import Desktop
from config import ACTION_TIMEOUT, ARTIFACT_DIR
class BasePage:
def __init__(self, window):
self.window = window
# --- ロケーター(優先順位順)---
def by_id(self, auto_id, **kw):
"""AutomationId — 最も安定。第一選択として使用する。"""
return self.window.child_window(auto_id=auto_id, **kw)
def by_name(self, name, **kw):
"""表示テキスト / アクセシブルな名前。"""
return self.window.child_window(title=name, **kw)
def by_class(self, cls, index=0, **kw):
"""コントロールクラス + インデックス — 脆弱、可能なら避ける。"""
return self.window.child_window(class_name=cls, found_index=index, **kw)
# --- ウェイト ---
def wait_visible(self, spec, timeout=ACTION_TIMEOUT):
spec.wait("visible", timeout=timeout)
return spec
def wait_gone(self, spec, timeout=ACTION_TIMEOUT):
spec.wait_not("visible", timeout=timeout)
return spec
def wait_window(self, title, timeout=ACTION_TIMEOUT):
"""新しいトップレベルウィンドウ(ダイアログ、子ウィンドウ)を待つ。"""
dlg = Desktop(backend="uia").window(title=title)
dlg.wait("visible", timeout=timeout)
return dlg
def wait_until(self, fn, timeout=ACTION_TIMEOUT, interval=0.3):
"""任意の条件をポーリング — UIAイベントが信頼できない場合に使用する。"""
deadline = time.time() + timeout
while time.time() < deadline:
try:
if fn():
return True
except Exception:
pass
time.sleep(interval)
raise TimeoutError(f"条件が{timeout}秒以内に満たされなかった")
# --- アクション ---
def click(self, spec):
self.wait_visible(spec)
spec.click_input()
def type_text(self, spec, text):
self.wait_visible(spec)
ctrl = spec.wrapper_object()
try:
ctrl.set_edit_text(text)
except Exception as e:
# Qt 5.x フォールバック:UIA Value Pattern が不完全な場合がある
import sys, pywinauto.keyboard as kb
print(f"[windows-desktop-e2e] set_edit_text 失敗 ({e})、キーボードフォールバックを使用", file=sys.stderr)
ctrl.click_input()
kb.send_keys("^a")
kb.send_keys(text, with_spaces=True)
def get_text(self, spec):
ctrl = spec.wrapper_object()
for attr in ("window_text", "get_value"):
try:
v = getattr(ctrl, attr)()
if v:
return v
except Exception:
pass
return ""
# --- アーティファクト ---
def screenshot(self, name):
os.makedirs(ARTIFACT_DIR, exist_ok=True)
path = os.path.join(ARTIFACT_DIR, f"{name}.png")
self.window.capture_as_image().save(path)
return pathlogin_page.py
from pages.base_page import BasePage
class LoginPage(BasePage):
@property
def username(self): return self.by_id("usernameInput")
@property
def password(self): return self.by_id("passwordInput")
@property
def btn_login(self): return self.by_id("btnLogin")
@property
def error_label(self): return self.by_id("lblError")
def login(self, user, pwd):
self.type_text(self.username, user)
self.type_text(self.password, pwd)
self.click(self.btn_login)
def login_ok(self, user, pwd, main_title="Main Window"):
self.login(user, pwd)
return self.wait_window(main_title)
def login_fail(self, user, pwd):
self.login(user, pwd)
self.wait_visible(self.error_label)
return self.get_text(self.error_label)conftest.py
新しいプロジェクトではTier 1サンドボックスフィクスチャ(以下参照)を優先してください — 追加コストゼロでファイルシステムの分離が追加されます。この基本フィクスチャは最小限/レガシーセットアップ専用です。
import os, pytest
os.environ["QT_ACCESSIBILITY"] = "1" # Qt 5.x UIAサポートに必要
from pywinauto import Application
from config import APP_PATH, MAIN_WINDOW_TITLE, LAUNCH_TIMEOUT, ARTIFACT_DIR
@pytest.fixture
def app(request):
if not APP_PATH:
pytest.exit("APP_PATH 環境変数が設定されていない", returncode=1)
proc = Application(backend="uia").start(APP_PATH, timeout=LAUNCH_TIMEOUT)
win = proc.window(title=MAIN_WINDOW_TITLE)
win.wait("visible", timeout=LAUNCH_TIMEOUT)
yield win
# 失敗時のスクリーンショット
if getattr(getattr(request.node, "rep_call", None), "failed", False):
os.makedirs(ARTIFACT_DIR, exist_ok=True)
try:
win.capture_as_image().save(
os.path.join(ARTIFACT_DIR, f"FAIL_{request.node.name}.png")
)
except Exception:
pass
# グレースフルな終了を試み、フォールバックとして強制終了
# proc は pywinauto Application — wait_for_process() ではなく wait_for_process_exit() を使用
try:
win.close()
proc.wait_for_process_exit(timeout=5)
except Exception:
proc.kill()
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yieldMore from affaan-m
Use this skill in a project
Open any RiverX project, search the Skills panel for affaan-m/ecc/windows-desktop-e2e, and install it in one click.
