fix: 兼容纵向颜色规格列表 (#264)

This commit is contained in:
chengma
2026-08-18 11:36:04 +08:00
parent 6fa23dcb0b
commit d69c9cc31c
3 changed files with 332 additions and 2 deletions
+212
View File
@@ -5,6 +5,8 @@ import unittest
import xml.etree.ElementTree as ET
from src.util.select_color_size import (
_color_scroll_direction,
_parse_xml,
color_selection_failure_reason,
normalize_spec_text,
select_color,
@@ -50,6 +52,108 @@ class StaticColorDevice:
self.swipes.append((args, kwargs))
class ScrollColorDevice:
"""模拟可横向或纵向滚动的颜色列表。"""
def __init__(
self,
direction: str,
target: str = "目标白色",
include_target: bool = True,
) -> None:
self.direction = direction
self.target = target
self.include_target = include_target
self.page = 0
self.selected = False
self.clicks = []
self.swipes = []
def dump_hierarchy(self) -> str:
if self.direction == "horizontal":
start = self.page * 2
visible = [f"颜色{index}" for index in range(start + 1, start + 4)]
if self.page == 2 and self.include_target:
visible[-1] = self.target
options = []
for index, text in enumerate(visible):
left = 40 + index * 320
selected = ' selected="true"' if text == self.target and self.selected else ""
options.append(
f'<node text="{text}" clickable="true"{selected} '
f'bounds="[{left},1080][{left + 270},1190]" />'
)
container = f'''<node class="androidx.recyclerview.widget.RecyclerView"
scrollable="true" bounds="[24,1040][1056,1240]">
{''.join(options)}
</node>'''
else:
visible = [f"颜色{index}" for index in range(1, 10)]
if self.page:
visible = [f"颜色{index}" for index in range(7, 14)] + [self.target]
if not self.include_target:
visible[-1] = "颜色14"
options = []
for index, text in enumerate(visible):
top = 1050 + index * 110
selected = ' selected="true"' if text == self.target and self.selected else ""
options.append(
f'<node text="{text}" clickable="true"{selected} '
f'bounds="[36,{top}][850,{top + 85}]" />'
)
container = ''.join(options)
summary = f"已选:{self.target}" if self.selected else "请选择:颜色 尺码"
return f'''<hierarchy>
<node bounds="[0,0][1080,2340]">
<node class="android.widget.ScrollView" scrollable="true"
bounds="[0,945][1080,2079]">
<node text="{summary}" bounds="[36,950][800,1000]" />
<node text="颜色 (14)" bounds="[36,971][216,1024]" />
{container}
</node>
</node>
</hierarchy>'''
def click(self, x: int, y: int) -> None:
self.clicks.append((x, y))
if self.target in self.dump_hierarchy():
self.selected = True
def swipe(self, x1, y1, x2, y2, duration=0.35) -> None:
self.swipes.append((x1, y1, x2, y2, duration))
if self.direction == "vertical" and abs(y2 - y1) > abs(x2 - x1):
self.page = 1 if y1 > y2 else 0
if self.direction == "horizontal" and abs(x2 - x1) > abs(y2 - y1):
self.page = min(2, self.page + 1) if x1 > x2 else max(0, self.page - 1)
class AmbiguousGridDevice:
"""模拟方向证据不足的正方形两行两列网格。"""
def __init__(self) -> None:
self.clicks = []
self.swipes = []
@staticmethod
def dump_hierarchy() -> str:
return '''<hierarchy><node bounds="[0,0][1080,2340]">
<node text="颜色 (8)" bounds="[36,820][220,880]" />
<node scrollable="true" bounds="[100,900][900,1700]">
<node text="颜色1" clickable="true" bounds="[140,940][360,1080]" />
<node text="颜色2" clickable="true" bounds="[540,940][760,1080]" />
<node text="颜色3" clickable="true" bounds="[140,1340][360,1480]" />
<node text="颜色4" clickable="true" bounds="[540,1340][760,1480]" />
</node>
</node></hierarchy>'''
def click(self, x: int, y: int) -> None:
self.clicks.append((x, y))
def swipe(self, *args, **kwargs) -> None:
self.swipes.append((args, kwargs))
class StaticColorSelectionTest(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
@@ -208,6 +312,114 @@ class StaticColorSelectionTest(unittest.TestCase):
"target_ambiguous",
)
def test_vertical_color_list_scrolls_down_to_target(self):
device = ScrollColorDevice("vertical")
selected = select_color(
device,
device.dump_hierarchy(),
device.target,
action_delay=0.001,
)
self.assertTrue(selected)
self.assertEqual(len(device.clicks), 1)
self.assertTrue(device.swipes)
self.assertTrue(
all(
abs(y2 - y1) > abs(x2 - x1)
for x1, y1, x2, y2, _duration in device.swipes
)
)
def test_horizontal_color_list_keeps_horizontal_scan(self):
device = ScrollColorDevice("horizontal")
selected = select_color(
device,
device.dump_hierarchy(),
device.target,
action_delay=0.001,
)
self.assertTrue(selected)
self.assertEqual(len(device.clicks), 1)
self.assertTrue(device.swipes)
self.assertTrue(
all(
abs(x2 - x1) > abs(y2 - y1)
for x1, y1, x2, y2, _duration in device.swipes
)
)
def test_ambiguous_grid_stops_without_swipe_or_click(self):
device = AmbiguousGridDevice()
selected = select_color(
device,
device.dump_hierarchy(),
"首屏外目标",
action_delay=0.001,
)
self.assertFalse(selected)
self.assertEqual(device.swipes, [])
self.assertEqual(device.clicks, [])
def test_grid_uses_container_shape_only_when_direction_is_clear(self):
xml_data = AmbiguousGridDevice.dump_hierarchy()
ambiguous_root = _parse_xml(xml_data)
self.assertIsNone(
_color_scroll_direction(ambiguous_root, (100, 900, 900, 1700))
)
tall_xml = xml_data.replace(
'bounds="[100,900][900,1700]"',
'bounds="[100,700][900,1900]"',
)
self.assertEqual(
_color_scroll_direction(
_parse_xml(tall_xml), (100, 700, 900, 1900)
),
"vertical",
)
wide_xml = xml_data.replace(
'bounds="[100,900][900,1700]"',
'bounds="[20,900][1060,1500]"',
)
self.assertEqual(
_color_scroll_direction(
_parse_xml(wide_xml), (20, 900, 1060, 1500)
),
"horizontal",
)
def test_vertical_list_stops_at_edge_without_approximate_click(self):
device = ScrollColorDevice(
"vertical",
target="不存在的近似颜色",
include_target=False,
)
selected = select_color(
device,
device.dump_hierarchy(),
device.target,
max_swipes=5,
action_delay=0.001,
)
self.assertFalse(selected)
self.assertEqual(device.clicks, [])
self.assertTrue(device.swipes)
self.assertTrue(
all(
abs(y2 - y1) > abs(x2 - x1)
for x1, y1, x2, y2, _duration in device.swipes
)
)
if __name__ == "__main__":
unittest.main()