Communitygithub.com

testes-e2e-desktop-windows

Testes E2E para aplicativos nativos de desktop Windows (WPF, WinForms, Win32/MFC, Qt) usando pywinauto e Windows UI Automation. Use ao escrever testes E2E para um aplicativo nativo de desktop Windows com pywinauto ou UI Automation.

O que é testes-e2e-desktop-windows?

Esta skill fornece um guia abrangente e prático para implementar testes End-to-End (E2E) em aplicativos de desktop nativos do Windows, utilizando as bibliotecas pywinauto e a API de UI Automation (UIA) da Microsoft. É direcionada para desenvolvedores e engenheiros de QA que precisam automatizar interfaces gráficas de usuário (GUI) em frameworks como WPF, WinForms, Win32/MFC e Qt (versões 5.x e 6.x). A skill aborda desde a configuração do ambiente de testes, a criação de elementos de teste (como AutomationId) no código do aplicativo, até a implementação de um robusto Page Object Model (POM) em Python. A plataforma principal é o Windows, e a skill é totalmente compatível com pipelines de CI/CD, como GitHub Actions no runner `windows-latest`. O guia inclui estratégias de localizadores (AutomationId, Name, ClassName), padrões de espera, gerenciamento de artefatos (screenshots, gravação de tela), tratamento de testes instáveis (flaky), e uma seção dedicada de configuração de sandbox para isolar o sistema de arquivos, garantindo testes mais confiáveis e reproduzíveis. O código é fornecido para ser copiado e adaptado diretamente.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/windows-desktop-e2e

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Testes E2E para Desktop Windows

Testes End-to-End para aplicativos nativos de desktop Windows usando pywinauto com o backend Windows UI Automation (UIA). Abrange WPF, WinForms, Win32/MFC e Qt (5.x / 6.x) — com orientações específicas para Qt em uma seção dedicada.

Quando Ativar

  • Escrevendo ou executando testes E2E para um aplicativo nativo de desktop Windows
  • Configurando uma suíte de testes GUI do zero
  • Diagnosticando testes de automação de desktop instáveis ou com falha
  • Adicionando testabilidade (AutomationId, nomes acessíveis) a um aplicativo existente
  • Integrando testes E2E de desktop em um pipeline CI/CD (GitHub Actions windows-latest)

Quando NÃO Usar

  • Aplicações web → use a skill e2e-testing (Playwright)
  • Aplicativos Electron / CEF / WebView2 → a camada HTML precisa de automação de navegador, não de UIA
  • Aplicativos mobile → use ferramentas específicas da plataforma (UIAutomator, XCUITest)
  • Testes de unidade ou integração puros que não precisam de uma GUI em execução

Conceitos Principais

Toda a automação de desktop Windows depende da UI Automation (UIA), uma API de acessibilidade integrada ao Windows. Cada framework suportado expõe uma árvore de elementos UIA com propriedades que o pywinauto pode ler e nas quais pode agir:

Seu teste (Python)
    └── pywinauto (backend UIA)
        └── API Windows UI Automation   ← integrada ao Windows, agnóstica ao framework
            └── Provedor UIA do app     ← cada framework fornece o seu
                └── .exe em execução

Qualidade da UIA por framework:

FrameworkAutomationIdConfiabilidadeObservações
WPF5/5Excelentex:Name mapeia diretamente para AutomationId
WinForms4/5BoaAccessibleName = AutomationId
UWP / WinUI 35/5ExcelenteSuporte completo da Microsoft
Qt 6.x5/5ExcelenteAcessibilidade habilitada por padrão; nomes de classe mudam para Qt6*
Qt 5.15+4/5BoaMódulo de Acessibilidade melhorado
Qt 5.7–5.143/5RazoávelPrecisa de QT_ACCESSIBILITY=1; objectName manual
Win32 / MFC3/5RazoávelIDs de controle acessíveis; correspondência de texto comum

Configuração e Pré-requisitos

# Python 3.8+, apenas Windows
pip install pywinauto pytest pytest-html Pillow pytest-timeout
# Opcional: gravação de tela
# Instale o ffmpeg e adicione ao PATH: https://ffmpeg.org/download.html

Verifique se a UIA está acessível:

from pywinauto import Desktop
Desktop(backend="uia").windows()  # lista todas as janelas de nível superior

Instale o Accessibility Insights for Windows (gratuito, da Microsoft) — sua ferramenta DevTools equivalente para inspecionar a árvore de elementos UIA antes de escrever qualquer teste.

Configuração de Testabilidade (por Framework)

A ação mais impactante que você pode tomar é atribuir um AutomationId estável a cada controle interativo antes de escrever os testes.

WPF

<!-- XAML: x:Name se torna AutomationId automaticamente -->
<TextBox x:Name="usernameInput" />
<PasswordBox x:Name="passwordInput" />
<Button x:Name="btnLogin" Content="Login" />
<TextBlock x:Name="lblError" />

WinForms

// Defina no designer ou no código
usernameInput.AccessibleName = "usernameInput";
passwordInput.AccessibleName = "passwordInput";
btnLogin.AccessibleName = "btnLogin";
lblError.AccessibleName = "lblError";

Win32 / MFC

// Os IDs de recurso do controle no arquivo .rc são expostos como strings AutomationId
// IDC_EDIT_USERNAME -> AutomationId "1001"
// Prefira SetWindowText para Name; adicione IAccessible para suporte mais rico

Qt — veja a seção dedicada abaixo


Page Object Model

tests/
├── conftest.py          # fixture de inicialização do app, screenshot de falha
├── pytest.ini
├── config.py
├── pages/
│   ├── __init__.py      # necessário para imports
│   ├── base_page.py     # localizadores, espera, helpers de screenshot
│   ├── login_page.py
│   └── main_page.py
├── tests/
│   ├── __init__.py
│   ├── test_login.py
│   └── test_main_flow.py
└── artifacts/           # screenshots, vídeos, 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

    # --- Localizadores (ordem de prioridade) ---

    def by_id(self, auto_id, **kw):
        """AutomationId — mais estável. Use como primeira escolha."""
        return self.window.child_window(auto_id=auto_id, **kw)

    def by_name(self, name, **kw):
        """Texto visível / nome acessível."""
        return self.window.child_window(title=name, **kw)

    def by_class(self, cls, index=0, **kw):
        """Classe de controle + índice — frágil, evite se possível."""
        return self.window.child_window(class_name=cls, found_index=index, **kw)

    # --- Esperas ---

    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):
        """Aguarda por uma nova janela de nível superior (diálogos, janelas filhas)."""
        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):
        """Avalia uma condição arbitrária — use quando os eventos UIA não são confiáveis."""
        deadline = time.time() + timeout
        while time.time() < deadline:
            try:
                if fn():
                    return True
            except Exception:
                pass
            time.sleep(interval)
        raise TimeoutError(f"Condição não atendida em {timeout}s")

    # --- Ações ---

    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 fallback: o Value Pattern da UIA pode estar incompleto
            import sys, pywinauto.keyboard as kb
            print(f"[windows-desktop-e2e] set_edit_text falhou ({e}), usando fallback do teclado", 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 ""

    # --- Artefatos ---

    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

Para novos projetos, prefira a fixture sandbox do Tier 1 (veja abaixo) — ela adiciona isolamento do sistema de arquivos sem custo extra. Esta fixture básica é apenas para configurações mínimas/legadas.

import os, pytest
os.environ["QT_ACCESSIBILITY"] = "1"  # Necessário para suporte UIA no 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("A variável de ambiente APP_PATH não está definida", 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
    # Screenshot em caso de falha
    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
    # Saída graciosa primeiro, kill forçado como fallback
    # proc é um Application do pywinauto — use wait_for_process_exit(), não 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", "")           # definido via env — sem caminho padrão
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: testes smoke rápidos para caminhos críticos
    flaky: testes conhecidamente instáveis
addopts = -v --tb=short --html=artifacts/report.html --self-contained-html

Estratégia de Localizadores

AutomationId  >  Nome (texto)  >  ClassName + índice  >  XPath
  (estável)         (legível)        (frágil)             (último recurso)

Inspecione com Accessibility Insights → painel Properties → procure AutomationId primeiro.

# Inspecione em tempo de execução — cole em um REPL para explorar a árvore
win.print_control_identifiers()
# ou restrinja o escopo:
win.child_window(auto_id="groupBox1").print_control_identifiers()

Padrões de Espera

# Aguarda o controle aparecer
page.wait_visible(page.by_id("statusLabel"))

# Aguarda o controle desaparecer (ex.: spinner de carregamento)
page.wait_gone(page.by_id("spinnerOverlay"))

# Aguarda um diálogo aparecer
dlg = page.wait_window("Confirmar Exclusão")

# Condição personalizada (ex.: mudanças de texto)
page.wait_until(lambda: page.get_text(page.by_id("lblStatus")) == "Pronto")

Nunca use time.sleep() como sincronização primária — use wait() ou wait_until().

Gerenciamento de Artefatos

# Screenshot sob demanda
page.screenshot("apos_login")

# Captura de tela cheia (quando a janela está fora da tela ou minimizada)
import pyautogui
pyautogui.screenshot("artifacts/fullscreen.png")

# Gravação de tela com ffmpeg (iniciar antes do teste, parar depois)
import subprocess

def iniciar_gravacao(nome):
    return subprocess.Popen([
        "ffmpeg", "-f", "gdigrab", "-framerate", "10",
        "-i", "desktop", "-y", f"artifacts/videos/{nome}.mp4"
    ], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def parar_gravacao(proc):
    proc.stdin.write(b"q"); proc.stdin.flush(); proc.wait(timeout=10)

Rastreamento por Etapa (opt-in)

O screenshot de falha padrão geralmente é muito superficial para diagnosticar testes instáveis. O rastreamento por etapa abaixo está desligado por padrão — ative-o apenas ao reproduzir um caso instável.

Ativar

E2E_TRACE=1 pytest tests/test_login.py -v
# Incluir texto digitado no log JSONL (NÃO use em testes que digitam credenciais/PII):
E2E_TRACE=1 E2E_TRACE_INCLUDE_TEXT=1 pytest ...

Adicionar ao 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  # falha na captura não deve quebrar o teste
        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)
        # ... implementação existente de set_edit_text / fallback de teclado ...
        self._trace("type_after", spec)

Advertências

  • PII / credenciais: o conteúdo de type_text é <redacted> por padrão. Nunca defina E2E_TRACE_INCLUDE_TEXT=1 em fluxos de login ou pagamento.
  • Sobrecarga: ~50–200ms por ação + um PNG por etapa no disco. Não ative na matriz CI padrão — apenas em um job dedicado de reprodução de instabilidade.
  • Inchaço de artefatos: um fluxo longo pode produzir dezenas de MB; ajuste o retention-days adequadamente.
  • Higiene de paralelo/re-execução: este exemplo simples anexa a trace.jsonl e usa um contador de nível de classe. Limpe o diretório de artefatos antes de reexecuções e use diretórios de artefatos por worker para testes paralelos.
  • Lacuna de cobertura: ações executadas fora de BasePage (chamadas diretas ao pywinauto no código de teste) não são rastreadas.

Tratamento de Testes Instáveis (Flaky)

# Quarentena — equivalente a test.fixme() do Playwright
@pytest.mark.skip(reason="Instável: race de animação em CI lento. Issue #42")
def test_transicao_animada(self, app): ...

# Pular ist...

[truncado para tradução]

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.

Habilidades Relacionadas