Skip to content

E2E 环设计 ​

E2E 用例 = 页面分片 + 总索引:唯一入口、机器可校验、一例一片、分片键只认页面;testid 应然源 = 用例分片,实然 = 前端源码,按页由机器双向集合校验。

契约正文位于 .atlas/rings/e2e/reference.md + .atlas/skills/atlas-e2e/SKILL.md,本页是设计依据。


一、它解决什么问题 ​

E2E 在真实项目里有三种典型死法:

死法表现对策
写不起脚本手写,改一版界面全报废,写与维护都贵到没人做用例即真相 + 脚本派生
跑不稳选择器脆、环境凑不齐,红的是环境不是产品单段真栈执行
假绿全绿但什么都没验证——断言初始即恒真、拿 toast 当验证、断言落在被操作控件自己身上零步基线 + 假绿三查(头号敌人)

前两者靠「用例即真相 + 脚本派生 + 单段真栈」解决;「假绿」是头号敌人——可靠性不能托付给任何人的自觉,只能把「该做的判断」变成流程里必然执行的机器动作。


二、它是一台什么机器 ​

测试不是手写的,是「AC → 分片 → 派生脚本」流水线的产物。工件只有四件:

  • 总索引 e2e-index.md(唯一入口 + 状态账本);
  • 页面分片 cases/<page>.md(每例一片,唯一要人维护的东西);
  • 派生脚本 scripts/<page>.ts(机器生成、可重生成);
  • 前端源码里的 data-testid(执行锚点)。

一条需求流过五步:

  1. 写:域级 PRD 的 AC 给业务预期;按页写分片,每条用例一块 atlas-case——intent(证明什么)+ testid:(锚哪些元素,应然清单)+ seed:(前置数据)+ step/expected(动作行 / 断言行)。
  2. 审:文本期校验器查形态(结构 / 词表 / 自相矛盾),设计期独立审查查语义(可证伪 / 异源 / AC 可达);两道都过才许生成,缺审查 = 未审,生成被拒。
  3. 生:生成器按页派生脚本(goto + testid 齐全守卫 + 逐步执行),并每页附一条零步基线测试,专抓「初始即真」的恒真断言。
  4. 跑:真实前端 + 真实后端单段执行,登录态用真实登录预置;结果回写索引状态列。
  5. 收:首跑红先三分类(mock 语义残留 / 命名漂移 / 真缺陷)再回写;域收口 = 全 green + 审查无 Critical + 用户验收走查。

九条设计原则(只写为什么) ​

  1. 应然先行——预期唯一来自 AC、锚点应然来自分片、实现只当参考基线。从 live app 出发是用实现验证实现:产品错了测试照样绿。
  2. 名字绑死、机器可校验——用例 ↔ testid ↔ 前端源码按页双向集合相等。凡依赖人肉对齐的必漂;名字是机器能核对的最低成本不变量。
  3. 判据分层——形态项进文本期门,能执行化的语义进运行期门,执行化不了的进独立审查。把语义塞进文本期 = 没判(regex 判句意的代价实测是 680 次重试 / $11 / 零用例);近似门必须自我声明「不构成证明」。
  4. 自检流程化,不提示词化——「写用例时注意别空转」类提示被否:提示词层补救 = 把质量托付给自觉。落地 = 生成器每页必产零步基线测试;恒真断言读不出来,只能跑。
  5. 假绿三查——恒真(初始即成立)/ 瞬时(toast 当验证)/ 同源(断言落在被操作控件自己身上)三族失效,对应刷新复断言、断言真可见、编辑前后两端等整族规则。
  6. 门也要被审计——判据句、探针、统计都可能单独错,「门报出来的数」不等于事实,须拿与被测对象不同来源的事实对拍。
  7. 脚本派生,用例是唯一要人维护的真相——手写脚本 + 索引对照会漂,且失去「改用例即重生成」。
  8. 单段真栈,桩只桩化外部依赖——验证的证明力来自「被测对象 = 交付对象」;mock 层的自证绿无证明力。后端可先返假数据,但不在前端塞 mock 层;登录态用真实登录预置。
  9. 无门等于无约束,人侧门兜底——机器门管形态与存在性、管不了业务对不对,故域收口配用户验收走查(走查载体 = 评审控制台,见 E2E 用例评审控制台)。

三、两个真相源 ​

内容真相源
用例索引(product/e2e/e2e-index.md)+ 分片(product/e2e/cases/<page>.md)
testid应然源 = 用例分片的 testid: 应然清单,实然 = 前端源码 data-testid 扫描,执行即对账

两者用名字绑死,按页由机器双向集合校验。

依据来源(硬优先级) ​

层来源提供不提供
① 业务预期(唯一来源)域级 PRD 的 AC成功 / 失败 / 边界 / 异常的结果与文案—
② 交互与锚点product/e2e/cases/*.md 分片页面路由、可交互元素、data-testid 应然业务规则
③ 参考基线(仅参照).trellis/spec/structure/{current,legacy}/现状功能 / 字段 / 接口存在性不得当结论,更不得反推预期

三禁令:禁用真实 / 旧实现行为反推预期;锚点应然只来自用例分片;禁无 AC 来源的用例(prd_ref 必填)。

与多数开源方案的根差别:它们从 **URL 或 live app(现状)**出发;Atlas 从 **PRD + 用例分片(应然)**出发——这是「应然先行」的根。


四、组织形态 ​

text
product/e2e/
├── e2e-index.md        总索引(唯一入口;主表 + 反向视图)
├── cases/<page>.md     页面分片(`atlas-case` 块)
└── scripts/<page>.ts   E2E 脚本(由分片派生,Node-Playwright 单轨)

三条硬规则:① 总索引唯一入口且机器可校验;② 一例一片(归断言落点页,跨页记 pages[] 不复制);③ 分片键只认页面。

  • 页面 = 分片文件名(去扩展名即 slug;静态路由 = / + slug、带参路由取业务段、根页 home);「页 ↔ 真实路由」映射的唯一真相源 = 索引页面表。
  • 索引主表列:用例ID | 页面 | 中文标题 | 类型 | 关联AC | 需求 | 状态 | 状态原因 | 分片(末列带锚点链接);另有反向视图 页面 ↔ 用例。
  • 分片块格式:## <ID> 标题 + ```atlas-case 行式键值块(stdlib 可解析,非合法 YAML)。
  • 字段不两处出现:状态类字段只在索引;testid 只在分片。

五、testid 规范与双向校验 ​

  • 唯一硬锚:必要元素(所有可交互元素 + 需断言的展示元素)必须带 data-testid;脚本一律以 testid 定位,不用语义 / 样式 / 位置选择器。
  • 命名 <页面slug>-<语义名>[-<变体>];元素段必须语义名,禁样式 / 位置 / 序号名;全站唯一、不复用。
  • 校验语义:抽取源 = 前端源码;按页比对「前端 data-testid 扫描集合」⇔「分片引用集合」;有分片的页必须相等(多 / 少即 FAIL);未实现的页 → SKIP(记「未实现页」清单,不算失败)。
  • 面板容器标记:前端里 Tab 级面板的容器必须标 data-atlas-panel,由动作展开的内容面板不得标——漏标 ⇒ 面板内 hidden 类断言在未展开时恒真 ⇒ 误报。
  • 实现期薄桩:.trellis/spec/conventions/testid.md(paths: = 前端 globs;生产者 scripts/gen_testid_stub.py),内含祈使句「实现按分片应然清单命名 data-testid,不得自造」+ 两块清单(分片应然 / 前端实然,差集 = 待实现面)。
  • 存量开户豁免:存量项目里已存在的不符合 Atlas 命名规范的 data-testid 降级为 WARN 容忍(避免开户即全面阻断);而新分片中新声明的 testid 违规仍严格报 FAIL。

双向集合对账门的实现细节见 门禁系统。


六、用例词表与 ID ​

字段取值
类型smoke / acceptance / exception / permission
状态red / green / blocked / skipped(E1 起);prototype-pass = E0 历史终态(存量合法、不再产生)
状态原因<枚举>:<中文说明>;枚举 ENV_ISSUE / NOT_IMPLEMENTED / SPEC_AMBIGUOUS / BLOCKED_DEP / DEFERRED / OUT_OF_SCOPE / OTHER
IDE2E-<PAGE>-NNN,PAGE 大写,每页独立、三位定长、删除不复用
  • 分片另有 intent 字段:可证伪的一句话声明(这条用例证明什么),不得是「页面显示正确」类模糊表述。
  • step / expected 行式语法:<动词> <testid> [<值>] # 中文说明,词表封闭:
    • 动作 9:click fill select check uncheck press hover refresh goto
    • 断言 11:text contains count visible hidden enabled disabled value / countOptions / attr / delta(相对值增量断言,应对未读数 / 购物车类累加业务,无需硬编码初始基数)
    • expected 行可加 initial: / unchanged: 豁免前缀;中文降为 # 注释。

七、质量门与校验清单 ​

  • 结构门(FAIL):字段齐 / ID 唯一 / 链接(文件 + 锚点)可达 / 无孤儿无死链 / 词表合法 / prd_ref 格式。
  • 有意义门:expected 空、占位字样、断言数 <1、skipped 无原因 → FAIL;intent 缺失 / 模糊、不可判定词、纯跳转步骤、瞬时断言(toast)、持久化未刷新复断言、「或 / 等 / e.g.」式步骤、i18n key / 枚举 / 组件名当文案 → WARN。
  • 结果断言硬规则(源真实事故):① 禁瞬时反馈当验证;② 持久化写必须刷新后复断言;③ 断言须与被操作控件异源;④ 编辑 / 切换类断言前后两端;⑤ 文案须为渲染后可见文案。
  • 用例结构硬规则:R7 同一用例内对同一实体不得既断言存在又断言消失(一个可判定终态 = 一条用例);R8 断言前必须让目标元素可见(先切页签 / 展开);R10 跨页用例的终态断言必须落在断言落点页上求值;precondition 不得说谎。
  • R9 空转禁止 + 零步基线(运行期门):expected 的每条断言在未执行任何 step 的初始态必须不成立。生成器按页产 test_<page>__zero_step_baseline——只 goto,逐条求值该页全部用例断言并集,命中即 FAIL 并指名。两条显式豁免:initial:(本就该在初始态成立 ⇒ 排除出基线);unchanged:(「动作不改变它」才是被测属性 ⇒ 双向判定)。
  • seed 数据模型:precondition 升为结构化 seed 块(实体 × 字段,幂等 upsert,session 实体统一「已登录」);播种通道 = stack-profile e2e 段声明式;声明 / 发射解耦开关(e2e.seed.mode: enforce | declare)——enforce 每条用例独立发射运行期播种,declare 只参与静态 models 对账与依赖标注(专为状态累加链项目设计,避免破坏性动作被前置播种强行撤销)。
  • 追溯门(跨环,WARN):域内页面全「已实现」的域,其域级 PRD 定义的每个 AC 必须被索引主表 ≥1 条 关联AC 指向,零挂靠 ⇒ WARN + 逐域清单。覆盖关系是派生量(真相源 = 主表 关联AC + 域级 AC 定义行),不设手工对账表。
  • 网络打桩门(validate_e2e_mocks):用例分片若声明 mock:,必须有对应的 fixture 代码文件存在——杜绝只声明不打桩的「虚假打桩」。
  • 判据 / 探针独立对拍:门的三部分(判据句、探针、统计)都可能单独错,须拿与被测对象不同来源的事实对拍。

八、脚本与执行 ​

  • 运行器由 product/stack-profile.yaml 的 e2e.runner 决定;一页一脚本,派生可重生成(定制放 scripts/_support/)。
  • baseURL 不硬编码:唯一地址 = e2e.app_base_url(真实应用单段);为 null ⇒ 生成器报「未就绪」(退出码 3 + --json 的 not_ready),调用方判 SKIP,不猜默认。
  • 就绪探针(区分真红与假红):执行前用标准库 TCP 探针探测靶场服务;未声明地址或端口不通 ⇒ 显式判 SKIP (ENV_ISSUE),防止「服务没起」被误当成整批用例断言 FAIL。
  • 运行器单轨化:整体退役 python-playwright,单轨定型为 Node-Playwright(@playwright/test);生成脚本一律为一页一脚本 scripts/<page>.ts,通过 testMatch: "scripts/*.ts" 确定性收集。
  • 测试环境运行隔离:全栈 E2E / 后端真实测试与本地 dev server 隔离,后端测试统一采用独立库(如 app_test),杜绝测试过程清空 / 污染前端日常联调数据。
  • 运行态排查留底与证据链:E2E 运行失败时保留排查现场,生成结构化证据快照(含终端错误留底与 failure 截屏),杜绝偶发红灯被静默吞掉。
  • 断言须真可见:text / contains / value 先断可见再比值(隐藏元素同样有文本,直比 = 假绿)。
  • 应用侧登录态(硬):以真实登录换取令牌,按真实前端读取的存储键写进运行器原生 storage;凭据只来自运行期环境变量(不读项目 .env、不硬编码);未设置 ⇒ 跳过预置并明确警告;换取失败 ⇒ 报错中止(静默续跑 = 未登录态跑用例的假绿)。
  • 元素齐全守卫 = 自动等待:testid 清单守卫用运行器原生轮询断言等元素出现(真实应用是 SPA,goto 后元素异步渲染,即时存在性判定必红);超时即失败并点名缺失清单。

九、应然 UI 变更的前置与影响面 ​

判据不是需求大小,而是是否改变应然 UI。应然 UI 的载体 = 域级 PRD 的文案 / 流程六件套 + 用例分片的 testid: 应然清单。

  • 改 UI → 更新分片 testid: 应然清单(新增页在索引页面表加行)为前置;
  • 不改 UI(规则 / 数据)→ 复用既有 testid;
  • 纯后端不可见 → 本环不产用例。

影响面识别归 apply(需求文本 + structure 路由表 + 索引页面表;失败问用户)。

前置人侧门:改变应然 UI 的需求进入本环前,域级 PRD「用户旅程走查」须已获用户确认,未确认不得落笔用例。


十、状态机与完成门 ​

  • 状态机:red → green(E1 起);prototype-pass = E0 历史终态;blocked / skipped 必带原因。
  • 首跑红三分类协议:首跑红逐条分类——mock 语义残留 → 改用例 / 命名漂移 → 改实现对齐 / 真缺陷 → 走需求级;不分类不得批量回写状态。
  • 完成门 = 硬门 + 显式例外:需求相关用例必须在真实应用上全 green 且实例 ⑤ 实现代码审查无 Critical;做不到的标 blocked + 原因与去向,方可例外收口。
  • 域收口人侧门:该域 E2E 真实应用全 green 后,用户在评审控制台逐用例执行 + 人工 review(控制台盖不住的项 = manual.md 手工面)确认后才算域收口;不符 ⇒ 差异登记不收口;真栈不可用 = blocked 带原因。走查门不阻塞开发:机器面全绿即开下一域,走查攒批补、过门后归档。

十一、独立审查 ​

  • 骨架同构:.atlas/shared/independent-review.md(五实例同构,各实例只声明自己的四项)。
  • 本环四项实例值:fan-out = 页面分片(复用 testid 双向校验的同一分区键)+ 阈值(20 条或 500 行)按用例 ID 区间再切 + 一个全局审查者;回退环 = prd | e2e;权威输入 = 域级 PRD 的 AC + 该页用例分片 testid 清单(缺则不得给「通过」);门禁 = 阻断脚本生成(判据「报告存在 + 无 Critical」,缺失 = 未审)。
  • 落点 = product/e2e/reviews/<日期>-<范围>/(与人工评审账本 console/ 分离,不得混用)。
  • 设计依据:子代理审查属设计期语义门(生成后、执行前),不能替代运行期门——零步基线仍独立必需,两者互补。

十二、与其它环的接口 ​

环接口
atlas prdAC → 用例上游(prd_ref 回指 AC ID);AC 文案规范 = 文案类断言来源;「用户旅程走查」用户确认 = 前置人侧门
atlas structurestructure/ 事实 = 参考基线仅参照;data-models 事实 = seed × models 静态对账对象;路由表 = 影响面识别输入
atlas apply影响面识别(需求文本 + 路由表 + 索引页面表);apply 在实现前跑,新用例置 red、不回写执行状态

Released under the Apache-2.0 License.