fix: 兼容无维度名颜色摘要归位 (#268)

This commit is contained in:
chengma
2026-08-18 14:42:41 +08:00
parent 233ff16d41
commit ad9f4e6eea
3 changed files with 265 additions and 3 deletions
+179
View File
@@ -19,6 +19,7 @@ from src.pdd_u2_purchase_adapter import (
U2PddLivePurchaseAdapter,
U2PddPurchaseAdapter,
_final_submit_targets,
_needs_color_region_restore,
_page_kind,
_tagged_shipping_address,
)
@@ -152,6 +153,98 @@ def vertically_scrolled_purchase_panel_xml(*, at_color_top: bool) -> str:
</hierarchy>"""
def numbered_vertical_color_panel_xml(
*,
at_color_top: bool,
target_selected: bool = False,
middle_option_count: int = 8,
extra_scrollable: bool = False,
) -> str:
"""模拟摘要不含维度名、当前停在 E09~E16 的纵向颜色列表。"""
if at_color_top:
selected = ' selected="true"' if target_selected else ""
content = f"""
<node text="颜色分类" bounds="[36,1012][216,1065]"/>
<node text="宠粉诱惑-E01【一件】 ¥3.13" clickable="true"{selected}
enabled="true" visible-to-user="true" bounds="[36,1090][604,1175]"/>
<node text="宠粉诱惑-E02【一件】 ¥3.37" clickable="true"
enabled="true" visible-to-user="true" bounds="[36,1205][604,1290]"/>
<node text="宠粉诱惑-E03【一件】 ¥3.95" clickable="true"
enabled="true" visible-to-user="true" bounds="[36,1320][604,1405]"/>
<node text="尺码" bounds="[36,1515][126,1568]"/>
<node text="均码【70-140斤】" clickable="true"
enabled="true" visible-to-user="true" bounds="[36,1590][520,1675]"/>
"""
else:
cards = []
for index in range(middle_option_count):
number = index + 9
top = 997 + index * 115
bottom = top + 85
cards.append(
f'<node text="宠粉诱惑-E{number:02d}【一件】 ¥3.13" '
f'clickable="true" enabled="true" visible-to-user="true" '
f'bounds="[36,{top}][604,{bottom}]"/>'
)
content = "\n".join(cards) + """
<node text="尺码" bounds="[36,1927][126,1980]"/>
<node text="均码【70-140斤】" clickable="true"
enabled="true" visible-to-user="true" bounds="[36,2006][520,2079]"/>
"""
extra = """
<node package="com.xunmeng.pinduoduo"
class="androidx.recyclerview.widget.RecyclerView"
scrollable="true" enabled="true" visible-to-user="true"
bounds="[0,300][1080,900]"/>
""" if extra_scrollable else ""
summary = (
"已选: 宠粉诱惑-E01【一件】 均码【70-140斤】"
if target_selected
else "已选: 宠粉诱惑-E10【一件】 均码【70-140斤】"
)
return f"""<hierarchy>
<node package="com.xunmeng.pinduoduo" bounds="[0,0][1080,2376]">
<node text="{summary}" bounds="[396,731][1053,845]"/>
<node class="android.widget.EditText" text="1" bounds="[462,881][540,959]"/>
<node content-desc="减少数量" clickable="true" bounds="[396,881][462,959]"/>
<node content-desc="增加数量" clickable="true" bounds="[540,881][606,959]"/>
<node package="com.xunmeng.pinduoduo"
class="androidx.recyclerview.widget.RecyclerView"
scrollable="true" enabled="true" visible-to-user="true"
bounds="[0,997][1080,2079]">
{content}
</node>
{extra}
<node text="提交订单 ¥3.13" clickable="true"
enabled="true" visible-to-user="true" bounds="[0,2181][1080,2328]"/>
</node>
</hierarchy>"""
def size_only_panel_xml() -> str:
"""单规格尺码面板不能被结构兜底误认为颜色列表中部。"""
return """<hierarchy>
<node package="com.xunmeng.pinduoduo" bounds="[0,0][1080,2376]">
<node text="已选: 均码" bounds="[396,731][1053,845]"/>
<node content-desc="增加数量" clickable="true" bounds="[540,881][606,959]"/>
<node package="com.xunmeng.pinduoduo"
class="androidx.recyclerview.widget.RecyclerView"
scrollable="true" enabled="true" visible-to-user="true"
bounds="[0,997][1080,2079]">
<node text="尺码" bounds="[36,1012][126,1065]"/>
<node text="均码" clickable="true" enabled="true"
visible-to-user="true" bounds="[36,1090][520,1175]"/>
<node text="大码" clickable="true" enabled="true"
visible-to-user="true" bounds="[36,1205][520,1290]"/>
</node>
<node text="提交订单 ¥3.13" clickable="true"
enabled="true" visible-to-user="true" bounds="[0,2181][1080,2328]"/>
</node>
</hierarchy>"""
class FakeEditor:
def __init__(self, device) -> None:
self.device = device
@@ -316,6 +409,40 @@ class VerticallyScrolledColorPanelDevice(FakeDevice):
self.at_color_top = True
class NumberedVerticalColorPanelDevice(FakeDevice):
"""模拟目标 E01 在纵向列表顶部、当前视口停在 E09~E16。"""
def __init__(self) -> None:
super().__init__()
self.at_color_top = False
self.target_selected = False
self.swipes = []
def dump_hierarchy(self):
if not self.has_opened:
return super().dump_hierarchy()
if self.mode == "home":
return home_xml()
if self.mode == "panel":
return numbered_vertical_color_panel_xml(
at_color_top=self.at_color_top,
target_selected=self.target_selected,
)
return super().dump_hierarchy()
def click(self, x, y):
self.clicks.append((x, y))
if self.mode == "panel" and self.at_color_top and 1090 <= y <= 1175:
self.target_selected = True
return
self.mode = "panel"
def swipe(self, x1, y1, x2, y2, duration=0.0):
self.swipes.append((x1, y1, x2, y2, duration))
if y2 > y1:
self.at_color_top = True
class ColdStartDevice(FakeDevice):
def __init__(self) -> None:
super().__init__()
@@ -1622,6 +1749,58 @@ class U2PddPurchaseAdapterTest(unittest.TestCase):
self.assertIn('text="颜色分类"', selected_xml[0])
adapter.close()
def test_purchase_restores_plain_summary_and_matches_traditional_target(self):
clock = FakeClock()
device = NumberedVerticalColorPanelDevice()
adapter = U2PddPurchaseAdapter(
"USB-001",
device_service=PddDeviceService(connector=lambda _serial: device),
sleeper=clock.sleep,
monotonic=clock.monotonic,
)
adapter.open_goods(GOODS_URL)
adapter.select_options({"color": "寵粉誘惑-E01【一件】"})
self.assertTrue(device.at_color_top)
self.assertTrue(device.target_selected)
self.assertEqual(len(device.swipes), 1)
# 一次打开规格面板、一次点击唯一的简体 E01 卡片。
self.assertEqual(len(device.clicks), 2)
adapter.close()
def test_plain_summary_restore_requires_strong_first_dimension_structure(self):
cases = (
(
"valid_vertical_first_dimension",
numbered_vertical_color_panel_xml(at_color_top=False),
True,
),
(
"only_one_candidate_before_size",
numbered_vertical_color_panel_xml(
at_color_top=False,
middle_option_count=1,
),
False,
),
(
"multiple_scrollables",
numbered_vertical_color_panel_xml(
at_color_top=False,
extra_scrollable=True,
),
False,
),
("size_only", size_only_panel_xml(), False),
)
for name, xml_data, expected in cases:
with self.subTest(name=name):
self.assertEqual(
_needs_color_region_restore(ET.fromstring(xml_data)),
expected,
)
def test_purchase_does_not_scroll_when_color_region_is_already_visible(self):
device = VerticallyScrolledColorPanelDevice(starts_at_top=True)
adapter = self._live_adapter(