fix: 稳定地址表单后单次保存 (#191)
This commit is contained in:
@@ -616,6 +616,85 @@ class HiddenSavedAddressDevice(AddressFlowDevice):
|
||||
return super().dump_hierarchy()
|
||||
|
||||
|
||||
class DelayedAddressEditor(FakeAddressEditor):
|
||||
"""先记录输入,等待控件树刷新后才显示新地址。"""
|
||||
|
||||
def set_text(self, value: str) -> None:
|
||||
self.device.pending_address = value
|
||||
|
||||
|
||||
class DelayedStableSaveTargetDevice(AddressFlowDevice):
|
||||
"""输入完成后保存目标先变化,随后才稳定。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.save_form_reads = 0
|
||||
self.save_clicks = 0
|
||||
self.pending_address = None
|
||||
|
||||
def dump_hierarchy(self):
|
||||
if self.mode != "address_edit" or self.pending_address is None:
|
||||
return super().dump_hierarchy()
|
||||
self.save_form_reads += 1
|
||||
if self.save_form_reads >= 2:
|
||||
self.address = self.pending_address
|
||||
xml_data = super().dump_hierarchy()
|
||||
if not self.address.endswith("_cg2"):
|
||||
return xml_data
|
||||
shift = min(self.save_form_reads, 3) * 10
|
||||
return xml_data.replace(
|
||||
'bounds="[120,1194][960,1329]"',
|
||||
f'bounds="[{120 + shift},1194][{960 + shift},1329]"',
|
||||
)
|
||||
|
||||
def click(self, x, y):
|
||||
if self.mode == "address_edit":
|
||||
self.save_clicks += 1
|
||||
super().click(x, y)
|
||||
|
||||
def __call__(self, **selector):
|
||||
if (
|
||||
selector.get("className") == "android.widget.EditText"
|
||||
and "text" in selector
|
||||
):
|
||||
return DelayedAddressEditor(self, selector["text"] == self.address)
|
||||
return super().__call__(**selector)
|
||||
|
||||
|
||||
class NeverStableSaveTargetDevice(DelayedStableSaveTargetDevice):
|
||||
"""保存目标在两个位置之间持续变化。"""
|
||||
|
||||
def dump_hierarchy(self):
|
||||
if self.mode != "address_edit" or self.pending_address is None:
|
||||
return AddressFlowDevice.dump_hierarchy(self)
|
||||
self.save_form_reads += 1
|
||||
if self.save_form_reads >= 2:
|
||||
self.address = self.pending_address
|
||||
xml_data = AddressFlowDevice.dump_hierarchy(self)
|
||||
if not self.address.endswith("_cg2"):
|
||||
return xml_data
|
||||
shift = 10 if self.save_form_reads % 2 else 30
|
||||
return xml_data.replace(
|
||||
'bounds="[120,1194][960,1329]"',
|
||||
f'bounds="[{120 + shift},1194][{960 + shift},1329]"',
|
||||
)
|
||||
|
||||
|
||||
class LostSaveClickAddressDevice(AddressFlowDevice):
|
||||
"""保存按钮点击一次后页面仍停留在编辑页。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.save_clicks = 0
|
||||
|
||||
def click(self, x, y):
|
||||
if self.mode == "address_edit":
|
||||
self.clicks.append((x, y))
|
||||
self.save_clicks += 1
|
||||
return
|
||||
super().click(x, y)
|
||||
|
||||
|
||||
class LostFirstModifyClickAddressDevice(AddressFlowDevice):
|
||||
"""模拟第一次“修改”点击被页面吞掉。"""
|
||||
|
||||
@@ -1071,6 +1150,83 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
|
||||
self.assertEqual(device.presses, [])
|
||||
adapter.close()
|
||||
|
||||
def test_address_save_waits_for_stable_target_before_single_click(self):
|
||||
clock = FakeClock()
|
||||
device = DelayedStableSaveTargetDevice()
|
||||
adapter = self._live_adapter(
|
||||
device,
|
||||
[],
|
||||
sleeper=clock.sleep,
|
||||
monotonic=clock.monotonic,
|
||||
)
|
||||
adapter.open_goods(GOODS_URL)
|
||||
adapter.select_options(
|
||||
{"color": "黑色", "size": "3XL【140-165斤】"}
|
||||
)
|
||||
|
||||
adapter.update_shipping_address("cg2")
|
||||
|
||||
self.assertGreaterEqual(device.save_form_reads, 5)
|
||||
self.assertEqual(device.save_clicks, 1)
|
||||
self.assertEqual(device.mode, "panel")
|
||||
self.assertGreaterEqual(clock.now, 0.7)
|
||||
adapter.close()
|
||||
|
||||
def test_unstable_address_save_target_is_never_clicked(self):
|
||||
clock = FakeClock()
|
||||
device = NeverStableSaveTargetDevice()
|
||||
expected_address = "测试省测试市测试区测试路1号_cg2"
|
||||
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("cg2")
|
||||
|
||||
self.assertEqual(
|
||||
raised.exception.code,
|
||||
"PURCHASE_ADDRESS_SAVE_NOT_READY",
|
||||
)
|
||||
self.assertEqual(device.save_clicks, 0)
|
||||
self.assertNotIn(expected_address, repr(raised.exception.diagnostics))
|
||||
adapter.close()
|
||||
|
||||
def test_lost_save_click_reports_not_effective_without_retry(self):
|
||||
clock = FakeClock()
|
||||
device = LostSaveClickAddressDevice()
|
||||
expected_address = "测试省测试市测试区测试路1号_cg2"
|
||||
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("cg2")
|
||||
|
||||
self.assertEqual(
|
||||
raised.exception.code,
|
||||
"PURCHASE_ADDRESS_SAVE_NOT_EFFECTIVE",
|
||||
)
|
||||
self.assertEqual(raised.exception.diagnostics["observed_page"], "edit")
|
||||
self.assertEqual(device.save_clicks, 1)
|
||||
self.assertNotIn(expected_address, repr(raised.exception.diagnostics))
|
||||
adapter.close()
|
||||
|
||||
def test_coloros_overlay_can_disappear_without_system_back(self):
|
||||
device = AddressSaveOverlayDevice(clear_after_reads=2)
|
||||
adapter = self._live_adapter(device, [])
|
||||
|
||||
Reference in New Issue
Block a user