From 3f44cd1491467cc9a4c38f37989521c12cd7aff9 Mon Sep 17 00:00:00 2001 From: butubb <1422726308@qq.com> Date: Thu, 10 Sep 2026 15:31:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=83=E7=B4=A0=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=99=A8=E5=BA=8F=E5=8F=B7=E8=AF=AD=E4=B9=89=20+=20=E6=8A=93?= =?UTF-8?q?=E5=8F=96=E5=BC=B9=E7=AA=97=E7=9B=B4=E6=8E=A5=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - uiauto_helper: 同属性多实例的选择器由 `//*[@id="x"][k]` 改为 `(//*[@id="x"])[k]`—— 前者在 XPath 里是"父节点内排第 k",多实例时 [2..n] 全部失配(实测抖音底部 4 个同 id tab: 仅 [1] 可用),任务里表现为"未找到元素"但界面上元素明明存在。 - tasks/generic/task.py: 新增 _norm_legacy_xpath,执行前把**历史遗留**的 `//*[@attr=…][k]` 窄范围纠正为带括号形式(只改前缀,结构路径 …/FrameLayout[2] 的 兄弟序号保持不动)——已存任务无需重抓即可恢复。 - editor.js: 抓取弹窗每条元素新增「▶ 点一下」(按 bounds 中心真点一次,/api/screen/tap snap=1 吸附)与「✓ 测选择器」(用将填入的选择器跑 /api/steps/test 验证命中), 点击后自动刷新截图;底部加用法提示。 - doc/TASK_DEV.md:写明 xpath 序号必须整体加括号 + 旧形态自动纠正 + 优先文字/唯一 id。 --- core/uiauto_helper.py | 22 ++++++++++++++------- doc/TASK_DEV.md | 6 ++++++ static/admin/editor.js | 44 +++++++++++++++++++++++++++++++++++++++++- tasks/generic/task.py | 25 ++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 8 deletions(-) diff --git a/core/uiauto_helper.py b/core/uiauto_helper.py index 69fe8ba..db6f448 100644 --- a/core/uiauto_helper.py +++ b/core/uiauto_helper.py @@ -89,9 +89,10 @@ def get_elements(serial): 1. 有 resource-id/text/content-desc 的元素: 先预统计该属性在整棵树中的出现次数—— - 唯一出现:直接用属性选择器 //*[@resource-id="x"] - - 重复出现(如抖音信息流点赞按钮同 id 几十个):附加 [k] 位置谓词 - 精确到具体实例 //*[@resource-id="x"][k]。uiautomator2 的 d.xpath() - 底层是 lxml 标准 XPath,[k] 与抓取时同一语义,不会误中屏幕外第一个。 + - 重复出现(如抖音底部导航 4 个 tab 同 id):用**整体加括号**的 + `(//*[@resource-id="x"])[k]` 精确到第 k 个匹配。 + 注意 XPath 语义:`//*[@id="x"][k]` 是"在其父节点中排第 k",不是 + 第 k 个匹配——历史实现踩过这个坑(多实例时 [2..n] 全部失配)。 2. 无任何属性的元素: 用最近一个有属性祖先的选择器限定范围 + 同 class 兄弟序号定位 (如 //*[@resource-id="x"]/FrameLayout/ImageView[2]); @@ -153,14 +154,21 @@ def _extract(root, out): count_attrs(c) def attr_selector(attr, value, cnt, seen): - """属性选择器:唯一直接出,重复加 [k] 位置谓词。返回 (选择器, suggested)。""" + """属性选择器:唯一直接出;重复时用 `(…)[k]` 精确到**第 k 个匹配**。 + + 重要(XPath 位置谓词语义): + //*[@resource-id="x"][2] → 「在**其父节点**中排第 2 的属性匹配」,**不是**第 2 个匹配 + (//*[@resource-id="x"])[2] → 「第 2 个匹配」← 我们要的 + 历史实现写成前者,导致同 id 多实例(如抖音底部导航 4 个 tab 同 id)时 + [2..n] 全部匹配不到 → 运行时"未找到元素"(2026-09-10 实测修复)。 + """ seen[value] += 1 - val = f'//*[@{attr}={_xpath_q(value)}]' + base = f'//*[@{attr}={_xpath_q(value)}]' if cnt[value] > 1: - val += f"[{seen[value]}]" + val = f"({base})[{seen[value]}]" return val, {"type": "xpath", "value": val, "indexed": True, "occ": seen[value], "total": cnt[value]} - return val, {"type": "xpath", "value": val} + return base, {"type": "xpath", "value": base} def flatten(node, depth=0, path="", ctx=None, tag_path="", tag_index=1): """递归扁平化。 diff --git a/doc/TASK_DEV.md b/doc/TASK_DEV.md index c9bacbc..0749dff 100644 --- a/doc/TASK_DEV.md +++ b/doc/TASK_DEV.md @@ -317,6 +317,12 @@ while not self.stopped(): `className`;**仅 `if_el` 额外支持 `ocr`**(截屏 OCR 按文字匹配,UI 树里没有的文字也能找到,可选 `ocr_click` 命中后自动点击)。 带选择器的步骤(click/long_click/swipe_until/wait_el/if_el)都必须填 `selector_value`。 +> **xpath 序号必须整体加括号**(2026-09-10 修复):同一属性多个实例时,"第 k 个匹配"要写 +> **`(//*[@resource-id="x"])[k]`**;写成 `//*[@resource-id="x"][k]` 是"在其**父节点**中排第 k", +> 多实例时 `[2..n]` 全部匹配不到(表现为运行时"未找到元素",但界面上明明有这个元素)。 +> 元素抓取器现已生成带括号形式;执行器 `_norm_legacy_xpath` 会自动纠正**旧任务**里的前者(只改前缀, +> 结构路径 `.../FrameLayout[2]` 的兄弟序号不动)。等价工具定位优先用 `text`/`resourceId`,比序号 xpath 稳。 + > **静默跳过语义**:worker 对**未知 type / 缺必填**(如 `package`、`selector_value` 为空)**只打 warning 跳过,不会报错失败**——任务会"看起来成功但啥也没干"。因此写任务必须**自行校验**:用编辑器内置 validate + "测试此步骤"逐个验证选择器(见 §2.11)。 > **权威 schema**:`tasks/generic/task.py` 的 `STEP_TYPES`(后端执行器)与 `static/admin/editor.js` 的 `STEP_LIB`(前端操作库)**必须保持同步**——改节点结构两边要一起改。 > **AI 辅助生成**:用一句话需求 → AI 生成 generic_steps 任务(步骤 JSON → 编辑器预填 → 人工确认)的规划见 **doc/AI_TASK_GEN.md**。 diff --git a/static/admin/editor.js b/static/admin/editor.js index 82b3b1b..7896f39 100644 --- a/static/admin/editor.js +++ b/static/admin/editor.js @@ -845,7 +845,10 @@ var _stepEditor={ ''; document.getElementById('el-picker-footer').innerHTML= ''+ - ''; + ''+ + '' + +'提示:先「▶ 点一下」在设备上验证位置,再点元素回填选择器;' + +'「✓ 测选择器」会用将填入的选择器真跑一次点击'; // 加载截图和元素树(_refreshShot 内部同时刷新两者) self._refreshShot(); }, @@ -999,6 +1002,14 @@ var _stepEditor={ var indent=' '.repeat(Math.min(el.depth||0,6)); var sg=el.suggested||{}; if(sg.invalid)attrs.push('⚠ '+esc(sg.reason||'无可用属性')+''); + var acts='