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:
+13
-5
@@ -882,7 +882,12 @@ var _stepEditor={
|
||||
if(!box)return;
|
||||
if(!el){box.innerHTML='<div class="se-empty">点左侧图上的元素,或点「层级」里的条目看属性</div>';return;}
|
||||
var sg=el.suggested||{};
|
||||
var rows=[['建议选择器', sg.value||'(无可靠选择器)'],
|
||||
// 稳定性说明:把"这个选择器靠什么定位"写清楚,用户才知道它会不会因为界面变化而失效
|
||||
var stab=sg.semantic?'✓ 语义型 —— 靠 @'+(sg.via||'')+' 限定,不依赖同类元素个数,换设备/换版本仍命中'
|
||||
:sg.indexed?'⚠ 序号型 —— 依赖「同类元素共 '+sg.total+' 个、取第 '+sg.occ+' 个」,界面一变就失配'
|
||||
:sg.broad?'⚠ 宽泛 —— 无唯一属性,可能误匹配其他元素'
|
||||
:(sg.value?'✓ 唯一属性,直接定位':'—');
|
||||
var rows=[['建议选择器', sg.value||'(无可靠选择器)'],['选择器稳定性', stab],
|
||||
['class', el.class||''], ['resource-id', el.resource_id||''],
|
||||
['text', el.text||''], ['content-desc', el.description||''],
|
||||
['package', el.package||''], ['clickable', el.clickable||''],
|
||||
@@ -1103,10 +1108,12 @@ var _stepEditor={
|
||||
var self=this;
|
||||
var html=list.map(function(el){
|
||||
var idx=self._elData.indexOf(el);
|
||||
// 属性顺序有讲究:**文字/描述排最前并加粗**——它们是最稳的定位依据,
|
||||
// 也是"一眼认出这是哪个元素"的凭据(id 只是补充)。见 doc/TASK_DEV.md §5.4。
|
||||
var attrs=[];
|
||||
if(el.resource_id)attrs.push('<span>id:'+esc(el.resource_id)+'</span>');
|
||||
if(el.text)attrs.push('<span>text:'+esc(el.text)+'</span>');
|
||||
if(el.description)attrs.push('<span>desc:'+esc(el.description)+'</span>');
|
||||
if(el.text)attrs.push('<span class="ep-text">text:<b>'+esc(el.text)+'</b></span>');
|
||||
if(el.description)attrs.push('<span class="ep-desc">desc:<b>'+esc(el.description)+'</b></span>');
|
||||
if(el.resource_id)attrs.push('<span class="ep-id">id:'+esc(el.resource_id)+'</span>');
|
||||
var indent=' '.repeat(Math.min(el.depth||0,6));
|
||||
var sg=el.suggested||{};
|
||||
if(sg.invalid)attrs.push('<span style="color:#dc2626">⚠ '+esc(sg.reason||'无可用属性')+'</span>');
|
||||
@@ -1125,7 +1132,8 @@ var _stepEditor={
|
||||
'<div class="ep-info"><div class="ep-name">'+indent+esc(el.name||el.class||'')+'</div>'+
|
||||
'<div class="ep-attrs">'+attrs.join('')+'</div></div>'+
|
||||
'<span class="ep-sel">'+esc(sg.type)+'</span>'+
|
||||
(sg.indexed?' <span class="label label-info" title="界面中同属性元素共 '+sg.total+' 个,按从上到下这是第 '+sg.occ+' 个,选择器已带序号精确定位">#'+sg.occ+'/'+sg.total+'</span>':'')+
|
||||
(sg.semantic?' <span class="label label-success" title="语义选择器:用 '+esc(sg.via||'')+' 限定,只认「这个元素长什么样」,不认「同类元素有几个」—— 换设备、换版本、界面增减元素都照样命中">✓ 语义</span>':'')+
|
||||
(sg.indexed?' <span class="label label-warning" title="⚠ 序号型:依赖「同属性元素共 '+sg.total+' 个、这是第 '+sg.occ+' 个」。界面上多/少一个同类元素(如抖音灰度版少一个 tab)就会指到别的元素上 —— 建议改选带 text 或 content-desc 的元素,拿到语义选择器">⚠ 序号 '+sg.occ+'/'+sg.total+'</span>':'')+
|
||||
(sg.broad?' <span class="label label-warning" title="无唯一属性,选择器可能误匹配其他元素,建议选带文字/id 的元素">⚠宽泛</span>':'')+
|
||||
acts+
|
||||
'</div>';
|
||||
|
||||
Reference in New Issue
Block a user