Communitygithub.com

windows-desktop-e2e

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

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

Ce skill fournit un guide complet pour les tests de bout en bout (E2E) des applications de bureau Windows natives en utilisant la bibliothèque pywinauto, qui s'appuie sur l'API Windows UI Automation (UIA). Il couvre les frameworks les plus courants comme WPF, WinForms, Win32/MFC et Qt (5.x et 6.x), en offrant des conseils spécifiques pour chaque cas. L'objectif principal est d'aider les développeurs à configurer une suite de tests automatisés robuste et fiable, en soulignant l'importance des AutomationId pour la stabilité des sélecteurs. Le skill détaille la structure du modèle Page Object, les stratégies de localisation des éléments (donnant la priorité à AutomationId), des modèles d'attente efficaces, la gestion des artefacts (captures d'écran, vidéos) et des techniques de dépannage pour les tests intermittents. Il est compatible avec les environnements d'intégration continue comme GitHub Actions (avec `windows-latest`) et conçu pour être utilisé avec Python et les frameworks de test comme pytest.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/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 pour Bureau Windows

Tests de bout en bout pour les applications de bureau Windows natives utilisant pywinauto et l'API Windows UI Automation (UIA). Couvre WPF, WinForms, Win32/MFC et Qt (5.x / 6.x) — avec une section dédiée aux conseils spécifiques pour Qt.

Quand Activer

  • Écrire ou exécuter des tests E2E pour une application de bureau Windows native
  • Mettre en place une suite de tests GUI pour bureau à partir de zéro
  • Diagnostiquer des tests d'automatisation de bureau intermittents ou échouant
  • Ajouter de la testabilité (AutomationId, noms accessibles) à une application existante
  • Intégrer les tests E2E de bureau dans un pipeline CI/CD (GitHub Actions windows-latest)

Quand NE PAS Utiliser

  • Applications web → utiliser le skill e2e-testing (Playwright)
  • Applications Electron / CEF / WebView2 → la couche HTML nécessite une automatisation de navigateur, pas UIA
  • Applications mobiles → utiliser des outils spécifiques à la plateforme (UIAutomator, XCUITest)
  • Tests unitaires ou d'intégration purs qui n'ont pas besoin d'une interface graphique en cours d'exécution

Concepts Fondamentaux

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

Votre test (Python)
    └── pywinauto (backend UIA)
        └── API Windows UI Automation   ← intégrée à Windows, indépendante du framework
            └── Fournisseur UIA de l'application ← chaque framework a le sien
                └── Fichier .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 complète de Microsoft
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 ; nom d'objet manuel
Win32 / MFC3/5MoyenneIDs de contrôle accessibles ; correspondance de texte courante

Configuration & Prérequis

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

Vérifier que UIA est accessible :

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

Installer Accessibility Insights for Windows (gratuit, de Microsoft) — votre équivalent DevTools pour inspecter l'arbre d'é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 AutomationId automatiquement -->
<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 IDs de ressource de contrôle dans le fichier .rc sont exposés comme des chaînes AutomationId
// IDC_EDIT_USERNAME -> AutomationId "1001"
// Préférer SetWindowText pour le nom ; ajouter IAccessible pour un support plus riche

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


Modèle Page Object

tests/
├── conftest.py          # fixture de lancement d'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, logs

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 plan (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):
        """Interroger 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 satisfaite 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 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 de la solution de 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 la fixture de bac à sable de niveau 1 (voir ci-dessous) — elle ajoute l'isolation 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 le support 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 gracieuse 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 non-régression 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)

Inspecter avec Accessibility Insights → volet Propriétés → rechercher AutomationId en premier.

# Inspecter à l'exécution — coller dans un REPL pour explorer l'arbre
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 (ex. 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 (ex. changement 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 réduite)
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 des 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.

Activation

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

Intégration 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 interrompre 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 set_edit_text / solution de repli clavier existant ...
        self._trace("type_after", spec)

Mises en garde

  • Données personnelles / 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 job dédié à la reproduction des tests intermittents.
  • Gonflement des artefacts : un long flux peut produire 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. Nettoyez le répertoire des 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 au test.fixme() de Playwright
@pytest.mark.skip(reason="Intermittent : course d'animation sur CI lent. Problème #42")
def test_animated_transition(self, app): ...

# Ignorer si...

…

[Le texte 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/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.

Skills associés