Communitygithub.com

windows-desktop-e2e

pywinautoとWindows UI Automationを使用して、WPF、WinForms、Win32/MFC、QtネイティブWindowsデスクトップアプリのE2Eテストを記述・実行するためのスキルです。

windows-desktop-e2e とは?

このスキルは、Windowsネイティブデスクトップアプリケーションのエンドツーエンド(E2E)テストを支援するために設計されています。pywinautoをWindows UI Automation(UIA)バックエンドとして使用し、WPF、WinForms、Win32/MFC、Qt(5.x/6.x)など、主要なWindowsデスクトップフレームワークをカバーします。テスト自動化の専門家は、このスキルを活用することで、テストスイートのセットアップから、Page Object Modelの実装、テスト容易性の向上、CI/CDパイプライン(例:GitHub Actionsのwindows-latest)への統合まで、一貫したワークフローを実現できます。スキルは、安定したロケーター戦略(AutomationIdの優先)、フレームワークごとの設定手順、フレーキーなテストの診断と修正、成果物管理(スクリーンショット、動画)などのベストプラクティスを提供します。これにより、開発者はメンテナンス性が高く信頼性のあるデスクトップUIテストを迅速に構築できます。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/windows-desktop-e2e

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Windows デスクトップ E2E テスト

pywinauto と Windows UI Automation (UIA) を使用した Windows ネイティブデスクトップアプリケーションのエンドツーエンドテスト。WPF、WinForms、Win32/MFC、Qt (5.x / 6.x) をカバーし、Qt 固有のガイダンスは専用セクションとして提供します。

使用するタイミング

  • Windows ネイティブデスクトップアプリケーションの E2E テストを作成または実行する場合
  • デスクトップ GUI テストスイートを新たにセットアップする場合
  • 信頼性が低い、または失敗するデスクトップ自動化テストを診断する場合
  • 既存のアプリにテスト容易性(AutomationId、アクセス可能な名前)を追加する場合
  • デスクトップ E2E テストを CI/CD パイプライン(GitHub Actions の windows-latest)に統合する場合

使用しないタイミング

  • Web アプリケーション → e2e-testing スキル(Playwright)を使用
  • Electron / CEF / WebView2 アプリ → HTML レイヤーにはブラウザ自動化が必要であり、UIA は不適切
  • モバイルアプリ → プラットフォーム固有のツール(UIAutomator、XCUITest)を使用
  • 実行中の GUI を必要としない純粋なユニットテストまたは統合テスト

基本概念

すべての Windows デスクトップ自動化は、UI Automation (UIA)、Windows 組み込みのアクセシビリティ API に依存しています。サポートされているすべてのフレームワークは、Claude が読み取り操作できるプロパティを持つ UIA 要素のツリーを公開します。

あなたのテスト (Python)
    └── pywinauto (UIA バックエンド)
        └── Windows UI Automation API   ← Windows に組み込み、フレームワークに依存しない
            └── アプリの UIA プロバイダー      ← 各フレームワークが独自のものを提供
                └── 実行中の .exe

フレームワーク別の UIA 品質:

フレームワークAutomationId信頼性備考
WPF5/5非常に良いx:Name が AutomationId に直接マッピング
WinForms4/5良いAccessibleName = AutomationId
UWP / WinUI 35/5非常に良いMicrosoft による完全サポート
Qt 6.x5/5非常に良いアクセシビリティはデフォルトで有効。クラス名は Qt6* に変更
Qt 5.15+4/5良い改善された Accessibility モジュール
Qt 5.7–5.143/5普通QT_ACCESSIBILITY=1 が必要。objectName は手動設定
Win32 / MFC3/5普通コントロール ID にアクセス可能。テキストマッチングが一般的

セットアップと前提条件

# Python 3.8+、Windows のみ
pip install pywinauto pytest pytest-html Pillow pytest-timeout
# オプション:画面録画
# ffmpeg をインストールし、PATH に追加: https://ffmpeg.org/download.html

UIA が到達可能か確認:

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"
// Name には 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 値パターンが不完全な可能性あり
            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 path

login_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_exit() を使用、wait_for_process() ではない
    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 = yield
    setattr(item, f"rep_{outcome.get_result().when}", outcome.get_result())

config.py

import os
APP_PATH          = os.environ.get("APP_PATH", "")           # 環境変数で設定 — デフォルトパスなし
MAIN_WINDOW_TITLE = os.environ.get("APP_TITLE", "")
LAUNCH_TIMEOUT    = int(os.environ.get("LAUNCH_TIMEOUT", "15"))
ACTION_TIMEOUT    = int(os.environ.get("ACTION_TIMEOUT", "10"))
ARTIFACT_DIR      = os.path.join(os.path.dirname(__file__), "artifacts")

pytest.ini

[pytest]
testpaths = tests
markers =
    smoke: クリティカルパスの高速スモークテスト
    flaky: 既知の不安定なテスト
addopts = -v --tb=short --html=artifacts/report.html --self-contained-html

ロケーター戦略

AutomationId  >  Name (テキスト)  >  ClassName + インデックス  >  XPath
  (安定)           (可読性)            (脆弱)                   (最後の手段)

Accessibility Insights で検査 → Properties ペイン → 最初に AutomationId を探す。

# 実行時に検査 — REPL に貼り付けてツリーを探索
win.print_control_identifiers()
# または範囲を絞る:
win.child_window(auto_id="groupBox1").print_control_identifiers()

待機パターン

# コントロールが表示されるのを待機
page.wait_visible(page.by_id("statusLabel"))

# コントロールが消えるのを待機(例:ローディングスピナー)
page.wait_gone(page.by_id("spinnerOverlay"))

# ダイアログがポップアップするのを待機
dlg = page.wait_window("Confirm Delete")

# カスタム条件(例:テキスト変更)
page.wait_until(lambda: page.get_text(page.by_id("lblStatus")) == "Ready")

主要な同期手段として time.sleep() を決して使用しない — wait() または wait_until() を使用する。

成果物管理

# 必要に応じてスクリーンショット
page.screenshot("after_login")

# 全画面キャプチャ(ウィンドウが画面外または最小化されている場合)
import pyautogui
pyautogui.screenshot("artifacts/fullscreen.png")

# ffmpeg での画面録画(テスト前に開始、テスト後に停止)
import subprocess

def start_recording(name):
    return subprocess.Popen([
        "ffmpeg", "-f", "gdigrab", "-framerate", "10",
        "-i", "desktop", "-y", f"artifacts/videos/{name}.mp4"
    ], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def stop_recording(proc):
    proc.stdin.write(b"q"); proc.stdin.flush(); proc.wait(timeout=10)

ステップごとのトレース(オプトイン)

デフォルトの失敗時スクリーンショットだけでは、フレーキーなテストを診断するには不十分な場合があります。以下のステップレベルのトレースはデフォルトではオフです。フレーキーなケースを再現する場合にのみ有効にしてください。

有効化

E2E_TRACE=1 pytest tests/test_login.py -v
# JSONL ログに入力テキストを含める(クレデンシャルや PII を入力するテストでは使用しない):
E2E_TRACE=1 E2E_TRACE_INCLUDE_TEXT=1 pytest ...

BasePage へのパッチ

import os, json, time
TRACE_ENABLED      = os.environ.get("E2E_TRACE") == "1"
TRACE_INCLUDE_TEXT = os.environ.get("E2E_TRACE_INCLUDE_TEXT") == "1"

class BasePage:
    _step = 0

    def _trace(self, action, spec=None, text=None):
        if not TRACE_ENABLED:
            return
        BasePage._step += 1
        idx = f"{BasePage._step:03d}"
        os.makedirs(ARTIFACT_DIR, exist_ok=True)
        try:
            self.window.capture_as_image().save(
                os.path.join(ARTIFACT_DIR, f"step_{idx}_{action}.png"))
        except Exception:
            pass  # キャプチャ失敗でテストが中断されないようにする
        rec = {
            "ts": time.time(), "step": BasePage._step, "action": action,
            "locator": getattr(spec, "criteria", None),
            "text": text if TRACE_INCLUDE_TEXT else ("<redacted>" if text else None),
        }
        with open(os.path.join(ARTIFACT_DIR, "trace.jsonl"), "a") as f:
            f.write(json.dumps(rec) + "\n")

    def click(self, spec):
        self.wait_visible(spec); self._trace("click_before", spec)
        spec.click_input();      self._trace("click_after",  spec)

    def type_text(self, spec, text):
        self.wait_visible(spec); self._trace("type_before", spec, text)
        # ... 既存の set_edit_text / キーボードフォールバック ...
        self._trace("type_after", spec)

注意点

  • PII / クレデンシャル: type_text の内容はデフォルトで <redacted> になります。ログインや支払いフローでは E2E_TRACE_INCLUDE_TEXT=1 を決して設定しないでください。
  • オーバーヘッド: アクションあたり約 50~200ms + ステップごとに 1 つの PNG がディスクに保存されます。デフォルトの CI マトリックスでは有効にせず、専用のフレーク再現ジョブでのみ使用してください。
  • 成果物の肥大化: 長いフローでは数十 MB になる可能性があります。それに応じて retention-days を調整してください。
  • 並列/再実行時の整合性: この単純な例では trace.jsonl に追記し、クラスレベルのカウンターを使用しています。再実行前にアーティファクトディレクトリをクリアし、並列テストではワーカーごとのアーティファクトディレクトリを使用してください。
  • カバレッジのギャップ: BasePage の外部で実行されたアクション(テストコード内での生の pywinauto 呼び出し)はトレースされません。

フレーキーテストの処理

# 隔離 — Playwright の test.fixme() に相当
@pytest.mark.skip(reason="フレーキー: 低速な CI でのアニメーションレース。Issue #42")
def test_animated_transition(self, app): ...

# Skip i

…

[truncated for translation]

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

関連スキル