test(client): accelerate T-103 feedback loop

This commit is contained in:
QiuSW
2026-08-05 12:47:22 +08:00
parent 1bf3acda70
commit ae4b9ef08b
3 changed files with 64 additions and 24 deletions
@@ -2,7 +2,9 @@
from __future__ import annotations
from functools import lru_cache
from hashlib import sha256
from io import BytesIO
import json
from pathlib import Path
import sys
@@ -48,6 +50,16 @@ def _hash(path: Path) -> str:
return digest.hexdigest()
@lru_cache(maxsize=None)
def _source_png(size: tuple[int, int]) -> bytes:
image = Image.new("RGB", size, color=(0, 180, 0))
if size == (EXPECTED_SCREENSHOT_WIDTH, EXPECTED_SCREENSHOT_HEIGHT):
image.paste((255, 0, 0), (0, 0, 8, 540))
output = BytesIO()
image.save(output, format="PNG")
return output.getvalue()
def _default_xml() -> str:
return (
"<hierarchy rotation='0'>"
@@ -116,12 +128,7 @@ def _write_raw(
raw = root / "raw"
raw.mkdir(parents=True)
screenshot = raw / "screenshot.png"
image = Image.new("RGB", size, color=(0, 180, 0))
if size == (EXPECTED_SCREENSHOT_WIDTH, EXPECTED_SCREENSHOT_HEIGHT):
for y in range(540):
for x in range(8):
image.putpixel((x, y), (255, 0, 0))
image.save(screenshot, format="PNG")
screenshot.write_bytes(_source_png(size))
hierarchy = raw / "hierarchy.xml"
hierarchy.write_text(_default_xml() if xml is None else xml, encoding="utf-8")
manifest = {
+46 -17
View File
@@ -124,6 +124,31 @@ def _center(bounds: str) -> tuple[int, int]:
return left + (right - left) // 2, top + (bottom - top) // 2
def _flow(device: _RawDevice, timeout_seconds: float = 0.02) -> SkuSelectionFlow:
now = [0.0]
return SkuSelectionFlow(
UiautomatorSkuPanelAdapter(device, 10),
timeout_seconds,
0.01,
lambda: now[0],
lambda seconds: now.__setitem__(0, now[0] + seconds),
)
def _fast_runner_flow(
device: SkuPanelDevice,
entry_wait_timeout_seconds: float = 0.02,
) -> SkuSelectionFlow:
now = [0.0]
return SkuSelectionFlow(
device,
entry_wait_timeout_seconds,
0.01,
lambda: now[0],
lambda seconds: now.__setitem__(0, now[0] + seconds),
)
def _mutate_unique_node(
hierarchy: str,
*,
@@ -321,8 +346,7 @@ class SkuSelectionFlowTests(unittest.TestCase):
def test_target_mapping_stops_at_unproven_reveal_then_s_to_m_is_exact(self) -> None:
device = _RawDevice()
adapter = UiautomatorSkuPanelAdapter(device, 10)
flow = SkuSelectionFlow(adapter)
flow = _flow(device)
flow.open_sku_panel(_TARGET_URL)
with self.assertRaisesRegex(SkuSelectionError, "受控显示动作尚未取证"):
@@ -332,7 +356,7 @@ class SkuSelectionFlowTests(unittest.TestCase):
self.assertEqual(_actions(device, "pressKey"), [])
restored = _RawDevice(_S_FIXTURE.read_text(encoding="utf-8"))
restored_flow = SkuSelectionFlow(UiautomatorSkuPanelAdapter(restored, 10))
restored_flow = _flow(restored)
restored_flow.select_sku_options(resolve_task_selection(_TASK_COLOR, _TASK_SIZE))
self.assertEqual(restored_flow.read_sku_unit_price(), "12.88")
restored_flow.exit_sku_panel_safely()
@@ -342,7 +366,7 @@ class SkuSelectionFlowTests(unittest.TestCase):
def test_full_verified_entry_structure_taps_exact_text_child_once(self) -> None:
device = _RawDevice()
SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10)).open_sku_panel(_TARGET_URL)
_flow(device).open_sku_panel(_TARGET_URL)
self.assertEqual(_tap_centers(device), [(865, 2218)])
self.assertNotIn((763, 2247), _tap_centers(device)) # 父容器中心不是获准目标。
@@ -408,7 +432,7 @@ class SkuSelectionFlowTests(unittest.TestCase):
def test_unverified_home_entry_labels_do_not_trigger_old_product_rejection(self) -> None:
device = _RawDevice()
SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10)).open_sku_panel(
_flow(device).open_sku_panel(
_TARGET_URL,
_home_with_unverified_entry_labels(),
)
@@ -477,7 +501,7 @@ class SkuSelectionFlowTests(unittest.TestCase):
):
with self.subTest():
device = _RawDevice()
flow = SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10))
flow = _flow(device)
with self.assertRaises(SkuSelectionError):
flow.open_sku_panel(_TARGET_URL, old_page)
@@ -531,7 +555,7 @@ class SkuSelectionFlowTests(unittest.TestCase):
device = _RawDevice(_FIXTURE.read_text(encoding="utf-8"))
with self.assertRaises(SkuSelectionError):
SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10)).select_sku_options(
_flow(device).select_sku_options(
resolve_task_selection(_TASK_COLOR, _TASK_SIZE).__class__("粉红", "L(建议115-130)")
)
self.assertEqual(_actions(device, "click"), [])
@@ -546,7 +570,7 @@ class SkuSelectionFlowTests(unittest.TestCase):
)
for hierarchy in cases:
with self.subTest(), self.assertRaises(SkuSelectionError):
SkuSelectionFlow(UiautomatorSkuPanelAdapter(_RawDevice(hierarchy), 10)).select_sku_options(resolve_task_selection(_TASK_COLOR, _TASK_SIZE))
_flow(_RawDevice(hierarchy)).select_sku_options(resolve_task_selection(_TASK_COLOR, _TASK_SIZE))
def test_unknown_clickable_action_ancestor_is_rejected_by_profile(self) -> None:
root = ElementTree.fromstring(_EMPTY_FIXTURE.read_text(encoding="utf-8"))
@@ -585,13 +609,13 @@ class SkuSelectionFlowTests(unittest.TestCase):
device = _RawDevice(_PRODUCT_PAGE.replace("[688,2184][1042,2253]", "[0,0][1081,1]"))
with self.assertRaises(SkuSelectionError):
SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10)).open_sku_panel(_TARGET_URL)
_flow(device).open_sku_panel(_TARGET_URL)
self.assertEqual(_actions(device, "click"), [])
def test_color_readback_failure_never_attempts_second_option(self) -> None:
device = _RawDevice()
device.fail_color_readback = True
flow = SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10))
flow = _flow(device)
flow.open_sku_panel(_TARGET_URL)
with self.assertRaises(SkuSelectionError):
flow.select_sku_options(resolve_task_selection(_TASK_COLOR, _TASK_SIZE))
@@ -599,7 +623,7 @@ class SkuSelectionFlowTests(unittest.TestCase):
def test_non_target_selection_restores_each_dimension_once(self) -> None:
device = _RawDevice()
flow = SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10))
flow = _flow(device)
flow.open_sku_panel(_TARGET_URL)
device.select_alternates()
flow.select_sku_options(resolve_task_selection(_TASK_COLOR, _TASK_SIZE))
@@ -613,10 +637,10 @@ class SkuSelectionFlowTests(unittest.TestCase):
with self.subTest(replacement=replacement):
device = _RawDevice(_FIXTURE.read_text(encoding="utf-8").replace("快卖完 ¥12.88", replacement))
with self.assertRaises(SkuSelectionError):
SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10)).read_sku_unit_price()
_flow(device).read_sku_unit_price()
device = _RawDevice(_FIXTURE.read_text(encoding="utf-8").replace("快卖完 ¥12.88", "提交订单 ¥12.88"))
with self.assertRaises(SkuSelectionError):
SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10)).read_sku_unit_price()
_flow(device).read_sku_unit_price()
clickable_parent = _mutate_unique_node(
_FIXTURE.read_text(encoding="utf-8"),
class_name="android.widget.LinearLayout",
@@ -625,7 +649,7 @@ class SkuSelectionFlowTests(unittest.TestCase):
value="true",
)
with self.assertRaises(SkuSelectionError):
SkuSelectionFlow(UiautomatorSkuPanelAdapter(_RawDevice(clickable_parent), 10)).read_sku_unit_price()
_flow(_RawDevice(clickable_parent)).read_sku_unit_price()
def test_public_api_and_protocol_have_no_broad_or_order_operations(self) -> None:
forbidden = {"quantity", "confirm", "authorization", "fence", "submit", "payment", "click"}
@@ -671,13 +695,13 @@ class SkuSelectionFlowTests(unittest.TestCase):
device = NoPanelAfterEntry()
with self.assertRaises(SkuSelectionError):
SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10)).open_sku_panel(_TARGET_URL)
_flow(device).open_sku_panel(_TARGET_URL)
self.assertEqual(_tap_centers(device), [(865, 2218)])
duplicate = _duplicate_entry()
device = _RawDevice(duplicate)
with self.assertRaises(SkuSelectionError):
SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10)).open_sku_panel(_TARGET_URL)
_flow(device).open_sku_panel(_TARGET_URL)
self.assertEqual(_actions(device, "click"), [])
def test_fixture_contains_no_address_phone_or_payment_credentials(self) -> None:
@@ -719,6 +743,11 @@ class _CompletedFlow:
class SkuSelectionRunnerTests(unittest.TestCase):
def setUp(self) -> None:
flow_patch = patch.object(runner_module, "SkuSelectionFlow", _fast_runner_flow)
flow_patch.start()
self.addCleanup(flow_patch.stop)
def _runner(self, adb: _FakeAdb, device: _RawDevice) -> SkuSelectionRunner:
device.hierarchy = "<hierarchy />"
adb.on_intent = lambda: setattr(device, "hierarchy", _PRODUCT_PAGE.replace("<hierarchy>", '<hierarchy post-intent="1">'))
@@ -1018,7 +1047,7 @@ class SkuSelectionRunnerTests(unittest.TestCase):
device = _RawDevice(); device.select_alternates()
device.package = "other"
with self.assertRaises(SkuSelectionError): SkuSelectionFlow(UiautomatorSkuPanelAdapter(device, 10)).exit_sku_panel_safely()
with self.assertRaises(SkuSelectionError): _flow(device).exit_sku_panel_safely()
self.assertEqual(_actions(device, "pressKey"), [])
def test_unproven_reveal_publishes_nothing_and_safely_exits_once(self) -> None:
+5 -1
View File
@@ -25,7 +25,7 @@ write_paths:
- docs/current-state.md
---
<!-- BEGIN VIKUNJA EXPORT id=23 synced=2026-08-05T04:34:06Z sha256=6e089363c4d47d10e02c1af12cebfb57a40dec4d9d04459c6438c9b5aa0987ba -->
<!-- BEGIN VIKUNJA EXPORT id=23 synced=2026-08-05T04:46:50Z sha256=f225a9770923a004a3bae956e052e0cecdccee2e475b220c0487f534589bfb39 -->
## 问题 / 背景
T-102 已证明 canonical 链接可进入目标商品。T-103 在 PKG110 / Android 16 / 拼多多 8.17.0、goods_id `937122477375` 上确认:详情页底部购买区第一行“快要抢光 + 金额”是唯一获准的受控规格入口;商品内容区同名小字和第二行“免拼购买”都不得点击。
@@ -274,6 +274,10 @@ T-103 sanitizer v2 坐标修正与主审:提交 44c027a 将 screenshot space
### 2026-08-05T04:33:56Z · ila
2026-08-05 T-103 reveal 离线实现与终审通过:新增独立 `capture_sku_reveal_spike.py`,只在拼多多 8.17.0 / PKG110 / Android 16 / 1080×2376、goods_id `937122477375`、完整 `COLOR_SELECTED_SIZE_HIDDEN` 前置两次重证后,封存并发出唯一一次固定 reveal 手势;RPC 正常或结果不明均只读调和且绝不重试。后置要求目标颜色保持精确 selected、S/M 均可见且 selected=false、摘要仍为“请选择:尺码”、提交硬拒绝区仍在面板下方;成功不 Back,等待人工核对。四态 classifier 已绑定 A/B/S/M 真机证据的完整父链与 exact selected set;未知 clickable 祖先、窄浮层、SystemUI 同 bounds、坏 bounds 均失败关闭。独立终审 PASS;聚焦 55/55、client 全量 255/255、compileall、agent-context、diff-check 全部 PASS。未引用数量、确认页、围栏、提交或支付能力。`needs_device=true`,T-103 保持 Doing,等待项目所有者从拼多多首页、仅 Wi-Fi ADB、使用全新 output-dir 执行唯一一次真机 reveal 并人工确认 before/after。
### 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。
<!-- END VIKUNJA EXPORT -->
## 边界