windows-desktop-e2e
E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation. Use when writing E2E tests for a Windows native desktop app with pywinauto or UI Automation.
DeepseekModel
Curated skill
Quality Excellent · 90
v1.0.0
Get
https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-skills-windows-desktop-e2e-skill-md&format=skill
Download .skill
Standard format with system_prompt and model_config, ready for any agent framework
The actual content of the system_prompt field in the .skill file.
name windows-desktop-e2e description E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation. Use when writing E2E tests for a Windows native desktop app with pywinauto or UI Automation. metadata {"origin":"ECC"} Windows Desktop E2E Testing End-to-end testing for Windows native desktop applications using pywinauto backed by Windows UI Automation (UIA). Covers WPF, WinForms, Win32/MFC, and Qt (5.x / 6.x) — with Qt-specific guidance as a dedicated section. When to Activate Writing or running E2E tests for a Windows native desktop application Setting up a desktop GUI test suite from scratch Diagnosing flaky or failing desktop automation tests Adding testability (AutomationId, accessible names) to an existing app Integrating desktop E2E into a CI/CD pipeline (GitHub Actions windows-latest ) When NOT to Use Web applications → use e2e-testing skill (Playwright) Electron / CEF / WebView2 apps → the HTML layer needs browser automation, not UIA Mobile apps → use platform-specific tools (UIAutomator, XCUITest) Pure unit or integration tests that don't need a running GUI Core Concepts All Windows desktop automation relies on UI Automation (UIA) , a Windows-built-in accessibility API. Every supported framework exposes a tree of UIA elements with properties Claude can read and act on: Your test (Python) └── pywinauto (UIA backend) └── Windows UI Automation API ← built into Windows, framework-agnostic └── App's UIA provider ← each framework ships its own └── Running .exe UIA quality by framework: Framework AutomationId Reliability Notes WPF 5/5 Excellent x:Name maps directly to AutomationId WinForms 4/5 Good AccessibleName = AutomationId UWP / WinUI 3 5/5 Excellent Full Microsoft support Qt 6.x 5/5 Excellent Accessibility enabled by default; class names change to Qt6* Qt 5.15+ 4/5 Good Improved Accessibility module Qt 5.7–5.14 3/5 Fair Needs QT_ACCESSIBILITY=1 ; objectName manual Win32 / MFC 3/5 Fair Control IDs accessible; text matching common Setup & Prerequisites # Python 3.8+, Windows only pip install pywinauto pytest pytest-html Pillow pytest-timeout # Optional: screen recording # Install ffmpeg and add to PATH: https://ffmpeg.org/download.html Verify UIA is reachable: from pywinauto import Desktop Desktop(backend= "uia" ).windows() # lists all top-level windows Install Accessibility Insights for Windows (free, from Microsoft) — your DevTools equivalent for inspecting the UIA element tree before writing any test. Testability Setup (by Framework) The single most impactful thing you can do is give every interactive control a stable AutomationId before writing tests. WPF <!-- XAML: x:Name becomes AutomationId automatically --> < TextBox x:Name = "usernameInput" /> < PasswordBox x:Name = "passwordInput" /> < Button x:Name = "btnLogin" Content = "Login" /> < TextBlock x:Name = "lblError" /> WinForms // Set in designer or code usernameInput.AccessibleName = "usernameInput" ; passwordInput.AccessibleName = "passwordInput" ; btnLogin.AccessibleName = "btnLogin" ; lblError.AccessibleName = "lblError" ; Win32 / MFC // Control resource IDs in .rc file are exposed as AutomationId strings // IDC_EDIT_USERNAME -> AutomationId "1001" // Prefer SetWindowText for Name; add IAccessible for richer support Qt — see dedicated section below Page Object Model tests/ ├── conftest.py # app launch fixture, failure screenshot ├── pytest.ini ├── config.py ├── pages/ │ ├── __init__.py # required for imports │ ├── base_page.py # locators, wait, screenshot helpers │ ├── login_page.py │ └── main_page.py ├── tests/ │ ├── __init__.py │ ├── test_login.py │ └── test_main_flow.py └── artifacts/ # screenshots, videos, 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 # --- Locators (priority order) --- def by_id ( self, auto_id, **kw ): """AutomationId — most stable. Use as first choice.""" return self .window.child_window(auto_id=auto_id, **kw) def by_name ( self, name, **kw ): """Visible text / accessible name.""" return self .window.child_window(title=name, **kw) def by_class ( self, cls, index= 0 , **kw ): """Control class + index — fragile, avoid if possible.""" return self .window.child_window(class_name=cls, found_index=index, **kw) # --- Waits --- 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 ): """Wait for a new top-level window (dialogs, child windows).""" 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 ): """Poll an arbitrary condition — use when UIA events are unreliable.""" deadline = time.time() + timeout while time.time() < deadline: try : if fn(): return True except Exception: pass time.sleep(interval) raise TimeoutError( f"Condition not met within {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: # Qt 5.x fallback: UIA Value Pattern may be incomplete import sys, pywinauto.keyboard as kb print ( f"[windows-desktop-e2e] set_edit_text failed ( {e} ), using keyboard fallback" , 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 "" # --- Artifacts --- 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 For new projects prefer the Tier 1 sandbox fixture (see below) — it adds filesystem isolation at zero extra cost. This basic fixture is for minimal/legacy setups only. import os, pytest os.environ[ "QT_ACCESSIBILITY" ] = "1" # Required for Qt 5.x UIA support 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( "APP_PATH environment variable is not set" , 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 on failure 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 # Graceful exit first, force-kill as fallback # proc is a pywinauto Application — use wait_for_process_exit(), not 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" , "" ) # set via env — no default path 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: fast smoke tests for critical paths flaky: known-unstable tests addopts = -v --tb=short --html=artifacts/report.html --self-contained-html Locator Strategy AutomationId > Name (text) > ClassName + index > XPath (stable) (readable) (fragile) (last resort) Inspect with Accessibility Insights → Properties pane → look for AutomationId first. # Inspect at runtime — paste into a REPL to explore the tree win.print_control_identifiers() # or narrow scope: win.child_window(auto_id= "groupBox1" ).print_control_identifiers() Wait Patterns # Wait for control to appear page.wait_visible(page.by_id( "statusLabel" )) # Wait for control to disappear (e.g. loading spinner) page.wait_gone(page.by_id( "spinnerOverlay" )) # Wait for a dialog to pop up dlg = page.wait_window( "Confirm Delete" ) # Custom condition (e.g. text changes) page.wait_until( lambda : page.get_text(page.by_id( "lblStatus" )) == "Ready" ) Never use time.sleep() as primary synchronization — use wait() or wait_until() . Artifact Management # Screenshot on demand page.screenshot( "after_login" ) # Full-screen capture (when window is off-screen or minimised) import pyautogui pyautogui.screenshot( "artifacts/fullscreen.png" ) # Screen recording with ffmpeg (start before test, stop after) 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 ) Per-Step Trace (opt-in) The default failure screenshot is often too thin for diagnosing flaky tests. The step-level trace below is off by default — enable it only when reproducing a flaky case. Enable E2E_TRACE=1 pytest tests/test_login.py -v # Include typed text in the JSONL log (DO NOT use on tests that type credentials/PII): E2E_TRACE=1 E2E_TRACE_INCLUDE_TEXT=1 pytest ... Patch into 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(
Keywords that activate this skill. Click one to copy it.
This skill does not provide trigger words.
The downloaded .skill package contains the following fields.
| Field | Description |
|---|---|
| format | Format tag (skill/v1) |
| skill_id | Unique skill ID |
| name | Skill name |
| version | Version |
| description | Description |
| category | Categories (array) |
| trigger_words | Trigger words |
| tags | Tags |
| source | Source |
| source_url | Source URL (this page) |
| exported_at | Exported at (set per download) |
| system_prompt | System prompt body |
| model_config | Model config: provider / model / temperature / max_tokens / top_p |
| examples | Examples |
| install_guide | Import guide for Coze / Dify / Claude / custom frameworks |
The same skill can be exported in different platform formats.