Reintroduce the Windows desktop E2E testing skill from stale PR #1334 with current manifest wiring, package publish coverage, catalog counts, and sanitized environment-path guidance.
This commit is contained in:
Affaan Mustafa2026-05-11 18:55:50 -04:00committed byAffaan Mustafa
# Everything Claude Code (ECC) — Agent Instructions
This is a **production-ready AI coding plugin** providing 54 specialized agents, 204 skills, 69 commands, and automated hook workflows for software development.
This is a **production-ready AI coding plugin** providing 54 specialized agents, 205 skills, 69 commands, and automated hook workflows for software development.
description: E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation.
origin: ECC
---
# Windows Desktop E2E Testing
End-to-end testing for Windows native desktop applications using **pywinauto** backed by Windows UI Automation (UIA). Covers WPF, WinForms, Win32/MFC, and Qt (5.x / 6.x) — with Qt-specific guidance as a dedicated section.
## When to Activate
- Writing or running E2E tests for a Windows native desktop application
- Setting up a desktop GUI test suite from scratch
- Diagnosing flaky or failing desktop automation tests
- Adding testability (AutomationId, accessible names) to an existing app
- Integrating desktop E2E into a CI/CD pipeline (GitHub Actions `windows-latest`)
### When NOT to Use
- Web applications → use `e2e-testing` skill (Playwright)
- Electron / CEF / WebView2 apps → the HTML layer needs browser automation, not UIA
- Mobile apps → use platform-specific tools (UIAutomator, XCUITest)
- Pure unit or integration tests that don't need a running GUI
## Core Concepts
All Windows desktop automation relies on **UI Automation (UIA)**, a Windows-built-in accessibility API. Every supported framework exposes a tree of UIA elements with properties Claude can read and act on:
```
Your test (Python)
└── pywinauto (UIA backend)
└── Windows UI Automation API ← built into Windows, framework-agnostic
└── App's UIA provider ← each framework ships its own
# Install ffmpeg and add to PATH: https://ffmpeg.org/download.html
```
Verify UIA is reachable:
```python
from pywinauto import Desktop
Desktop(backend="uia").windows() # lists all top-level windows
```
Install **Accessibility Insights for Windows** (free, from Microsoft) — your DevTools equivalent for inspecting the UIA element tree before writing any test.
## Testability Setup (by Framework)
The single most impactful thing you can do is **give every interactive control a stable AutomationId** before writing tests.
> For new projects prefer the **Tier 1 sandbox fixture** (see below) — it adds filesystem isolation at zero extra cost. This basic fixture is for minimal/legacy setups only.
```python
import os, pytest
os.environ["QT_ACCESSIBILITY"] = "1" # Required for Qt 5.x UIA support
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 environment variable is not set", returncode=1)
Each test gets its own `APPDATA` / `LOCALAPPDATA` / `TEMP` via `subprocess.Popen` and `Application.connect()`. pytest's `tmp_path` fixture handles cleanup automatically.
```python
# conftest.py — replace the basic `app` fixture with this
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):
"""Fresh process + isolated user-data dirs per test."""
if not APP_PATH:
pytest.exit("APP_PATH not set", returncode=1)
# Redirect all per-user storage to an isolated tmp directory
| 1 — `tmp_path` env redirect | Filesystem | Zero | Always | Default for all tests |
| 2 — Job Object | Process tree | Low | Always | Prevent child-process escape |
| 3 — Windows Sandbox | Full OS | Medium | Needs Pro/Enterprise image | Nightly clean-room runs |
### Prevent hanging tests
Add `pytest-timeout` to cap any single test. In `pytest.ini` set `timeout = 60` and `timeout_method = thread`. Note: `thread` method cannot kill Qt app subprocesses on Windows — add `atexit.register(lambda: [p.kill() for p in psutil.Process().children(recursive=True)])` in `conftest.py` to reap orphans.
## CI/CD Integration
```yaml
# .github/workflows/e2e-desktop.yml
name: Desktop E2E
on: [push, pull_request]
jobs:
e2e:
runs-on: windows-latest # real GUI environment, no Xvfb needed
Qt 5.x accessibility is disabled by default in some builds (especially 5.7–5.14). Set the environment variable **before** launching. Qt 6.x enables accessibility by default — skip this step for Qt 6.
```python
# conftest.py — add at module top
import os
os.environ["QT_ACCESSIBILITY"] = "1"
```
Or export it in CI:
```yaml
env:
QT_ACCESSIBILITY: "1"
```
### Add Stable Identifiers to Qt Widgets
```cpp
// Preferred: both objectName and accessibleName
void setTestId(QWidget* w, const char* id) {
w->setObjectName(id);
w->setAccessibleName(id); // becomes UIA Name property
}
// In your dialog constructor:
setTestId(ui->usernameEdit, "usernameInput");
setTestId(ui->passwordEdit, "passwordInput");
setTestId(ui->loginButton, "btnLogin");
setTestId(ui->errorLabel, "lblError");
```
Centralise all IDs in a header to avoid typos:
```cpp
// test_ids.h
#define TID_USERNAME "usernameInput"
#define TID_PASSWORD "passwordInput"
#define TID_BTN_LOGIN "btnLogin"
#define TID_LBL_ERROR "lblError"
```
### Qt-Specific Quirks
**QComboBox** — the dropdown is a separate top-level window:
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.