aps-agent/docs/development/e2e.md

214 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 前端 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,需网络):
```powershell
cd apps/web
npm run test:e2e:install # npx playwright install chromium
```
### 1.2 一键运行(本地门禁)
```powershell
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 本地部分)
```powershell
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:
```powershell
$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 中运行:
```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 的显式例外
只有在操作者明确接受“向当前现场工作区创建真实会话”时,才允许:
```powershell
$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。