Communitygithub.com

windows-desktop-e2e

pywinautoとWindows UI Automationを使用したWindowsネイティブデスクトップアプリ(WPF、WinForms、Win32/MFC、Qt)のE2Eテスト。

windows-desktop-e2e とは?

このスキルは、WindowsネイティブデスクトップアプリケーションのエンドツーエンドテストをpywinautoとWindows UI Automation(UIA)を用いて実施するためのものです。WPF、WinForms、Win32/MFC、Qt(5.x/6.x)といった主要なUIフレームワークをカバーし、それぞれのフレームワークに応じたテスト容易性の設定方法(AutomationIdの付与など)を提供します。ページオブジェクトモデルに基づいたテスト構造のテンプレート、安定したロケーター戦略、`wait_visible`や`wait_until`などの同期パターン、フレークなテストの診断に役立つステップトレース機能などを備えています。さらに、CI/CDパイプライン(特にGitHub Actionsの`windows-latest`ランナー)への統合を前提としており、テスト失敗時のスクリーンショット取得や画面録画などのアーティファクト管理機能も含まれています。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/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 要素のツリーを公開します。

Your test (Python)
    └── pywinauto (UIA backend)
        └── Windows UI Automation API   ← Windows に組み込み、フレームワーク非依存
            └── App's UIA provider      ← 各フレームワークが独自に提供
                └── Running .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 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 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", "")           # env 経由で設定 — デフォルトパスなし
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 (text)  >  ClassName + index  >  XPath
  (安定)           (可読性)          (脆弱)             (最終手段)

Accessibility Insights で検査 → プロパティ ペイン → 最初に 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~200 ミリ秒 + ステップあたり 1 つの PNG。デフォルトの CI マトリックスでは有効にせず、専用の不安定再現ジョブでのみ有効にしてください。
  • アーティファクトの肥大化: 長いフローでは数十 MB になります。retention-days を適宜調整してください。
  • 並列/再実行の衛生: このシンプルな例では trace.jsonl に追記し、クラスレベルのカウンターを使用しています。再実行前にアーティファクトディレクトリをクリアし、並列テストにはワーカーごとのアーティファクトディレクトリを使用してください。
  • カバレッジのギャップ: BasePage の外部で実行されたアクション(テストコード内の raw pywinauto 呼び出し)はトレースされません。

不安定なテストの処理

# 隔離 — Playwright の test.fixme() に相当
@pytest.mark.skip(reason="不安定: CI が遅い場合のアニメーション競合。Issue #42")
def test_animated_transition(self, app): ...

# スキップ

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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

関連スキル