feat(抓取): 选择器优先语义化(同 id 多实例用 @text 限定,而非序号)+ 修掉两类"死选择器"
语义消歧(B 项):主属性在整棵树里重复时,先找第二个属性把目标单独圈出来 ——
//*[@resource-id="x" and @text="我"] (次属性 text > content-desc > class,
单个不够就两两组合)
标 semantic:true + via;只有组合也分不开(列表里同 id 同文字)才退回
(//*[@resource-id="x"])[k] (标 indexed,前端黄标提醒脆弱)
抖音底部导航正是这个场景:tab 个数随灰度版本变(4 个 ↔ 3 个),序号必然错位。
顺带修掉两个结构性缺陷(给上面做验证时逐条 lxml 求值发现的,均非本次引入):
1. 结构步进把 class 当标签名 —— dump 的 XML 标签**一律是 <node>**,class 在
@class 上,所以 //FrameLayout[1]/… 这类路径**永远零命中**;改 *[@class="…"][n]
2. 兜底结构路径用 @index 定位兄弟 —— 实测同级 index 会重复(状态栏/内容区/
导航栏三个兄弟全是 index="0");改按子节点位置 //hierarchy/*[1]/*[2]
前端:抓取列表把 text/content-desc 排到最前并加粗上色(最稳的定位依据);
序号型从蓝标改**黄标 ⚠ 序号 k/n**,语义型给**绿标 ✓ 语义**;属性页新增
「选择器稳定性」一行说明这个选择器靠什么定位、会不会因界面变化失效。
真机实测(192.168.20.100,248 个元素):
精确命中目标 221 → 247 | 死选择器 26 → 0 | 语义型 0 → 26(序号型 141 → 115)
「我」的语义选择器经 /api/steps/test 真机点击 → 命中 ✓
文档:TASK_DEV §5.3/5.4(含两个 XPath 坑)、API §8(suggested 字段表 +
snapshot 行)、research/U2_ELEMENT_SELECTORS §五/§六、backlog ②标记完成。
This commit is contained in:
@@ -125,16 +125,50 @@
|
||||
|
||||
---
|
||||
|
||||
## 五、建议的改动(待排期)
|
||||
## 五、建议的改动
|
||||
|
||||
1. **抓取器(`core/uiauto_helper.py`)**:同一 id 出现多个实例时,**优先产出
|
||||
`//*[@resource-id="x" and @text="…"]`** 而不是 `(…)[k]`;抓取列表里把
|
||||
`text`/`content-desc` 放在最显眼位置,让用户能看着选。
|
||||
2. **抓取弹窗**:对**带序号**的选择器加醒目提示("这个选择器依赖界面元素个数,
|
||||
界面一变就失效")。
|
||||
3. **已存任务**:把 `(…)[k]` 型选择器扫一遍,能改成文字限定的自动改(脚本),
|
||||
其余在编辑器里逐个复核。
|
||||
4. **运行期**:`click` 未命中时,日志里顺带 dump 一下"同 id 现在有几个实例",
|
||||
1. ✅ **抓取器优先产出语义选择器**(2026-09-13 已实现,`core/uiauto_helper._extract`):
|
||||
同一属性值出现多个实例时,**先找第二个属性把目标单独圈出来** ——
|
||||
`//*[@resource-id="x" and @text="…"]`(次属性按 `text` > `content-desc` > `class`
|
||||
排,单个不够就两个组合);只有怎么都分不开(列表里同 id 同文字)才退回
|
||||
`(…)[k]`。返回值标 `semantic:true` + `via`(限定用的属性)或 `indexed:true`。
|
||||
抓取列表里 **text/content-desc 排在最前并加粗上色**,让用户能看着选。
|
||||
2. ✅ **序号型加醒目提示**(同日):前端把 `indexed` 的徽标从蓝色 `#k/n` 换成
|
||||
黄色 **⚠ 序号 k/n**,悬浮说明"依赖同类元素个数,界面一变就指到别的元素上";
|
||||
语义型给绿色 **✓ 语义**;右侧「属性」页新增一行**选择器稳定性**。
|
||||
3. ⏳ **已存任务**:把 `(…)[k]` 型选择器扫一遍,能改成文字限定的自动改(脚本),
|
||||
其余在编辑器里逐个复核。(用户自行处理)
|
||||
4. ⏳ **运行期**:`click` 未命中时,日志里顺带 dump 一下"同 id 现在有几个实例",
|
||||
让"序号错位"当场可诊断(现在是干巴巴一句"未找到元素")。
|
||||
5. **(针对第四节的"抓取错位")** 抓取接口合并成一个快照接口 + 双截图校验,
|
||||
5. ✅ **(针对第四节的"抓取错位")** 抓取接口合并成一个快照接口 + 双截图校验,
|
||||
具体见第四节「解法」。
|
||||
|
||||
---
|
||||
|
||||
## 六、顺带修掉的两个"结构性"坑(2026-09-13)
|
||||
|
||||
给 §五.1 做验证时(对真机 dump 逐条用 lxml 求值,比对"命中数==1 且 bounds 一致"),
|
||||
发现旧实现 248 个元素里有 **26 条选择器永远匹配不到任何东西** —— 都是"结构路径"类:
|
||||
|
||||
| 坑 | 现象 | 原因 |
|
||||
|---|---|---|
|
||||
| `//FrameLayout[1]/LinearLayout[2]` | 零命中 | **dump 的 XML 标签名一律是 `<node>`**,class 在 `@class` 属性上 —— 把类名当标签名写的路径永远匹配不到 |
|
||||
| `//hierarchy/node[@index="1"]` | 零命中或一次命中多个 | 同级节点的 **`index` 属性会重复**(实测状态栏/内容区/导航栏三个兄弟的 `index` 全是 `0`) |
|
||||
|
||||
**修法**:
|
||||
|
||||
- 子路径步进改成 `*[@class="android.widget.ImageView"][n]`(谓词 `[n]` 在步进选出的
|
||||
节点集上按文档序定位,语义与原来的"同 class 兄弟序号"一致)
|
||||
- 兜底结构路径改成按**子节点位置**:`//hierarchy/*[1]/*[3]`
|
||||
|
||||
**效果**(同一份真机 XML,248 个元素):
|
||||
|
||||
| | 旧 | 新 |
|
||||
|---|---|---|
|
||||
| 先按 lxml 求值,精确命中目标 | 221 | **247** |
|
||||
| 零命中(死选择器) | 26 | **0** |
|
||||
| 语义型 / 序号型 | 0 / 141 | **26 / 115** |
|
||||
|
||||
`core/uiauto_helper.py` 里 `_child_path()` 的注释与 `doc/TASK_DEV.md` §5.4 都记了这两个坑,
|
||||
避免以后又写回来。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user