fix: 兼容非滚动图片颜色采购选择 (#178)
This commit is contained in:
@@ -49,7 +49,11 @@ from .pdd_purchase_adapter import (
|
||||
PurchasePageState,
|
||||
)
|
||||
from .util.get_size_panle_coord import get_size_panel_coord
|
||||
from .util.select_color_size import select_color, select_size
|
||||
from .util.select_color_size import (
|
||||
color_selection_failure_reason,
|
||||
select_color,
|
||||
select_size,
|
||||
)
|
||||
|
||||
|
||||
Bounds = tuple[int, int, int, int]
|
||||
@@ -734,10 +738,32 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
device, panel_xml, color, action_delay=0.2
|
||||
)
|
||||
if not color_selected:
|
||||
failed_xml = self._dump_hierarchy()
|
||||
failure_reason = color_selection_failure_reason(
|
||||
failed_xml, color
|
||||
)
|
||||
diagnostics: dict[str, Any] = {
|
||||
"selection_failure": failure_reason,
|
||||
}
|
||||
artifact = self._save_last_xml(
|
||||
"purchase-color-selection-mismatch"
|
||||
)
|
||||
if artifact is not None:
|
||||
diagnostics["artifacts"] = [artifact]
|
||||
messages = {
|
||||
"target_not_visible": f"没有找到目标颜色:{color}",
|
||||
"safe_target_missing": (
|
||||
f"目标颜色没有完整可见的安全点击位置:{color}"
|
||||
),
|
||||
"selection_unconfirmed": (
|
||||
f"点击颜色后页面没有确认已选中:{color}"
|
||||
),
|
||||
}
|
||||
raise PddPurchaseError(
|
||||
"PURCHASE_OPTIONS_MISMATCH",
|
||||
f"没有精确选中颜色:{color}",
|
||||
messages[failure_reason],
|
||||
step="purchase_select_options",
|
||||
diagnostics=diagnostics,
|
||||
)
|
||||
size = checked.get("size")
|
||||
if size:
|
||||
|
||||
@@ -173,9 +173,16 @@ def _horizontal_color_region(
|
||||
if candidates:
|
||||
return max(candidates, key=lambda item: item[0])[1]
|
||||
|
||||
# 部分自绘横向列表不会暴露 scrollable,使用颜色标题下方区域兜底。
|
||||
# 部分自绘列表不会暴露 scrollable。目标已经出现时,用完整卡片和
|
||||
# 下一规格标题确定纵向边界,避免固定高度截断较高的图片卡片。
|
||||
if heading is None or screen is None:
|
||||
return None
|
||||
if target:
|
||||
target_region = _non_scrollable_target_region(
|
||||
root, target, heading, screen
|
||||
)
|
||||
if target_region is not None:
|
||||
return target_region
|
||||
|
||||
top = heading[3]
|
||||
bottom = min(screen[3], top + max(160, int(screen[3] * 0.14)))
|
||||
@@ -184,6 +191,51 @@ def _horizontal_color_region(
|
||||
return int(screen[2] * 0.04), top, int(screen[2] * 0.96), bottom
|
||||
|
||||
|
||||
def _non_scrollable_target_region(
|
||||
root: ET.Element,
|
||||
target: str,
|
||||
heading: Bounds,
|
||||
screen: Bounds,
|
||||
) -> Optional[Bounds]:
|
||||
"""用目标卡片和下一规格标题界定非滚动颜色区域。"""
|
||||
|
||||
parents = _parent_map(root)
|
||||
target_bounds = [
|
||||
bounds
|
||||
for node in root.iter("node")
|
||||
if _is_available(node) and _matches_target(node, target)
|
||||
if (bounds := _nearest_click_bounds(node, parents, screen)) is not None
|
||||
if bounds[1] >= heading[3]
|
||||
]
|
||||
if not target_bounds:
|
||||
return None
|
||||
|
||||
card = max(
|
||||
target_bounds,
|
||||
key=lambda item: (item[2] - item[0]) * (item[3] - item[1]),
|
||||
)
|
||||
next_heading_tops = []
|
||||
for node in root.iter("node"):
|
||||
text = re.sub(
|
||||
r"[((]\s*\d+\s*[))]\s*$",
|
||||
"",
|
||||
node.get("text", "").replace(" ", ""),
|
||||
)
|
||||
bounds = _parse_bounds(node.get("bounds", ""))
|
||||
if (
|
||||
text in ("尺码", "套餐")
|
||||
and node.get("clickable") != "true"
|
||||
and bounds is not None
|
||||
and bounds[1] > heading[3]
|
||||
):
|
||||
next_heading_tops.append(bounds[1])
|
||||
|
||||
bottom = min(next_heading_tops) if next_heading_tops else card[3]
|
||||
if card[3] > bottom or bottom <= heading[3]:
|
||||
return None
|
||||
return screen[0], heading[3], screen[2], bottom
|
||||
|
||||
|
||||
def _vertical_panel_region(root: ET.Element) -> Optional[Bounds]:
|
||||
heading = _heading_bounds(root)
|
||||
vertical: list[tuple[float, Bounds]] = []
|
||||
@@ -327,6 +379,28 @@ def _target_is_selected(root: ET.Element, target: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def color_selection_failure_reason(xml_data: XmlData, target: str) -> str:
|
||||
"""说明颜色选择失败阶段,供采购 Adapter 生成准确诊断。"""
|
||||
|
||||
root = _parse_xml(xml_data)
|
||||
matching_nodes = [
|
||||
node
|
||||
for node in root.iter("node")
|
||||
if _is_available(node) and _matches_target(node, target)
|
||||
]
|
||||
if not matching_nodes:
|
||||
return "target_not_visible"
|
||||
region = _horizontal_color_region(root, target)
|
||||
if region is None or _target_click_bounds(
|
||||
root,
|
||||
target,
|
||||
region,
|
||||
require_horizontal_safe=True,
|
||||
) is None:
|
||||
return "safe_target_missing"
|
||||
return "selection_unconfirmed"
|
||||
|
||||
|
||||
def _visible_signature(
|
||||
root: ET.Element,
|
||||
region: Bounds,
|
||||
@@ -439,6 +513,10 @@ def select_color(
|
||||
raise ValueError("target_color 不能为空")
|
||||
|
||||
root = _parse_xml(xml_data)
|
||||
target_is_static = (
|
||||
_target_scrollable_ancestor(root, target_color) is None
|
||||
and any(_matches_target(node, target_color) for node in root.iter("node"))
|
||||
)
|
||||
region = _horizontal_color_region(root, target_color)
|
||||
if region is None:
|
||||
return False
|
||||
@@ -453,6 +531,10 @@ def select_color(
|
||||
)
|
||||
if selected:
|
||||
return True
|
||||
if target_is_static:
|
||||
# 静态面板中目标已经出现,点击失败后继续横向滑动只会增加等待,
|
||||
# 还可能触碰其他控件;必须立即交给上层报告未确认。
|
||||
return False
|
||||
region = _horizontal_color_region(root, target_color) or region
|
||||
|
||||
# 当前横向位置未知:先用手指向右滑到列表左端。每次刷新 XML 时,
|
||||
|
||||
Reference in New Issue
Block a user