提交 ac823858 authored 作者: 陈泽健's avatar 陈泽健

feat(executor): 系统配置切换被测系统后用例执行自动跟随新地址

方案A·执行期地址重写:执行器新增 _rebase_sut_url + KNOWN_SUT_HOSTS,
导航/接口步骤若指向已知被测系统主机(默认 https://192.168.5.44)则自动
替换为当前配置的 SUT 地址(保留 scheme/路径/查询/hash),130 条存量
硬编码用例自动跟随、不动任何数据;新增 TestRebaseSutUrl 3 条回归测试;
已部署 5.60 生产(容器内真实验证通过)
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 9d3c24b9
......@@ -3,7 +3,7 @@
> **生成时间**: 2026-08-19
> **当前分支**: `platform-auto-test`
> **最近提交**: `1bb8756a` chore: .gitignore 遮蔽历史会话遗留的一次性脚本与临时输出
> **状态**: 🟢 **会话27:UI自动化定时任务模块 + 系统配置被测系统URL 已实现并本地全链路验证通过——调度引擎(30s 轮询 + 到点异步触发 + 冲突跳过)复用现有执行引擎生成 `trigger_type=scheduled` 执行记录,报告按模块分组并展示被测系统 URL;SUT URL 保存后重启持久化。后端 179 测试全过、前端 build 通过、真实端到端验收通过**
> **状态**: 🟢 **会话28:系统配置切换被测系统后用例自动跟随(方案A·执行期地址重写)已实现并部署 5.60 生产——执行器新增 `_rebase_sut_url`,导航/接口步骤若指向已知被测系统主机(默认 5.44)则自动替换为当前配置的 SUT 地址,130 条存量硬编码用例自动跟随,不动任何数据。后端 182 测试全过、已部署 5.60**
---
......@@ -180,7 +180,34 @@
- git-bash 的 `/tmp` 与 Windows Python 的 `/tmp` 指向不同路径(`C:\tmp` vs Git 安装目录)——跨工具传 JSON 文件务必用显式绝对路径
- 多窗口并行时注意与另一后端共用 SQLite,避免并发写锁
**遗留**:验证用定时执行记录 + 报告留在开发库(`verify-interval-1min-20260819-140054`,可作演示数据);**尚未部署 5.60 生产**(按用户要求待后续)。
**遗留**:验证用定时执行记录 + 报告留在开发库(`verify-interval-1min-20260819-140054`,可作演示数据);**已部署 5.60 生产**(2026-08-19:13 后端文件 + 前端 dist 90 文件上传 → 容器重启 healthy;调度引擎启动、`/api/scheduled-tasks``/api/system/sut-config` 端点正常、定时任务菜单/页面/系统配置被测系统卡片在 5.60 网页验证通过;5.60 被测系统 URL 当前为 env 默认 `https://192.168.5.44`,在系统配置页保存一次即持久化到 MySQL)。
### ✅ 系统配置切换被测系统后用例自动跟随(方案A·执行期地址重写)(会话 28,2026-08-19)
**需求**:在「系统配置」修改被测系统 URL 后,用例执行自动使用修改后的地址——存量与新建用例均无需改动。
**背景**:切换被测系统前已核实,平台 **289 条用例**(247 UI + 42 安全),其中 **130 条 UI 用例**在 navigate 步骤硬编码 `https://192.168.5.44`(131 个绝对导航步骤,0 相对),而 `_do_navigate` 原样 `goto` 不重写主机 → 仅改配置无法让这些用例跟随。
**方案对比**
| | A. 执行期地址重写(**采用**) | B. 批量迁移用例数据 |
|------|------|------|
| 改动 | 执行器一处代码 | 改 130 条用例数据(本地 + 生产库) |
| 数据 | **不动** | 需备份、有写坏风险 |
| 新录用例 | 自动兼容 | 录制仍写绝对地址则还会复发 |
| 回滚 | 删除重写调用即可 | 需恢复数据库 |
**实现**`backend/app/executors/playwright_executor.py`):
- 新增 `KNOWN_SUT_HOSTS` 类常量(从默认 `https://192.168.5.44` 派生,可扩展追加)
- 新增 `_rebase_sut_url()`:目标 URL 主机属于已知被测系统主机 → 替换为当前配置 `self.base_url` 主机(保留 scheme/路径/查询/hash);相对路径、外部链接不受影响
- `_do_navigate` 导航前调用 → **130 条存量硬编码导航步骤全部自动跟随**
- `_do_api_call` 接口步骤 URL 走同一重写 → 鉴权注入(`_inject_session_auth`)与实际请求同步跟随
**验证**`TestRebaseSutUrl` 3 条回归测试(切换跟随/路径hash保留/未切换不误改/外链相对地址不动)+ 后端全量 **182 passed**
**部署**:5.60 生产(upload 1 个后端文件 `executors/playwright_executor.py` → 容器重启 healthy → 端点/日志验证通过)。
**效果**:系统配置保存新 SUT → 后续用例执行(手动/定时)的导航与接口步骤自动打到新地址。
---
......@@ -188,7 +215,8 @@
| 会话 | 日期 | 主题 | 结果 |
|------|------|------|------|
| 27 | 08-19 | **UI自动化定时任务模块 + 系统配置被测系统URL**:调度引擎(30s 轮询 + 到期异步触发 + 冲突跳过)+ 定时任务 CRUD/启停/立即执行/运行记录 + 报告按模块分组并展示被测系统 URL + SUT URL 保存/重启持久化 + 前端定时任务页与系统配置卡片 | ✅ 按 PRD 工作流实现,后端 179 测试全过、前端 build 通过、真实端到端验收通过(未部署生产) |
| 28 | 08-19 | **系统配置切换被测系统后用例自动跟随(方案A·执行期地址重写)**`_rebase_sut_url` 导航/接口主机重写(已知 SUT 主机→当前配置地址),130 条存量硬编码用例自动跟随、不动数据 | ✅ 后端 182 测试全过、已部署 5.60 生产 |
| 27 | 08-19 | **UI自动化定时任务模块 + 系统配置被测系统URL**:调度引擎(30s 轮询 + 到期异步触发 + 冲突跳过)+ 定时任务 CRUD/启停/立即执行/运行记录 + 报告按模块分组并展示被测系统 URL + SUT URL 保存/重启持久化 + 前端定时任务页与系统配置卡片 | ✅ 按 PRD 工作流实现,后端 179 测试全过、前端 build 通过、真实端到端验收通过(已部署生产) |
| 26 | 08-19 | **新建会议用例 step 11/17 执行失败修复**:保留页面直达 + 执行器策略5 增强(轮询等待/force勾选/校验防假通过)+ 用例 step 12 改「保持立即开始」(选择器修正零匹配坑);部署 5.60 | ✅ 本地 + 生产各连跑 3 次全绿、零残留,提交 `c0dedbdf` |
| 25 | 08-19 | **新建会议用例执行失败根因定位**:智能定位通过但执行 step 11 失败 → 页面直达跳过导航 + 直达后表单懒加载 + checkbox `is-hidden` 隐藏列 | ✅ 根因已定位(会话26 已修复闭环) |
| 24 | 08-18 | **finally api_call 端点真实化 + 完整 17 步验收**:真实端点 + 浏览器 token 自动注入 + `body_not_contains` 断言 + 修复 finally 双跑 | ✅ 完整 17 步 5.60 全绿、零残留,阶段 6 完成 |
......@@ -294,11 +322,16 @@ cd frontend && npm run build
### P0(高优先级)
- [x] **系统配置切换被测系统后用例自动跟随(方案A·执行期地址重写)**(会话28 实现 + 部署)
- ✅ 执行器 `_rebase_sut_url`:导航/接口步骤若指向已知被测系统主机(默认 5.44)→ 自动替换为当前配置 SUT 地址(保留 scheme/路径/查询/hash)
- ✅ 130 条存量硬编码 `https://192.168.5.44` 的 UI 用例全部自动跟随,**不动任何用例数据**
- ✅ 后端 182 测试全过(新增 `TestRebaseSutUrl` 3 条回归);**已部署 5.60 生产**(1 后端文件 → 容器重启 healthy)
- ✅ 效果:系统配置保存新 SUT → 后续手动/定时执行导航与接口自动打到新地址
- [x] **UI自动化定时任务模块 + 系统配置被测系统URL**(会话27 实现 + 本地验收)
- ✅ 调度引擎(30s 轮询/到期异步触发/冲突跳过/防重入)+ 定时任务 CRUD/启停/立即执行/运行记录
- ✅ 报告按模块分组 + 被测系统 URL + 触发方式列;SUT URL 保存/重启持久化
- ✅ 后端 179 测试全过、前端 build 通过、真实端到端验收通过
- [ ] **待部署 5.60 生产**(用户后续要求时执行;开发库留有验证用执行记录与报告
- **已部署 5.60 生产**(2026-08-19:13 后端文件 + 前端 dist 90 文件上传 → 容器重启 healthy;调度引擎启动、`/api/scheduled-tasks``/api/system/sut-config` 端点正常、定时任务菜单/页面/系统配置被测系统卡片在 5.60 网页验证通过;5.60 被测系统 URL 当前为 env 默认 `https://192.168.5.44`,在系统配置页保存一次即持久化到 MySQL
- [x] **阶段6 最终验收:完整 17 步示范用例(含 finally 自清理)**(会话24 完成)
-**16 步主流程真实验证通过**(本地会话22 + 5.60 生产会话23,各连跑两次全绿、零残留)——提交 `345572f2`
-**已部署 5.60 生产**(37 后端文件 + 前端 dist,含 semantic_resolver / ui_context / 映射表 v2 / 前端语义编辑)——部署脚本 `backend/_deploy_560_semantic.py`
......@@ -329,4 +362,4 @@ cd frontend && npm run build
---
*本文档由 Claude Code 于 2026-08-19 更新(会话 27:UI自动化定时任务模块 + 系统配置被测系统URL——调度引擎 + 定时任务管理 + 报告按模块分组 + SUT URL 持久化,后端 179 测试全过、前端 build 通过、真实端到端验收通过,未部署生产)。*
*本文档由 Claude Code 于 2026-08-19 更新(会话 28:系统配置切换被测系统后用例自动跟随(方案A·执行期地址重写)——`_rebase_sut_url` 导航/接口主机重写,130 条存量硬编码用例自动跟随,后端 182 测试全过,已部署 5.60 生产)。*
......@@ -15,6 +15,7 @@ import re
import asyncio
import time
from typing import Optional, Callable, Dict, Any, List
from urllib.parse import urlparse, urlunsplit
from datetime import datetime
from dataclasses import dataclass, field
......@@ -134,6 +135,12 @@ class PlaywrightExecutor:
"captcha": "csba",
}
# 已知被测系统主机集合(方案A·SUT地址跟随):
# 导航/接口步骤若指向这些主机,执行时自动替换为当前配置的 SUT 主机(self.base_url)。
# 默认从 LOGIN_CONFIG 派生(https://192.168.5.44),覆盖全部存量用例;
# 未来若切到其他被测系统并回切,可在此追加主机(如 "192.168.5.69")。
KNOWN_SUT_HOSTS = {urlparse(LOGIN_CONFIG["base_url"]).netloc}
def __init__(self, config: Optional[Dict[str, Any]] = None):
"""
初始化执行器
......@@ -1430,6 +1437,37 @@ class PlaywrightExecutor:
# ==================== 动作实现 ====================
def _rebase_sut_url(self, url: str) -> str:
"""
被测系统地址跟随(方案A·执行期重写):
若目标 URL 的主机属于 KNOWN_SUT_HOSTS(默认 https://192.168.5.44),
则将其主机替换为当前配置的 self.base_url 主机(保留 scheme/路径/查询/hash)。
作用:在系统配置中切换被测系统后,存量与新建用例的导航/接口步骤自动跟随,
无需批量修改用例数据;非被测系统的外部地址不受影响。
Args:
url (str): 原始目标 URL
Returns:
str: 重写后的 URL(无需改写时原样返回)
"""
try:
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
return url
if parsed.netloc not in self.KNOWN_SUT_HOSTS:
return url
base = urlparse(self.base_url)
if not base.netloc or base.netloc == parsed.netloc:
return url # 已指向当前配置地址,无需改写
return urlunsplit(
(parsed.scheme, base.netloc, parsed.path, parsed.query, parsed.fragment)
)
except Exception as e:
logger.debug(f"[SUT跟随] URL 重写失败(保留原样): {e}")
return url
def _do_navigate(self, params: dict) -> None:
"""
页面导航
......@@ -1440,6 +1478,8 @@ class PlaywrightExecutor:
url = params.get("url", "")
if not url:
raise ValueError("导航URL不能为空")
# 系统配置切换被测系统后,导航地址自动跟随(硬编码旧 SUT 地址的存量用例同样生效)
url = self._rebase_sut_url(url)
current_url = self._page.url
# 如果只是 hash 变化(同源),用 JS 修改 location 更可靠,
# 避免 goto 等待 navigation 事件在 SPA hash 路由上超时
......@@ -2157,6 +2197,11 @@ class PlaywrightExecutor:
if isinstance(rendered, dict):
api_case = rendered
# 系统配置切换被测系统后,接口地址自动跟随(与导航步骤同一套重写规则)
steps = api_case.get("steps") or {}
if isinstance(steps, dict) and steps.get("url"):
steps["url"] = self._rebase_sut_url(str(steps["url"]))
# 被测系统接口需鉴权(无 token 返回 401 令牌不能为空):
# 目标为 TARGET_URL 且未显式指定鉴权头时,自动从浏览器会话注入
# localStorage.meet_token —— finally 数据自清理复用主流程已建立的登录态。
......
......@@ -264,3 +264,30 @@ class TestDoApiCall:
with patcher:
result = ex._do_api_call({})
assert result.status == "passed"
# ==================== 方案A·SUT地址跟随(_rebase_sut_url) ====================
class TestRebaseSutUrl:
"""系统配置切换被测系统后,导航/接口地址自动跟随(执行期重写,不改用例数据)"""
def test_switch_sut_rebases_hardcoded_legacy(self):
"""存量用例硬编码 192.168.5.44 → 跟随切换到 192.168.5.69(保留路径/查询/hash/scheme)"""
ex = make_executor(base_url="https://192.168.5.69")
assert ex._rebase_sut_url("https://192.168.5.44") == "https://192.168.5.69"
assert ex._rebase_sut_url("https://192.168.5.44/#/platform") == "https://192.168.5.69/#/platform"
assert ex._rebase_sut_url("https://192.168.5.44/login?x=1&y=2") == "https://192.168.5.69/login?x=1&y=2"
assert ex._rebase_sut_url("http://192.168.5.44/api/meeting") == "http://192.168.5.69/api/meeting"
def test_default_sut_unchanged(self):
"""未切换 SUT(默认 5.44)时地址原样,绝不误改写"""
ex = make_executor() # base_url 默认 192.168.5.44
assert ex._rebase_sut_url("https://192.168.5.44/x") == "https://192.168.5.44/x"
assert ex._rebase_sut_url("https://192.168.5.69/x") == "https://192.168.5.69/x"
def test_relative_and_external_untouched(self):
"""相对路径 / 外部链接 不受影响"""
ex = make_executor(base_url="https://192.168.5.69")
assert ex._rebase_sut_url("/relative/path") == "/relative/path"
assert ex._rebase_sut_url("https://example.com/docs") == "https://example.com/docs"
assert ex._rebase_sut_url("") == ""
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论