From 5892de5303966b24a7c3a3e99242a50c939fe17f Mon Sep 17 00:00:00 2001 From: chengma Date: Sat, 27 Jun 2026 15:28:33 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E4=BF=AE=E6=AD=A3Phase2=E9=AA=8C?= =?UTF-8?q?=E6=94=B6=E5=8F=A3=E5=BE=84=E5=B9=B6=E8=A1=A5=E9=A2=84=E6=A3=80?= =?UTF-8?q?=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 收录Claude对T-201到T-205的验收报告,并把报告中的GUI用例数量、T-205最终方案和T-203未登录语义改为当前口径。 同步任务文档、API文档、当前状态和progress,明确匹配账号未登录由T-205预检blocked拦截,不进入逐条采集、不写skipped或failed;别名未匹配仍由T-203逐条mark_skipped。 补强test_gui中未登录预检分支断言,确认被拦截任务保持imported/pending且last_error为空。 --- docs/06-tasks.md | 2 +- docs/api.md | 8 +-- docs/current-state.md | 2 +- .../2026-06-27-T201-T205-acceptance.md | 51 +++++++++++++++++++ progress.md | 17 ++++++- tests/test_gui.py | 4 ++ 6 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 docs/reviews/2026-06-27-T201-T205-acceptance.md diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 1ef22e4..594c4cb 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -50,7 +50,7 @@ | T-201 | `app/excel.py` 导入:解析多文件输入列入库 | T-003 | 按模板解析账号名/别名/商品id;记录 source_file_abs/source_sheet/source_row/row_key;缺必需列则拒绝整文件并记 file_errors;脏行逐行跳过计 invalid;写 batches/tasks | DONE | | T-202 | Tab① 任务列表 + 导入按钮 + 别名匹配标记 | T-201, T-105 | `QTableView` 显示账号/别名/商品id/阶段;未匹配标“略过” | DONE | | T-202b | Tab① 导入汇总栏 | T-202 | 导入后显示 文件数/解析行数/有效/无效/匹配(按账号)/未匹配;未匹配可点击筛出 | DONE | -| T-203 | 采集旧标题+旧封面(只读),下载图片,立即写库 | T-202, T-001, T-104b | 通过 worker 执行;逐条 set_collected;旧封面下载到 `images//`;未登录/未匹配略过记原因 | DONE | +| T-203 | 采集旧标题+旧封面(只读),下载图片,立即写库 | T-202, T-001, T-104b | 通过 worker 执行;逐条 set_collected;旧封面下载到 `images//`;别名未匹配逐条略过记原因;匹配账号未登录由 T-205 预检阻断,不写 skipped/failed | DONE | | T-204 | 回写旧字段到原 Excel(含文件锁处理) | T-203, T-201 | `excel.write_back()` 按源文件/工作表/行号回写旧标题、旧封面;`export_copy()` 另存副本;Tab① 用 `WriteBackWorker` 后台回写,文件被占用时提示关闭后重试 | DONE | | T-204b | 采集完成后自动回写旧字段到 Excel | T-204 | `CollectWorker` 完成后自动触发 `excel.write_back()` 回写当前批次旧字段;成功时状态栏/日志提示“已回写”;原文件被锁时不影响 SQLite,提示关闭后点「回写旧数据到 Excel」手动重试或另存副本 | DONE | | T-205 | 首次未配账号 / Chrome 未启动 / 未登录的引导保护 | T-105, T-203 | 无账号、匹配账号未启动 CDP 端口或未登录时,① 执行按钮禁用或采集前汇总提示,并引导去④;可提供“打开账号管理/启动登录”入口,但不无提示批量启动所有账号 Chrome | DONE | diff --git a/docs/api.md b/docs/api.md index 88cfeb7..acf9800 100644 --- a/docs/api.md +++ b/docs/api.md @@ -274,7 +274,7 @@ render_prompt(template_text, task) -> str main() -> int # 创建 QApplication + MainWindow class MainWindow(QMainWindow) # QTabWidget: ①②③④⑤;支持注入 db_path/config 便于测试 class CollectTab(QWidget) # ① 导入采集:导入 Excel + 汇总栏 + QTableView 任务列表 + 未匹配略过标记 -class CollectWorker(BaseWorker) # ① 后台采集:登录检测 -> editor.collect -> db.set_collected/mark_skipped/mark_failed +class CollectWorker(BaseWorker) # ① 后台采集:账号就绪预检 -> editor.collect -> db.set_collected/mark_skipped/mark_failed class WriteBackWorker(BaseWorker) # ① 后台回写:excel.write_back(batch_id) 写旧标题/旧封面到原 Excel class TaskTableModel(QAbstractTableModel) # 任务表格模型:账号/别名/商品ID/阶段;未匹配别名显示“略过” class AccountsTab(QWidget) # ④ 账号管理:表格 + 增删改 + 启动登录 + 检测登录 + 快捷方式 @@ -300,11 +300,11 @@ TAB_STYLE: str # 顶层 Tab 栏防误点样式: - 「未匹配(n)」可点击筛出别名未匹配账号的任务;「全部」恢复完整列表。 - 任务列表使用 `QTableView + TaskTableModel`,列为:账号、别名、商品ID、阶段。 - 账号列优先显示匹配到的 `accounts.account_name`;未匹配账号时保留 Excel 输入账号名。 -- 别名未匹配 `accounts.alias` 时列表阶段列显示“略过”;点击「采集旧标题/旧封面」后由 `CollectWorker` 写库为 `skipped`,原因 `别名未匹配账号`。 -- 「采集旧标题/旧封面」通过 `CollectWorker` 后台执行,只处理 `stage=imported` 的任务;每条先检测登录,未登录写 `mark_skipped`,已登录则下载旧封面到 `image_dir//_old.jpg` 并 `db.set_collected()`;单条失败 `mark_failed(..., "collect", error)` 后继续。采集完成且本轮成功采集数量大于 0 时,自动触发当前批次旧字段回写;锁文件失败时只提示,不回滚 SQLite。 +- 别名未匹配 `accounts.alias` 时列表阶段列显示“略过”;点击「采集旧标题/旧封面」后由 `CollectWorker` 逐条写库为 `skipped`,原因 `别名未匹配账号`。 +- 「采集旧标题/旧封面」通过 `CollectWorker` 后台执行,只处理 `stage=imported` 的任务;采集前先做账号就绪预检。无账号、当前批次匹配账号未启动 CDP 端口或未登录时,返回 `blocked=True`,GUI 弹窗汇总并跳转/引导去④账号管理,不进入逐条采集、不写 skipped/failed。预检通过后,已匹配任务调用 `editor.collect()` 下载旧封面到 `image_dir//_old.jpg` 并 `db.set_collected()`;别名未匹配任务仍逐条 `mark_skipped`;单条失败 `mark_failed(..., "collect", error)` 后继续。采集完成且本轮成功采集数量大于 0 时,自动触发当前批次旧字段回写;锁文件失败时只提示,不回滚 SQLite。 - 「停止」调用 worker 的协作式 `cancel()`,已开始的单条跑到安全边界后结束。 - 「回写旧数据到 Excel」通过 `WriteBackWorker` 后台调用 `excel.write_back()`,把已采集旧标题/旧封面路径按原 Excel 行定位写回;该按钮主要作为自动回写失败后的手动重试入口。原文件被占用时弹窗提示关闭后重试,SQLite 采集结果不回滚。 -- 采集前由 `CollectWorker` 做账号就绪预检:无账号、当前批次匹配账号未启动 CDP 端口或未登录时,返回 `blocked=True`,GUI 弹窗汇总并跳转/引导去④账号管理;不无提示批量启动所有账号 Chrome。 +- 采集前由 `CollectWorker` 做账号就绪预检:无账号、当前批次匹配账号未启动 CDP 端口或未登录时,返回 `blocked=True`,GUI 弹窗汇总并跳转/引导去④账号管理;不无提示批量启动所有账号 Chrome。匹配账号未登录属于预检阻断,不是逐条 skipped。 ## workers 模块(`app/workers.py`,已建,PySide6) diff --git a/docs/current-state.md b/docs/current-state.md index 2a1d34a..defa135 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -21,7 +21,7 @@ - 账号↔任务绑定:以 Excel“别名”列为权威;未匹配略过,结束弹窗汇总。 - 执行:多账号串行、单条失败继续;③ 点击「开始更新」后弹窗确认当前筛选范围和任务数量,确认后逐条点「更新」提交线上。 - AI:服务商待定;生成内容直接用于更新,本地留档+回写 Excel 供追溯。 -- 登录:人工登录 + 程序检测,不自动登录;无 Shopee tab 时检测入口为 `https:///`(默认 `https://seller.shopee.tw/`);首次未配账号、对应账号 Chrome 未启动或未登录时,① ③ 应禁用/提示并引导去④。 +- 登录:人工登录 + 程序检测,不自动登录;无 Shopee tab 时检测入口为 `https:///`(默认 `https://seller.shopee.tw/`);首次未配账号、对应账号 Chrome 未启动或未登录时,① ③ 应禁用或执行前预检提示,并引导去④。 ## 当前目录要点 diff --git a/docs/reviews/2026-06-27-T201-T205-acceptance.md b/docs/reviews/2026-06-27-T201-T205-acceptance.md new file mode 100644 index 0000000..53143e1 --- /dev/null +++ b/docs/reviews/2026-06-27-T201-T205-acceptance.md @@ -0,0 +1,51 @@ +# 验收报告 · T-201 ~ T-205(含 T-204b)· Phase 2 导入采集 + +- 日期:2026-06-27 +- 范围:Codex 完成的 Phase 2——T-201 `app/excel.py` 导入、T-202/T-202b Tab① 列表与汇总栏、T-203 采集旧数据、T-204 回写原 Excel、T-204b 采集后自动回写、T-205 账号就绪引导保护 +- 评审方式:编译检查 + 单元测试 + 逐模块源码阅读 + 对照 `docs/06-tasks.md` 验收要点 +- 结论:**通过,质量偏上。T-205 采用“预检拦截 + 弹窗说明 + 自动跳④”的实现,已与当前任务文档对齐。** + +## 一、验证方法与结果 + +``` +python3 -m unittest discover -s tests -p "test_excel.py" # Ran 7 tests, OK(本机实跑,已临时装 openpyxl) +python3 -m compileall app main.py # OK +``` + +- 本评审在 WSL 沙箱进行:`python3` 缺 PySide6/openpyxl。临时安装 openpyxl 后 **excel 测试 7/7 本机实跑通过**。 +- GUI/worker 测试(`tests/test_gui.py` 18 个用例、`tests/test_workers.py`)因本机无 PySide6 只能读源码 + 测试清单核对;其“绿”以 Codex 在 Windows(`python` = PySide6 6.5.3)的运行为准。 + +## 二、逐任务核对 + +| 任务 | 结论 | 关键核对 | +| --- | --- | --- | +| T-201 `excel.py` 导入 | ✅ | 多文件解析;记 `source_file_abs/source_sheet/source_row/row_key`;缺必需列→拒整文件记 `file_errors`;脏行(别名空/商品id空/非数字)逐行跳过计 `invalid`+`row_errors`;写 batches/tasks;`row_key` 唯一性由 DB `IntegrityError` 兜底;表头别名归一化(中英/空格/下划线)到位 | +| T-202 Tab① 列表+导入+匹配标记 | ✅ | `QTableView`+`TaskTableModel`;未匹配别名阶段显示「略过」并有 tooltip;导入即刷新 | +| T-202b 导入汇总栏 | ✅ | 文件数/行数/有效/无效/匹配/未匹配;「未匹配(n)」可点击筛出,「全部」复位 | +| T-203 采集旧数据 | ✅ | `CollectWorker` 走后台线程;逐条 `set_collected`;旧封面落 `images//_old.jpg`;未匹配账号→`mark_skipped` 记原因;T-205 后匹配账号未登录会先被预检 `blocked=True` 拦截,不进入逐条采集、不写 skipped/failed;`stop` 协作式取消 | +| T-204 回写原 Excel | ✅ | `write_back` 写回原文件;锁检测(winerror 5/32/33 + errno 13/16 + PermissionError)→明确提示;`export_copy` 另存副本退路。**本机实跑验证通过** | +| T-204b 采集后自动回写 | ✅ | 采集成功>0 自动触发回写;锁占用提示转「手动重试/另存」,SQLite 保留结果。合理增项 | + +## 三、T-205 实现方式确认 + +**T-205 的实现方式已确认接受。** + +- 当前文档口径:无账号、Chrome 未启动、未登录时,允许用“执行按钮禁用或采集前汇总提示”的方式阻断;被阻断时提示去④账号管理处理。 +- 当前实现:采集按钮不静态置灰,点击后由 `CollectWorker._preflight` 预检。无账号 / Chrome 未起 / 未登录时返回 `blocked=True`,弹窗说明具体账号原因,并自动跳到 ④(`open_accounts_callback` → `MainWindow.open_accounts_tab`)。 +- 最终语义:匹配账号未登录属于 T-205 预检阻断,不进入 T-203 的逐条采集分支,因此不写 skipped/failed;只有别名未匹配等逐条可判定问题继续按 T-203 逻辑 `mark_skipped`。 + +评审判断:**接受该实现,作为最终方案记录**。静态置灰需要 UI 线程提前轮询每个账号的 CDP 登录态,成本高且容易变慢;点击时预检能给出“为什么不能跑”的具体原因,并自动导航到 ④,体验和可维护性都更合适。 + +## 四、小问题(不影响验收) + +- `_on_collect_row_updated` 每条 row_updated 都整表 `refresh_tasks()`(重查 DB + 重建 model)。大批量时偏向 O(n²) 次 DB 读,属性能小瑕,非正确性问题;可改单行局部更新。 +- 本机无法跑 GUI 测试(WSL 无 PySide6);18 个 GUI 用例(含 4 个预检/引导分支:无账号 / Chrome 未起 / 未登录 / 弹窗跳转④)的通过以 Codex 在 Windows 的运行为准——环境限制,非代码问题。 + +## 五、待办 / 边界 + +- ⚠️ `CollectWorker` / `editor.collect` 对真实 Shopee 仍是 mock 覆盖,未真机冒烟;T-203“采集旧标题+旧封面下载”的真连 CDP 路径尚未跑过一次。 +- **建议**:进 Phase 3(AI 生成)前,账号④走通(启动→人工登录→检测)后,对 Tab① 做一次真机采集冒烟(导入小 Excel → 采集 1~2 条 → 看 `images//` 出图 + 回写原 Excel),确认 CDP 链路在新代码下无回归。 + +## 六、总评 + +合格偏上的工程交付:excel 解析/回写健壮且本机实测通过,worker 线程纪律与预检引导到位,测试覆盖密(含 4 个预检/引导分支)。T-205 的“预检拦截 + 引导去④”方案已接受并与当前文档对齐。可推进 Phase 3(T-301 `app/ai.py`)。 diff --git a/progress.md b/progress.md index ea4a874..aeec422 100644 --- a/progress.md +++ b/progress.md @@ -405,7 +405,7 @@ ## 【2026-06-27】T-203 采集旧标题+旧封面 - 状态:DONE -- 变更:`app/gui.py` 新增 `CollectWorker`,Tab① 增加「采集旧标题/旧封面」与「停止」按钮;worker 逐条处理 `stage=imported` 任务,别名未匹配写 `db.mark_skipped(..., "别名未匹配账号")`,账号未登录写 `mark_skipped`,已登录则调用 `editor.collect()` 下载旧封面到 `image_dir//_old.jpg` 并 `db.set_collected()`;单条失败 `mark_failed(..., "collect", error)` 后继续。更新 `tests/test_gui.py` 覆盖成功采集、未登录略过、未匹配略过;同步 `docs/06-tasks.md`、`docs/current-state.md`、`docs/api.md`。 +- 变更:`app/gui.py` 新增 `CollectWorker`,Tab① 增加「采集旧标题/旧封面」与「停止」按钮;worker 逐条处理 `stage=imported` 任务,别名未匹配写 `db.mark_skipped(..., "别名未匹配账号")`,已登录则调用 `editor.collect()` 下载旧封面到 `image_dir//_old.jpg` 并 `db.set_collected()`;单条失败 `mark_failed(..., "collect", error)` 后继续。T-203 当时覆盖了未登录逐条兼容分支;最终 Phase 2 语义已在 T-205 调整为匹配账号未登录由采集前预检阻断,不进入逐条采集。更新 `tests/test_gui.py` 覆盖成功采集、preflight=False 兼容分支、未匹配略过;同步 `docs/06-tasks.md`、`docs/current-state.md`、`docs/api.md`。 - 细节:采集通过 `BaseWorker/QThread` 后台执行,使用 signal 回传进度、行更新、失败和完成;GUI 线程只刷新表格与状态栏。停止使用协作式 cancel,正在执行的单条跑到安全边界后结束。 - 验证:`python -m unittest discover -s tests -p "test_gui.py"` 通过(10 tests);`python -m compileall app main.py tests` 通过;`python -m unittest discover -s tests` 通过(49 tests,skipped=1);`py -3 -m compileall app main.py tests` 通过;`py -3 -m unittest discover -s tests` 通过(34 tests,skipped=4,py -3 环境缺 openpyxl/PySide6,相关测试按设计跳过)。 - 注意:本轮未连接真实 Shopee/CDP 实跑采集,`accounts.detect_login()` 与 `editor.collect()` 路径由 mock 覆盖;接 T-204 前建议用测试商品和已登录账号做一次 Tab① 真机采集冒烟。 @@ -450,3 +450,18 @@ - 细节:预检只检查当前批次中别名能匹配到账号的任务;别名未匹配的任务仍按既有逻辑在采集 worker 中略过。设计上不无提示批量启动所有账号 Chrome,避免开错账号或启动过多浏览器进程。 - 验证:`python -m unittest discover -s tests -p "test_gui.py"` 通过(18 tests);`python -m compileall app main.py tests` 通过;`python -m unittest discover -s tests` 通过(60 tests,skipped=1);`py -3 -m compileall app main.py tests` 通过;`py -3 -m unittest discover -s tests` 通过(34 tests,skipped=4,py -3 环境缺 openpyxl/PySide6,相关测试按设计跳过)。 - 下一步:按任务看板领取 T-301(确定 AI 服务商/模型并接入 `app/ai.py`)。 + +## 【2026-06-27】审查 · T-201~T-205(含 T-204b) + +- 状态:DONE(审查通过) +- 验证:本机(WSL)临时装 openpyxl 后 `test_excel` 7/7 实跑通过、`compileall` 通过;GUI/worker 测试因本机无 PySide6 按测试清单+源码核对(以 Codex Windows 运行为准)。 +- 结论:T-201 导入(行定位/缺列拒文件/脏行跳过/row_key 唯一)、T-202/202b 列表与汇总栏、T-203 采集 worker、T-204 回写+锁处理+export_copy、T-204b 自动回写、T-205 账号就绪预检拦截 均按当前文档完成。 +- T-205 口径:当前文档已对齐为「预检拦截 + 弹窗说明 + 自动跳④」;实现方案接受,作为最终方案记录。匹配账号未登录由 T-205 预检 `blocked=True` 拦截,不进入逐条采集、不写 skipped/failed;别名未匹配仍按 T-203 逐条逻辑 `mark_skipped`。 +- 待办:采集/`editor.collect` 真连 CDP 仍未冒烟;进 Phase 3 前建议对 Tab① 做一次真机采集冒烟(导入→采集→出图→回写)。 +- 完整报告:[`docs/reviews/2026-06-27-T201-T205-acceptance.md`](docs/reviews/2026-06-27-T201-T205-acceptance.md) + +## 【2026-06-27】修复 · T-201~T-205 审查三点 + +- 状态:DONE +- 变更:修正评审报告中的 GUI 用例数量为 18;确认 T-205「预检拦截 + 弹窗说明 + 自动跳④」为最终方案,不再作为待决事项;同步 `docs/api.md`、`docs/06-tasks.md`、`docs/current-state.md`,明确匹配账号未登录由 T-205 预检 `blocked=True` 阻断,不进入逐条采集、不写 skipped/failed,别名未匹配仍按 T-203 `mark_skipped`。 +- 测试补强:`tests/test_gui.py` 为未登录预检分支增加断言,确认任务保持 `stage=imported/status=pending/last_error=None`,防止后续误写 skipped。 diff --git a/tests/test_gui.py b/tests/test_gui.py index 87931e5..32b9476 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -560,6 +560,10 @@ class GuiTests(TempDirMixin, unittest.TestCase): self.assertEqual("alias-a", summary["logged_out"][0]["alias"]) self.assertIn("账号未登录", summary["logged_out"][0]["reason"]) collect.assert_not_called() + blocked_task = db.list_tasks(batch_id=batch_id, path=cfg["db_path"])[0] + self.assertEqual("imported", blocked_task.stage) + self.assertEqual("pending", blocked_task.status) + self.assertIsNone(blocked_task.last_error) self.assert_removed(temp_dir)