sap-qa-agent 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. sap_qa_agent-0.1.0/.github/workflows/test.yml +31 -0
  2. sap_qa_agent-0.1.0/.gitignore +18 -0
  3. sap_qa_agent-0.1.0/PKG-INFO +26 -0
  4. sap_qa_agent-0.1.0/README.md +81 -0
  5. sap_qa_agent-0.1.0/app/__init__.py +1 -0
  6. sap_qa_agent-0.1.0/app/ai/__init__.py +1 -0
  7. sap_qa_agent-0.1.0/app/ai/glm_client.py +398 -0
  8. sap_qa_agent-0.1.0/app/ai/prompt_manager.py +193 -0
  9. sap_qa_agent-0.1.0/app/config.py +127 -0
  10. sap_qa_agent-0.1.0/app/core/__init__.py +1 -0
  11. sap_qa_agent-0.1.0/app/core/execution_engine.py +407 -0
  12. sap_qa_agent-0.1.0/app/core/exploration_engine.py +231 -0
  13. sap_qa_agent-0.1.0/app/core/orchestrator.py +606 -0
  14. sap_qa_agent-0.1.0/app/core/result_verifier.py +330 -0
  15. sap_qa_agent-0.1.0/app/core/reuse_engine.py +211 -0
  16. sap_qa_agent-0.1.0/app/core/rule_engine.py +191 -0
  17. sap_qa_agent-0.1.0/app/core/screen_matcher.py +74 -0
  18. sap_qa_agent-0.1.0/app/core/self_healing_controller.py +206 -0
  19. sap_qa_agent-0.1.0/app/core/step_splitter.py +97 -0
  20. sap_qa_agent-0.1.0/app/core/template_matcher.py +82 -0
  21. sap_qa_agent-0.1.0/app/core/test_case_parser.py +578 -0
  22. sap_qa_agent-0.1.0/app/data/__init__.py +1 -0
  23. sap_qa_agent-0.1.0/app/data/fingerprint_store.py +119 -0
  24. sap_qa_agent-0.1.0/app/data/flow_variable.py +142 -0
  25. sap_qa_agent-0.1.0/app/data/knowledge_base.py +227 -0
  26. sap_qa_agent-0.1.0/app/data/models.py +552 -0
  27. sap_qa_agent-0.1.0/app/data/report_generator.py +253 -0
  28. sap_qa_agent-0.1.0/app/desktop_apps.py +250 -0
  29. sap_qa_agent-0.1.0/app/excel_parser.py +104 -0
  30. sap_qa_agent-0.1.0/app/executor.py +642 -0
  31. sap_qa_agent-0.1.0/app/main.py +90 -0
  32. sap_qa_agent-0.1.0/app/mcp_server/__init__.py +15 -0
  33. sap_qa_agent-0.1.0/app/mcp_server/channel_lock.py +326 -0
  34. sap_qa_agent-0.1.0/app/mcp_server/cli.py +450 -0
  35. sap_qa_agent-0.1.0/app/mcp_server/config.py +364 -0
  36. sap_qa_agent-0.1.0/app/mcp_server/context.py +346 -0
  37. sap_qa_agent-0.1.0/app/mcp_server/errors.py +235 -0
  38. sap_qa_agent-0.1.0/app/mcp_server/metrics.py +120 -0
  39. sap_qa_agent-0.1.0/app/mcp_server/middleware.py +270 -0
  40. sap_qa_agent-0.1.0/app/mcp_server/prompts.py +126 -0
  41. sap_qa_agent-0.1.0/app/mcp_server/resources.py +468 -0
  42. sap_qa_agent-0.1.0/app/mcp_server/safety.py +203 -0
  43. sap_qa_agent-0.1.0/app/mcp_server/server.py +221 -0
  44. sap_qa_agent-0.1.0/app/mcp_server/skills_loader/__init__.py +29 -0
  45. sap_qa_agent-0.1.0/app/mcp_server/skills_loader/cache.py +81 -0
  46. sap_qa_agent-0.1.0/app/mcp_server/skills_loader/loader.py +353 -0
  47. sap_qa_agent-0.1.0/app/mcp_server/skills_loader/validator.py +89 -0
  48. sap_qa_agent-0.1.0/app/mcp_server/token_budget.py +185 -0
  49. sap_qa_agent-0.1.0/app/mcp_server/tools/__init__.py +7 -0
  50. sap_qa_agent-0.1.0/app/mcp_server/tools/atomic.py +662 -0
  51. sap_qa_agent-0.1.0/app/mcp_server/tools/capture.py +154 -0
  52. sap_qa_agent-0.1.0/app/mcp_server/tools/execution.py +398 -0
  53. sap_qa_agent-0.1.0/app/mcp_server/tools/knowledge.py +172 -0
  54. sap_qa_agent-0.1.0/app/mcp_server/tools/parse.py +278 -0
  55. sap_qa_agent-0.1.0/app/mcp_server/tools/recording.py +254 -0
  56. sap_qa_agent-0.1.0/app/mcp_server/tools/session.py +357 -0
  57. sap_qa_agent-0.1.0/app/mcp_server/tools/skills.py +148 -0
  58. sap_qa_agent-0.1.0/app/mcp_server/tools/tasks.py +379 -0
  59. sap_qa_agent-0.1.0/app/mcp_server/transport/__init__.py +9 -0
  60. sap_qa_agent-0.1.0/app/mcp_server/transport/http.py +109 -0
  61. sap_qa_agent-0.1.0/app/mcp_server/transport/stdio.py +54 -0
  62. sap_qa_agent-0.1.0/app/models.py +92 -0
  63. sap_qa_agent-0.1.0/app/prompts/__init__.py +24 -0
  64. sap_qa_agent-0.1.0/app/prompts/batch_recovery.py +50 -0
  65. sap_qa_agent-0.1.0/app/prompts/car.py +82 -0
  66. sap_qa_agent-0.1.0/app/prompts/dialog_analyze.py +27 -0
  67. sap_qa_agent-0.1.0/app/prompts/element_match.py +20 -0
  68. sap_qa_agent-0.1.0/app/prompts/exception_handle.py +25 -0
  69. sap_qa_agent-0.1.0/app/prompts/failure_analysis.py +13 -0
  70. sap_qa_agent-0.1.0/app/prompts/recovery_plan.py +56 -0
  71. sap_qa_agent-0.1.0/app/prompts/recovery_script.py +86 -0
  72. sap_qa_agent-0.1.0/app/prompts/sap.py +74 -0
  73. sap_qa_agent-0.1.0/app/prompts/script_generate.py +61 -0
  74. sap_qa_agent-0.1.0/app/prompts/step_adjust.py +44 -0
  75. sap_qa_agent-0.1.0/app/prompts/step_split.py +281 -0
  76. sap_qa_agent-0.1.0/app/prompts/verify_result.py +23 -0
  77. sap_qa_agent-0.1.0/app/recorder.py +132 -0
  78. sap_qa_agent-0.1.0/app/routes.py +247 -0
  79. sap_qa_agent-0.1.0/app/sap/__init__.py +1 -0
  80. sap_qa_agent-0.1.0/app/sap/dialog_handler.py +150 -0
  81. sap_qa_agent-0.1.0/app/sap/dialog_watchdog.py +128 -0
  82. sap_qa_agent-0.1.0/app/sap/element_path_finder.py +899 -0
  83. sap_qa_agent-0.1.0/app/sap/fingerprint_generator.py +302 -0
  84. sap_qa_agent-0.1.0/app/sap/sap_gui_wrapper.py +502 -0
  85. sap_qa_agent-0.1.0/app/sap/screen_state_collector.py +99 -0
  86. sap_qa_agent-0.1.0/app/sap/script_executor.py +1880 -0
  87. sap_qa_agent-0.1.0/app/sap/vlm_fallback.py +210 -0
  88. sap_qa_agent-0.1.0/app/sap_routes.py +488 -0
  89. sap_qa_agent-0.1.0/app/task_store.py +221 -0
  90. sap_qa_agent-0.1.0/app/utils.py +40 -0
  91. sap_qa_agent-0.1.0/app/vlm_executor.py +1915 -0
  92. sap_qa_agent-0.1.0/artifacts/9b539a374682/report.json +17 -0
  93. sap_qa_agent-0.1.0/artifacts/de9cd8df75b5/report.json +17 -0
  94. sap_qa_agent-0.1.0/artifacts/mcp-parsed/9c73327f7bb7/case_0.json +15 -0
  95. sap_qa_agent-0.1.0/artifacts/mcp-parsed/9c73327f7bb7/case_1.json +13 -0
  96. sap_qa_agent-0.1.0/artifacts/screenshots/step_004.png +0 -0
  97. sap_qa_agent-0.1.0/artifacts/screenshots/step_005.png +0 -0
  98. sap_qa_agent-0.1.0/artifacts/screenshots/step_006.png +0 -0
  99. sap_qa_agent-0.1.0/collect_elements.py +284 -0
  100. sap_qa_agent-0.1.0/conftest.py +72 -0
  101. sap_qa_agent-0.1.0/docs/PUBLISHING.md +192 -0
  102. sap_qa_agent-0.1.0/docs/mcp-integration.md +525 -0
  103. sap_qa_agent-0.1.0/examples/mcp/claude_desktop_config.json +10 -0
  104. sap_qa_agent-0.1.0/examples/mcp/kiro_mcp.json +18 -0
  105. sap_qa_agent-0.1.0/knowledge_base/page_fingerprints.json +88 -0
  106. sap_qa_agent-0.1.0/knowledge_base/patterns.json +11 -0
  107. sap_qa_agent-0.1.0/knowledge_base/templates.json +615 -0
  108. sap_qa_agent-0.1.0/mcp_skills/sap-knowledge-base-reuse/SKILL.md +66 -0
  109. sap_qa_agent-0.1.0/mcp_skills/sap-parse-and-execute/SKILL.md +76 -0
  110. sap_qa_agent-0.1.0/mcp_skills/sap-recording-to-testcase/SKILL.md +68 -0
  111. sap_qa_agent-0.1.0/mcp_skills/sap-self-healing-playbook/SKILL.md +74 -0
  112. sap_qa_agent-0.1.0/mcp_skills/sap-session-bootstrap/SKILL.md +63 -0
  113. sap_qa_agent-0.1.0/pyproject.toml +45 -0
  114. sap_qa_agent-0.1.0/requirements.txt +19 -0
  115. sap_qa_agent-0.1.0/run.py +28 -0
  116. sap_qa_agent-0.1.0/run_test_case.py +201 -0
  117. sap_qa_agent-0.1.0/test_e2e_validation.py +379 -0
  118. sap_qa_agent-0.1.0/test_login_validation.py +148 -0
  119. sap_qa_agent-0.1.0/test_sap_com_diag.py +110 -0
  120. sap_qa_agent-0.1.0/test_sap_connection.py +101 -0
  121. sap_qa_agent-0.1.0/test_sap_login.py +155 -0
  122. sap_qa_agent-0.1.0/tests/__init__.py +1 -0
  123. sap_qa_agent-0.1.0/tests/mcp/__init__.py +1 -0
  124. sap_qa_agent-0.1.0/tests/mcp/conftest.py +111 -0
  125. sap_qa_agent-0.1.0/tests/mcp/contract/__init__.py +1 -0
  126. sap_qa_agent-0.1.0/tests/mcp/contract/test_initialize.py +4 -0
  127. sap_qa_agent-0.1.0/tests/mcp/contract/test_prompts_list.py +4 -0
  128. sap_qa_agent-0.1.0/tests/mcp/contract/test_resources_list.py +4 -0
  129. sap_qa_agent-0.1.0/tests/mcp/contract/test_tools_list.py +4 -0
  130. sap_qa_agent-0.1.0/tests/mcp/e2e/__init__.py +1 -0
  131. sap_qa_agent-0.1.0/tests/mcp/e2e/test_full_flow_real_sap.py +18 -0
  132. sap_qa_agent-0.1.0/tests/mcp/e2e/test_smoke_checklist.py +245 -0
  133. sap_qa_agent-0.1.0/tests/mcp/property/__init__.py +1 -0
  134. sap_qa_agent-0.1.0/tests/mcp/property/test_error_envelope.py +4 -0
  135. sap_qa_agent-0.1.0/tests/mcp/property/test_initialize_contract.py +4 -0
  136. sap_qa_agent-0.1.0/tests/mcp/property/test_no_secret_leak.py +4 -0
  137. sap_qa_agent-0.1.0/tests/mcp/property/test_path_whitelist.py +4 -0
  138. sap_qa_agent-0.1.0/tests/mcp/property/test_skill_required_tools.py +4 -0
  139. sap_qa_agent-0.1.0/tests/mcp/property/test_task_uniqueness.py +4 -0
  140. sap_qa_agent-0.1.0/tests/mcp/property/test_token_budget.py +4 -0
  141. sap_qa_agent-0.1.0/tests/mcp/resources/__init__.py +1 -0
  142. sap_qa_agent-0.1.0/tests/mcp/resources/test_uri_router.py +4 -0
  143. sap_qa_agent-0.1.0/tests/mcp/test_metrics.py +167 -0
  144. sap_qa_agent-0.1.0/tests/mcp/test_response_scrubbing.py +226 -0
  145. sap_qa_agent-0.1.0/tests/mcp/tools/__init__.py +1 -0
  146. sap_qa_agent-0.1.0/tests/mcp/tools/test_capture.py +4 -0
  147. sap_qa_agent-0.1.0/tests/mcp/tools/test_execute_test_case.py +4 -0
  148. sap_qa_agent-0.1.0/tests/mcp/tools/test_knowledge.py +4 -0
  149. sap_qa_agent-0.1.0/tests/mcp/tools/test_parse_test_case.py +4 -0
  150. sap_qa_agent-0.1.0/tests/mcp/tools/test_recording.py +4 -0
  151. sap_qa_agent-0.1.0/tests/mcp/tools/test_session.py +4 -0
  152. sap_qa_agent-0.1.0/tests/mcp/tools/test_skills.py +4 -0
  153. sap_qa_agent-0.1.0/tests/mcp/tools/test_tasks.py +4 -0
  154. sap_qa_agent-0.1.0/tests/mcp/tools/test_token_budget.py +228 -0
  155. sap_qa_agent-0.1.0/tests/test_dialog_watchdog.py +214 -0
  156. sap_qa_agent-0.1.0/tests/test_excel_parser.py +134 -0
  157. sap_qa_agent-0.1.0/tests/test_executor.py +251 -0
  158. sap_qa_agent-0.1.0/tests/test_exploration_engine.py +123 -0
  159. sap_qa_agent-0.1.0/tests/test_flow_variable.py +155 -0
  160. sap_qa_agent-0.1.0/tests/test_knowledge_base.py +286 -0
  161. sap_qa_agent-0.1.0/tests/test_orchestrator.py +352 -0
  162. sap_qa_agent-0.1.0/tests/test_prompt_manager.py +127 -0
  163. sap_qa_agent-0.1.0/tests/test_result_verifier.py +156 -0
  164. sap_qa_agent-0.1.0/tests/test_reuse_engine.py +105 -0
  165. sap_qa_agent-0.1.0/tests/test_screen_matcher.py +141 -0
  166. sap_qa_agent-0.1.0/tests/test_step_splitter.py +273 -0
  167. sap_qa_agent-0.1.0/tests/test_task_store.py +92 -0
  168. sap_qa_agent-0.1.0/tests/test_template_matcher.py +68 -0
  169. sap_qa_agent-0.1.0/tests/test_test_case_parser.py +326 -0
  170. sap_qa_agent-0.1.0/tests/test_utils.py +81 -0
  171. sap_qa_agent-0.1.0/uploads/SAP_FB01_/350/264/242/345/212/241/345/207/255/350/257/201/345/275/225/345/205/245/346/265/213/350/257/225.xlsx +0 -0
  172. sap_qa_agent-0.1.0/uploads/VA02/346/265/213/350/257/225/350/204/232/346/234/254.xlsx +0 -0
  173. sap_qa_agent-0.1.0/uploads/rules.xlsx +0 -0
  174. sap_qa_agent-0.1.0/uploads/sample_steps.xlsx +0 -0
  175. sap_qa_agent-0.1.0/uploads/testcase.xlsx +0 -0
  176. sap_qa_agent-0.1.0/uv.lock +2639 -0
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Set up Python 3.12
17
+ uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+
21
+ - name: Install dependencies
22
+ run: |
23
+ python -m pip install --upgrade pip
24
+ pip install -e ".[all]"
25
+ pip install pytest pytest-asyncio hypothesis
26
+
27
+ - name: Validate Skills
28
+ run: sap-qa-mcp validate-skills
29
+
30
+ - name: Run MCP tests
31
+ run: pytest tests/mcp/ -v --tb=short
@@ -0,0 +1,18 @@
1
+ # OS
2
+ .DS_Store
3
+ Thumbs.db
4
+
5
+ # IDE
6
+ .idea/
7
+ .vscode/
8
+ *.swp
9
+ *.swo
10
+
11
+ .dist/
12
+ .build/
13
+ .node_modules/
14
+
15
+ ./.venv
16
+
17
+ pyproject.toml
18
+ .artifacts/
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: sap-qa-agent
3
+ Version: 0.1.0
4
+ Summary: SAP QA Agent — UI automation engine + MCP Server packaging for SAP Public Cloud
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: fastmcp>=0.2.0
7
+ Requires-Dist: httpx
8
+ Requires-Dist: opencv-python
9
+ Requires-Dist: openpyxl>=3.1
10
+ Requires-Dist: pillow
11
+ Requires-Dist: psutil
12
+ Requires-Dist: pydantic>=2.0
13
+ Requires-Dist: python-frontmatter
14
+ Requires-Dist: pywin32; sys_platform == 'win32'
15
+ Requires-Dist: pyyaml
16
+ Requires-Dist: rapidocr-onnxruntime
17
+ Requires-Dist: ultralytics
18
+ Provides-Extra: all
19
+ Requires-Dist: fastapi>=0.110; extra == 'all'
20
+ Requires-Dist: python-multipart; extra == 'all'
21
+ Requires-Dist: uvicorn>=0.29; extra == 'all'
22
+ Provides-Extra: backend
23
+ Requires-Dist: fastapi>=0.110; extra == 'backend'
24
+ Requires-Dist: python-multipart; extra == 'backend'
25
+ Requires-Dist: uvicorn>=0.29; extra == 'backend'
26
+ Provides-Extra: ui
@@ -0,0 +1,81 @@
1
+ # SAP QA Agent
2
+
3
+ 基于 AI 驱动的 SAP GUI 自动化测试引擎,支持测试用例解析、智能执行、AI 自愈和操作录制。
4
+
5
+ ## 快速开始
6
+
7
+ ```bash
8
+ # 安装
9
+ pip install -e .
10
+
11
+ # 启动 FastAPI 后端(配合 Vue 前端使用)
12
+ sap-qa-backend
13
+
14
+ # 或启动 MCP Server(配合 Claude Desktop / Kiro / Cursor 使用)
15
+ sap-qa-mcp
16
+ ```
17
+
18
+ ## 核心功能
19
+
20
+ - **测试用例解析** — 支持 Excel (.xlsx) 和 Markdown 格式
21
+ - **SAP GUI 自动化** — 基于 COM Scripting API 驱动 SAP 操作
22
+ - **AI 自愈** — 执行失败时自动分析并修复(基于智谱 GLM)
23
+ - **知识库复用** — 积累脚本模板,加速后续执行
24
+ - **操作录制** — 记录手动操作转化为可复用步骤
25
+
26
+ ## 两种使用方式
27
+
28
+ ### 1. Web 界面(FastAPI + Vue)
29
+
30
+ ```bash
31
+ pip install sap-qa-agent[backend]
32
+ sap-qa-backend
33
+ # 访问 http://localhost:8000
34
+ ```
35
+
36
+ ### 2. 作为 MCP Server 被使用
37
+
38
+ SAP QA Agent 可作为 MCP (Model Context Protocol) Server,被 Claude Desktop、Kiro、Cursor 等 AI Agent 直接调用。
39
+
40
+ ```bash
41
+ pip install sap-qa-agent
42
+ sap-qa-mcp
43
+ ```
44
+
45
+ 详细接入指南请参阅 **[docs/mcp-integration.md](docs/mcp-integration.md)**,包含:
46
+
47
+ - Claude Desktop / Kiro / Cursor 三种宿主的配置示例
48
+ - SAP Scripting 启用步骤
49
+ - GLM API Key 配置方式
50
+ - 端到端使用示例
51
+ - 可用工具列表与故障排查
52
+
53
+ ## 项目结构
54
+
55
+ ```
56
+ ui_test_python/
57
+ ├── app/ # 应用代码
58
+ │ ├── main.py # FastAPI 入口
59
+ │ ├── mcp_server/ # MCP Server 实现
60
+ │ ├── core/ # 核心引擎(解析、执行、自愈)
61
+ │ ├── sap/ # SAP GUI 封装
62
+ │ ├── ai/ # GLM AI 客户端
63
+ │ └── data/ # 数据模型与知识库
64
+ ├── mcp_skills/ # MCP Skills 包
65
+ ├── knowledge_base/ # 脚本模板知识库
66
+ ├── tests/ # 测试套件
67
+ ├── examples/mcp/ # MCP 配置示例
68
+ ├── docs/ # 文档
69
+ │ └── mcp-integration.md # MCP 接入指南
70
+ └── pyproject.toml # 项目配置
71
+ ```
72
+
73
+ ## 环境要求
74
+
75
+ - Python 3.12+
76
+ - Windows 10/11
77
+ - SAP GUI(已启用 Scripting)
78
+
79
+ ## 许可证
80
+
81
+ Private
@@ -0,0 +1 @@
1
+ # backend app package
@@ -0,0 +1 @@
1
+ """AI layer modules for GLM integration."""
@@ -0,0 +1,398 @@
1
+ """智谱 AI GLM 模型客户端 — 封装所有 AI 调用。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from typing import Any
8
+
9
+ from zhipuai import ZhipuAI
10
+
11
+ from app.config import (
12
+ GLM_API_KEY,
13
+ GLM_BASE_URL,
14
+ GLM_EXCEPTION_TIMEOUT,
15
+ GLM_MODEL,
16
+ GLM_TIMEOUT,
17
+ )
18
+ from app.data.models import ElementInfo, StepActionType, StepResult, TestStep
19
+ from app.ai.prompt_manager import PromptManager
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class GlmClient:
25
+ """智谱 AI GLM 模型客户端。
26
+
27
+ 所有公开方法均为 async(保持接口兼容),内部直接同步调用 SDK。
28
+ 由于执行线程是独立的 worker thread,阻塞调用不会影响 FastAPI 主 loop。
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ api_key: str = GLM_API_KEY,
34
+ base_url: str = GLM_BASE_URL,
35
+ model: str = GLM_MODEL,
36
+ ) -> None:
37
+ self._client = ZhipuAI(api_key=api_key, base_url=base_url)
38
+ self._base_url = base_url
39
+ self._model = model
40
+ self._prompt_manager = PromptManager()
41
+
42
+ # ------------------------------------------------------------------
43
+ # Internal helpers
44
+ # ------------------------------------------------------------------
45
+
46
+ def _chat_sync(self, prompt: str, timeout: int = GLM_TIMEOUT) -> str:
47
+ """Synchronous GLM chat call."""
48
+ logger.info("GLM request (model=%s, timeout=%ds):\n%s", self._model, timeout, prompt[:500])
49
+ response = self._client.chat.completions.create(
50
+ model=self._model,
51
+ messages=[{"role": "user", "content": prompt}],
52
+ timeout=timeout,
53
+ )
54
+ content = response.choices[0].message.content
55
+ logger.info("GLM response:\n%s", content[:500])
56
+ return content
57
+
58
+ @staticmethod
59
+ def _parse_json(text: str) -> Any:
60
+ """Extract and parse JSON from GLM response text."""
61
+ cleaned = text.strip()
62
+ if cleaned.startswith("```"):
63
+ lines = cleaned.splitlines()
64
+ inner_lines = []
65
+ started = False
66
+ for line in lines:
67
+ if not started:
68
+ if line.strip().startswith("```"):
69
+ started = True
70
+ continue
71
+ elif line.strip() == "```":
72
+ break
73
+ else:
74
+ inner_lines.append(line)
75
+ cleaned = "\n".join(inner_lines)
76
+ try:
77
+ return json.loads(cleaned)
78
+ except json.JSONDecodeError as exc:
79
+ logger.error("Failed to parse GLM JSON response: %s\nRaw: %s", exc, text[:300])
80
+ raise
81
+
82
+ # ------------------------------------------------------------------
83
+ # Public async API (sync internally, async interface for compatibility)
84
+ # ------------------------------------------------------------------
85
+
86
+ async def split_steps(self, test_case_text: str, test_data: dict[str, str] | None = None) -> list[TestStep]:
87
+ """调用 GLM 拆分测试步骤,返回 TestStep 列表。"""
88
+ import json as _json
89
+ test_data_str = _json.dumps(test_data, ensure_ascii=False) if test_data else ""
90
+ prompt = self._prompt_manager.render_step_split(
91
+ transaction_code="",
92
+ steps_text=test_case_text,
93
+ test_data=test_data_str,
94
+ )
95
+ raw = self._chat_sync(prompt, GLM_TIMEOUT)
96
+ items = self._parse_json(raw)
97
+ steps: list[TestStep] = []
98
+ for item in items:
99
+ # GLM 可能返回不同的字段名,做容错处理
100
+ target = (
101
+ item.get("target_description")
102
+ or item.get("target")
103
+ or item.get("description")
104
+ or item.get("field")
105
+ or ""
106
+ )
107
+ action = item.get("action_type") or item.get("action") or "input"
108
+ value = item.get("value") or item.get("input_value")
109
+ # 解析表格行号和列名(结构化表格输入)
110
+ row = None
111
+ raw_row = item.get("row")
112
+ if raw_row is not None:
113
+ try:
114
+ row = int(raw_row)
115
+ except (ValueError, TypeError):
116
+ pass
117
+ column = item.get("column") # str or None
118
+ steps.append(
119
+ TestStep(
120
+ index=item.get("index", len(steps) + 1),
121
+ action_type=StepActionType(action),
122
+ target_description=target,
123
+ value=value,
124
+ flow_variable_ref=item.get("flow_variable_ref"),
125
+ row=row,
126
+ column=column,
127
+ )
128
+ )
129
+ logger.info("split_steps produced %d steps", len(steps))
130
+ return steps
131
+
132
+ async def generate_script(self, test_steps: list[TestStep], screen_elements: str = "") -> str:
133
+ """调用 GLM 生成 SAP GUI Scripting API 脚本。"""
134
+ steps_dicts = [
135
+ {
136
+ "index": s.index,
137
+ "action_type": s.action_type.value,
138
+ "target_description": s.target_description,
139
+ "value": s.value,
140
+ "flow_variable_ref": s.flow_variable_ref,
141
+ "row": s.row,
142
+ "column": s.column,
143
+ }
144
+ for s in test_steps
145
+ ]
146
+ prompt = self._prompt_manager.render_script_generate(
147
+ test_steps_json=json.dumps(steps_dicts, ensure_ascii=False, indent=2),
148
+ screen_elements=screen_elements,
149
+ )
150
+ raw = self._chat_sync(prompt, GLM_TIMEOUT)
151
+ script = raw.strip()
152
+ if script.startswith("```"):
153
+ lines = script.splitlines()
154
+ inner: list[str] = []
155
+ started = False
156
+ for line in lines:
157
+ if not started:
158
+ if line.strip().startswith("```"):
159
+ started = True
160
+ continue
161
+ elif line.strip() == "```":
162
+ break
163
+ else:
164
+ inner.append(line)
165
+ script = "\n".join(inner)
166
+ logger.info("generate_script produced %d chars", len(script))
167
+ return script
168
+
169
+ async def analyze_exception(
170
+ self,
171
+ error_info: str,
172
+ element_tree_snapshot: str,
173
+ ) -> dict:
174
+ """调用 GLM 分析异常并生成恢复方案。"""
175
+ prompt = self._prompt_manager.render_exception_handle(
176
+ error_info=error_info,
177
+ current_step="",
178
+ ui_state=element_tree_snapshot,
179
+ status_bar_message="",
180
+ dialog_content="",
181
+ )
182
+ raw = self._chat_sync(prompt, GLM_EXCEPTION_TIMEOUT)
183
+ plan = self._parse_json(raw)
184
+ logger.info("analyze_exception decision: %s", plan.get("action"))
185
+ return plan
186
+
187
+ async def select_element(
188
+ self,
189
+ candidates: list[ElementInfo],
190
+ step_context: str,
191
+ ) -> int:
192
+ """调用 GLM 从候选元素中选择最佳匹配,返回索引。"""
193
+ candidates_dicts = [
194
+ {
195
+ "id_path": c.id_path,
196
+ "element_type": c.element_type,
197
+ "tooltip": c.tooltip,
198
+ "label_text": c.label_text,
199
+ "current_value": c.current_value,
200
+ }
201
+ for c in candidates
202
+ ]
203
+ prompt = self._prompt_manager.render_element_match(
204
+ target_description=step_context,
205
+ step_context=step_context,
206
+ window_context="",
207
+ candidates_json=json.dumps(candidates_dicts, ensure_ascii=False, indent=2),
208
+ )
209
+ raw = self._chat_sync(prompt, GLM_TIMEOUT)
210
+ result = self._parse_json(raw)
211
+ selected = int(result["selected_index"])
212
+ logger.info("select_element chose index %d: %s", selected, result.get("reason", ""))
213
+ return selected
214
+
215
+ async def analyze_exception_v2(
216
+ self,
217
+ error_info: str,
218
+ step_description: str,
219
+ screen_snapshot: str,
220
+ retry_history: list[dict],
221
+ ) -> dict:
222
+ """调用 GLM 分析异常并生成 RecoveryPlan(v2)。"""
223
+ history_str = "\n".join(
224
+ f"第{h.get('attempt', '?')}次: 错误={h.get('error', '')}, 快照={h.get('snapshot', '')}"
225
+ for h in retry_history
226
+ ) if retry_history else "无"
227
+
228
+ prompt = self._prompt_manager.render_recovery_plan(
229
+ step_description=step_description,
230
+ error_info=error_info,
231
+ screen_snapshot=screen_snapshot,
232
+ retry_history=history_str,
233
+ )
234
+ raw = self._chat_sync(prompt, GLM_EXCEPTION_TIMEOUT)
235
+ plan = self._parse_json(raw)
236
+ logger.info("analyze_exception_v2 decision: %s (confidence=%.2f)",
237
+ plan.get("action"), plan.get("confidence", 0))
238
+ return plan
239
+
240
+ async def analyze_dialog(
241
+ self,
242
+ dialog_title: str,
243
+ dialog_text: str,
244
+ timeout: int = GLM_EXCEPTION_TIMEOUT,
245
+ ) -> dict:
246
+ """调用 GLM 分析未知 SAP 弹窗并返回处理建议。"""
247
+ prompt = self._prompt_manager.render_dialog_analyze(
248
+ dialog_title=dialog_title,
249
+ dialog_text=dialog_text,
250
+ )
251
+ raw = self._chat_sync(prompt, timeout)
252
+ suggestion = self._parse_json(raw)
253
+ logger.info("analyze_dialog action: %s, reason: %s",
254
+ suggestion.get("action"), suggestion.get("reason", ""))
255
+ return suggestion
256
+
257
+ async def analyze_failure(self, step_result: StepResult) -> str:
258
+ """调用 GLM 生成失败根因分析。"""
259
+ prompt = self._prompt_manager.render_failure_analysis(
260
+ step_description=f"Step {step_result.step_index}",
261
+ error_message=step_result.error_message or "",
262
+ status_bar_message=step_result.status_bar_message or "",
263
+ screenshot_description="",
264
+ )
265
+ raw = self._chat_sync(prompt, GLM_TIMEOUT)
266
+ analysis = raw.strip()
267
+ logger.info("analyze_failure result: %s", analysis[:200])
268
+ return analysis
269
+
270
+ async def verify_result(
271
+ self,
272
+ expected: str,
273
+ window_title: str,
274
+ status_bar: str,
275
+ screen_elements: str,
276
+ ) -> dict:
277
+ """调用 GLM 判断当前屏幕状态是否满足测试预期。"""
278
+ prompt = self._prompt_manager.render_verify_result(
279
+ expected=expected,
280
+ window_title=window_title,
281
+ status_bar=status_bar,
282
+ screen_elements=screen_elements,
283
+ )
284
+ raw = self._chat_sync(prompt, timeout=10)
285
+ result = self._parse_json(raw)
286
+ logger.info("verify_result: pass=%s, reason=%s",
287
+ result.get("pass"), result.get("reason", ""))
288
+ return result
289
+
290
+ async def adjust_steps(
291
+ self,
292
+ test_case_context: str,
293
+ window_title: str,
294
+ status_bar: str,
295
+ screen_elements: str,
296
+ original_steps: list[dict],
297
+ ) -> list[dict]:
298
+ """根据当前屏幕状态调整步骤列表。
299
+
300
+ Returns:
301
+ 调整后的步骤 dict 列表。
302
+ """
303
+ original_steps_json = json.dumps(original_steps, ensure_ascii=False, indent=2)
304
+ prompt = self._prompt_manager.render_step_adjust(
305
+ test_case_context=test_case_context,
306
+ window_title=window_title,
307
+ status_bar=status_bar,
308
+ screen_elements=screen_elements,
309
+ original_steps=original_steps_json,
310
+ )
311
+ raw = self._chat_sync(prompt, GLM_TIMEOUT)
312
+ adjusted = self._parse_json(raw)
313
+ logger.info("adjust_steps: %d → %d steps", len(original_steps), len(adjusted))
314
+ return adjusted
315
+
316
+ async def generate_recovery_script(
317
+ self,
318
+ test_case_context: str,
319
+ step_index: int,
320
+ action_type: str,
321
+ target_description: str,
322
+ value: str,
323
+ error_message: str,
324
+ completed_steps: str,
325
+ window_title: str,
326
+ status_bar: str,
327
+ screen_elements: str,
328
+ fingerprint_info: str = "",
329
+ ) -> str:
330
+ """调用 GLM 生成修复脚本 — 步骤失败时根据当前屏幕状态生成可执行代码。"""
331
+ prompt = self._prompt_manager.render_recovery_script(
332
+ test_case_context=test_case_context,
333
+ step_index=step_index,
334
+ action_type=action_type,
335
+ target_description=target_description,
336
+ value=value,
337
+ error_message=error_message,
338
+ completed_steps=completed_steps,
339
+ window_title=window_title,
340
+ status_bar=status_bar,
341
+ screen_elements=screen_elements,
342
+ fingerprint_info=fingerprint_info,
343
+ )
344
+ raw = self._chat_sync(prompt, GLM_TIMEOUT)
345
+ script = raw.strip()
346
+ if script.startswith("```"):
347
+ lines = script.splitlines()
348
+ inner: list[str] = []
349
+ started = False
350
+ for line in lines:
351
+ if not started:
352
+ if line.strip().startswith("```"):
353
+ started = True
354
+ continue
355
+ elif line.strip() == "```":
356
+ break
357
+ else:
358
+ inner.append(line)
359
+ script = "\n".join(inner)
360
+ logger.info("generate_recovery_script produced %d chars", len(script))
361
+ return script
362
+
363
+ async def generate_batch_recovery_script(
364
+ self,
365
+ test_case_context: str,
366
+ batch_steps: str,
367
+ completed_steps: str,
368
+ window_title: str,
369
+ status_bar: str,
370
+ screen_elements: str,
371
+ ) -> str:
372
+ """调用 GLM 生成批量修复脚本 — 多个连续输入步骤一次性处理。"""
373
+ prompt = self._prompt_manager.render_batch_recovery(
374
+ test_case_context=test_case_context,
375
+ batch_steps=batch_steps,
376
+ completed_steps=completed_steps,
377
+ window_title=window_title,
378
+ status_bar=status_bar,
379
+ screen_elements=screen_elements,
380
+ )
381
+ raw = self._chat_sync(prompt, GLM_TIMEOUT)
382
+ script = raw.strip()
383
+ if script.startswith("```"):
384
+ lines = script.splitlines()
385
+ inner: list[str] = []
386
+ started = False
387
+ for line in lines:
388
+ if not started:
389
+ if line.strip().startswith("```"):
390
+ started = True
391
+ continue
392
+ elif line.strip() == "```":
393
+ break
394
+ else:
395
+ inner.append(line)
396
+ script = "\n".join(inner)
397
+ logger.info("generate_batch_recovery_script produced %d chars", len(script))
398
+ return script