aps-agent/docs/development/e2e.md

13 KiB
Raw Permalink Blame History

前端 E2E 冒烟 · Sidecar 本地冒烟 · CI 构建矩阵

对应验收矩阵(docs/product/plan-completion-matrix.md):

  • 第 118 行「黄金测试是发布门禁」剩余项——完整前端 E2E、干净离线机安装冒烟、CI 构建矩阵;
  • 第 99 行「自包含 Python Sidecar」剩余本地可测项——20 次冷启动、端口冲突、崩溃重启、中文/空格路径。

本轮交付的本地可测部分全部落地并跑通;签名 / SBOM 上链 / Defender / 干净离线机安装 属外部环境项, 需签名证书、Defender 门户与离线验证机,见文末「剩余项与门禁说明」。


1. 前端 E2E 冒烟(Playwright)

1.1 运行前提

  • Node ≥ 22(仓库根 package.json engines);需要 JMS 的用例必须通过环境变量提供隔离测试账号,禁止把账号密码写入仓库。
  • 首次运行先装浏览器二进制(约 150 MB,需网络):
cd apps/web
npm run test:e2e:install   # npx playwright install chromium

1.2 一键运行(本地门禁)

cd apps/web
npm run test:e2e           # = npm run build && playwright test(preview 模式,推荐)
  • 默认 preview 模式:npm run build 产出 dist/ 后由 Playwright 起 vite preview(独立端口 43117), 并通过 VITE_API_TARGET=http://127.0.0.1:8003 把 /api 代理到本地后端。
  • 为什么用 preview 而不是 dev:preview 走生产构建,行为最接近发布包,作为 E2E 默认门禁。 round-41 发现的 dev 模式 StrictMode 缺陷(refreshWorld 结果捕获 + alive 存活标记被 StrictMode 模拟卸载永久置 false,导致 world/resultStore 更新被吞、视口不出现)已在 round-42 修复: ① alive effect 体内复位(mount→cleanup→mount 后保持 true);② captureSessionResult 移出 setWorld 更新器(纯函数化,经 worldRef 计算合并世界)。dev 模式(E2E_MODE=dev)已作为附加 冒烟验证通过;E2E 默认仍为 preview,CI 亦然。
  • E2E_MODE=dev 仅作本地调试用:Playwright 会自动复用已运行的 5173(reuseExistingServer)。

1.3 覆盖清单(中文断言)

用例 断言
访客冒烟·品牌与登录入口 .sidebar-brand 含「工业智核」;登录触发按钮可用
访客冒烟·登录弹窗 弹窗标题「登录工业智核」;企业/账号/密码三字段;Esc 可关闭
核心路径(JMS) 使用 E2E_JMS_* 注入的测试账号登录 → 输入「跑一版柔性排产」→ 智能体中文回复非空 → .flex-block-title 含「柔性排产」→ 结果入口可用 → 视口打开(页签含「世界对比」,点击后 .world-diff 可见)→ 右侧「柔性工作台」打开 .flex-bench → Ctrl+K 命令面板:中文过滤「世界」命中「两个世界对比」、过滤「柔性」命中「柔性工作台」,Esc 关闭

1.4 常用变量

环境变量 默认 说明
E2E_MODE preview preview(build 后 vite preview,默认门禁)或 dev(vite dev;round-42 起 dev StrictMode 缺陷已修复,可作附加冒烟)
E2E_FRONTEND_PORT 43117(preview)/ 5173(dev) 前端端口(dev 模式复用已运行 5173)
E2E_API_TARGET http://127.0.0.1:8003 /api 代理目标(CI 指向矩阵自起后端端口)
E2E_BASE_URL http://localhost:<port> 覆盖前端地址
E2E_OUTPUT_DIR 系统临时目录 截图/追踪产物目录(默认不污染仓库)
APS_E2E_SKIP_JMS 空 1 时跳过核心路径(无外网 JMS 场景),只跑离线访客冒烟
E2E_JMS_TENANT/USER/PASSWORD 无默认值,必须由本地安全环境或 CI Secret/Variable 注入 JMS 登录参数

1.5 离线/无外网回退

  • 若运行器无法访问 JMS_AUTH_BASE_URL 指向的 JMS 服务,设 APS_E2E_SKIP_JMS=1: 核心路径自动 skip,访客冒烟(2 例,不依赖后端)仍作为最小离线冒烟门禁。
  • 浏览器二进制无法下载时:仍需已装 Chromium 才能跑访客冒烟;Chromium 二进制下载域名 cdn.playwright.dev 需可达。

2. Sidecar 本地冒烟(矩阵 99 本地部分)

node scripts/smoke-sidecar-local.mjs

只读调用 server/sidecar.py(python -m server.sidecar)与桌面 Sidecar 生命周期语义,不修改后端逻辑; 每次启动都使用隔离的临时 APS_HOME / APS_DB_PATH / APS_KNOWLEDGE_PATH / APS_APPROVAL_PATH / APS_WORLD_PATH, 不触碰仓库 server/data 或 ~/.aps。任一检查失败退出码非 0。

检查 预期
20 次冷启动 随机端口 + 随机 nonce 起/停,健康探针(nonce 校验 200 + 回显头)通过
端口冲突 预占端口后启动 → 显式失败(退出码非 0,stderr 含 WinError 10048 / address in use)
崩溃重启 kill sidecar 子进程后父进程(脚本模拟)以新端口+新 nonce 拉起新实例
父进程看门狗 父进程退出后 sidecar 由守护线程自行退出(exit 0)
中文/空格路径 在含中文+空格的 cwd / APS_HOME / APS_UI_DIR 下启动,健康 + UI 均正常,无 Traceback

可选 APS_SIDECAR_PYTHON=<python> 指定解释器;默认 .sidecar-venv → .venv → PATH。 「干净 Win10/11 离线机安装冒烟」需外部验证机,不在本地冒烟范围。


3. CI 构建矩阵(.github/workflows/ci.yml)

push / pull_request 触发,5 个 job:

Job 平台 内容
lint Ubuntu ruff check server tests scripts;informational(基线 55 项存量告警,round-41 记录;清零后改硬门禁)
frontend-build Windows + Ubuntu apps/web:npm ci → npm run build(tsc + vite)
backend-tests Windows + Ubuntu pip install -r requirements.txt pytest → pytest tests/golden -q;APS_KNOWLEDGE_PATH 等全部隔离到 runner.temp
e2e Ubuntu 起后端(独立端口 8100、隔离数据、jms)→ npm ci + build → playwright install --with-deps chromium → npx playwright test(preview 模式);失败自动上传 aps-e2e-artifacts
sbom Ubuntu pip install uv 提供 uvx → npm run sbom → 校验 dist/sbom/*.cdx.json 与 release-manifest.json

CI 变量/密钥:

  • APS_E2E_SKIP_JMS(仓库变量,可选):1 时 E2E 只跑离线访客冒烟。
  • APS_JMS_SESSION_SECRET(仓库密钥,可选):JMS 会话签名密钥;未配置时用内置 CI 回退值。
  • JMS 登录依赖运行器可访问配置的认证服务;若企业侧 IP 白名单限制 GitHub 出口,按上条降级。

3.5 数据隔离(C-P0 fail-closed)

核心路径 E2E 会在目标后端创建真实会话(会话标题=首条消息,如「柔性排产」)。 apps/web/playwright.config.ts 因此默认拒绝 127.0.0.1:8003 / localhost:8003:该端口是现场开发后端, 直接复用会污染当前用户的项目、会话和排产结果。

唯一支持的本地启动方式

在仓库根目录新开 PowerShell,完整执行下面这一段;不要删减路径变量,也不要复用已经运行的 8003:

$root = Join-Path ([System.IO.Path]::GetTempPath()) ("aps-e2e-" + [guid]::NewGuid().ToString("N"))
$home = Join-Path $root "home"
$data = Join-Path $home "data"
New-Item -ItemType Directory -Force -Path $data | Out-Null

$env:APS_HOME = $home
$env:APS_DATA_DIR = $data
$env:APS_DB_PATH = Join-Path $data "master.db"
$env:APS_WORLD_PATH = Join-Path $data "world.json"
$env:APS_PROJECTS_PATH = Join-Path $home "sessions\workspace.json"
$env:APS_KNOWLEDGE_PATH = Join-Path $data "knowledge.json"
$env:APS_CHECKPOINT_PATH = Join-Path $data "checkpoints.json"
$env:APS_PREFERENCE_PATH = Join-Path $data "preferences.json"
$env:APS_EMBEDDINGS_PATH = Join-Path $data "embeddings.json"
$env:APS_APPROVAL_PATH = Join-Path $data "approvals.json"
$env:APS_APPROVAL_BACKEND = "file"
$env:APS_AUDIT_LEDGER_DIR = Join-Path $data "audit-ledger"
$env:APS_AUDIT_MIRROR_DIR = Join-Path $data "audit-mirror"
$env:APS_AUTOMATION_STATE_PATH = Join-Path $data "automation.json"
$env:APS_MCP_BUS_PATH = Join-Path $data "mcp-bus.json"
$env:APS_BRANCH_PATH = Join-Path $data "branches.json"
$env:APS_BRANCH_DIR = Join-Path $data "branches"
$env:APS_GOLDEN_CACHE = Join-Path $data "golden-tests.json"
Remove-Item Env:APS_DATABASE_URL -ErrorAction SilentlyContinue

# R71.1 图纸 E2E 要求显式开启测试认证,避免仓库 .env 免登录配置让登录按钮永不启用
$env:APS_AUTH_ENABLED = "1"

.\.venv\Scripts\python.exe -X utf8 -m uvicorn server.main:app --host 127.0.0.1 --port 8100

后端健康后,在第二个 PowerShell 中运行:

cd apps/web
$env:E2E_API_TARGET = "http://127.0.0.1:8100"
Remove-Item Env:E2E_ALLOW_LIVE_BACKEND -ErrorAction SilentlyContinue
npm run test:e2e

CI 同样使用独立端口 8100 和 runner.temp 持久化路径。

现场 8003 的显式例外

只有在操作者明确接受“向当前现场工作区创建真实会话”时,才允许:

$env:E2E_API_TARGET = "http://127.0.0.1:8003"
$env:E2E_ALLOW_LIVE_BACKEND = "1"
npm run test:e2e

未设置 E2E_ALLOW_LIVE_BACKEND=1 时,Playwright 在加载配置阶段直接失败,不会启动浏览器或发送请求。

4. 门禁达成与剩余项

已达成(本地门禁,均实测通过)

  • 按 §3.5 启动 8100 隔离后端后,cd apps/web && npm run test:e2e:3/3 通过(登录 → 排产 → 结果块 → 视口/工作台 → 命令面板;中文断言)。
  • node scripts/smoke-sidecar-local.mjs:5/5 通过,退出码 0。
  • CI 矩阵定义落地(Windows+Ubuntu 后端黄金测试与前端构建、E2E、SBOM)。

剩余(需外部环境,非本轮可测)

  1. 干净离线 Win10/11 安装冒烟——需无网验证机执行 NSIS 安装包安装/卸载/首次启动(矩阵 99/118)。
  2. 代码签名——需受信任代码签名证书;当前 release-manifest.json 明确 signed: false,仅为本地完整性清单。
  3. Defender 扫描入 CI 门禁——需 Defender for Endpoint 门户/API 与提交策略。
  4. SBOM 上链/签名 attestation——当前 npm run sbom 产出 CycloneDX 1.6 + SHA-256 清单,但未做签名与供应链上链。

前端缺陷状态(round-42 已修复):dev 模式 StrictMode 下 refreshWorld / captureSessionResult 状态更新被吞的问题已修复——根因 ① alive ref 存活标记:effect 只写 cleanup 置 false,StrictMode mount→cleanup→mount 双调用后永久为 false,refreshWorld 的全部提交被跳过;修复为 effect 体内复位 true。 根因 ② captureSessionResult 在 setWorld 更新器内调用(更新器非纯函数、dev 双调用重复执行);修复为 更新器外经 worldRef 计算 merged 后提交。dev 模式核心路径已 E2E 冒烟通过,preview 默认门禁保持 3/3。

5. CP RHS 参数重算浏览器验收

  1. 打开“设置 -> 算法 Skill -> 异步任务”,选择开始日期。
  2. 保留 C8=60 分钟、班组/工装容量=1,费率留空并提交 CP RHS 增量重算。
  3. 轮询完成后打开结果:C8 显示全部待排订单;C12 仅显示真实激活资源的 code/id;未接线参数显示 inactive,不启动变体重解。
  4. 无费率时每行只显示“未配置”,不得出现 0、CNY 或估算成本。再显式填写费率提交一次,确认成本按对应增量计算,同时页面声明目标改善与货币成本不可直接相减。
  5. OPTIMAL 行显示精确改善;FEASIBLE 行只显示区间。C8 若出现“恢复可行”必须作为可行性异常,而不是收益。
  6. 验收记录必须包含 /api/jobs 请求/轮询结果、浏览器 console warning/error、新增任务取消、8003/5173 或隔离端口健康状态;不得在浏览器验收中使用生产业务数据做写操作。

5.1 C7 line/day 实例

  1. 进入异步任务后,先选开始日期,再选择 ACTIVE 产线和 C7 工作日;输入增加分钟,费率留空提交。
  2. 结果必须显示具体 lineCode/#lineId + bucketDate、baselineRhs → perturbedRhs,并声明开工日整笔记账、非物化班次模型。
  3. 无费率时 C7 货币字段全 null/显示未配置。仅填写 C7 实例费率后重算,成本只能出现在该 line/day 行,单位为每产线/工作日增量分钟。
  4. DatePicker 必须禁用所选产线的非工作日;伪造非工作日、未启用产线、越界日期或非法实例时仍须在创建线程前 422,任务总数不增加。
  5. 浏览器网络中不得出现发布/下发请求;最终 console warning/error 为 0。