atlas-project-harness 0.2.0-beta.3 → 0.2.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@
4
4
 
5
5
  ---
6
6
 
7
+ ## [v0.2.0-beta.4] - 2026-10-23
8
+
9
+ ### 🧷 testid 提取器容忍 `setAttribute` 尾逗号(D135)
10
+
11
+ * **修复**:Prettier `trailingComma: all` 会把多行 `el.setAttribute("data-testid", "x")` 写成尾逗号形态,旧正则漏抽导致已实现 testid 被判缺失;`SETATTR_TESTID` / `SETATTR_TESTID_TPL` 现允许值后可选 `,`。
12
+ * **钉**:`test_f22_imperative_testid_extraction_and_counts` 增加多行 + 尾逗号样本(Red First),变异 **M135**。
13
+
14
+ ---
15
+
7
16
  ## [v0.2.0-beta.3] - 2026-10-23
8
17
 
9
18
  ### 🩹 `value` 断言读可见文本(D133)
package/DESIGN.md CHANGED
@@ -601,6 +601,7 @@ adapters:
601
601
  | D132 | 新增 **E2E hash 查询参数契约**(`hash参数:` + `e2e.query_preset.hash_keys`)(2026-10-22;`rings/e2e/reference.md` §5.5/§5.7/§8.2;`shared/stack-profile.md` §2;`_support/case_block.py` / `_support/url_merge.py` / `scripts/gen_e2e_scripts.py` / `validators/validate_e2e_index.py`) | ① **病灶**:HashRouter 页面的 `useSearchParams` 只读 `#` 之后的查询段;D109 `参数:` 只覆盖 `#` 之前的常规查询,`会话:` 是 sessionStorage,导致快照页这类「`#` 后查询前提」用例无合法写法,只能手补生成物(重跑即丢)。② **定案**:`hash参数:` 只声明键,值内联在 `goto` 的 hash 查询串;白名单 `query_preset.hash_keys`;守卫导航把同一段 hash 查询追加到 hash 段(`append_hash_query`),base query 仍在 `#` 前;零步基线不施加;路径门两侧 `strip_queries` 后比对;同键不得跨 `参数:`/`会话:`/`hash参数:` 双写。③ **被否**:把 `#` 后查询硬塞进 `参数:`(会丢 hash 段 / 与 base query 合并语义冲突);项目侧手补生成物(不可重生成,属已否的 D98 类补丁)。④ **钉**:生成器 6 条 + 校验器 7 条(含 `test_hash_param_carried_to_guard_and_goto`、`test_zero_step_baseline_carries_no_hash_param`、`test_hash_query_merge_helpers` 与正向/未白名单/白名单外/死声明/值形态/未声明 hash 键/双载体);`tests/test_validators.py` 计数 138 已登记 `shared/gates.md`。 |
602
602
  | D133 | `value` 断言/探针改读**可见文本**,排除触发器内的视觉隐藏 a11y 镜像(2026-10-23;`scripts/gen_e2e_scripts.py` 发射点;契约 §5.5 `value` 行) | ① **病灶**:`value` 对非原生控件用 `innerText` 读触发器文本。Ant Design Select 在用户交互后向触发器内注入 `aria-live="polite"` 的无障碍播报 span(`width:0;height:0;opacity:0`),其文本与选中项相同;`innerText` 不排除视觉隐藏节点 ⇒ 选中文案被读两遍(现场:`模式一…\n模式一…`),同一标签的 `value` 断言在交互后必红,而交互前同断言正常(基线 green / 动作后 red 的假红)。契约本体写的是「触发器**可见**文本」,实现与契约不符。② **定案**:新增发射期助手 `atlasVisibleText(loc)`:采值前遍历触发器子树,用 `checkVisibility({opacityProperty:true,visibilityProperty:true})`(缺 API 时退 `getComputedStyle`)判定,把不可见节点的 `style` 暂存后置 `display:none !important`,读 `innerText` 后**按逆序逐字节恢复 style**(`try/finally` 覆盖异常路径),保留 `innerText` 的排版归一半径;`atlasValueOf`(探针,含三态语义)与 `atlasExpectValue`(正式断言,改用 `expect.poll` 保留轮询)统一走该助手;原生 `input/select/textarea` 的 `toHaveValue` 路径不变。③ **被否**:按 `aria-live` / `aria-hidden` 选择器硬排除(只治 Ant Design 一处,其他组件库的视觉隐藏节点仍会污染;`aria-hidden` 还可能承载可见文本);读 `textContent`(会把 `display:none` 的隐藏下拉项也算进来,扩大假绿面);对读数做去重(同一文案合法重复出现时无法区分,属语义猜测)。④ **钉与变异**:`tests/test_gen_e2e_scripts.py::test_value_reads_visible_text_excluding_a11y_mirror` —— 从生成物抽出助手源码,在 Node 里以假 DOM **真求值**:可见项 + `opacity:0` 的同文案播报项 ⇒ 只返回一次;采值后隐藏节点 `style` 恢复为 `null`;同时钉发射面含 `atlasVisibleText`、不再出现 `toHaveText(v, { useInnerText: true })`。 |
603
603
  | D134 | 新增 E2E **`type` 逐字输入动词**(2026-10-23;契约 §5.5;`scripts/gen_e2e_scripts.py` / `validators/validate_e2e_index.py`) | ① **病灶**:Playwright `fill` 一次性写 DOM、只触发 `input`,不触发带掩码 / 受控输入组件的内部解析(现场:Ant Design RangePicker 日期输入,文本可见但保存 payload 无 `startTime/endTime`);逐条 `press` 每次先 focus、只发单键,无法形成连续输入链(实测同样不提交)⇒ 项目侧只能留人工或用 `_support` 钩子代输入(步骤不可见、绕过 DSL)。② **定案**:`type <tid> <text>` → `locator.pressSequentially(text)`,逐字触发 `keydown/keypress/input`,**不先清空**原值(替换语义用 `fill`);值必填,空值 ⇒ 生成器报错中止;动作词表 13 → 14,校验器词表同步,样例守恒表 +1。③ **被否**:用 N 条 `press` 拼输入(不可读且实测不提交);`_support` 钩子代输入(步骤不可见、绕过 case DSL);移除产品侧掩码(UX 变更、超出 E2E 能力面)。④ **钉**:`test_type_verb_emits_press_sequentially_without_clearing`(发射形态 + 空值中止 + `[N]` 定位)与 `test_e2e_index_type_is_action_not_assertion`(step 侧放行 / expected 侧拒收);变异 **M134**(摘生成器 `type` 分支 ⇒ 钉红)。 |
604
+ | D135 | testid 提取器容忍 **`setAttribute` 尾逗号**(2026-10-23;`validators/validate_testids.py`;F22 扩展) | ① **病灶**:Prettier `trailingComma: all` 会把多行 `el.setAttribute("data-testid", "x")` 写成尾逗号形态(`"x",\n)`),旧正则要求 `"x"` 后紧邻 `)`,导致已实现 testid 被判缺失、双向集合 FAIL(现场:邮件面板 RangePicker 日期输入的多行 setAttribute)。② **定案**:`SETATTR_TESTID` / `SETATTR_TESTID_TPL` 在值后允许可选 `,`;与静态属性同档,不改变 counts / 页面归属。③ **被否**:项目侧改成单行调用(lint-staged 的 Prettier 仍会加回尾逗号);把 testid 挂到 RangePicker 容器(`fill` / `value` 指不到内层 input)。④ **钉**:`test_f22_imperative_testid_extraction_and_counts` 增加多行 + 尾逗号样本(Red First:修前 `notes-avatar` 缺失;修后抽取成功);变异 **M135**(去掉 `,?` ⇒ 钉红)。 |
604
605
 
605
606
 
606
607
 
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Atlas — 践行 Project Harness 的全流程 AI 编程工作流
2
2
 
3
3
  <p align="left">
4
- <img src="https://img.shields.io/badge/version-0.2.0--beta.3-blue.svg" alt="Version" />
4
+ <img src="https://img.shields.io/badge/version-0.2.0--beta.4-blue.svg" alt="Version" />
5
5
  <img src="https://img.shields.io/badge/license-Apache--2.0-blue.svg" alt="License" />
6
6
  <img src="https://img.shields.io/badge/tests-passing-success.svg" alt="Tests" />
7
7
  <a href="https://sunwenzhe-git.github.io/atlas/"><img src="https://img.shields.io/badge/docs-VitePress-green.svg" alt="Docs" /></a>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas-project-harness",
3
- "version": "0.2.0-beta.3",
3
+ "version": "0.2.0-beta.4",
4
4
  "description": "践行 Project Harness 的全流程 AI 编程工作流治理引擎",
5
5
  "bin": {
6
6
  "atlas": "bin/atlas.js"
@@ -2778,6 +2778,11 @@ def test_f22_imperative_testid_extraction_and_counts() -> None:
2778
2778
  li.dataset.testid = "notes-item";
2779
2779
  // setAttribute 字面量
2780
2780
  btn.setAttribute("data-testid", "notes-submit");
2781
+ // setAttribute 多行 + Prettier 尾逗号
2782
+ img.setAttribute(
2783
+ "data-testid",
2784
+ "notes-avatar",
2785
+ );
2781
2786
  // setAttribute 模板展开
2782
2787
  el.setAttribute("data-testid", `${slug}-header`);
2783
2788
  """,
@@ -2792,6 +2797,7 @@ def test_f22_imperative_testid_extraction_and_counts() -> None:
2792
2797
  # 1. 抽取源覆盖:字面量与注入全部抽取
2793
2798
  assert "notes-item" in counts
2794
2799
  assert "notes-submit" in counts
2800
+ assert "notes-avatar" in counts
2795
2801
  # 2. counts 为声明文件数:同文件出现多次 notes-item,文件数仍为 1
2796
2802
  assert counts["notes-item"] == 1
2797
2803
  assert counts["notes-submit"] == 1
@@ -68,10 +68,11 @@ TESTID_ATTR = re.compile(r"""data-testid\s*=\s*(?:\{\s*)?["']([a-zA-Z0-9_\-]+)["
68
68
  TEMPLATE_TESTID_ATTR = re.compile(r"""data-testid\s*=\s*\{\s*`([^`]+)`\s*\}""")
69
69
  # 命令式注入 testid(F22,2026-10-15;v0.1.0-beta.14):原生 JS / Web Components 常见注入形态
70
70
  # 识别 `el.dataset.testid = "..."`、`el.dataset['testid'] = "..."` 与 `el.setAttribute("data-testid", "...")`,消除假桩诱因
71
+ # setAttribute 允许尾逗号(Prettier `trailingComma: all` 会把多行调用写成 `...,\n)`;2026-10-23 D135)
71
72
  DATASET_TESTID = re.compile(r"""(?:\.dataset\.testid|\.dataset\[["']testid["']\])\s*=\s*["']([a-zA-Z0-9_\-]+)["']""")
72
- SETATTR_TESTID = re.compile(r"""\.setAttribute\(\s*["']data-testid["']\s*,\s*["']([a-zA-Z0-9_\-]+)["']\s*\)""")
73
+ SETATTR_TESTID = re.compile(r"""\.setAttribute\(\s*["']data-testid["']\s*,\s*["']([a-zA-Z0-9_\-]+)["']\s*,?\s*\)""")
73
74
  DATASET_TESTID_TPL = re.compile(r"""(?:\.dataset\.testid|\.dataset\[["']testid["']\])\s*=\s*`([^`]+)`""")
74
- SETATTR_TESTID_TPL = re.compile(r"""\.setAttribute\(\s*["']data-testid["']\s*,\s*`([^`]+)`\s*\)""")
75
+ SETATTR_TESTID_TPL = re.compile(r"""\.setAttribute\(\s*["']data-testid["']\s*,\s*`([^`]+)`\s*,?\s*\)""")
75
76
  # 同文件静态字符串常量 + 单层引用(issue #19,2026-10-16):`const X = 'a-b-c'; … data-testid={X}`。
76
77
  # 值仍**静态可枚举**(单跳、同文件),且字面量仍只出现在常量声明处**一处** ⇒ 与字面量同档:
77
78
  # 计入声明处计数(跨文件重名照报)、参与双向集合两侧判罚。