Communitygithub.com

Pruebas E2E para Escritorio Windows

Pruebas de extremo a extremo para aplicaciones nativas de escritorio Windows (WPF, WinForms, Win32/MFC, Qt) usando pywinauto y UI Automation de Windows. Úsalo al escribir pruebas E2E para una aplicación de escritorio nativa de Windows con pywinauto o UI Automation.

¿Qué es Pruebas E2E para Escritorio Windows?

Esta habilidad proporciona una guía completa para implementar pruebas de extremo a extremo (E2E) en aplicaciones de escritorio nativas de Windows, compatible con frameworks como WPF, WinForms, Win32/MFC y Qt (5.x/6.x), utilizando **pywinauto** con el backend de UI Automation (UIA) de Windows. Es útil para desarrolladores y testers que necesitan configurar un suite de pruebas desde cero, diagnosticar pruebas inestables o fallidas, agregar capacidad de prueba (AutomationId, nombres accesibles) a una aplicación existente, o integrar las pruebas E2E en un pipeline CI/CD (por ejemplo, GitHub Actions en `windows-latest`). La habilidad cubre conceptos centrales como el árbol de elementos UIA, estrategias de localización (AutomationId > Name > ClassName), patrones de espera, manejo de artefactos (capturas de pantalla, grabación de video) y un modelo de objetos de página (Page Object Model) detallado con ejemplos de código en Python. Además, incluye secciones específicas para Qt, gestión de pruebas inestables y un sistema de traza por paso para diagnóstico, todo documentado en español para facilitar su uso en equipos hispanohablantes.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace Pruebas E2E para Escritorio Windows?

Pruebas de extremo a extremo para aplicaciones de escritorio nativas de Windows usando pywinauto respaldado por UI Automation (UIA) de Windows. Cubre WPF, WinForms, Win32/MFC y Qt (5.x / 6.x), con orientación específica para Qt en una sección dedicada.

Cuándo activarlo

  • Escribir o ejecutar pruebas E2E para una aplicación de escritorio nativa de Windows
  • Configurar un suite de pruebas GUI de escritorio desde cero
  • Diagnosticar pruebas de automatización de escritorio inestables o fallidas
  • Agregar capacidad de prueba (AutomationId, nombres accesibles) a una aplicación existente
  • Integrar pruebas E2E de escritorio en un pipeline CI/CD (GitHub Actions windows-latest)

Cuándo NO usarlo

  • Aplicaciones web → usa la habilidad e2e-testing (Playwright)
  • Aplicaciones Electron / CEF / WebView2 → la capa HTML necesita automatización del navegador, no UIA
  • Aplicaciones móviles → usa herramientas específicas de la plataforma (UIAutomator, XCUITest)
  • Pruebas unitarias o de integración puras que no necesitan una GUI en ejecución

Conceptos centrales

Toda la automatización de escritorio de Windows se basa en UI Automation (UIA), una API de accesibilidad integrada en Windows. Cada framework compatible expone un árbol de elementos UIA con propiedades que Claude puede leer y sobre las que puede actuar:

Tu prueba (Python)
    └── pywinauto (backend UIA)
        └── API de UI Automation de Windows   ← integrada en Windows, independiente del framework
            └── Proveedor UIA de la app      ← cada framework envía el suyo
                └── .exe en ejecución

Calidad de UIA por framework:

FrameworkAutomationIdConfiabilidadNotas
WPF5/5Excelentex:Name se mapea directamente a AutomationId
WinForms4/5BuenaAccessibleName = AutomationId
UWP / WinUI 35/5ExcelenteSoporte completo de Microsoft
Qt 6.x5/5ExcelenteAccesibilidad habilitada por defecto; los nombres de clase cambian a Qt6*
Qt 5.15+4/5BuenaMódulo de accesibilidad mejorado
Qt 5.7–5.143/5RegularNecesita QT_ACCESSIBILITY=1; objectName manual
Win32 / MFC3/5RegularIDs de control accesibles; coincidencia de texto común

Configuración y requisitos previos

# Python 3.8+, solo Windows
pip install pywinauto pytest pytest-html Pillow pytest-timeout
# Opcional: grabación de pantalla
# Instala ffmpeg y agrégalo al PATH: https://ffmpeg.org/download.html

Verifica que UIA sea accesible:

from pywinauto import Desktop
Desktop(backend="uia").windows()  # lista todas las ventanas de nivel superior

Instala Accessibility Insights for Windows (gratuito, de Microsoft) — tu equivalente de DevTools para inspeccionar el árbol de elementos UIA antes de escribir cualquier prueba.

Configuración de capacidad de prueba (por framework)

Lo más impactante que puedes hacer es darle a cada control interactivo un AutomationId estable antes de escribir pruebas.

WPF

<!-- XAML: x:Name se convierte en AutomationId automáticamente -->
<TextBox x:Name="usernameInput" />
<PasswordBox x:Name="passwordInput" />
<Button x:Name="btnLogin" Content="Login" />
<TextBlock x:Name="lblError" />

WinForms

// Configura en el diseñador o en el código
usernameInput.AccessibleName = "usernameInput";
passwordInput.AccessibleName = "passwordInput";
btnLogin.AccessibleName = "btnLogin";
lblError.AccessibleName = "lblError";

Win32 / MFC

// Los IDs de recursos de control en el archivo .rc se exponen como cadenas AutomationId
// IDC_EDIT_USERNAME -> AutomationId "1001"
// Prefiere SetWindowText para Name; agrega IAccessible para soporte más rico

Qt — consulta la sección dedicada a continuación


Modelo de objetos de página (Page Object Model)

tests/
├── conftest.py          # fixture de lanzamiento de la app, captura de pantalla en fallo
├── pytest.ini
├── config.py
├── pages/
│   ├── __init__.py      # requerido para importaciones
│   ├── base_page.py     # localizadores, espera, ayudantes de captura de pantalla
│   ├── login_page.py
│   └── main_page.py
├── tests/
│   ├── __init__.py
│   ├── test_login.py
│   └── test_main_flow.py
└── artifacts/           # capturas de pantalla, videos, registros

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 (orden de prioridad) ---

    def by_id(self, auto_id, **kw):
        """AutomationId — el más estable. Úsalo como primera opción."""
        return self.window.child_window(auto_id=auto_id, **kw)

    def by_name(self, name, **kw):
        """Texto visible / nombre accesible."""
        return self.window.child_window(title=name, **kw)

    def by_class(self, cls, index=0, **kw):
        """Clase de control + índice — frágil, evítalo si es posible."""
        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):
        """Espera una nueva ventana de nivel superior (diálogos, ventanas secundarias)."""
        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):
        """Evalúa una condición arbitraria — úsalo cuando los eventos UIA no sean confiables."""
        deadline = time.time() + timeout
        while time.time() < deadline:
            try:
                if fn():
                    return True
            except Exception:
                pass
            time.sleep(interval)
        raise TimeoutError(f"La condición no se cumplió dentro de {timeout}s")

    # --- Acciones ---

    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: el patrón de valor UIA puede estar incompleto
            import sys, pywinauto.keyboard as kb
            print(f"[windows-desktop-e2e] set_edit_text falló ({e}), usando fallback del 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 ""

    # --- Artefactos ---

    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 proyectos nuevos, prefiere el fixture de sandbox Nivel 1 (ver más abajo) — agrega aislamiento del sistema de archivos sin costo adicional. Este fixture básico es solo para configuraciones mínimas/heredadas.

import os, pytest
os.environ["QT_ACCESSIBILITY"] = "1"  # Requerido para soporte UIA en 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 de entorno APP_PATH no está configurada", 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
    # Captura de pantalla en fallo
    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
    # Cierre elegante primero, finalización forzada como respaldo
    # proc es una aplicación de pywinauto — usa wait_for_process_exit(), no 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", "")           # se configura mediante env — sin ruta predeterminada
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: pruebas rápidas de humo para rutas críticas
    flaky: pruebas conocidas como inestables
addopts = -v --tb=short --html=artifacts/report.html --self-contained-html

Estrategia de localizadores

AutomationId  >  Name (texto)  >  ClassName + índice  >  XPath
  (estable)         (legible)       (frágil)           (último recurso)

Inspecciona con Accessibility Insights → panel Propiedades → busca AutomationId primero.

# Inspecciona en tiempo de ejecución — pégalo en un REPL para explorar el árbol
win.print_control_identifiers()
# o reduce el alcance:
win.child_window(auto_id="groupBox1").print_control_identifiers()

Patrones de espera

# Espera a que aparezca un control
page.wait_visible(page.by_id("statusLabel"))

# Espera a que desaparezca un control (por ejemplo, indicador de carga)
page.wait_gone(page.by_id("spinnerOverlay"))

# Espera a que aparezca un diálogo
dlg = page.wait_window("Confirm Delete")

# Condición personalizada (por ejemplo, cambios de texto)
page.wait_until(lambda: page.get_text(page.by_id("lblStatus")) == "Ready")

Nunca uses time.sleep() como sincronización principal — usa wait() o wait_until().

Gestión de artefactos

# Captura de pantalla bajo demanda
page.screenshot("after_login")

# Captura de pantalla de pantalla completa (cuando la ventana está fuera de la pantalla o minimizada)
import pyautogui
pyautogui.screenshot("artifacts/fullscreen.png")

# Grabación de pantalla con ffmpeg (inicia antes de la prueba, detén despué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)

Traza por paso (opt-in)

La captura de pantalla predeterminada en fallo a menudo es insuficiente para diagnosticar pruebas inestables. La traza a nivel de paso a continuación está desactivada por defecto — actívala solo al reproducir un caso inestable.

Habilitar

E2E_TRACE=1 pytest tests/test_login.py -v
# Incluye el texto escrito en el registro JSONL (NO lo uses en pruebas que escriban credenciales/Datos Personales Identificables):
E2E_TRACE=1 E2E_TRACE_INCLUDE_TEXT=1 pytest ...

Integrar en 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  # el fallo de captura no debe romper la prueba
        rec = {
            "ts": time.time(), "step": BasePage._step, "action": action,
            "locator": getattr(spec, "criteria", None),
            "text": text if TRACE_INCLUDE_TEXT else ("<redactado>" 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)
        # ... lógica existente de set_edit_text / fallback del teclado ...
        self._trace("type_after", spec)

Advertencias

  • DPI / credenciales: el contenido de type_text está <redactado> por defecto. Nunca establezcas E2E_TRACE_INCLUDE_TEXT=1 en flujos de inicio de sesión o pagos.
  • Sobrecarga: ~50–200ms por acción + un PNG por paso en disco. No lo habilites en la matriz CI predeterminada, solo en un trabajo dedicado de reproducción de inestabilidad.
  • Inflado de artefactos: un flujo largo produce decenas de MB; ajusta retention-days en consecuencia.
  • Higiene de paralelismo/reintentos: este ejemplo simple agrega a trace.jsonl y usa un contador a nivel de clase. Limpia el directorio de artefactos antes de reintentos, y usa directorios de artefactos por worker para pruebas paralelas.
  • Brecha de cobertura: las acciones realizadas fuera de BasePage (llamadas directas a pywinauto en el código de prueba) no se trazan.

Manejo de pruebas inestables

# Cuarentena — equivalente a test.fixme() de Playwright
@pytest.mark.skip(reason="Inestable: carrera de animación en CI lenta. Issue #42")
def test_animated_transition(self, app): ...

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 relacionados