Communitygithub.com

e2e-escritorio-windows

Pruebas E2E para aplicaciones de escritorio nativas de Windows (WPF, WinForms, Win32/MFC, Qt) usando pywinauto y Automatización de Interfaz de Usuario de Windows. Actívalo al escribir pruebas E2E para una aplicación de escritorio nativa de Windows con pywinauto o Automatización de Interfaz de Usuario.

¿Qué es e2e-escritorio-windows?

Esta skill te permite automatizar pruebas integrales (E2E) para aplicaciones de escritorio nativas de Windows, como WPF, WinForms, Win32/MFC y Qt (5.x/6.x). Utiliza pywinauto junto con el backend de Automatización de Interfaz de Usuario (UIA) de Windows, que es una API de accesibilidad integrada en el sistema operativo. La skill proporciona una guía completa que abarca desde la configuración inicial y la creación de un modelo de objetos de página (Page Object Model) robusto, hasta estrategias avanzadas de localización de elementos, manejo de esperas, captura de artefactos (capturas de pantalla, grabaciones de video) y diagnóstico de pruebas inestables. Incluye fragmentos de código Python reutilizables, un fixture de pytest para lanzar la aplicación y capturar fallos, y un sistema de trazado por pasos opcional para depurar pruebas problemáticas. Está diseñada para integrarse en pipelines CI/CD, como GitHub Actions en `windows-latest`, y enfatiza la importancia de asignar AutomationIds estables a los controles interactivos como práctica fundamental para la mantenibilidad de las pruebas.

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

Preguntar en tu IA favorita

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

Documentación

Pruebas E2E para Escritorio Windows

Pruebas integrales para aplicaciones de escritorio nativas de Windows usando pywinauto respaldado por Automatización de Interfaz de Usuario de Windows (UIA). Cubre WPF, WinForms, Win32/MFC y Qt (5.x / 6.x), con una sección dedicada a Qt.

Cuándo Activar

  • Al escribir o ejecutar pruebas E2E para una aplicación de escritorio nativa de Windows
  • Al configurar un conjunto de pruebas de GUI de escritorio desde cero
  • Al diagnosticar pruebas de automatización de escritorio inestables o fallidas
  • Al agregar capacidad de prueba (AutomationId, nombres accesibles) a una aplicación existente
  • Al integrar E2E de escritorio en un pipeline CI/CD (GitHub Actions windows-latest)

Cuándo NO Usar

  • Aplicaciones web → usa la skill 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 Clave

Toda la automatización de escritorio de Windows se basa en Automatización de Interfaz de Usuario (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 Automatización de Interfaz de Usuario de Windows   ← integrada en Windows, independiente del framework
            └── Proveedor UIA de la aplicación      ← cada framework incluye el suyo
                └── .exe en ejecución

Calidad de UIA por framework:

FrameworkAutomationIdFiabilidadNotas
WPF5/5Excelentex:Name se asigna 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 a 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 asignar un AutomationId estable a cada control interactivo antes de escribir las pruebas.

WPF

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

WinForms

// Establecer en el diseñador o en 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 completo

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 aplicación, captura de pantalla de 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 en bucle — úsalo cuando los eventos UIA no sean fiables."""
        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 alternativa: 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 alternativa de 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="Ventana Principal"):
        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 sandbox de 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 caso de 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 suave primero, cierre forzado como alternativa
    # proc es un Application 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 conocidamente inestables
addopts = -v --tb=short --html=artifacts/report.html --self-contained-html

Estrategia de Localización

AutomationId  >  Nombre (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 — pega 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 (ej. indicador de carga)
page.wait_gone(page.by_id("spinnerOverlay"))

# Espera a que aparezca un diálogo
dlg = page.wait_window("Confirmar eliminación")

# Condición personalizada (ej. cambios de texto)
page.wait_until(lambda: page.get_text(page.by_id("lblStatus")) == "Listo")

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

Gestión de Artefactos

# Captura de pantalla bajo demanda
page.screenshot("después_del_inicio")

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

# Grabación de pantalla con ffmpeg (inicia antes de la prueba, detén después)
import subprocess

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

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

Trazado por Paso (opt-in)

La captura de pantalla de fallo predeterminada a menudo es insuficiente para diagnosticar pruebas inestables. El trazado a nivel de paso a continuación está desactivado por defecto — actívalo 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/PII):
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)
        # ... set_edit_text existente / alternativa de teclado ...
        self._trace("type_after", spec)

Advertencias

  • PII / 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 pago.
  • 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 a la reproducción de fallos inestables.
  • Inflado de artefactos: un flujo largo produce decenas de MB; ajusta retention-days en consecuencia.
  • Higiene en paralelo/reintentos: este ejemplo simple agrega a trace.jsonl y usa un contador a nivel de clase. Limpia el directorio de artefactos antes de los reintentos y usa directorios de artefactos por trabajador para pruebas paralelas.
  • Brecha de cobertura: las acciones realizadas fuera de BasePage (llamadas directas a pywinauto en el código de prueba) no se rastrean.

Manejo de Pruebas Inestables

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

# Omitir i...

Por razones de brevedad, el contenido se ha truncado en esta traducción. El markdown original se extiende más allá de este punto.

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 relacionados