feat(发布计划): 新增「校验页面」步骤(expect_el)—— 每一步先确认上一步真的完成了
用户要求:"进行每一步的时候先校验上一步有没有完成,比如相册界面肯定有特征,如果没有到相册界面
就出现执行,你看看怎么处理" → 方案 A:新增只做校验、不带副作用的步骤。
· tasks/generic/task.py:新步骤 expect_el
- 参数:特征元素选择器 / timeout(10) / what("在等什么") / on_missing
- on_missing:skip_rest(默认,**跳过本轮剩余步骤** —— 这一条不发、换下一个号) / notify / stop
- 为此加了 worker 级的 `_skip_rest`:_exec_steps 每步前检查,foreach_account 每轮复位
· 骨架:关键点击前各插一个 —— 相册页(//*[@text='所有照片']) → 素材已选中(desc 选择/预览)
→ 剪辑页(desc 文字/话题) → 发布页(含'添加标题'/'添加作品描述') → 发布按钮
· 通知:新增 task.page.mismatch(页面不对,本轮剩余步骤已跳过)
· 昵称选择器改**两版通吃**(实测 A06=40.7.0):xpath 联合
`//*[@resource-id="…:id/tkv"] | //*[@resource-id="…:id/tk+"]`
—— 40.7 是 tkv、40.6 是 tk+,联合在 40.7 上实测能读到名字;.env(dev)已改,切号入口两版同为 desc=切换账号
· 前端:编辑器面板 + 参数表单 + 发布页就地编辑字段表都认 expect_el
· 文档:TASK_DEV §3(27→28 种)+ §4.7("每步先校验上一步")、NOTIFY、research §七·补(40.7.0 对照表
+ 联合 xpath + 各页特征 + "别按坐标点页签"的坑)、.env.example
· 测试:新增 scripts/tests/test_expect_el.py(特征在/不在、三种 on_missing、选择器为空),全套 7 个用例通过
This commit is contained in:
@@ -130,6 +130,9 @@ STEP_TYPES = [
|
||||
"then": [], "else": []}},
|
||||
{"type": "mark_done", "label": "记为已做", "icon": "✅",
|
||||
"params": {"selector_type": "text", "selector_value": ""}},
|
||||
{"type": "expect_el", "label": "校验页面", "icon": "🔎",
|
||||
"params": {"selector_type": "xpath", "selector_value": "", "timeout": 10,
|
||||
"on_missing": "skip_rest", "what": ""}},
|
||||
{"type": "read_account_name", "label": "读取本机账号名字", "icon": "🏷",
|
||||
"params": {"selector_type": "xpath", "selector_value": "",
|
||||
"timeout": 8, "on_missing": "fail"}},
|
||||
@@ -268,6 +271,8 @@ class GenericStepsWorker(BaseWorker):
|
||||
# 保证"检查"与"记账"用的是**同一个身份字符串**(否则 key 对不上、去重失效)
|
||||
self._last_ident = ""
|
||||
self._last_release = None # 「推送发布视频」推的那条计划,供「标记发布结果」用
|
||||
# 「校验页面」(expect_el) 判"这页不对"时置位:跳过本轮剩余步骤(见 _exec_steps)
|
||||
self._skip_rest = False
|
||||
# 运行期变量(一台设备一个 worker 实例,天然隔离,不同任务不会串):
|
||||
# account_name 「读取本机账号名字」刚读到的名字
|
||||
# name_selector 读名字用的选择器 (sel_type, sel_val) —— 推送前核对要用同一个控件
|
||||
@@ -334,6 +339,12 @@ class GenericStepsWorker(BaseWorker):
|
||||
for i, step in enumerate(steps):
|
||||
if self.stopped() or self.is_time_up():
|
||||
return
|
||||
# 「校验页面」判"这一页不对"时置位:**本轮剩下的步骤不再执行**
|
||||
# (由 foreach_account 在下一轮开头复位 —— 效果就是"这一条不发,换下一个号")
|
||||
if self._skip_rest:
|
||||
_log.warning(f"[{self.serial}] 本页校验没过 → 跳过本轮剩余 {len(steps) - i} 步"
|
||||
f"(不再盲点后面的按钮)")
|
||||
return
|
||||
self._maybe_patrol(d) # 穿插:每步之前看一眼有没有巡检到点了
|
||||
# 压入本步在兄弟里的序号(1 起):容器步骤执行 children 时会在其后
|
||||
# 继续追加,得到 "2.1" 这样的嵌套路径
|
||||
@@ -979,6 +990,76 @@ class GenericStepsWorker(BaseWorker):
|
||||
f"({plan.get('release_date')} #{plan.get('seq')})")
|
||||
return cands, note
|
||||
|
||||
def _exec_expect_el(self, d, params, depth=0):
|
||||
"""校验页面:**只做校验、不带副作用** —— 确认"上一步真的完成了"(该出现的特征元素在不在)。
|
||||
|
||||
参数:`selector_type`/`selector_value`(**这一页的特征元素**,例如相册页的「所有照片」、
|
||||
剪辑页的「文字/话题」、发布页的标题框)、`timeout`(10,最多等它出现多久)、
|
||||
`what`(可选,人话说明"在等什么",写进日志/通知)、`on_missing`:
|
||||
|
||||
| 值 | 等不到时 |
|
||||
|---|---|
|
||||
| `skip_rest`(默认,推荐) | **本轮剩下的步骤不再执行**(不盲点后面的按钮)—— 配合「循环本机待发的号」= 这一条不发、换下一个号 |
|
||||
| `notify` | 发一条通知,然后**继续**后面的步骤(你自己判断要不要接着点) |
|
||||
| `stop` | 停本设备 + 通知(出了真问题、要人来看时用) |
|
||||
|
||||
为什么要有它:抖音发布流程是一串"点 A → 点 B → 点 C"。中间任何一步没生效,
|
||||
后面就是**盲点**(点到别的控件上),最后"任务跑完了、什么都没发出去"还不报错。
|
||||
在每个关键点击**前面**放一个"应当在某页"的校验,页面不对就停下来留痕/换号。
|
||||
|
||||
返回 True=看到了;False=没看到(已按 `on_missing` 处置)。
|
||||
"""
|
||||
sel_type = (params.get("selector_type") or "xpath").strip()
|
||||
sel_val = (params.get("selector_value") or "").strip()
|
||||
what = (params.get("what") or "").strip() or sel_val[:40]
|
||||
timeout = float(params.get("timeout") or 10)
|
||||
on_missing = (params.get("on_missing") or "skip_rest").strip().lower()
|
||||
if not sel_val:
|
||||
_log.error(f"[{self.serial}] 校验页面:选择器是空的(应填『这一页的特征元素』)")
|
||||
self.set_action("校验页面:没填选择器")
|
||||
return None
|
||||
found = False
|
||||
try:
|
||||
if sel_type == "xpath":
|
||||
found = bool(d.xpath(sel_val).wait(timeout=timeout))
|
||||
else:
|
||||
found = bool(d(**{sel_type: sel_val}).exists(timeout=timeout))
|
||||
except Exception as e:
|
||||
_log.warning(f"[{self.serial}] 校验页面异常(按没看到处理): {e}")
|
||||
if found:
|
||||
_log.info(f"[{self.serial}] 校验页面 ✅ {what}")
|
||||
self.set_action(f"页面对:{what}")
|
||||
return True
|
||||
_log.error(f"[{self.serial}] 校验页面 ❌ 没等到「{what}」—— {self._screen_digest(d)}")
|
||||
self.set_action(f"页面不对:没等到「{what}」")
|
||||
if on_missing == "stop":
|
||||
try:
|
||||
notifier.notify("task.page.mismatch", serial=self.serial,
|
||||
device_name=(self.ctx or {}).get("device_name", ""),
|
||||
model=(self.ctx or {}).get("model", ""),
|
||||
want=what, msg="页面不对,本设备已停止(请看设备屏幕)")
|
||||
except Exception:
|
||||
pass
|
||||
self.stop()
|
||||
elif on_missing == "notify":
|
||||
try:
|
||||
notifier.notify("task.page.mismatch", serial=self.serial,
|
||||
device_name=(self.ctx or {}).get("device_name", ""),
|
||||
model=(self.ctx or {}).get("model", ""),
|
||||
want=what, msg="页面不对,已跳过校验继续执行(有盲点风险)")
|
||||
except Exception:
|
||||
pass
|
||||
else: # skip_rest(默认)
|
||||
self._skip_rest = True
|
||||
try:
|
||||
notifier.notify("task.page.mismatch", serial=self.serial,
|
||||
device_name=(self.ctx or {}).get("device_name", ""),
|
||||
model=(self.ctx or {}).get("model", ""),
|
||||
want=what, msg="页面不对,本轮剩余步骤已跳过(这一条没发)")
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _exec_read_account_name(self, d, params, depth=0):
|
||||
"""读取本机当前登录的账号名字(抖音「我」页面上的昵称控件)。
|
||||
|
||||
@@ -1770,6 +1851,7 @@ class GenericStepsWorker(BaseWorker):
|
||||
target = names[0]
|
||||
tried.append(target) # 试过就不回头(防死循环)
|
||||
self._vars["target_name"] = target
|
||||
self._skip_rest = False # 新一轮:复位"跳过剩余步骤"(上一轮某步判过页面不对)
|
||||
sent += 1
|
||||
self.set_action(f"第 {sent} 个号:{target}(本机还有 {len(names)} 个待发名字)")
|
||||
_log.info(f"[{self.serial}] 循环本机待发的号:轮到『{target}』"
|
||||
|
||||
Reference in New Issue
Block a user