Agent skill

Windows Desktop E2E

by affaan-m in affaan-m/ECC

E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation.

MITAuto-check passedTesting & QA

Install Windows Desktop E2E

skills CLI
$ npx skills add affaan-m/ECC --skill windows-desktop-e2e -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install affaan-m/ECC windows-desktop-e2e --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/affaan-m/ECC.git skills-src && mkdir -p .claude/skills && cp -r skills-src/docs/ja-JP/skills/windows-desktop-e2e .claude/skills/windows-desktop-e2e && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
windows-desktop-e2e
GitHub stars
276k
Token cost
~5.5k tokens
SKILL.md length
268 words
Files
1
Skills in repo
673
Repo updated
First seen
Licence
MIT

At a glance

E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation.

  • Tasks that involve End-to-end testing
  • SKILL.md covers アクティベートするタイミング, コアコンセプト, セットアップと前提条件 and テスタビリティのセットアップ(フレームワーク別), plus 8 more sections
  • Calls pytest and pip; reaches ffmpeg.org; needs TID_PASSWORD

What it does

Windows Desktop E2E is an agent skill from affaan-m/ECC. E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation.

Its SKILL.md is about 5.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Testing & QA, covering End-to-end testing. It works with Windows. The repository describes itself as: The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond. The licence is MIT.

When your agent uses it

  • Tasks that involve End-to-end testing

Example prompts

  • “/windows-desktop-e2e”

Requirements

  • Python 3

What it can do on your machine

Read from SKILL.md and the folder at commit ef648e0. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • pytest
    • pip

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • ffmpeg.org

    Also links to:

    • learn.microsoft.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • TID_PASSWORD

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Windows Desktop E2E loads about 5.5k tokens when it runs. Until then it costs about 34 tokens; SKILL.md has 268 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~34
When it runs · the whole SKILL.md, loaded when a task matches
~5.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from affaan-m/ECC at commit ef648e0, republished under its MIT licence (© affaan-m). 268 words, ~5,536 tokens.

Download SKILL.mdSave it as .claude/skills/windows-desktop-e2e/SKILL.md (or your agent's skills folder).
name
windows-desktop-e2e
description
E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation.
origin
ECC

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信頼性注記
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にアクセス可能;テキストマッチングが一般的

セットアップと前提条件

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

UIAが到達可能か確認:

python
from pywinauto import Desktop
Desktop(backend="uia").windows()  # すべてのトップレベルウィンドウを一覧表示

Accessibility Insights for Windowsをインストールしてください(Microsoft提供、無料)— テストを書く前にUIA要素ツリーを検査するためのDevTools相当のツールです。

テスタビリティのセットアップ(フレームワーク別)

テストを書く前に全てのインタラクティブなコントロールに安定したAutomationIdを設定することが最も効果的です。

WPF
xml
<!-- XAML: x:Name が自動的にAutomationIdになる -->
<TextBox x:Name="usernameInput" />
<PasswordBox x:Name="passwordInput" />
<Button x:Name="btnLogin" Content="Login" />
<TextBlock x:Name="lblError" />
WinForms
csharp
// デザイナーまたはコードで設定
usernameInput.AccessibleName = "usernameInput";
passwordInput.AccessibleName = "passwordInput";
btnLogin.AccessibleName = "btnLogin";
lblError.AccessibleName = "lblError";
Win32 / MFC
cpp
// .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
python
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
python
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サンドボックスフィクスチャ(以下参照)を優先してください — 追加コストゼロでファイルシステムの分離が追加されます。この基本フィクスチャは最小限/レガシーセットアップ専用です。

python
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 = yield
    setattr(item, f"rep_{outcome.get_result().when}", outcome.get_result())
config.py
python
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
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 を確認。

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

ウェイトパターン

python
# コントロールが表示されるのを待つ
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() を使用してください。

アーティファクト管理

python
# オンデマンドスクリーンショット
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)

不安定なテストの対処

python
# 隔離 — PlaywrightのtestのFixmeと同等
@pytest.mark.skip(reason="不安定:遅いCIでのアニメーションレース。Issue #42")
def test_animated_transition(self, app): ...

# CIのみでスキップ
@pytest.mark.skipif(os.environ.get("CI") == "true", reason="CIで不安定 #43")
def test_heavy_load(self, app): ...

一般的な原因と修正:

原因修正
コントロールが準備できていないtime.sleep を wait_visible に置き換える
ウィンドウがフォーカスされていないインタラクション前に win.set_focus() を追加する
アニメーション進行中wait_until(lambda: not loading_indicator.exists())
ダイアログのタイミングwait_window(title, timeout=15)
CI環境のディスプレイが準備できていないDISPLAY を設定するかCIで仮想デスクトップを使用する

テスト分離とサンドボックス

分離の3つの階層 — ニーズを満たす最も軽い階層を使用してください。

Tier 1 — ファイルシステム分離(デフォルト、常に使用)

各テストは subprocess.Popen と Application.connect() を通じて独自の APPDATA / LOCALAPPDATA / TEMP を取得します。pytestの tmp_path フィクスチャがクリーンアップを自動的に処理します。

python
# conftest.py — 基本的な `app` フィクスチャをこれに置き換える
import os, subprocess, pytest
from pywinauto import Application
from config import APP_PATH, APP_ARGS, APP_TITLE, LAUNCH_TIMEOUT, ACTION_TIMEOUT, ARTIFACT_DIR

@pytest.fixture(scope="function")
def app(request, tmp_path):
    """テストごとに新しいプロセス + 分離されたユーザーデータディレクトリ。"""
    if not APP_PATH:
        pytest.exit("APP_PATH が設定されていない", returncode=1)

    # 全てのユーザーストレージを分離されたtmpディレクトリにリダイレクト
    sandbox_env = os.environ.copy()
    sandbox_env["QT_ACCESSIBILITY"]  = "1"
    sandbox_env["APPDATA"]           = str(tmp_path / "AppData" / "Roaming")
    sandbox_env["LOCALAPPDATA"]      = str(tmp_path / "AppData" / "Local")
    sandbox_env["TEMP"] = sandbox_env["TMP"] = str(tmp_path / "Temp")
    for p in (sandbox_env["APPDATA"], sandbox_env["LOCALAPPDATA"], sandbox_env["TEMP"]):
        os.makedirs(p, exist_ok=True)

    if not APP_TITLE:
        pytest.exit("APP_TITLE 環境変数が設定されていない", returncode=1)

    # shlex.splitはスペースを含む引用符付き引数を処理;plain split()は壊れる
    import shlex
    # subprocessで起動して環境変数を渡し;PIDでpywinautoを接続
    proc = subprocess.Popen(
        [APP_PATH] + shlex.split(APP_ARGS),
        env=sandbox_env,
    )
    pw_app = Application(backend="uia").connect(process=proc.pid, timeout=LAUNCH_TIMEOUT)
    win    = pw_app.window(title=APP_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
    try:
        win.close()
        proc.wait(timeout=5)
    except Exception:
        proc.kill()
    # tmp_pathはpytestによって自動的にクリーンアップされる

@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())
Tier 2 — Windowsジョブオブジェクト(オプション:プロセスライフタイムの封じ込め)

プロセスをジョブオブジェクトにアタッチして、テストフィクスチャのジョブハンドルがGCされたときに自動的に終了させます。また、フィクスチャのクリーンアップから逃れる子プロセスのスポーンも防止します。

分離のスコープ: ジョブオブジェクトはファイルシステムアクセスの仮想化や ネットワークトラフィックのブロックを行いません。ファイル書き込みとネットワーク分離には AppContainer、Windowsファイアウォールルール、またはTier 3(Windowsサンドボックス)が必要です。 Tier 2はプロセスライフタイムと子プロセスの封じ込めにのみ使用してください。

追加の依存関係は不要です。

python
import ctypes, ctypes.wintypes as wt

def restrict_process(pid: int):
    """
    プロセスをジョブオブジェクトにアタッチして以下を防止:
    - ジョブ外でのプロセスのスポーン(LIMIT_KILL_ON_JOB_CLOSE)
    ネットワークはブロックしません — Windowsファイアウォールルールを使用してください。
    """
    JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000
    # 最小限の権限:SET_QUOTA (0x0100) | TERMINATE (0x0001)
    PROCESS_SET_QUOTA_AND_TERMINATE    = 0x0101

    kernel32 = ctypes.windll.kernel32
    job   = kernel32.CreateJobObjectW(None, None)
    hproc = kernel32.OpenProcess(PROCESS_SET_QUOTA_AND_TERMINATE, False, pid)

    # 正しい構造体レイアウト — LimitFlagsはオフセット+16(+44ではない)
    class JOBOBJECT_BASIC_LIMIT_INFORMATION(ctypes.Structure):
        _fields_ = [
            ("PerProcessUserTimeLimit", wt.LARGE_INTEGER),
            ("PerJobUserTimeLimit",     wt.LARGE_INTEGER),
            ("LimitFlags",             wt.DWORD),
            ("MinimumWorkingSetSize",   ctypes.c_size_t),
            ("MaximumWorkingSetSize",   ctypes.c_size_t),
            ("ActiveProcessLimit",      wt.DWORD),
            ("Affinity",               ctypes.c_size_t),
            ("PriorityClass",          wt.DWORD),
            ("SchedulingClass",        wt.DWORD),
        ]

    info = JOBOBJECT_BASIC_LIMIT_INFORMATION()
    info.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
    ok = kernel32.SetInformationJobObject(job, 2, ctypes.byref(info), ctypes.sizeof(info))
    if not ok:
        raise ctypes.WinError()
    kernel32.AssignProcessToJobObject(job, hproc)
    kernel32.CloseHandle(hproc)
    return job  # 生存を維持 — ジョブが閉じると(GC時)プロセスが終了する

# proc = subprocess.Popen(...) の後:  job = restrict_process(proc.pid)
Tier 3 — Windowsサンドボックス(CI完全OS分離)

実行ごとにクリーンなWindowsイメージが必要な場合(残留レジストリキーなし、共有GPUステートなし、真の分離)、Windowsサンドボックス内でテストスイート全体を実行します。

要件: Windows 10/11 Pro またはエンタープライズ、仮想化が有効。

プロジェクトルートに e2e-sandbox.wsb を作成:

xml
<Configuration>
  <MappedFolders>
    <!-- アプリバイナリ(読み取り専用) -->
    <MappedFolder>
      <HostFolder>C:\path\to\your\build\Release</HostFolder>
      <SandboxFolder>C:\app</SandboxFolder>
      <ReadOnly>true</ReadOnly>
    </MappedFolder>
    <!-- テストスイート(アーティファクト用に読み書き可能) -->
    <MappedFolder>
      <HostFolder>C:\path\to\your\e2e_test</HostFolder>
      <SandboxFolder>C:\e2e_test</SandboxFolder>
      <ReadOnly>false</ReadOnly>
    </MappedFolder>
  </MappedFolders>
  <LogonCommand>
    <!--
      WindowsサンドボックスはデフォルトでPythonがない。まずサイレントインストール、
      次に依存関係をインストールしてテストを実行する。アーティファクトは
      上記のMappedFolderを通じてホストに書き戻される。
    -->
    <Command>powershell -Command "
      winget install --id Python.Python.3.11 --silent --accept-package-agreements;
      $env:PATH += ';' + $env:LOCALAPPDATA + '\Programs\Python\Python311\Scripts';
      cd C:\e2e_test;
      pip install -r requirements.txt;
      pytest tests\ -v
    "</Command>
  </LogonCommand>
</Configuration>

起動:WindowsSandbox.exe e2e-sandbox.wsb

pywinautoとアプリは両方ともサンドボックス内で実行されます(同じセッションが必要)。 アーティファクトはマップされたフォルダーを通じてホストに書き戻されます。

階層の比較
階層分離セットアップコストCIで動作使用タイミング
1 — tmp_path 環境リダイレクトファイルシステムゼロ常に全テストのデフォルト
2 — ジョブオブジェクトプロセスツリー低常に子プロセスの逃走を防止
3 — Windowsサンドボックス完全OS中Pro/Enterpriseイメージが必要定期的なクリーンルーム実行
テストのハングを防止する

pytest-timeout を追加して単一テストに上限を設けます。pytest.ini で timeout = 60 と timeout_method = thread を設定します。注意:thread メソッドはWindows上でQtアプリのサブプロセスを終了できません — conftest.py に atexit.register(lambda: [p.kill() for p in psutil.Process().children(recursive=True)]) を追加してオーファンを刈り取ってください。

CI/CDインテグレーション

yaml
# .github/workflows/e2e-desktop.yml
name: Desktop E2E
on: [push, pull_request]

jobs:
  e2e:
    runs-on: windows-latest   # 実際のGUI環境、Xvfb不要
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with: { python-version: "3.11" }

      - name: 依存関係をインストール
        run: pip install pywinauto pytest pytest-html Pillow

      - name: アプリをビルド
        run: cmake --build build --config Release  # ビルドシステムに合わせて調整

      - name: E2Eを実行
        env:
          APP_PATH: ${{ github.workspace }}\build\Release\MyApp.exe
          APP_TITLE: "My Application"
          CI: "true"
        run: pytest tests/ --html=artifacts/report.html --self-contained-html --junitxml=artifacts/results.xml -v

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: e2e-artifacts
          path: artifacts/
          retention-days: 14

Qt固有

Qt 5.xでのUIA有効化

Qt 5.xのアクセシビリティは一部のビルド(特に5.7〜5.14)でデフォルトが無効です。起動前に環境変数を設定してください。Qt 6.xはデフォルトでアクセシビリティが有効です — Qt 6ではこのステップをスキップしてください。

python
# conftest.py — モジュールの先頭に追加
import os
os.environ["QT_ACCESSIBILITY"] = "1"

またはCIでエクスポート:

yaml
env:
  QT_ACCESSIBILITY: "1"
Qtウィジェットへの安定した識別子の追加
cpp
// 優先:objectNameとaccessibleNameの両方
void setTestId(QWidget* w, const char* id) {
    w->setObjectName(id);
    w->setAccessibleName(id);  // UIA Nameプロパティになる
}

// ダイアログコンストラクタ内:
setTestId(ui->usernameEdit, "usernameInput");
setTestId(ui->passwordEdit, "passwordInput");
setTestId(ui->loginButton,  "btnLogin");
setTestId(ui->errorLabel,   "lblError");

タイポを避けるためにすべてのIDをヘッダーに集約:

cpp
// test_ids.h
#define TID_USERNAME   "usernameInput"
#define TID_PASSWORD   "passwordInput"
#define TID_BTN_LOGIN  "btnLogin"
#define TID_LBL_ERROR  "lblError"
Qt固有の注意点

QComboBox — ドロップダウンは別のトップレベルウィンドウです:

python
from pywinauto import Desktop

def select_combo_item(page, combo_spec, item_text):
    page.click(combo_spec)
    # ドロップダウンは新しいルートレベルウィンドウとして表示される
    # class_nameはQtバージョンによって異なる — Accessibility Insightsで確認
    # Qt 5.x: "Qt5QWindowIcon"  |  Qt 6.x: "Qt6QWindowIcon" — Accessibility Insightsで確認
    popup = Desktop(backend="uia").window(class_name_re="Qt[56]QWindowIcon")
    popup.wait("visible", timeout=5)
    popup.child_window(title=item_text).click_input()

QMessageBox / QDialog — これも別のトップレベルウィンドウです:

python
dlg = page.wait_window("Confirm")          # ダイアログタイトルを待つ
dlg.child_window(title="OK").click_input() # 内部のボタンをクリック

QTableWidget / QTableView — 行/セルアクセス:

python
table = page.by_id("tblUsers").wrapper_object()
cell  = table.cell(row=0, column=1)
print(cell.window_text())

自己描画コントロール(paintEventのみ、QGraphicsView、QOpenGLWidget)— UIAは内部を見ることができません。以下のフォールバックセクションを使用してください。

フォールバック:スクリーンショットモード

コントロールがUIAで到達できない場合(自己描画、サードパーティ、ゲームエンジン):

bash
pip install pyautogui Pillow opencv-python
python
import pyautogui, cv2, numpy as np
from PIL import Image

def find_image_on_screen(template_path, confidence=0.85):
    """画面上のテンプレート画像を探す。(x, y) の中心またはNoneを返す。"""
    screen   = np.array(pyautogui.screenshot())
    template = np.array(Image.open(template_path))
    result   = cv2.matchTemplate(
        cv2.cvtColor(screen, cv2.COLOR_RGB2BGR),
        cv2.cvtColor(template, cv2.COLOR_RGB2BGR),
        cv2.TM_CCOEFF_NORMED,
    )
    _, max_val, _, max_loc = cv2.minMaxLoc(result)
    if max_val >= confidence:
        h, w = template.shape[:2]
        return max_loc[0] + w // 2, max_loc[1] + h // 2
    return None

def click_image(template_path, confidence=0.85):
    pos = find_image_on_screen(template_path, confidence)
    if pos is None:
        raise RuntimeError(f"画面上で画像が見つからない:{template_path}")
    pyautogui.click(*pos)

控えめに使用すること — 画像マッチングはDPI変更、テーマ切り替え、部分的な遮蔽で壊れます。 常にUIAを最初に試し、本当に到達できないコントロールにのみスクリーンショットにフォールバックしてください。

アンチパターン

python
# BAD: 固定スリープ
time.sleep(3)
page.click(page.by_id("btnSubmit"))

# GOOD: 条件ウェイト
page.wait_visible(page.by_id("btnSubmit"))
page.click(page.by_id("btnSubmit"))
python
# BAD: 主要戦略として脆弱なクラス+インデックスロケーター
page.by_class("Edit", index=2).type_keys("hello")

# GOOD: AutomationId
page.by_id("usernameInput").set_edit_text("hello")
python
# BAD: ピクセル座標でのアサート
assert btn.rectangle().left == 120

# GOOD: コンテンツ/状態でのアサート
assert page.get_text(page.by_id("lblStatus")) == "Logged in"
assert page.by_id("btnLogout").is_enabled()
python
# BAD: 全テストにわたってアプリインスタンスを共有(状態の漏洩)
@pytest.fixture(scope="session")
def app(): ...

# GOOD: テストごとに新しいプロセス(または最大でもクラスごと)
@pytest.fixture(scope="function")
def app(): ...

テストの実行

bash
# 全テスト
pytest tests/ -v

# スモークのみ
pytest tests/ -m smoke -v

# 特定ファイル
pytest tests/test_login.py -v

# カスタムアプリパスで実行
APP_PATH="C:\build\Release\MyApp.exe" APP_TITLE="MyApp" pytest tests/ -v

# 不安定なテストを検出(各テストを5回繰り返す)
pip install pytest-repeat
pytest tests/test_login.py --count=5 -v

関連スキル

  • e2e-testing — WebアプリケーションのPlaywright E2Eテスト
  • cpp-testing — GoogleTestを使用したC++ユニット/統合テスト
  • cpp-coding-standards — C++コードスタイルとパターン

© affaan-m, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in docs/ja-JP/skills/windows-desktop-e2e of affaan-m/ECC.

Open the folder on GitHubat commit ef648e0

Compare with similar skills

Windows Desktop E2E next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Windows Desktop E2E compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Windows Desktop E2E this skillaffaan-m/ECC276k—~5.5kAutomated safety check: PassMIT
Web Application Testinganthropics/skills180k51 repos~966Automated safety check: PassApache-2.0
OpenHarness End-to-End EvalsHKUDS/OpenHarness16k1 repos~2.1kAutomated safety check: NotesMIT
Write and Verify Playwright Testsappsmithorg/appsmith41k—~2.9kAutomated safety check: NotesApache-2.0
playwright-cli Browser Automationgithub/gh-aw5.4k23 repos~2.8kAutomated safety check: PassMIT
Cucumber and Playwright E2E Testslanggenius/dify158k—~682Automated safety check: PassCustom licence

Similar skills

  • Web Application Testing

    anthropics/skills

    Official

    Tests local web applications with Python Playwright scripts, checking frontend behavior, capturing screenshots and reading browser console logs.

    180k GitHub starsUsed in 51 repos~966 tokens
    Testing & QAAuto-check passed
  • Validates OpenHarness features by running real multi-turn agent loops with live LLM calls against an unfamiliar codebase, checking actual tool execution.

    16k GitHub starsUsed in 1 repo~2.1k tokens
    Testing & QAAuto-check: notes
  • Writes a Playwright end-to-end test from a prompt, runs it against a live Appsmith deployment and retries with fixes up to three times until it passes.

    41k GitHub stars~2.9k tokensUpdated today
    Testing & QAAuto-check: notes
  • Official

    Drives a real browser from the command line with playwright-cli to open pages, interact, mock requests, save state and work with Playwright tests.

    5.4k GitHub starsUsed in 23 repos~2.8k tokens
    Testing & QAAuto-check passed
  • Guides changes and reviews of the Cucumber and Playwright end-to-end suite under `e2e/`: feature files, step definitions, support code, tags, locators and assertions.

    158k GitHub stars~682 tokensUpdated today
    Testing & QAAuto-check passed
  • E2E Testing

    langflow-ai/langflow

    Write and review Playwright E2E tests for Langflow. An agent skill from langflow-ai/langflow.

    155k GitHub stars~3.3k tokensUpdated today
    Testing & QAAuto-check passed

More from affaan-m/ECC

All 673 skills in this repo
  • Skill Stocktake

    affaan-m/ECC

    Audits your installed Claude skills and commands for quality, with a quick mode for recently changed skills and a full mode that evaluates all of them through subagents.

    276k GitHub starsUsed in 5 repos~1.9k tokens
    Auto-check passed
  • Ingests, indexes, searches, edits and monitors video, audio and live streams through the VideoDB Python SDK, returning stream links, clips and timestamps.

    276k GitHub starsUsed in 3 repos~3.5k tokens
    Auto-check: notes
  • Rules Distillation

    affaan-m/ECC

    Scans installed skills for principles that recur across them and proposes rule-file changes: append, revise, add a section, create a file or leave as covered.

    276k GitHub starsUsed in 2 repos~2.3k tokens
    Auto-check passed
  • Builds DRAFT counterparty agreements from one markdown template and a small JSON spec per party, with clauses picked by the party's role.

    276k GitHub stars~2.9k tokensUpdated 4 days ago
    Auto-check passed
  • Measures whether agents actually follow a skill, rule or agent definition by generating scenarios at three strictness levels and scoring tool-call traces.

    276k GitHub starsUsed in 1 repo~623 tokens
    Auto-check passed
  • Instinct-based learning system that observes sessions via hooks, creates atomic instincts with confidence scoring, and evolves them into skills/commands/agents.

    276k GitHub stars~3.5k tokensUpdated 4 days ago
    Auto-check passed

Works with

Categories

Questions about Windows Desktop E2E

What does Windows Desktop E2E do?

E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation. Windows Desktop E2E is an agent skill from affaan-m/ECC. E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation.

When should I use Windows Desktop E2E?

Windows Desktop E2E fits situations like: tasks that involve End-to-end testing.

How do I install Windows Desktop E2E in Claude Code?

Run `npx skills add affaan-m/ECC --skill windows-desktop-e2e -a claude-code`. Or copy the skill folder (docs/ja-JP/skills/windows-desktop-e2e in affaan-m/ECC) into .claude/skills/windows-desktop-e2e in your project. Claude Code loads it when a task matches its description.

How do I install Windows Desktop E2E in Codex?

Run `npx skills add affaan-m/ECC --skill windows-desktop-e2e -a codex`. Or copy the skill folder (docs/ja-JP/skills/windows-desktop-e2e in affaan-m/ECC) into .agents/skills/windows-desktop-e2e in your project. Codex loads it when a task matches its description.

Can I use Windows Desktop E2E in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add affaan-m/ECC --skill windows-desktop-e2e -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/windows-desktop-e2e, .gemini/skills/windows-desktop-e2e, .github/skills/windows-desktop-e2e and .opencode/skills/windows-desktop-e2e in your project.

What does Windows Desktop E2E need to run?

Going by SKILL.md and its folder, Windows Desktop E2E needs the command-line tools its instructions call (pytest and pip) and credentials named TID_PASSWORD. Our summary lists: Python 3.

Does Windows Desktop E2E access the network?

SKILL.md names 2 domains. In commands or code: ffmpeg.org; the agent is likely to contact it when it follows the instructions. As links in the text: learn.microsoft.com. This is read from the text; nothing was executed.

Is Windows Desktop E2E safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Windows Desktop E2E use?

Windows Desktop E2E is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Windows Desktop E2E use?

About 5.5k tokens (SKILL.md is roughly 22k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Windows Desktop E2E?

Skills that share tags, products or a category with Windows Desktop E2E: Web Application Testing (anthropics/skills, 180k stars), OpenHarness End-to-End Evals (HKUDS/OpenHarness, 16k stars), Write and Verify Playwright Tests (appsmithorg/appsmith, 41k stars) and playwright-cli Browser Automation (github/gh-aw, 5.4k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Windows Desktop E2E?

affaan-m (a GitHub user) maintains it in affaan-m/ECC, which has 275,546 GitHub stars. The repository holds 673 skills in this directory. The repository was last updated on October 5, 2026.

Source: affaan-m/ECC on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.