fix: 等待多地址列表稳定加载 (#189)

This commit is contained in:
chengma
2026-08-12 17:08:03 +08:00
parent 3f5ee8303d
commit 41d1b1383f
2 changed files with 219 additions and 43 deletions
+147 -3
View File
@@ -460,6 +460,50 @@ class MultipleAddressFlowDevice(AddressFlowDevice):
super().click(x, y)
class DelayedMultipleAddressFlowDevice(MultipleAddressFlowDevice):
"""模拟地址标题先出现,地址列表稍后才进入控件树。"""
def __init__(self, loading_reads=2) -> None:
super().__init__(default_indexes=(0,))
self.loading_reads = loading_reads
self.address_panel_reads = 0
def dump_hierarchy(self):
if self.mode == "address_panel":
self.address_panel_reads += 1
if self.address_panel_reads <= self.loading_reads:
return """<hierarchy>
<node package="com.xunmeng.pinduoduo"
bounds="[0,0][1080,2376]">
<node package="com.xunmeng.pinduoduo" clickable="true"
bounds="[0,102][217,226]">
<node package="com.xunmeng.pinduoduo" text="返回"
bounds="[38,132][98,195]"/>
</node>
<node package="com.xunmeng.pinduoduo" text="收货地址"
bounds="[418,102][602,226]"/>
</node>
</hierarchy>"""
return super().dump_hierarchy()
class AlternatingDefaultAddressDevice(MultipleAddressFlowDevice):
"""模拟默认地址目标坐标持续变化。"""
def __init__(self) -> None:
super().__init__(default_indexes=(0,))
self.address_panel_reads = 0
def dump_hierarchy(self):
if self.mode == "address_panel":
self.address_panel_reads += 1
indexes = (self.address_panel_reads % 2,)
return multiple_address_panel_xml(
self.address, default_indexes=indexes
)
return super().dump_hierarchy()
class AmbiguousDefaultAddressDevice(MultipleAddressFlowDevice):
"""模拟默认地址卡片内部出现两个修改目标。"""
@@ -864,6 +908,54 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
self.assertEqual(device.mode, "panel")
adapter.close()
def test_address_list_can_load_after_panel_title(self):
device = DelayedMultipleAddressFlowDevice(loading_reads=2)
adapter = self._live_adapter(device, [])
adapter.open_goods(GOODS_URL)
adapter.select_options(
{"color": "黑色", "size": "3XL【140-165斤】"}
)
adapter.update_shipping_address("cg11")
self.assertGreaterEqual(device.address_panel_reads, 4)
self.assertEqual(device.address_modify_clicks, [(948, 571)])
self.assertEqual(device.mode, "panel")
adapter.close()
def test_address_list_missing_until_timeout_has_structural_diagnostics(self):
clock = FakeClock()
device = DelayedMultipleAddressFlowDevice(loading_reads=100)
with tempfile.TemporaryDirectory() as directory:
adapter = self._live_adapter(
device,
[],
sleeper=clock.sleep,
monotonic=clock.monotonic,
panel_timeout=1.0,
artifact_directory=Path(directory),
)
adapter.open_goods(GOODS_URL)
adapter.select_options(
{"color": "黑色", "size": "3XL【140-165斤】"}
)
with self.assertRaises(PddPurchaseError) as raised:
adapter.update_shipping_address("cg11")
diagnostics = raised.exception.diagnostics
self.assertEqual(
raised.exception.code, "PURCHASE_ADDRESS_EDIT_AMBIGUOUS"
)
self.assertEqual(diagnostics["address_card_count"], 0)
self.assertEqual(diagnostics["default_card_count"], 0)
self.assertEqual(diagnostics["candidate_count"], 0)
self.assertEqual(diagnostics["stable_reads"], 0)
self.assertEqual(device.address_modify_clicks, [])
artifact = diagnostics["artifacts"][0]
self.assertTrue(Path(artifact["path"]).is_file())
adapter.close()
def test_default_address_can_be_second_card(self):
device = MultipleAddressFlowDevice(default_indexes=(1,))
adapter = self._live_adapter(device, [])
@@ -885,7 +977,14 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
device = MultipleAddressFlowDevice(
default_indexes=default_indexes
)
adapter = self._live_adapter(device, [])
clock = FakeClock()
adapter = self._live_adapter(
device,
[],
sleeper=clock.sleep,
monotonic=clock.monotonic,
panel_timeout=1.0,
)
adapter.open_goods(GOODS_URL)
adapter.select_options(
{"color": "黑色", "size": "3XL【140-165斤】"}
@@ -900,11 +999,25 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
)
self.assertEqual(device.address_modify_clicks, [])
self.assertEqual(device.address_back_clicks, [])
self.assertEqual(
raised.exception.diagnostics["address_card_count"], 2
)
self.assertEqual(
raised.exception.diagnostics["default_card_count"],
len(default_indexes),
)
adapter.close()
def test_default_address_with_multiple_modify_targets_stops_safely(self):
device = AmbiguousDefaultAddressDevice()
adapter = self._live_adapter(device, [])
clock = FakeClock()
adapter = self._live_adapter(
device,
[],
sleeper=clock.sleep,
monotonic=clock.monotonic,
panel_timeout=1.0,
)
adapter.open_goods(GOODS_URL)
adapter.select_options(
{"color": "黑色", "size": "3XL【140-165斤】"}
@@ -920,6 +1033,30 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
self.assertEqual(device.address_modify_clicks, [])
adapter.close()
def test_changing_default_target_never_clicks_until_timeout(self):
clock = FakeClock()
device = AlternatingDefaultAddressDevice()
adapter = self._live_adapter(
device,
[],
sleeper=clock.sleep,
monotonic=clock.monotonic,
panel_timeout=1.0,
)
adapter.open_goods(GOODS_URL)
adapter.select_options(
{"color": "黑色", "size": "3XL【140-165斤】"}
)
with self.assertRaises(PddPurchaseError) as raised:
adapter.update_shipping_address("cg11")
self.assertEqual(raised.exception.code, "PURCHASE_ADDRESS_PAGE_TIMEOUT")
self.assertEqual(raised.exception.diagnostics["candidate_count"], 1)
self.assertLessEqual(raised.exception.diagnostics["stable_reads"], 1)
self.assertEqual(device.address_modify_clicks, [])
adapter.close()
def test_saved_address_panel_can_hide_full_address(self):
device = HiddenSavedAddressDevice()
adapter = self._live_adapter(device, [])
@@ -1095,8 +1232,15 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
adapter.close()
def test_address_edit_does_not_retry_when_modify_target_is_ambiguous(self):
clock = FakeClock()
device = AmbiguousAfterModifyAddressDevice()
adapter = self._live_adapter(device, [])
adapter = self._live_adapter(
device,
[],
sleeper=clock.sleep,
monotonic=clock.monotonic,
panel_timeout=1.0,
)
adapter.open_goods(GOODS_URL)
adapter.select_options(
{"color": "黑色", "size": "3XL【140-165斤】"}