fix: 兼容快速确认页进入规格面板 (#190)

This commit is contained in:
chengma
2026-08-12 17:30:03 +08:00
parent 275b9888b7
commit 9109b8891d
3 changed files with 300 additions and 2 deletions
+48
View File
@@ -0,0 +1,48 @@
<hierarchy rotation="0">
<node package="com.xunmeng.pinduoduo" class="android.widget.FrameLayout"
bounds="[0,0][1080,2340]" visible-to-user="true" enabled="true">
<node package="com.xunmeng.pinduoduo" class="android.widget.FrameLayout"
bounds="[0,220][1080,2210]" visible-to-user="true" enabled="true">
<node package="com.xunmeng.pinduoduo" class="android.widget.ImageView"
content-desc="关闭" clickable="true" bounds="[940,240][1010,310]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.TextView"
text="测试收货信息" bounds="[40,360][900,430]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.TextView"
text="已选: 测试款式 测试尺码" bounds="[360,820][980,890]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.ImageView"
content-desc="减少数量" clickable="true" bounds="[360,920][430,990]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.EditText"
text="1" clickable="true" bounds="[435,920][510,990]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.ImageView"
content-desc="增加数量" clickable="true" bounds="[515,920][585,990]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.TextView"
text="款式" bounds="[32,1170][150,1220]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.TextView"
text="测试款式" clickable="true" bounds="[32,1240][470,1320]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.TextView"
text="尺码" bounds="[32,1360][150,1410]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.TextView"
text="测试尺码" clickable="true" bounds="[32,1430][360,1510]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.TextView"
text="使用微信支付,可更换支付方式" bounds="[120,1900][900,1970]"
visible-to-user="true" enabled="true" />
<node package="com.xunmeng.pinduoduo" class="android.widget.FrameLayout"
clickable="true" bounds="[240,2010][800,2140]"
visible-to-user="true" enabled="true">
<node package="com.xunmeng.pinduoduo" class="android.widget.TextView"
text="现在买,仅 ¥15.60" bounds="[280,2040][760,2110]"
visible-to-user="true" enabled="true" />
</node>
</node>
</node>
</hierarchy>
+108
View File
@@ -158,6 +158,30 @@ class PanelDoesNotOpenDevice(FakeCollectDevice):
self.clicks.append((x, y))
class QuickConfirmationDevice(FakeCollectDevice):
"""点击当前款式后,从快速确认页进入正常规格面板。"""
def __init__(self, quick_confirmation_xml, spec_xml):
super().__init__(quick_confirmation_xml, spec_xml)
self.opened_url = "https://mobile.yangkeduo.com/goods.html?goods_id=123"
self.panel_open = False
def dump_hierarchy(self):
return self.spec_xml if self.panel_open else self.home_xml
def click(self, x, y):
self.clicks.append((x, y))
if 32 <= x <= 470 and 1240 <= y <= 1320:
self.panel_open = True
class StuckQuickConfirmationDevice(QuickConfirmationDevice):
"""快速确认页接受点击后仍不打开规格选择区域。"""
def click(self, x, y):
self.clicks.append((x, y))
TRANSIENT_EMPTY_SPEC_XML = """<hierarchy>
<node class="android.widget.FrameLayout" package="com.xunmeng.pinduoduo"
bounds="[0,0][1080,2340]">
@@ -612,6 +636,9 @@ class PddCollectParserTest(unittest.TestCase):
cls.submit_hint_spec_xml = (
FIXTURES / "pdd_spec_panel_submit_hint.xml"
).read_text(encoding="utf-8")
cls.quick_confirmation_xml = (
FIXTURES / "pdd_quick_confirmation.xml"
).read_text(encoding="utf-8")
cls.sold_out_xml = (
FIXTURES / "pdd_sold_out_recommendations.xml"
).read_text(encoding="utf-8")
@@ -944,6 +971,87 @@ class PddCollectParserTest(unittest.TestCase):
self.assertEqual([item.key for item in snapshot.dimensions], ["color", "size"])
self.assertEqual(clock.sleeps, [])
self.assertEqual(device.clicks, [])
def test_quick_confirmation_is_not_a_complete_spec_panel(self):
self.assertFalse(_is_spec_panel_open(self.quick_confirmation_xml))
with self.assertRaises(PddCollectError) as raised:
parse_spec_panel(self.quick_confirmation_xml)
self.assertEqual(raised.exception.code, "PDD_DATA_SPEC_INCOMPLETE")
def test_wait_opens_spec_selector_from_quick_confirmation_once(self):
device = QuickConfirmationDevice(
self.quick_confirmation_xml,
self.non_scrollable_spec_xml,
)
clock = FakeClock()
service = PddCollectService(
PddDeviceService(lambda _serial: device),
"USB-001",
"client-001",
sleeper=clock.sleep,
monotonic=clock.monotonic,
spec_panel_timeout=1.0,
)
snapshot = service._wait_spec_panel(device)
self.assertEqual([item.key for item in snapshot.dimensions], ["color", "size"])
self.assertEqual(device.clicks, [(251, 1280)])
self.assertTrue(all(y < 2000 for _, y in device.clicks))
def test_quick_confirmation_recovery_is_attempted_only_once(self):
device = StuckQuickConfirmationDevice(
self.quick_confirmation_xml,
self.non_scrollable_spec_xml,
)
clock = FakeClock()
service = PddCollectService(
PddDeviceService(lambda _serial: device),
"USB-001",
"client-001",
sleeper=clock.sleep,
monotonic=clock.monotonic,
spec_panel_timeout=1.0,
)
with self.assertRaises(PddCollectError) as raised:
service._wait_spec_panel(device)
self.assertEqual(raised.exception.code, "PDD_DATA_SPEC_INCOMPLETE")
self.assertIn("快速确认页", raised.exception.message)
self.assertEqual(device.clicks, [(251, 1280)])
self.assertTrue(
raised.exception.diagnostics["quick_confirmation_recovery_attempted"]
)
def test_quick_confirmation_without_payment_evidence_is_not_clicked(self):
incomplete_xml = self.quick_confirmation_xml.replace(
"使用微信支付,可更换支付方式",
"普通页面说明",
)
device = StuckQuickConfirmationDevice(
incomplete_xml,
self.non_scrollable_spec_xml,
)
clock = FakeClock()
service = PddCollectService(
PddDeviceService(lambda _serial: device),
"USB-001",
"client-001",
sleeper=clock.sleep,
monotonic=clock.monotonic,
spec_panel_timeout=1.0,
)
with self.assertRaises(PddCollectError) as raised:
service._wait_spec_panel(device)
self.assertEqual(raised.exception.code, "PDD_DATA_SPEC_INCOMPLETE")
self.assertEqual(device.clicks, [])
self.assertFalse(
raised.exception.diagnostics["quick_confirmation_recovery_attempted"]
)
def test_goods_page_is_not_non_scrollable_spec_panel(self):
self.assertFalse(_is_spec_panel_open(self.home_xml))