来源:2026-09-18 真机现场分析。依赖 #308。
回填扫描停在 PDD「我的订单」列表,cards() 命中 0 个。真机现场(427 节点)实测两个独立原因:
cards()
一、订单卡片在无障碍树里不可点。
全页仅 36 个 clickable=true 节点:顶栏 2 个 + 每张卡片的动作按钮(确认收货/查看物流/申请退款/更多/催发货/再次拼单/删除订单)。没有代表整张卡片的可点容器。 而 cards() 第一关就是 safe(),safe() 要求 node.clickable,因此永远返回空。
clickable=true
safe()
node.clickable
二、图片类名判据与页面不符。
children.any { it.className?.endsWith("ImageView") == true }
该页为 WebView 渲染,商品图类名是 android.widget.Image(56 个),整页只有 1 个真正的 ImageView。即使卡片容器可点,该条也过不了。
android.widget.Image
ImageView
实测:通过 safe() 的 16 个可点节点逐条评估 cards(),命中 0。
卡片容器 [0,369][1080,1122] 高 753 clickable=false 店铺名 远信运动专营店 [114,396][399,447] 商品图 class=Image [33,471][273,711] 商品标题 拐杖老人手杖带灯防滑… [291,471][876,585] TextView 动作行 查看物流/确认收货/申请退款 [180,900][1047,990] ← 仅此排 clickable
父容器 .../3 高 1959 是整个列表,卡片是 .../3/0 高 753,用「最小满足条件的容器」可区分。
.../3
.../3/0
clickable
Image
[必须] 本工单只修正识别,不改变点击方式。识别出的卡片此时仍无法点击(驱动要求 isClickable),扫描仍会失败,但失败原因会从「卡片无法安全识别」变为点击失败——这是预期的中间状态,点击能力由 后续工单 决定。
[必须]
isClickable
list()
allTab()
expansion()
./gradlew :app:testDebugUnitTest,新增基于真机树的夹具用例。
./gradlew :app:testDebugUnitTest
卡片识别判据变化,按实际改动同步 Business-Rules-and-Glossary。
No dependencies set.
The note is not visible to the blocked user.
问题
回填扫描停在 PDD「我的订单」列表,
cards()命中 0 个。真机现场(427 节点)实测两个独立原因:一、订单卡片在无障碍树里不可点。
全页仅 36 个
clickable=true节点:顶栏 2 个 + 每张卡片的动作按钮(确认收货/查看物流/申请退款/更多/催发货/再次拼单/删除订单)。没有代表整张卡片的可点容器。 而cards()第一关就是safe(),safe()要求node.clickable,因此永远返回空。二、图片类名判据与页面不符。
该页为 WebView 渲染,商品图类名是
android.widget.Image(56 个),整页只有 1 个真正的ImageView。即使卡片容器可点,该条也过不了。实测:通过
safe()的 16 个可点节点逐条评估cards(),命中 0。现场结构(第一张卡片)
父容器
.../3高 1959 是整个列表,卡片是.../3/0高 753,用「最小满足条件的容器」可区分。方案
clickable:子树同时包含图片节点、≥4 字文本、动作行标签,取最小满足容器。Image与ImageView两种。[必须]本工单只修正识别,不改变点击方式。识别出的卡片此时仍无法点击(驱动要求isClickable),扫描仍会失败,但失败原因会从「卡片无法安全识别」变为点击失败——这是预期的中间状态,点击能力由 后续工单 决定。非目标
safe()的禁词与几何重叠规则。list()/allTab()/expansion()。验收
cards()能识别出卡片容器且数量与页面可见订单数一致。验证
./gradlew :app:testDebugUnitTest,新增基于真机树的夹具用例。文档影响
卡片识别判据变化,按实际改动同步 Business-Rules-and-Glossary。