Communitygithub.com

windows-desktop-e2e

Tests E2E pour applications de bureau Windows natives (WPF, WinForms, Win32/MFC, Qt) utilisant pywinauto et UI Automation. À utiliser pour écrire des tests E2E pour une application de bureau Windows native avec pywinauto ou UI Automation.

Qu'est-ce que windows-desktop-e2e ?

Cette compétence est conçue pour les développeurs et ingénieurs QA qui ont besoin d'automatiser les tests E2E d'applications de bureau Windows natives. Elle s'appuie sur **pywinauto** avec le backend Windows UI Automation (UIA) pour interagir avec les interfaces utilisateur des frameworks WPF, WinForms, Win32/MFC et Qt. La compétence couvre la mise en place de l'environnement de test, la création d'un modèle Page Object, les stratégies de localisation des éléments, les mécanismes d'attente robustes, la gestion des artefacts (captures d'écran, enregistrement vidéo) et le débogage des tests intermittents. Elle fournit des directives spécifiques à chaque framework pour améliorer la testabilité (notamment via AutomationId) et intègre des pratiques CI/CD avec GitHub Actions. Elle inclut également un système de trace facultatif par étape pour diagnostiquer les tests flaky et des modèles de gestion des tests instables, comme la quarantaine de tests avec `pytest.mark.skip`. La compétence est utilisable avec n'importe quel éditeur de code ou agent IA qui supporte l'exécution de scripts Python et de commandes shell sur Windows.

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

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Tests E2E Windows Desktop

Tests de bout en bout pour les applications de bureau Windows natives utilisant pywinauto avec le backend Windows UI Automation (UIA). Couvre WPF, WinForms, Win32/MFC et Qt (5.x / 6.x) — avec une section dédiée à Qt.

Quand activer

  • Rédaction ou exécution de tests E2E pour une application de bureau Windows native
  • Mise en place d'une suite de tests d'interface graphique de bureau à partir de zéro
  • Diagnostic de tests d'automatisation de bureau intermittents ou échoués
  • Ajout de testabilité (AutomationId, noms accessibles) à une application existante
  • Intégration de tests E2E de bureau dans un pipeline CI/CD (GitHub Actions windows-latest)

Quand NE PAS utiliser

  • Applications Web → utiliser la compétence e2e-testing (Playwright)
  • Applications Electron / CEF / WebView2 → la couche HTML nécessite l'automatisation du navigateur, pas UIA
  • Applications mobiles → utiliser les outils spécifiques à la plateforme (UIAutomator, XCUITest)
  • Tests unitaires ou d'intégration purs qui ne nécessitent pas d'interface graphique en cours d'exécution

Concepts de base

Toute l'automatisation de bureau Windows repose sur UI Automation (UIA), une API d'accessibilité intégrée à Windows. Chaque framework pris en charge expose une arborescence d'éléments UIA avec des propriétés que Claude peut lire et sur lesquelles il peut agir :

Votre test (Python)
    └── pywinauto (backend UIA)
        └── API Windows UI Automation   ← intégré à Windows, indépendant du framework
            └── Fournisseur UIA de l'application ← chaque framework fournit le sien
                └── .exe en cours d'exécution

Qualité UIA par framework :

FrameworkAutomationIdFiabilitéRemarques
WPF5/5Excellentex:Name correspond directement à AutomationId
WinForms4/5BonneAccessibleName = AutomationId
UWP / WinUI 35/5ExcellentePrise en charge Microsoft complète
Qt 6.x5/5ExcellenteAccessibilité activée par défaut ; les noms de classe deviennent Qt6*
Qt 5.15+4/5BonneModule d'accessibilité amélioré
Qt 5.7–5.143/5MoyenneNécessite QT_ACCESSIBILITY=1 ; objectName manuel
Win32 / MFC3/5MoyenneID de contrôle accessibles ; correspondance de texte courante

Installation et prérequis

# Python 3.8+, Windows uniquement
pip install pywinauto pytest pytest-html Pillow pytest-timeout
# Facultatif : enregistrement d'écran
# Installer ffmpeg et l'ajouter au PATH : https://ffmpeg.org/download.html

Vérifiez que UIA est accessible :

from pywinauto import Desktop
Desktop(backend="uia").windows()  # liste toutes les fenêtres de premier niveau

Installez Accessibility Insights for Windows (gratuit, de Microsoft) — votre équivalent DevTools pour inspecter l'arborescence des éléments UIA avant d'écrire un test.

Configuration de la testabilité (par framework)

La chose la plus importante que vous puissiez faire est de donner à chaque contrôle interactif un AutomationId stable avant d'écrire des tests.

WPF

<!-- XAML : x:Name devient automatiquement AutomationId -->
<TextBox x:Name="usernameInput" />
<PasswordBox x:Name="passwordInput" />
<Button x:Name="btnLogin" Content="Connexion" />
<TextBlock x:Name="lblError" />

WinForms

// Définir dans le designer ou le code
usernameInput.AccessibleName = "usernameInput";
passwordInput.AccessibleName = "passwordInput";
btnLogin.AccessibleName = "btnLogin";
lblError.AccessibleName = "lblError";

Win32 / MFC

// Les ID de ressource de contrôle dans le fichier .rc sont exposés sous forme de chaînes AutomationId
// IDC_EDIT_USERNAME -> AutomationId "1001"
// Préférez SetWindowText pour le nom ; ajoutez IAccessible pour une prise en charge plus riche

Qt — voir la section dédiée ci-dessous


Modèle Page Object

tests/
├── conftest.py          # fixture de lancement de l'application, capture d'écran en cas d'échec
├── pytest.ini
├── config.py
├── pages/
│   ├── __init__.py      # requis pour les imports
│   ├── base_page.py     # localisateurs, attente, aides à la capture d'écran
│   ├── login_page.py
│   └── main_page.py
├── tests/
│   ├── __init__.py
│   ├── test_login.py
│   └── test_main_flow.py
└── artifacts/           # captures d'écran, vidéos, journaux

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

    # --- Localisateurs (ordre de priorité) ---

    def by_id(self, auto_id, **kw):
        """AutomationId — le plus stable. À utiliser en premier choix."""
        return self.window.child_window(auto_id=auto_id, **kw)

    def by_name(self, name, **kw):
        """Texte visible / nom accessible."""
        return self.window.child_window(title=name, **kw)

    def by_class(self, cls, index=0, **kw):
        """Classe de contrôle + index — fragile, à éviter si possible."""
        return self.window.child_window(class_name=cls, found_index=index, **kw)

    # --- Attentes ---

    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):
        """Attendre une nouvelle fenêtre de premier niveau (boîtes de dialogue, fenêtres enfants)."""
        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):
        """Sonder une condition arbitraire — à utiliser lorsque les événements UIA ne sont pas fiables."""
        deadline = time.time() + timeout
        while time.time() < deadline:
            try:
                if fn():
                    return True
            except Exception:
                pass
            time.sleep(interval)
        raise TimeoutError(f"Condition non remplie dans les {timeout}s")

    # --- Actions ---

    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:
            # Solution de repli pour Qt 5.x : le modèle de valeur UIA peut être incomplet
            import sys, pywinauto.keyboard as kb
            print(f"[windows-desktop-e2e] set_edit_text a échoué ({e}), utilisation du repli clavier", 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 ""

    # --- Artefacts ---

    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="Fenêtre principale"):
        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

Pour les nouveaux projets, préférez l'utilisation de la fixture sandbox de niveau 1 (voir ci-dessous) — elle ajoute un isolement du système de fichiers sans coût supplémentaire. Cette fixture de base est réservée aux configurations minimales/héritées.

import os, pytest
os.environ["QT_ACCESSIBILITY"] = "1"  # Requis pour la prise en charge UIA de Qt 5.x

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("La variable d'environnement APP_PATH n'est pas définie", 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
    # Capture d'écran en cas d'échec
    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
    # Fermeture en douceur d'abord, arrêt forcé en dernier recours
    # proc est une Application pywinauto — utiliser wait_for_process_exit(), pas 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", "")           # défini via env — pas de chemin par défaut
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: tests de fumée rapides pour les chemins critiques
    flaky: tests connus pour être instables
addopts = -v --tb=short --html=artifacts/report.html --self-contained-html

Stratégie de localisation

AutomationId  >  Nom (texte)  >  ClassName + index  >  XPath
  (stable)         (lisible)        (fragile)           (dernier recours)

Inspectez avec Accessibility Insights → volet Propriétés → recherchez d'abord AutomationId.

# Inspecter au moment de l'exécution — coller dans un REPL pour explorer l'arborescence
win.print_control_identifiers()
# ou réduire la portée :
win.child_window(auto_id="groupBox1").print_control_identifiers()

Modèles d'attente

# Attendre l'apparition d'un contrôle
page.wait_visible(page.by_id("statusLabel"))

# Attendre la disparition d'un contrôle (par exemple, indicateur de chargement)
page.wait_gone(page.by_id("spinnerOverlay"))

# Attendre l'ouverture d'une boîte de dialogue
dlg = page.wait_window("Confirmer la suppression")

# Condition personnalisée (par exemple, changements de texte)
page.wait_until(lambda: page.get_text(page.by_id("lblStatus")) == "Prêt")

N'utilisez jamais time.sleep() comme synchronisation principale — utilisez wait() ou wait_until().

Gestion des artefacts

# Capture d'écran à la demande
page.screenshot("after_login")

# Capture plein écran (lorsque la fenêtre est hors écran ou minimisée)
import pyautogui
pyautogui.screenshot("artifacts/fullscreen.png")

# Enregistrement d'écran avec ffmpeg (démarrer avant le test, arrêter après)
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)

Trace par étape (opt-in)

La capture d'écran par défaut en cas d'échec est souvent insuffisante pour diagnostiquer les tests intermittents. La trace au niveau de l'étape ci-dessous est désactivée par défaut — activez-la uniquement lors de la reproduction d'un cas intermittent.

Activer

E2E_TRACE=1 pytest tests/test_login.py -v
# Inclure le texte saisi dans le journal JSONL (NE PAS utiliser sur les tests qui saisissent des identifiants/PII) :
E2E_TRACE=1 E2E_TRACE_INCLUDE_TEXT=1 pytest ...

Appliquer le patch dans 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  # l'échec de la capture ne doit pas casser le test
        rec = {
            "ts": time.time(), "step": BasePage._step, "action": action,
            "locator": getattr(spec, "criteria", None),
            "text": text if TRACE_INCLUDE_TEXT else ("<masqué>" 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)
        # ... code existant set_edit_text / repli clavier ...
        self._trace("type_after", spec)

Mises en garde

  • PII / identifiants : le contenu de type_text est <masqué> par défaut. Ne définissez jamais E2E_TRACE_INCLUDE_TEXT=1 sur les flux de connexion ou de paiement.
  • Surcharge : ~50–200 ms par action + un PNG par étape sur le disque. N'activez pas sur la matrice CI par défaut — uniquement sur un travail dédié de reproduction des échecs intermittents.
  • Gonflement des artefacts : un flux long produit des dizaines de Mo ; ajustez retention-days en conséquence.
  • Hygiène parallèle/réexécution : cet exemple simple ajoute à trace.jsonl et utilise un compteur au niveau de la classe. Videz le répertoire d'artefacts avant les réexécutions et utilisez des répertoires d'artefacts par worker pour les tests parallèles.
  • Lacune de couverture : les actions effectuées en dehors de BasePage (appels bruts pywinauto dans le code de test) ne sont pas tracées.

Gestion des tests intermittents

# Quarantaine — équivalent à test.fixme() de Playwright
@pytest.mark.skip(reason="Intermittent : course d'animation sur CI lent. Problème #42")
def test_animated_transition(self, app): ...

# Sauter une ressource insuffisante

... (contenu tronqué)

[Le contenu complet a été tronqué pour la traduction.]

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.

Skills associés