fix: avoid focusing chrome during collection
This commit is contained in:
+10
-2
@@ -160,12 +160,20 @@ def find_product_tab(item_id, host=None):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def create_tab_info(url, host=None):
|
def create_tab_info(url, host=None, background=False):
|
||||||
"""用 browser 级 Target.createTarget 新建 tab,返回其 page target 信息。"""
|
"""用 browser 级 Target.createTarget 新建 tab,返回其 page target 信息。"""
|
||||||
ver = http_get("/json/version", host=host)
|
ver = http_get("/json/version", host=host)
|
||||||
b = CDP(ver["webSocketDebuggerUrl"])
|
b = CDP(ver["webSocketDebuggerUrl"])
|
||||||
try:
|
try:
|
||||||
tid = b.send("Target.createTarget", {"url": url})["targetId"]
|
params = {"url": url}
|
||||||
|
if background:
|
||||||
|
params["background"] = True
|
||||||
|
try:
|
||||||
|
tid = b.send("Target.createTarget", params)["targetId"]
|
||||||
|
except RuntimeError:
|
||||||
|
if not background:
|
||||||
|
raise
|
||||||
|
tid = b.send("Target.createTarget", {"url": url})["targetId"]
|
||||||
finally:
|
finally:
|
||||||
b.close()
|
b.close()
|
||||||
end = time.time() + 15
|
end = time.time() + 15
|
||||||
|
|||||||
+8
-7
@@ -753,7 +753,7 @@ def _close_open_product_failure(cdp):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def open_product(account, item_id, on_step=None) -> CDP:
|
def open_product(account, item_id, on_step=None, bring_to_front=True) -> CDP:
|
||||||
"""Open or reuse a product edit tab, navigate to a clean edit URL, and wait ready."""
|
"""Open or reuse a product edit tab, navigate to a clean edit URL, and wait ready."""
|
||||||
|
|
||||||
_notify_collect_step(on_step, "open_product")
|
_notify_collect_step(on_step, "open_product")
|
||||||
@@ -763,7 +763,7 @@ def open_product(account, item_id, on_step=None) -> CDP:
|
|||||||
tab = find_product_tab(item_id, host=host)
|
tab = find_product_tab(item_id, host=host)
|
||||||
created_by_app = tab is None
|
created_by_app = tab is None
|
||||||
if tab is None:
|
if tab is None:
|
||||||
tab = create_tab_info(url, host=host)
|
tab = create_tab_info(url, host=host, background=not bring_to_front)
|
||||||
ws = tab["webSocketDebuggerUrl"]
|
ws = tab["webSocketDebuggerUrl"]
|
||||||
cdp = CDP(ws)
|
cdp = CDP(ws)
|
||||||
cdp.target_id = tab.get("id")
|
cdp.target_id = tab.get("id")
|
||||||
@@ -772,10 +772,11 @@ def open_product(account, item_id, on_step=None) -> CDP:
|
|||||||
try:
|
try:
|
||||||
_ensure_page_domains(cdp)
|
_ensure_page_domains(cdp)
|
||||||
install_toast_observer(cdp)
|
install_toast_observer(cdp)
|
||||||
try:
|
if bring_to_front:
|
||||||
cdp.send("Page.bringToFront")
|
try:
|
||||||
except Exception:
|
cdp.send("Page.bringToFront")
|
||||||
pass
|
except Exception:
|
||||||
|
pass
|
||||||
cdp.send("Page.navigate", {"url": url})
|
cdp.send("Page.navigate", {"url": url})
|
||||||
install_toast_observer(cdp)
|
install_toast_observer(cdp)
|
||||||
_notify_collect_step(on_step, "wait_ready")
|
_notify_collect_step(on_step, "wait_ready")
|
||||||
@@ -826,7 +827,7 @@ def collect(account, task, on_step=None) -> dict:
|
|||||||
"""Collect current title and cover snapshot before any edits."""
|
"""Collect current title and cover snapshot before any edits."""
|
||||||
|
|
||||||
item_id = _item_id(task)
|
item_id = _item_id(task)
|
||||||
cdp = open_product(account, item_id, on_step=on_step)
|
cdp = open_product(account, item_id, on_step=on_step, bring_to_front=False)
|
||||||
try:
|
try:
|
||||||
_notify_collect_step(on_step, "read_title")
|
_notify_collect_step(on_step, "read_title")
|
||||||
old_title = read_title(cdp)
|
old_title = read_title(cdp)
|
||||||
|
|||||||
@@ -389,6 +389,7 @@ data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.<ext> # AI 生成的新
|
|||||||
|
|
||||||
- 用账号 Chrome 打开商品页,等就绪,读旧标题(标题输入框 value)。
|
- 用账号 Chrome 打开商品页,等就绪,读旧标题(标题输入框 value)。
|
||||||
- `open_product` 先复用已打开的同商品 tab;没有才新建商品编辑页 tab。采集完成后只关闭本次程序自动新建的商品 tab,不关闭用户原本已经打开的 tab。`CDP.close()` 只断开 WebSocket 控制连接,不等于关闭浏览器 tab。
|
- `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。③更新仍使用默认 `bring_to_front=True`,保持可观察的上传、拖拽和提交行为。
|
||||||
- 若商品 ID 失效、无权限或店铺不匹配导致商品编辑页无法就绪,`open_product` 必须读取/捕获 Shopee toast,把最近错误文案写入采集失败原因和诊断日志,不能只返回泛化超时。①列表只在明确捕获商品失效类 toast 时把“阶段”显示为“商品失效”;底层 `stage` 不新增中文值。若这个失败发生在程序自动新建的商品 tab 内,`open_product` 要关闭该 tab;复用用户已有 tab 不关闭。
|
- 若商品 ID 失效、无权限或店铺不匹配导致商品编辑页无法就绪,`open_product` 必须读取/捕获 Shopee toast,把最近错误文案写入采集失败原因和诊断日志,不能只返回泛化超时。①列表只在明确捕获商品失效类 toast 时把“阶段”显示为“商品失效”;底层 `stage` 不新增中文值。若这个失败发生在程序自动新建的商品 tab 内,`open_product` 要关闭该 tab;复用用户已有 tab 不关闭。
|
||||||
|
|
||||||
- 旧封面:取第一张 itembox 的 `img.src`(CDN 链接),下载到 `data/images/<batch_id>/<slug>/<task_id>_<item_id>_old.jpg`。
|
- 旧封面:取第一张 itembox 的 `img.src`(CDN 链接),下载到 `data/images/<batch_id>/<slug>/<task_id>_<item_id>_old.jpg`。
|
||||||
@@ -452,6 +453,7 @@ data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.<ext> # AI 生成的新
|
|||||||
| 代理干扰 | 清除 `*_proxy`(requests `trust_env=False`),否则连本地 CDP 超时 |
|
| 代理干扰 | 清除 `*_proxy`(requests `trust_env=False`),否则连本地 CDP 超时 |
|
||||||
| WebSocket Origin | `websocket-client` `suppress_origin=True` |
|
| WebSocket Origin | `websocket-client` `suppress_origin=True` |
|
||||||
| 关闭连接 vs 关闭 tab | `CDP.close()` 只关闭 WebSocket;需要关闭浏览器页面时必须调用浏览器 target 关闭接口。采集只关闭本轮自动新建的商品页,复用的用户已有 tab 不关闭;③ 仅在设置 `close_success_tab=true`、成功提交、且 tab 为本轮自动新建时关闭;确认成功跳回商品列表页时,关闭前等待 2 秒 |
|
| 关闭连接 vs 关闭 tab | `CDP.close()` 只关闭 WebSocket;需要关闭浏览器页面时必须调用浏览器 target 关闭接口。采集只关闭本轮自动新建的商品页,复用的用户已有 tab 不关闭;③ 仅在设置 `close_success_tab=true`、成功提交、且 tab 为本轮自动新建时关闭;确认成功跳回商品列表页时,关闭前等待 2 秒 |
|
||||||
|
| 采集前台激活 | ①采集只读打开商品页时不主动 `Page.bringToFront`;新建 tab 尝试 `Target.createTarget(background=true)`,不支持时退回普通新建。③更新仍保持默认前台激活 |
|
||||||
| SPA 就绪 | 不用 load 事件;轮询“标题输入框 + 图片 itembox + 上传输入框”三者都在 |
|
| 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、未登录、页面超时、风控等其他失败仍显示“失败” |
|
| 商品页错误 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
@@ -65,7 +65,7 @@
|
|||||||
|
|
||||||
- 导入:openpyxl 解析**输入列**(账号名/别名/商品id)入 SQLite。
|
- 导入:openpyxl 解析**输入列**(账号名/别名/商品id)入 SQLite。
|
||||||
- **导入汇总栏**(导入后即时刷新,跑采集前的校验关口):显示 文件数、解析行数(原始数据量)、有效/无效行、匹配账号行数(按账号细分)、未匹配行数。未匹配/无效数字标红可点,点击在列表筛出便于定位纠错。
|
- **导入汇总栏**(导入后即时刷新,跑采集前的校验关口):显示 文件数、解析行数(原始数据量)、有效/无效行、匹配账号行数(按账号细分)、未匹配行数。未匹配/无效数字标红可点,点击在列表筛出便于定位纠错。
|
||||||
- 采集:点击后先为本轮匹配账号确保 Chrome 就绪(已开复用、未开启动),再检测登录;未登录账号的任务整组略过并汇总提示。登录账号用对应 Chrome 只读打开商品页,读旧标题、下载旧封面到 `data/images/<batch_id>/<slug>/<task_id>_<item_id>_old.jpg`,写 `old_title/old_cover_path`,stage=collected。若程序为采集自动新建商品页 tab,采集结束后自动关闭;若复用用户原本打开的 tab,则不关闭。采集结束不关闭账号 Chrome,用户可自行关闭。
|
- 采集:点击后先为本轮匹配账号确保 Chrome 就绪(已开复用、未开启动),再检测登录;未登录账号的任务整组略过并汇总提示。登录账号用对应 Chrome 只读打开商品页,读旧标题、下载旧封面到 `data/images/<batch_id>/<slug>/<task_id>_<item_id>_old.jpg`,写 `old_title/old_cover_path`,stage=collected。采集不主动把商品页切到前台;程序自动新建商品页 tab 时尽量后台创建,采集结束后自动关闭;若复用用户原本打开的 tab,则不关闭。采集结束不关闭账号 Chrome,用户可自行关闭。
|
||||||
- 若商品 ID 已失效、无权限或店铺不匹配,Shopee 可能只弹出短暂错误 toast;采集失败时界面日志应显示捕获到的 toast 文案,并把 toast HTML/URL 写入本地诊断日志,避免用户手动抢复制。只有明确捕获商品失效/商品不存在/无权限类 toast 时,①列表“阶段”列显示“商品失效”;其他商品页打开失败仍显示“失败”。如果失败发生在 `open_product()` 内部,本轮自动新建的商品 tab 必须关闭,复用用户已有 tab 不关闭。
|
- 若商品 ID 已失效、无权限或店铺不匹配,Shopee 可能只弹出短暂错误 toast;采集失败时界面日志应显示捕获到的 toast 文案,并把 toast HTML/URL 写入本地诊断日志,避免用户手动抢复制。只有明确捕获商品失效/商品不存在/无权限类 toast 时,①列表“阶段”列显示“商品失效”;其他商品页打开失败仍显示“失败”。如果失败发生在 `open_product()` 内部,本轮自动新建的商品 tab 必须关闭,复用用户已有 tab 不关闭。
|
||||||
|
|
||||||
- 回写:采集完成后自动把旧标题/旧封面路径批量回写原 Excel;保留「回写旧数据到 Excel」作为手动重试入口(原文件被锁→提示关闭后重试/另存)。
|
- 回写:采集完成后自动把旧标题/旧封面路径批量回写原 Excel;保留「回写旧数据到 Excel」作为手动重试入口(原文件被锁→提示关闭后重试/另存)。
|
||||||
|
|||||||
+5
-3
@@ -3,7 +3,7 @@ id: T-562
|
|||||||
title: ①采集打开商品页不主动切换 Chrome 前台
|
title: ①采集打开商品页不主动切换 Chrome 前台
|
||||||
phase: 2
|
phase: 2
|
||||||
deps: [T-203, T-205b, T-560]
|
deps: [T-203, T-205b, T-560]
|
||||||
status: TODO
|
status: DONE
|
||||||
created: 2026-07-08
|
created: 2026-07-08
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -29,11 +29,11 @@ created: 2026-07-08
|
|||||||
- `cdp.create_tab_info(url, host=None, background=False)` 增加 `background` 参数。
|
- `cdp.create_tab_info(url, host=None, background=False)` 增加 `background` 参数。
|
||||||
- 当 `background=True` 时,`Target.createTarget` 参数中带 `background: true`。
|
- 当 `background=True` 时,`Target.createTarget` 参数中带 `background: true`。
|
||||||
- `open_product(..., bring_to_front=False)` 新建商品 tab 时传 `background=True`;复用已有 tab 时不激活。
|
- `open_product(..., bring_to_front=False)` 新建商品 tab 时传 `background=True`;复用已有 tab 时不激活。
|
||||||
- 如果 Chrome 版本忽略 `background`,仍至少不再执行 `Page.bringToFront`。
|
- 如果 Chrome 版本忽略或拒绝 `background`,则退回普通新建 tab,仍至少不再执行 `Page.bringToFront`。
|
||||||
|
|
||||||
3. **测试**
|
3. **测试**
|
||||||
- editor 单测覆盖:采集调用不发送 `Page.bringToFront`,更新调用仍发送。
|
- editor 单测覆盖:采集调用不发送 `Page.bringToFront`,更新调用仍发送。
|
||||||
- cdp 单测覆盖:`create_tab_info(background=True)` 会把 `background: true` 传给 `Target.createTarget`。
|
- cdp 单测覆盖:`create_tab_info(background=True)` 会把 `background: true` 传给 `Target.createTarget`,并覆盖 `background` 被拒绝后的普通新建回退。
|
||||||
- 保持失败时自动关闭本轮新建 tab、复用用户已有 tab 不关闭的原语义。
|
- 保持失败时自动关闭本轮新建 tab、复用用户已有 tab 不关闭的原语义。
|
||||||
|
|
||||||
## 验收要点
|
## 验收要点
|
||||||
@@ -50,3 +50,5 @@ created: 2026-07-08
|
|||||||
|
|
||||||
## 执行记录
|
## 执行记录
|
||||||
|
|
||||||
|
- 2026-07-08:完成。`editor.open_product()` 增加 `bring_to_front` 参数;①采集调用 `bring_to_front=False`,不主动 `Page.bringToFront`;③更新继续默认前台激活。`cdp.create_tab_info()` 增加 `background` 参数,后台创建失败时自动回退普通新建。已同步 `docs/04-architecture.md` 和 `docs/routes.md`。
|
||||||
|
- 2026-07-08:验证通过:`python -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`py -3.10 -m unittest discover -s tests`、`git diff --check`。
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
|
||||||
|
from _helpers import REPO_ROOT
|
||||||
|
|
||||||
|
if str(REPO_ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(REPO_ROOT))
|
||||||
|
|
||||||
|
from app import cdp
|
||||||
|
|
||||||
|
|
||||||
|
class FakeBrowserCDP:
|
||||||
|
sent = []
|
||||||
|
fail_first_create = False
|
||||||
|
|
||||||
|
def __init__(self, ws_url):
|
||||||
|
self.ws_url = ws_url
|
||||||
|
self.closed = False
|
||||||
|
|
||||||
|
def send(self, method, params=None):
|
||||||
|
self.sent.append((method, params or {}))
|
||||||
|
if method == "Target.createTarget":
|
||||||
|
if self.fail_first_create:
|
||||||
|
type(self).fail_first_create = False
|
||||||
|
raise RuntimeError("Target.createTarget: Invalid parameters")
|
||||||
|
return {"targetId": "target-new"}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
|
||||||
|
class CdpTests(unittest.TestCase):
|
||||||
|
def test_create_tab_info_can_request_background_target(self):
|
||||||
|
FakeBrowserCDP.sent = []
|
||||||
|
FakeBrowserCDP.fail_first_create = False
|
||||||
|
responses = [
|
||||||
|
{"webSocketDebuggerUrl": "ws-browser"},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "target-new",
|
||||||
|
"type": "page",
|
||||||
|
"url": "https://seller.shopee.tw/portal/product/1",
|
||||||
|
"webSocketDebuggerUrl": "ws-page",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
with mock.patch("app.cdp.http_get", side_effect=responses), \
|
||||||
|
mock.patch("app.cdp.CDP", FakeBrowserCDP), \
|
||||||
|
mock.patch("app.cdp.time.sleep"):
|
||||||
|
tab = cdp.create_tab_info(
|
||||||
|
"https://seller.shopee.tw/portal/product/1",
|
||||||
|
host="127.0.0.1:9222",
|
||||||
|
background=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual("ws-page", tab["webSocketDebuggerUrl"])
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"Target.createTarget",
|
||||||
|
{
|
||||||
|
"url": "https://seller.shopee.tw/portal/product/1",
|
||||||
|
"background": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
FakeBrowserCDP.sent,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_create_tab_info_keeps_foreground_default_params(self):
|
||||||
|
FakeBrowserCDP.sent = []
|
||||||
|
FakeBrowserCDP.fail_first_create = False
|
||||||
|
responses = [
|
||||||
|
{"webSocketDebuggerUrl": "ws-browser"},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "target-new",
|
||||||
|
"type": "page",
|
||||||
|
"url": "https://seller.shopee.tw/portal/product/1",
|
||||||
|
"webSocketDebuggerUrl": "ws-page",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
with mock.patch("app.cdp.http_get", side_effect=responses), \
|
||||||
|
mock.patch("app.cdp.CDP", FakeBrowserCDP), \
|
||||||
|
mock.patch("app.cdp.time.sleep"):
|
||||||
|
cdp.create_tab_info(
|
||||||
|
"https://seller.shopee.tw/portal/product/1",
|
||||||
|
host="127.0.0.1:9222",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"Target.createTarget",
|
||||||
|
{"url": "https://seller.shopee.tw/portal/product/1"},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
FakeBrowserCDP.sent,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_create_tab_info_falls_back_when_background_is_rejected(self):
|
||||||
|
FakeBrowserCDP.sent = []
|
||||||
|
FakeBrowserCDP.fail_first_create = True
|
||||||
|
responses = [
|
||||||
|
{"webSocketDebuggerUrl": "ws-browser"},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "target-new",
|
||||||
|
"type": "page",
|
||||||
|
"url": "https://seller.shopee.tw/portal/product/1",
|
||||||
|
"webSocketDebuggerUrl": "ws-page",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
with mock.patch("app.cdp.http_get", side_effect=responses), \
|
||||||
|
mock.patch("app.cdp.CDP", FakeBrowserCDP), \
|
||||||
|
mock.patch("app.cdp.time.sleep"):
|
||||||
|
cdp.create_tab_info(
|
||||||
|
"https://seller.shopee.tw/portal/product/1",
|
||||||
|
host="127.0.0.1:9222",
|
||||||
|
background=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"Target.createTarget",
|
||||||
|
{
|
||||||
|
"url": "https://seller.shopee.tw/portal/product/1",
|
||||||
|
"background": True,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Target.createTarget",
|
||||||
|
{"url": "https://seller.shopee.tw/portal/product/1"},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
FakeBrowserCDP.sent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -442,6 +442,7 @@ class EditorLoginTests(unittest.TestCase):
|
|||||||
self.assertEqual("127.0.0.1:9222", cdp.cdp_host)
|
self.assertEqual("127.0.0.1:9222", cdp.cdp_host)
|
||||||
find_product_tab.assert_called_once_with("51100639510", host="127.0.0.1:9222")
|
find_product_tab.assert_called_once_with("51100639510", host="127.0.0.1:9222")
|
||||||
create_tab_info.assert_not_called()
|
create_tab_info.assert_not_called()
|
||||||
|
self.assertIn(("Page.bringToFront", {}), fake.sent)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
(
|
(
|
||||||
"Page.navigate",
|
"Page.navigate",
|
||||||
@@ -469,7 +470,52 @@ class EditorLoginTests(unittest.TestCase):
|
|||||||
self.assertEqual("target-new", cdp.target_id)
|
self.assertEqual("target-new", cdp.target_id)
|
||||||
self.assertTrue(cdp.created_by_app)
|
self.assertTrue(cdp.created_by_app)
|
||||||
self.assertEqual("127.0.0.1:9223", cdp.cdp_host)
|
self.assertEqual("127.0.0.1:9223", cdp.cdp_host)
|
||||||
create_tab_info.assert_called_once()
|
create_tab_info.assert_called_once_with(
|
||||||
|
(
|
||||||
|
"https://seller.shopee.tw/portal/product/51100639511"
|
||||||
|
"?pageEntry=product_list&ignore-html-cache=1"
|
||||||
|
),
|
||||||
|
host="127.0.0.1:9223",
|
||||||
|
background=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_open_product_can_skip_bring_to_front_for_collection(self):
|
||||||
|
fake = FakeProductCDP("ws-new")
|
||||||
|
with mock.patch("app.editor.find_product_tab", return_value=None), mock.patch(
|
||||||
|
"app.editor.create_tab_info",
|
||||||
|
return_value={"id": "target-new", "webSocketDebuggerUrl": "ws-new"},
|
||||||
|
) as create_tab_info, mock.patch(
|
||||||
|
"app.editor.CDP",
|
||||||
|
return_value=fake,
|
||||||
|
), mock.patch("app.editor._ensure_page_domains"), mock.patch("app.editor._wait_ready"):
|
||||||
|
cdp = editor.open_product(
|
||||||
|
{"debug_port": 9223},
|
||||||
|
"51100639511",
|
||||||
|
bring_to_front=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIs(fake, cdp)
|
||||||
|
self.assertNotIn(("Page.bringToFront", {}), fake.sent)
|
||||||
|
self.assertIn(
|
||||||
|
(
|
||||||
|
"Page.navigate",
|
||||||
|
{
|
||||||
|
"url": (
|
||||||
|
"https://seller.shopee.tw/portal/product/51100639511"
|
||||||
|
"?pageEntry=product_list&ignore-html-cache=1"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
fake.sent,
|
||||||
|
)
|
||||||
|
create_tab_info.assert_called_once_with(
|
||||||
|
(
|
||||||
|
"https://seller.shopee.tw/portal/product/51100639511"
|
||||||
|
"?pageEntry=product_list&ignore-html-cache=1"
|
||||||
|
),
|
||||||
|
host="127.0.0.1:9223",
|
||||||
|
background=True,
|
||||||
|
)
|
||||||
|
|
||||||
def test_open_product_installs_toast_observer(self):
|
def test_open_product_installs_toast_observer(self):
|
||||||
fake = FakeProductCDP("ws-new", ready=True)
|
fake = FakeProductCDP("ws-new", ready=True)
|
||||||
@@ -560,7 +606,7 @@ class EditorLoginTests(unittest.TestCase):
|
|||||||
cdp.created_by_app = True
|
cdp.created_by_app = True
|
||||||
cdp.cdp_host = "127.0.0.1:9222"
|
cdp.cdp_host = "127.0.0.1:9222"
|
||||||
|
|
||||||
with mock.patch("app.editor.open_product", return_value=cdp), mock.patch(
|
with mock.patch("app.editor.open_product", return_value=cdp) as open_product, mock.patch(
|
||||||
"app.editor.read_title",
|
"app.editor.read_title",
|
||||||
return_value="旧标题",
|
return_value="旧标题",
|
||||||
), mock.patch(
|
), mock.patch(
|
||||||
@@ -576,6 +622,12 @@ class EditorLoginTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual("旧标题", result["old_title"])
|
self.assertEqual("旧标题", result["old_title"])
|
||||||
|
open_product.assert_called_once_with(
|
||||||
|
{"debug_port": 9222},
|
||||||
|
"51100639510",
|
||||||
|
on_step=None,
|
||||||
|
bring_to_front=False,
|
||||||
|
)
|
||||||
self.assertTrue(cdp.closed)
|
self.assertTrue(cdp.closed)
|
||||||
close_tab.assert_called_once_with("target-new", host="127.0.0.1:9222")
|
close_tab.assert_called_once_with("target-new", host="127.0.0.1:9222")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user