diff --git a/client/src/pdd_collect_service.py b/client/src/pdd_collect_service.py index f088fbd..6c391fa 100644 --- a/client/src/pdd_collect_service.py +++ b/client/src/pdd_collect_service.py @@ -815,6 +815,11 @@ class PddCollectService: if initial_app_state is not None else device.app_current() ) + package_hint = ( + PDD_PACKAGE_NAME + if current.get("package") == PDD_PACKAGE_NAME + else "" + ) before_open = self._read_page_observation(device, current) if current.get("package") != PDD_PACKAGE_NAME: stage = trace.stage("pdd_start_or_wait") if trace else nullcontext() @@ -849,7 +854,6 @@ class PddCollectService: last_recorded = "" while self._monotonic() < deadline: self._check_cancelled() - current = device.app_current() if first_dump and trace is not None: with trace.stage("first_dump_hierarchy"): xml_data = self._dump_hierarchy(device) @@ -857,9 +861,9 @@ class PddCollectService: else: xml_data = self._dump_hierarchy(device) root = _parse_xml(xml_data) - observation = classify_pdd_page( - root, str(current.get("package") or "") - ) + # 部分设备的 app_current() 一次会阻塞十秒以上,并错误报告 + # 设置页。循环中直接使用最新控件树,PDD 节点包名足以分类。 + observation = classify_pdd_page(root, package_hint) last_kind = observation.kind if trace is not None and last_kind != last_recorded: trace.record( diff --git a/client/test/test_pdd_collect_service.py b/client/test/test_pdd_collect_service.py index b5e56cd..e20f167 100644 --- a/client/test/test_pdd_collect_service.py +++ b/client/test/test_pdd_collect_service.py @@ -35,9 +35,11 @@ class FakeCollectDevice: self.swipes = [] self.swipe_panel_states = [] self.app_wait_calls = 0 + self.app_current_calls = 0 self.opened_url = None def app_current(self): + self.app_current_calls += 1 return {"package": "com.xunmeng.pinduoduo"} def app_start(self, _package): @@ -94,13 +96,13 @@ class LoadingDevice(FakeCollectDevice): class DisconnectingDevice(FakeCollectDevice): def __init__(self, home_xml, spec_xml): super().__init__(home_xml, spec_xml) - self.current_calls = 0 + self.hierarchy_calls = 0 - def app_current(self): - self.current_calls += 1 - if self.current_calls > 1: + def dump_hierarchy(self): + self.hierarchy_calls += 1 + if self.hierarchy_calls > 1: raise RuntimeError("device not found") - return super().app_current() + return super().dump_hierarchy() class FocusMismatchDevice(FakeCollectDevice): @@ -113,6 +115,18 @@ class FocusMismatchDevice(FakeCollectDevice): self.app_started = True +class SlowFocusMismatchDevice(FocusMismatchDevice): + def __init__(self, home_xml, spec_xml, clock): + super().__init__(home_xml, spec_xml) + self.clock = clock + self.app_current_calls = 0 + + def app_current(self): + self.app_current_calls += 1 + self.clock.sleep(11.0) + return {"package": "com.android.settings"} + + class PanelDoesNotOpenDevice(FakeCollectDevice): def click(self, x, y): self.clicks.append((x, y)) @@ -434,6 +448,7 @@ class PddCollectParserTest(unittest.TestCase): self.assertEqual(data["source"]["device_address"], "USB-001") self.assertIsNone(data["purchase"]) self.assertEqual(device.app_wait_calls, 0) + self.assertEqual(device.app_current_calls, 1) def test_collect_scrolls_before_spec_panel_and_accumulates_metadata(self): initial_page = self.home_xml.replace( @@ -823,6 +838,28 @@ class PddCollectParserTest(unittest.TestCase): self.assertEqual(2, raised.exception.diagnostics["open_attempts"]) self.assertEqual([], device.clicks) + def test_slow_wrong_focus_does_not_consume_home_reopen_timeout(self): + home = (FIXTURES / "pdd_home_page.xml").read_text(encoding="utf-8") + clock = FakeClock() + device = SlowFocusMismatchDevice(home, self.spec_xml, clock) + service = PddCollectService( + PddDeviceService(lambda _serial: device), + "USB-001", + "client-001", + sleeper=clock.sleep, + monotonic=clock.monotonic, + page_timeout=10.0, + ) + + with self.assertRaises(PddCollectError) as raised: + service.collect( + FakeTask("https://mobile.yangkeduo.com/goods.html?goods_id=123") + ) + + self.assertEqual("PDD_GOODS_UNAVAILABLE", raised.exception.code) + self.assertEqual(1, device.app_current_calls) + self.assertEqual([], device.clicks) + def test_non_pdd_tree_with_purchase_words_is_not_ready(self): device = FocusMismatchDevice( '