fix: 抓取元素回填垃圾选择器——无属性元素生成 //*[N](匹配任意节点点不到),改为 invalid 标记前端提示不可选;结构路径跳过无 class 层消除 //*[N] 前缀污染;模块拆分遗漏修复(get_all_worker_status/list_installed_apps 致 summary/设备已装应用 500)
This commit is contained in:
+13
-1
@@ -190,9 +190,14 @@ def _extract(root, out):
|
|||||||
desc = props.get("content-desc", "")
|
desc = props.get("content-desc", "")
|
||||||
cls = props.get("class", "")
|
cls = props.get("class", "")
|
||||||
tag = cls.split(".")[-1] if cls else "*"
|
tag = cls.split(".")[-1] if cls else "*"
|
||||||
# 始终带同 class 兄弟序号(含 [1]),结构路径才精确无歧义
|
# 始终带同 class 兄弟序号(含 [1]),结构路径才精确无歧义;
|
||||||
|
# class 为空的层(tag=*)不参与结构路径,避免 //*[N] 前缀污染导致选择器定位到任意节点
|
||||||
|
if tag != "*":
|
||||||
seg = f"{tag}[{tag_index}]"
|
seg = f"{tag}[{tag_index}]"
|
||||||
own_tag_path = f"{tag_path}/{seg}" if tag_path else seg
|
own_tag_path = f"{tag_path}/{seg}" if tag_path else seg
|
||||||
|
else:
|
||||||
|
seg = ""
|
||||||
|
own_tag_path = tag_path
|
||||||
|
|
||||||
# ---- 推荐选择器:唯一属性 > 锚点祖先限定 > 全结构路径 ----
|
# ---- 推荐选择器:唯一属性 > 锚点祖先限定 > 全结构路径 ----
|
||||||
if rid:
|
if rid:
|
||||||
@@ -205,6 +210,13 @@ def _extract(root, out):
|
|||||||
val = f"{ctx}/{seg}"
|
val = f"{ctx}/{seg}"
|
||||||
suggested = {"type": "xpath", "value": val}
|
suggested = {"type": "xpath", "value": val}
|
||||||
child_ctx = val
|
child_ctx = val
|
||||||
|
else:
|
||||||
|
if tag == "*":
|
||||||
|
# class 也为空的元素:结构路径 //*[N] 无意义(匹配到文档任意节点),
|
||||||
|
# 标记 invalid,前端提示不可选,避免回填垃圾选择器导致"点击不到"
|
||||||
|
suggested = {"type": "xpath", "value": "",
|
||||||
|
"invalid": True,
|
||||||
|
"reason": "该元素无可用属性(id/文本/class),无法生成可靠选择器"}
|
||||||
else:
|
else:
|
||||||
# 无唯一属性且无锚点祖先:用从根开始的结构路径(脆弱,标记 broad 让前端提示)
|
# 无唯一属性且无锚点祖先:用从根开始的结构路径(脆弱,标记 broad 让前端提示)
|
||||||
suggested = {"type": "xpath", "value": f"//{own_tag_path}", "broad": True}
|
suggested = {"type": "xpath", "value": f"//{own_tag_path}", "broad": True}
|
||||||
|
|||||||
@@ -998,6 +998,7 @@ var _stepEditor={
|
|||||||
if(el.description)attrs.push('<span>desc:'+esc(el.description)+'</span>');
|
if(el.description)attrs.push('<span>desc:'+esc(el.description)+'</span>');
|
||||||
var indent=' '.repeat(Math.min(el.depth||0,6));
|
var indent=' '.repeat(Math.min(el.depth||0,6));
|
||||||
var sg=el.suggested||{};
|
var sg=el.suggested||{};
|
||||||
|
if(sg.invalid)attrs.push('<span style="color:#dc2626">⚠ '+esc(sg.reason||'无可用属性')+'</span>');
|
||||||
return '<div class="el-picker-item" data-idx="'+idx+'" title="点击填入: '+esc(sg.value||'')+'" onclick="_stepEditor._pickEl('+idx+')">'+
|
return '<div class="el-picker-item" data-idx="'+idx+'" title="点击填入: '+esc(sg.value||'')+'" onclick="_stepEditor._pickEl('+idx+')">'+
|
||||||
'<span class="ep-depth">'+(el.depth||0)+'</span>'+
|
'<span class="ep-depth">'+(el.depth||0)+'</span>'+
|
||||||
'<div class="ep-info"><div class="ep-name">'+indent+esc(el.name||el.class||'')+'</div>'+
|
'<div class="ep-info"><div class="ep-name">'+indent+esc(el.name||el.class||'')+'</div>'+
|
||||||
@@ -1022,6 +1023,10 @@ var _stepEditor={
|
|||||||
_pickEl:function(i){
|
_pickEl:function(i){
|
||||||
var el=this._elData[i];
|
var el=this._elData[i];
|
||||||
if(!el||!this._elTargetInput)return;
|
if(!el||!this._elTargetInput)return;
|
||||||
|
if(el.suggested&&el.suggested.invalid){
|
||||||
|
showToast(el.suggested.reason||'该元素无法生成选择器','error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 填入推荐选择器值,并切换选择器类型
|
// 填入推荐选择器值,并切换选择器类型
|
||||||
this._elTargetInput.value=el.suggested.value;
|
this._elTargetInput.value=el.suggested.value;
|
||||||
var sel=this._elTargetInput.closest('.sc-body').querySelector('[data-key="selector_type"]');
|
var sel=this._elTargetInput.closest('.sc-body').querySelector('[data-key="selector_type"]');
|
||||||
|
|||||||
+3
-2
@@ -9,8 +9,9 @@ from flask import (Blueprint, jsonify, request, Response, render_template)
|
|||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
|
|
||||||
from core import device_pool
|
from core import device_pool
|
||||||
from core.adb_helper import (screenshot, adb_connect, adb_connect_light,
|
from core.adb_helper import (screenshot, list_installed_apps, adb_connect,
|
||||||
_ADB_LOCK, _adb)
|
adb_connect_light, _ADB_LOCK, _adb)
|
||||||
|
from core.device_worker import get_all_worker_status
|
||||||
from web import context
|
from web import context
|
||||||
from web.auth import perm_required, PERM_DEVICES
|
from web.auth import perm_required, PERM_DEVICES
|
||||||
from web.common import _merged_device_list, _device_screen_state
|
from web.common import _merged_device_list, _device_screen_state
|
||||||
|
|||||||
Reference in New Issue
Block a user