# 前端 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:` | 覆盖前端地址 | | `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=` 指定解释器;默认 `.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 在加载配置阶段直接失败,不会启动浏览器或发送请求。 ### 3.6 计划员视口的真实数据回归(离线桩,不碰现场库) `apps/web/e2e/planner-real-data-views.spec.ts` 覆盖计划员实际会看的七个视口 (设备负荷 / 排产甘特 / 方案对比 / 版本对比 / 绩效指标 / 时段计划 / 设备与班组负荷), 断言的是现场反馈过的一批问题:已排产却整屏“休”、甘特报“尚无排产版本”、 方案对比加载失败、版本对比出现内部字段名、绩效口径不清、日产能 0 件却显示超载、资源利用率整屏 0%。 桩数据不是手写的,而是用**同一个 `world.json`、同一批域函数**生成端点原始响应, 因此页面看到的数字与真实读数一致: ```powershell python scripts/gen_planner_e2e_fixtures.py ` server/data/tenants//projects//world.json ` $env:TEMP\aps-planner-fix ``` 生成脚本只读 `world.json`,不写回项目。随后离线跑(不需要 8100/8003 后端): ```powershell cd apps/web $env:E2E_MODE = "dev" $env:E2E_FRONTEND_PORT = "5173" $env:E2E_BASE_URL = "http://127.0.0.1:5173" $env:E2E_PLANNER_FIXTURES = "$env:TEMP\aps-planner-fix" npx playwright test planner-real-data-views --reporter=list ``` 未设置 `E2E_PLANNER_FIXTURES` 时整组用例跳过,不会因为缺少现场快照而误报失败。 `E2E_SHOT_DIR` 可选,用来指定截图输出目录(默认系统临时目录)。 ## 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。