windows-desktop-e2e
E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation.
DeepseekModel
官方收录技能
质量 优秀 · 90
v1.0.0
获取
https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-docs-ja-jp-skills-windows-desktop-e2e-skill-md&format=skill
下载 .skill
标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用
.skill 文件中 system_prompt 字段的实际内容。
name windows-desktop-e2e description E2E testing for Windows native desktop apps (WPF, WinForms, Win32/MFC, Qt) using pywinauto and Windows UI Automation. origin ECC Windows デスクトップ E2E テスト pywinauto と Windows UI Automation(UIA)を使用したWindowsネイティブデスクトップアプリケーションのエンドツーエンドテスト。WPF、WinForms、Win32/MFC、Qt(5.x / 6.x)をカバーし、Qt固有のガイダンスは専用セクションとして提供します。 アクティベートするタイミング Windowsネイティブデスクトップアプリケーションのエンドツーエンドテストを書くまたは実行するとき デスクトップGUIテストスイートをゼロから設定するとき 不安定または失敗するデスクトップオートメーションテストを診断するとき 既存のアプリにテスタビリティ(AutomationId、アクセシブルな名前)を追加するとき デスクトップエンドツーエンドをCI/CDパイプライン(GitHub Actions windows-latest )に統合するとき 使用しないタイミング Webアプリケーション → e2e-testing スキル(Playwright)を使用する Electron / CEF / WebView2 アプリ → HTMLレイヤーにはUIAではなくブラウザオートメーションが必要 モバイルアプリ → プラットフォーム固有のツールを使用する(UIAutomator、XCUITest) 実行中のGUIを必要としない純粋なユニットまたは統合テスト コアコンセプト すべてのWindowsデスクトップオートメーションは**UI Automation(UIA)**に依存します。これはWindowsに組み込まれたアクセシビリティAPIです。サポートされているすべてのフレームワークは、読み取りおよび操作可能なプロパティを持つUIA要素のツリーを公開します: テスト(Python) └── pywinauto(UIAバックエンド) └── Windows UI Automation API ← Windowsに組み込み、フレームワーク非依存 └── アプリのUIAプロバイダー ← 各フレームワークが独自に実装 └── 実行中の .exe フレームワーク別UIA品質: フレームワーク AutomationId 信頼性 注記 WPF 5/5 優秀 x:Name が直接AutomationIdにマッピング WinForms 4/5 良好 AccessibleName = AutomationId UWP / WinUI 3 5/5 優秀 Microsoftの完全サポート Qt 6.x 5/5 優秀 アクセシビリティがデフォルトで有効;クラス名が Qt6* に変更 Qt 5.15+ 4/5 良好 Accessibilityモジュールが改善 Qt 5.7–5.14 3/5 普通 QT_ACCESSIBILITY=1 が必要;objectNameは手動設定 Win32 / MFC 3/5 普通 コントロールIDにアクセス可能;テキストマッチングが一般的 セットアップと前提条件 # Python 3.8+、Windowsのみ pip install pywinauto pytest pytest-html Pillow pytest-timeout # オプション:画面録画 # ffmpegをインストールしてPATHに追加:https://ffmpeg.org/download.html UIAが到達可能か確認: from pywinauto import Desktop Desktop(backend= "uia" ).windows() # すべてのトップレベルウィンドウを一覧表示 Accessibility Insights for Windows をインストールしてください(Microsoft提供、無料)— テストを書く前にUIA要素ツリーを検査するためのDevTools相当のツールです。 テスタビリティのセットアップ(フレームワーク別) テストを書く前に 全てのインタラクティブなコントロールに安定したAutomationIdを設定すること が最も効果的です。 WPF <!-- XAML: x:Name が自動的にAutomationIdになる --> < TextBox x:Name = "usernameInput" /> < PasswordBox x:Name = "passwordInput" /> < Button x:Name = "btnLogin" Content = "Login" /> < TextBlock x:Name = "lblError" /> WinForms // デザイナーまたはコードで設定 usernameInput.AccessibleName = "usernameInput" ; passwordInput.AccessibleName = "passwordInput" ; btnLogin.AccessibleName = "btnLogin" ; lblError.AccessibleName = "lblError" ; Win32 / MFC // .rcファイルのコントロールリソースIDがAutomationId文字列として公開される // IDC_EDIT_USERNAME -> AutomationId "1001" // 名前にはSetWindowTextを優先;より豊かなサポートにはIAccessibleを追加する Qt — 以下の専用セクションを参照 ページオブジェクトモデル tests/ ├── conftest.py # アプリ起動フィクスチャ、失敗時スクリーンショット ├── pytest.ini ├── config.py ├── pages/ │ ├── __init__.py # インポートに必須 │ ├── base_page.py # ロケーター、ウェイト、スクリーンショットヘルパー │ ├── login_page.py │ └── main_page.py ├── tests/ │ ├── __init__.py │ ├── test_login.py │ └── test_main_flow.py └── artifacts/ # スクリーンショット、動画、ログ 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 # --- ロケーター(優先順位順)--- def by_id ( self, auto_id, **kw ): """AutomationId — 最も安定。第一選択として使用する。""" return self .window.child_window(auto_id=auto_id, **kw) def by_name ( self, name, **kw ): """表示テキスト / アクセシブルな名前。""" return self .window.child_window(title=name, **kw) def by_class ( self, cls, index= 0 , **kw ): """コントロールクラス + インデックス — 脆弱、可能なら避ける。""" return self .window.child_window(class_name=cls, found_index=index, **kw) # --- ウェイト --- 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 ): """新しいトップレベルウィンドウ(ダイアログ、子ウィンドウ)を待つ。""" 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 ): """任意の条件をポーリング — UIAイベントが信頼できない場合に使用する。""" deadline = time.time() + timeout while time.time() < deadline: try : if fn(): return True except Exception: pass time.sleep(interval) raise TimeoutError( f"条件が {timeout} 秒以内に満たされなかった" ) # --- アクション --- 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 フォールバック:UIA Value Pattern が不完全な場合がある import sys, pywinauto.keyboard as kb print ( f"[windows-desktop-e2e] set_edit_text 失敗 ( {e} )、キーボードフォールバックを使用" , 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 "" # --- アーティファクト --- 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 新しいプロジェクトでは Tier 1サンドボックスフィクスチャ (以下参照)を優先してください — 追加コストゼロでファイルシステムの分離が追加されます。この基本フィクスチャは最小限/レガシーセットアップ専用です。 import os, pytest os.environ[ "QT_ACCESSIBILITY" ] = "1" # Qt 5.x UIAサポートに必要 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 環境変数が設定されていない" , 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 # 失敗時のスクリーンショット 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 # グレースフルな終了を試み、フォールバックとして強制終了 # proc は pywinauto Application — wait_for_process() ではなく wait_for_process_exit() を使用 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" , "" ) # 環境変数で設定 — デフォルトパスなし 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: 重要なパスの高速スモークテスト flaky: 既知の不安定なテスト addopts = -v --tb=short --html=artifacts/report.html --self-contained-html ロケーター戦略 AutomationId > Name(テキスト) > ClassName + インデックス > XPath (安定) (可読) (脆弱) (最後の手段) Accessibility Insights → Properties ペインで検査 → まず AutomationId を確認。 # 実行時の検査 — REPLに貼り付けてツリーを探索 win.print_control_identifiers() # またはスコープを絞る: win.child_window(auto_id= "groupBox1" ).print_control_identifiers() ウェイトパターン # コントロールが表示されるのを待つ page.wait_visible(page.by_id( "statusLabel" )) # コントロールが消えるのを待つ(ローディングスピナーなど) page.wait_gone(page.by_id( "spinnerOverlay" )) # ダイアログが表示されるのを待つ dlg = page.wait_window( "Confirm Delete" ) # カスタム条件(テキストの変化など) page.wait_until( lambda : page.get_text(page.by_id( "lblStatus" )) == "Ready" ) time.sleep() を主要な同期手段として使用しないこと — wait() または wait_until() を使用してください。 アーティファクト管理 # オンデマンドスクリーンショット page.screenshot( "after_login" ) # フルスクリーンキャプチャ(ウィンドウが画面外または最小化されている場合) import pyautogui pyautogui.screenshot( "artifacts/fullscreen.png" ) # ffmpegによる画面録画(テスト前に開始し、テスト後に停止) 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 ) 不安定なテストの対処 # 隔離 — PlaywrightのtestのFixmeと同等 @pytest.mark.skip( reason= "不安定:遅いCIでのアニメーションレース。Issue #42" ) def test_animated_transition ( self, app ): ... # CIのみでスキップ @pytest.mark.skipif( os.environ.get( "CI" ) == "true" , reason= "CIで不安定 #43" ) def test_heavy_load ( self, app ): ... 一般的な原因と修正: 原因 修正 コントロールが準備できていない time.sleep を wait_visible に置き換える ウィンドウがフォーカスされていない インタラクション前に win.set_focus() を追加する アニメーション進行中 wait_until(lambda: not loading_indicator.exists()) ダイアログのタイミング wait_window(title, timeout=15) CI環境のディスプレイが準備できていない DISPLAY を設定するかCIで仮想デスクトップを使用する テスト分離とサンドボックス 分離の3つの階層 — ニーズを満たす最も軽い階層を使用してください。 Tier 1 — ファイルシステム分離(デフォルト、常に使用) 各テストは subprocess.Popen と Application.connect() を通じて独自の APPDATA / LOCALAPPDATA / TEMP を取得します。pytestの tmp_path フィクスチャがクリーンアップを自動的に処理します。 # conftest.py — 基本的な `app` フィクスチャをこれに置き換える import os, subprocess, pytest from pywinauto import Application from config import APP_PATH, APP_ARGS, APP_TITLE, LAUNCH_TIMEOUT, ACTION_TIMEOUT, ARTIFACT_DIR @pytest.fixture( scope= "function" ) def app ( request, tmp_path ): """テストごとに新しいプロセス + 分離されたユーザーデータディレクトリ。""" if not APP_PATH: pytest.exit( "APP_PATH が設定されていない" , returncode= 1 ) # 全てのユーザーストレージを分離されたtmpディレクトリにリダイレクト sandbox_env = os.environ.copy() sandbox_env[ "QT_ACCESSIBILITY" ] = "1" sandbox_env[ "APPDATA" ] = str (tmp_path / "AppData" / "Roaming" )
Agent 识别该技能的关键词,点击任意一个即可复制。
该技能未提供触发词。
下载的 .skill 包内含以下字段。
| 字段 | 说明 |
|---|---|
| format | 格式标识(skill/v1) |
| skill_id | 技能唯一 ID |
| name | 技能名称 |
| version | 版本号 |
| description | 技能描述 |
| category | 所属分类(数组) |
| trigger_words | 触发词列表 |
| tags | 标签列表 |
| source | 来源标识 |
| source_url | 来源链接(本页地址) |
| exported_at | 导出时间(每次下载生成) |
| system_prompt | 系统提示词正文 |
| model_config | 模型参数:provider / model / temperature / max_tokens / top_p |
| examples | 示例 |
| install_guide | 各平台导入说明(Coze / Dify / Claude / 自定义框架) |