diff --git a/client/tests/pdd/test_sku_reveal_spike.py b/client/tests/pdd/test_sku_reveal_spike.py index d1c88a8..85c6f88 100644 --- a/client/tests/pdd/test_sku_reveal_spike.py +++ b/client/tests/pdd/test_sku_reveal_spike.py @@ -100,6 +100,41 @@ def _candidate_unselected() -> str: return ElementTree.tostring(root, encoding="unicode") +def _candidate_with_color_unselected() -> str: + root = ElementTree.fromstring(_candidate_unselected()) + target = next( + node + for node in root.iter("node") + if node.get("class") == "android.view.ViewGroup" + and node.get("content-desc") == "黑色 CHA (纯棉)" + and node.get("bounds") == "[372,1000][684,1024]" + ) + for node in target.iter("node"): + node.set("selected", "false") + return ElementTree.tostring(root, encoding="unicode") + + +def _candidate_with_submit_risk(kind: str) -> str: + root = ElementTree.fromstring(_candidate_unselected()) + parents = {child: parent for parent in root.iter() for child in parent} + submit = next( + node + for node in root.iter("node") + if "提交订单" in node.get("text", "") + and node.get("class") == "android.widget.TextView" + ) + parent = parents[submit] + if kind == "missing": + parent.remove(submit) + elif kind == "duplicate": + parent.append(ElementTree.fromstring(ElementTree.tostring(submit, encoding="unicode"))) + elif kind == "moved_up": + submit.set("bounds", "[285,1900][795,1959]") + else: + raise AssertionError(kind) + return ElementTree.tostring(root, encoding="unicode") + + def _with_clickable_overlay(package: str, class_name: str, bounds: str) -> str: root = ElementTree.fromstring(_COLOR_ONLY) container = next(root.iter("node")) @@ -200,6 +235,22 @@ class SkuRevealSpikeTests(unittest.TestCase): sleep_function=lambda seconds: now.__setitem__(0, now[0] + seconds), ) + def _assert_after_rejected_once(self, after_hierarchy: str, *, ambiguous: bool = False) -> None: + device = _FakeDevice() + device.after_hierarchy = after_hierarchy + device.swipe_error = ambiguous + with TemporaryDirectory() as directory: + target = Path(directory) / "evidence" + with self.assertRaises(SkuRevealSpikeError): + self._capturer(device).capture( + "192.168.0.173:5555", + "937122477375", + target, + ) + self.assertEqual(len(_swipes(device)), 1) + self.assertFalse(target.exists()) + self.assertEqual(list(Path(directory).glob(".*.staging-*")), []) + def test_success_publishes_before_after_and_exactly_one_reveal(self) -> None: device = _FakeDevice() with TemporaryDirectory() as directory: @@ -274,6 +325,20 @@ class SkuRevealSpikeTests(unittest.TestCase): ) self.assertEqual(_swipes(device), []) + def test_hidden_sizes_after_swipe_are_not_published_or_retried(self) -> None: + self._assert_after_rejected_once(_COLOR_ONLY) + + def test_post_reveal_color_selected_drift_is_not_published_or_retried(self) -> None: + self._assert_after_rejected_once(_candidate_with_color_unselected()) + + def test_submit_hard_reject_zone_risk_is_not_published_or_retried(self) -> None: + for kind in ("missing", "duplicate", "moved_up"): + with self.subTest(kind=kind): + self._assert_after_rejected_once(_candidate_with_submit_risk(kind)) + + def test_ambiguous_rpc_with_unchanged_page_is_not_published_or_retried(self) -> None: + self._assert_after_rejected_once(_COLOR_ONLY, ambiguous=True) + def test_invalid_goods_and_existing_target_are_zero_action(self) -> None: device = _FakeDevice() with TemporaryDirectory() as directory: diff --git a/docs/tasks/T-103.md b/docs/tasks/T-103.md index f6ced91..764ed2b 100644 --- a/docs/tasks/T-103.md +++ b/docs/tasks/T-103.md @@ -25,7 +25,7 @@ write_paths: - docs/current-state.md --- - + ## 问题 / 背景 T-102 已证明 canonical 链接可进入目标商品。T-103 在 PKG110 / Android 16 / 拼多多 8.17.0、goods_id `937122477375` 上确认:详情页底部购买区第一行“快要抢光 + 金额”是唯一获准的受控规格入口;商品内容区同名小字和第二行“免拼购买”都不得点击。 @@ -278,6 +278,10 @@ T-103 sanitizer v2 坐标修正与主审:提交 44c027a 将 screenshot space ### 2026-08-05T04:45:40Z · ila 2026-08-05 T-011 方案 B 由 T-103 所有者就地完成 pdd/device 测试提速:仅修改测试。`test_sku_selection` 保持 47 项,无删除/合并/skip,使用生产既有时钟注入缝和 runner 测试工厂,把 4.029s 降至 1.725s(≤2s);`test_sku_evidence_sanitizer` 保持 25 项,确认根因不是时钟而是重复编码 1080×2376 合成 PNG,改为按尺寸缓存同一确定性 PNG bytes,把 5.383s 降至 4.360s。两模块合计 72/72 PASS;与 T-304 所有者的 polling 改动合并后 client 全量仍为 255/255,12.892s(≤15s),compileall/diff-check/agent-context 均通过。未修改生产代码或任何规格/价格/提交安全断言。T-103 仍因真机 reveal 与最终完整链路人工验收保持 Doing。 + +### 2026-08-05T04:56:30Z · ila + +2026-08-05 T-103 reveal 放行前独立审计返修:实现未发现生产缺陷,但原测试未永久覆盖验收条目中的四类后置失败。仅修改 client/tests/pdd/test_sku_reveal_spike.py,新增端到端反例:swipe 后页面不变/尺码仍隐藏、目标颜色 selected 漂移、提交硬拒绝区文本缺失/重复/上移、RPC 结果不明且页面不变。每例均断言 swipe 恰好一次且不重试、target/manifest 不发布、staging 无残留。受托 agent 聚焦 59/59 PASS;主 agent 独立逐行审查并复跑 client 全量 259/259(13.883s)、compileall、agent-context、diff-check 全部 PASS。未改生产代码,未引入数量、确认页、围栏、提交或支付能力。T-103 继续保持 Doing;可在该修复提交推送后放行项目所有者执行唯一一次真机 reveal。 ## 边界