Skills Plugins MCP Prompt Model 博客 我的中心
Development #automation #writing #testing

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
formatFormat tag (skill/v1)
skill_idUnique skill ID
nameSkill name
versionVersion
descriptionDescription
categoryCategories (array)
trigger_wordsTrigger words
tagsTags
sourceSource
source_urlSource URL (this page)
exported_atExported at (set per download)
system_promptSystem prompt body
model_configModel config: provider / model / temperature / max_tokens / top_p
examplesExamples
install_guideImport guide for Coze / Dify / Claude / custom frameworks
The same skill can be exported in different platform formats.
.skill Standard format with system_prompt and model_config, ready for any agent framework Download
.skillpro Enhanced format with scripts, tools, dependencies and hooks Download
.json Plain JSON export with system_prompt and model parameters only Download
Coze Markdown with frontmatter, for Coze platform import Download
Dify Dify DSL, import directly after creating an app Download

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

验证码 --

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。