fix: 采集商品页轮询避开慢速前台查询 (#115)
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
'<hierarchy><node package="com.android.settings" text="免拼购买" '
|
||||
|
||||
@@ -123,7 +123,7 @@ Client 不保存或读取 `purchase.live_*` 手工授权设置。`purchase_mode`
|
||||
商品失效。打开前后的商品页控件树签名相同则视为旧页面,不能据此执行规格选择或
|
||||
下单;采购进入不可逆阶段前仍须按商品编号等业务字段再次确认目标身份。
|
||||
|
||||
页面轮询不得在每一轮依赖 `app_current()`:部分设备的前台查询会阻塞十秒以上,
|
||||
商品页和规格面板的页面轮询不得在每一轮依赖 `app_current()`:部分设备的前台查询会阻塞十秒以上,
|
||||
还可能把已经显示 PDD 的页面报告为系统设置。连接时可以读取一次前台状态;后续
|
||||
商品页和规格面板判断优先使用最新控件树中的包名与组合语义。单次设备调用即使跨过
|
||||
截止时间,也要先分析刚返回的最新控件树,再决定成功或超时。规格面板超时必须记录
|
||||
|
||||
Reference in New Issue
Block a user