fix(apply): foreground every product update
This commit is contained in:
+1
-14
@@ -953,8 +953,6 @@ class ApplyWorker(BaseWorker):
|
||||
self._current_batch_size = None
|
||||
self._batch_count = 0
|
||||
self._progress_lock = threading.Lock()
|
||||
self._foreground_lock = threading.Lock()
|
||||
self._foregrounded_aliases = set()
|
||||
self.diagnostic_log_dir = diagnostic_log_dir
|
||||
self._run_id = None
|
||||
|
||||
@@ -1287,12 +1285,11 @@ class ApplyWorker(BaseWorker):
|
||||
db.mark_running(task.id, "apply", path=self.db_path)
|
||||
self.row_updated.emit(task.id, {"status": "running", "last_error": None})
|
||||
current_step = "apply_task"
|
||||
bring_to_front = self._should_bring_account_to_front(account)
|
||||
result = editor.apply_task(
|
||||
account,
|
||||
task,
|
||||
on_step=on_step,
|
||||
bring_to_front=bring_to_front,
|
||||
bring_to_front=True,
|
||||
update_mode=self.update_mode,
|
||||
)
|
||||
committed = bool(result.get("committed")) and not result.get("error")
|
||||
@@ -1374,16 +1371,6 @@ class ApplyWorker(BaseWorker):
|
||||
)
|
||||
return "failed"
|
||||
|
||||
def _should_bring_account_to_front(self, account):
|
||||
alias = str(getattr(account, "alias", "") or "").strip()
|
||||
if not alias:
|
||||
return True
|
||||
with self._foreground_lock:
|
||||
if alias in self._foregrounded_aliases:
|
||||
return False
|
||||
self._foregrounded_aliases.add(alias)
|
||||
return True
|
||||
|
||||
def _record_outcome(self, counters, total, outcome):
|
||||
with self._progress_lock:
|
||||
counters["done"] += 1
|
||||
|
||||
@@ -393,7 +393,7 @@ data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.<ext> # AI 生成的新
|
||||
|
||||
- 用账号 Chrome 打开商品页,等就绪,读旧标题(标题输入框 value)。
|
||||
- `open_product` 先复用已打开的同商品 tab;没有才新建商品编辑页 tab。采集完成后只关闭本次程序自动新建的商品 tab,不关闭用户原本已经打开的 tab。`CDP.close()` 只断开 WebSocket 控制连接,不等于关闭浏览器 tab。
|
||||
- ①采集调用 `open_product(..., bring_to_front=False)`,不主动执行 `Page.bringToFront`;新建商品 tab 时尝试 `Target.createTarget(background=true)` 降低 Chrome 抢焦点概率,若当前 Chrome/CDP 不接受该参数则退回普通新建 tab。③更新只在本轮每个账号的首条任务主动前台一次,后续同账号任务后台打开;若后台态上传/拖拽疑似受遮挡节流影响失败,才将当前 tab 提前台并做一次非破坏性安全恢复。
|
||||
- ①采集调用 `open_product(..., bring_to_front=False)`,不主动执行 `Page.bringToFront`;新建商品 tab 时尝试 `Target.createTarget(background=true)` 降低 Chrome 抢焦点概率,若当前 Chrome/CDP 不接受该参数则退回普通新建 tab。③更新是上传、拖拽和线上提交流程,每条任务均以前台方式打开或激活当前商品 tab,优先保障页面交互稳定;后台态失败安全恢复逻辑仅为兼容直接调用保留,正常③批量路径不依赖它。
|
||||
- 采集前和采集中途的登录检测必须区分“明确未登录”和“暂时不确定”。明确 `LOGIN_PAGE` / 登录页 URL 才整组略过该账号后续任务;`NO_SESSION_COOKIE`、检测超时或 CDP 短暂异常只记录为不确定并继续尝试采集当前商品,不得级联跳过同账号剩余任务。
|
||||
- 若商品 ID 失效、无权限或店铺不匹配导致商品编辑页无法就绪,`open_product` 必须读取/捕获 Shopee toast,把最近错误文案写入采集失败原因和诊断日志,不能只返回泛化超时。①列表只在明确捕获商品失效类 toast 时把“阶段”显示为“商品失效”;底层 `stage` 不新增中文值。若这个失败发生在程序自动新建的商品 tab 内,`open_product` 要关闭该 tab;复用用户已有 tab 不关闭。
|
||||
|
||||
@@ -461,7 +461,7 @@ data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.<ext> # AI 生成的新
|
||||
| 代理干扰 | 清除 `*_proxy`(requests `trust_env=False`),否则连本地 CDP 超时 |
|
||||
| WebSocket Origin | `websocket-client` `suppress_origin=True` |
|
||||
| 关闭连接 vs 关闭 tab | `CDP.close()` 只关闭 WebSocket;需要关闭浏览器页面时必须调用浏览器 target 关闭接口。采集只关闭本轮自动新建的商品页,复用的用户已有 tab 不关闭;③ 更新时程序自动新建的商品页成功/失败都关闭,成功提交且确认跳回商品列表页时关闭前等待 2 秒;③ 复用用户已有商品页时不关闭页面 |
|
||||
| 前台激活 | ①采集只读打开商品页时不主动 `Page.bringToFront`;新建 tab 尝试 `Target.createTarget(background=true)`,不支持时退回普通新建。③更新真实提交仍需要 UI 交互稳定性,但为降低抢焦点,本轮每账号只在首条任务主动前台一次;后台态封面上传/拖拽遇到疑似遮挡节流失败时,再提前台做一次非破坏性安全恢复,不完整重跑删图上传流程 |
|
||||
| 前台激活 | ①采集和⑥AI工场只读打开商品页时不主动 `Page.bringToFront`;新建 tab 尝试 `Target.createTarget(background=true)`,不支持时退回普通新建。③更新真实提交每条任务都以前台方式新建或激活商品 tab,并执行 `Page.bringToFront`,保障上传、图片管理器刷新和拖拽排序稳定;后台态封面恢复逻辑仅保留给兼容直接调用,不作为正常③批量路径 |
|
||||
| SPA 就绪 | 不用 load 事件;轮询“标题输入框 + 图片 itembox + 上传输入框”三者都在 |
|
||||
| 商品页错误 toast | Shopee 错误提示使用 `.eds-toasts` / `.eds-toast__content`,可能很快隐藏或 `display:none`。打开商品页/等待 SPA 就绪前应注入 `MutationObserver` 或等价监听,把 toast 文本、`outerHTML`、当前 URL、时间、可见状态保存到页面缓存(如 `window.__cmshopee_toasts`);等待详情页关键元素超时时,再兜底读取当前 DOM 中的 toast。最近错误 toast 应优先成为 `open_product` 失败原因,并写入 DB 运行日志和本地脱敏诊断日志。只有明确商品失效/不存在/无权限类 toast 才驱动①阶段列显示“商品失效”;网络、CDP、未登录、页面超时、风控等其他失败仍显示“失败” |
|
||||
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@
|
||||
- 弹窗展示本次筛选条件、更新内容、任务总数、每批最大条数、预计批次数、执行设置和“将提交线上”的风险提示;用户点「是/确认」才开始,点「否/取消」不执行。
|
||||
- 真实更新第一条商品前做账号就绪预检:按当前筛选结果汇总需要的账号;无账号、Chrome 未启动、CDP 端口不可访问、未登录或端口冲突时,弹窗列出具体账号/原因并中止本轮,不自动调用「启动登录」或静默打开 Chrome。
|
||||
- 对确认后的**已生成(generated)任务**按当前更新内容执行:打开编辑页换标题和/或换封面 → 点页面「更新」 → 如 Shopee 弹出“確定您要更新商品嗎?”确认框(`.eds-modal__content` / `.eds-modal__box`),则只点弹窗主按钮「更新」提交,不点「立即優化」。
|
||||
- 为降低批量更新时 Chrome 抢前台,③本轮每个账号只在第一条任务主动把 Chrome 提到前台,后续同账号任务后台打开;如果后台态封面上传/拖拽出现疑似遮挡节流失败,程序只对当前 tab 提前台做一次安全恢复,不重新执行完整删图上传流程。
|
||||
- ③每条更新任务都会把对应 Chrome 的当前商品 tab 切到前台;新建 tab 不使用后台创建,复用 tab 时也显式激活。上传封面、等待图片管理器刷新、拖到第一位和提交线上均以稳定性优先,因此批量更新期间 Chrome 会随任务切换到前台。后台态封面安全恢复逻辑仅为兼容直接调用保留,正常③批量路径不依赖它。
|
||||
- 打开编辑页失败时,如果 Shopee 弹出错误 toast(如商品 ID 不正确、商品不存在、无权限),③运行日志和任务失败原因必须显示该 toast 文案;同时把 toast HTML/URL/时间写入本地诊断日志。用户不需要手动复制瞬时 toast 的 HTML。
|
||||
|
||||
- 更新封面时统一按替换第一张执行:删除第一张前必须已有该任务的本地旧封面备份(①采集得到的 `old_cover_path` 且文件存在);备份缺失时阻断该条更新并提示先采集/修复备份,不盲删线上图片。
|
||||
|
||||
+5
-2
@@ -3,7 +3,7 @@ id: T-611
|
||||
title: ③更新蝦皮恢复每条商品前台激活以保障封面上传与拖拽
|
||||
phase: 7
|
||||
deps: [T-571, T-562]
|
||||
status: TODO
|
||||
status: DONE
|
||||
created: 2026-07-11
|
||||
---
|
||||
|
||||
@@ -85,4 +85,7 @@ git diff --check
|
||||
|
||||
## 执行记录
|
||||
|
||||
- 待执行。
|
||||
- 2026-07-11:移除 `ApplyWorker` 的账号前台集合与锁,③每条更新任务固定传 `bring_to_front=True`;`open_product` 既有前台新建/复用 tab 与 `Page.bringToFront` 路径保持不变。
|
||||
- 2026-07-11:保留①采集与⑥AI工场的 `bring_to_front=False` 后台只读调用,也保留 T-571 的后台失败安全恢复代码作为兼容直接调用兜底;正常③批量路径不再进入该分支。
|
||||
- 2026-07-11:更新 `docs/04-architecture.md`、`docs/routes.md`,明确③每条真实更新以前台交互稳定性优先;更新 GUI/editor 单测,覆盖同账号跨批次每条均前台、前台新建 tab 和后台采集路径。
|
||||
- 2026-07-11:干净 worktree 验证通过:`python -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`py -3.10 -m unittest discover -s tests`(387 项)、`git diff --check`。无提交 CDP 冒烟先遇到首个账号调试端口未运行,后在已运行账号打开默认测试商品时收到“商品 ID 不正确”;两次均未上传、删图或提交。连续 3 条真实封面更新需在具备有效测试商品的账号环境中补验。
|
||||
|
||||
@@ -473,6 +473,7 @@ class EditorLoginTests(unittest.TestCase):
|
||||
self.assertEqual("target-new", cdp.target_id)
|
||||
self.assertTrue(cdp.created_by_app)
|
||||
self.assertEqual("127.0.0.1:9223", cdp.cdp_host)
|
||||
self.assertIn(("Page.bringToFront", {}), fake.sent)
|
||||
create_tab_info.assert_called_once_with(
|
||||
(
|
||||
"https://seller.shopee.tw/portal/product/51100639511"
|
||||
|
||||
+2
-2
@@ -5871,7 +5871,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_apply_worker_brings_each_account_to_front_only_once_across_batches(self):
|
||||
def test_apply_worker_brings_every_product_to_front_across_batches(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
cfg = self.make_config(temp_dir)
|
||||
accounts.create_account("主店", "alias-a", debug_port=9222, config=cfg)
|
||||
@@ -5921,7 +5921,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
).execute()
|
||||
|
||||
self.assertTrue(summary["ok"])
|
||||
self.assertEqual([True, False], foreground_flags)
|
||||
self.assertEqual([True, True], foreground_flags)
|
||||
self.assertEqual(2, summary["batch_count"])
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
Reference in New Issue
Block a user