修复点击验证误报与相似元素列表全重复等运行时问题

- 点击可信验证增加两类生效旁证: 页面跳转/新开标签页(淘宝搜索按钮),
  点击后元素整体消失(关闭登录弹窗); 此前均被误报为"目标未收到可信点击"
  并在节点超时内反复重试, 造成"点击生效却等很久还报错"
- 相似元素列表引用增加 index 序号: 动态id泛化后各卡片选择器相同,
  46 条引用全部命中第一张卡导致抓取结果全重复;
  修复 NodeList 无 indexOf 的 TypeError 被静默吞掉导致序号从未写入
- 元素定位改为两段式: 先在当前附着页查找, 找不到才跟随前台标签页,
  避免多标签环境下被无条件切到 pages[0] 导致找错页面
- 新开标签页优先复用启动残留的 about:blank 空白页, 不再堆积空标签
- AI 能力输入识别增加 hint_word 字段(此前提示词读到的是空)
- try_catch 捕获子步骤失败时记录 warn 日志, 不再静默
- Excel 落盘遇 EBUSY(文件被打开占用)时自动另存为带时间戳的文件
- 新增 similar-list.test.ts 端到端回归(46 卡片动态 id 真实浏览器验证)
- 新增 export-last-run.js: 从运行记录导出比价 Excel
This commit is contained in:
yu committed 2026-11-05 14:57:33 +08:00
1 parent 47b7a43be2
commit e25121660d
8 files changed
+337 -23

No files matched your search

@@ -152,8 +152,24 @@ register("Bivartable_PivotDf", (_ctx, _node, args) => {
async function exportExcel(source: Table, filePath: string): Promise<string> {
const workbook = new ExcelJS.Workbook(); const sheet = workbook.addWorksheet("Sheet1"); sheet.addRows(source as ExcelJS.CellValue[][]); await fs.mkdir(path.dirname(filePath), { recursive: true }); await workbook.xlsx.writeFile(filePath); return filePath;
}
register(["Bivartable_DfWriteToFile", "Bivartable_DoSave"], async (_ctx, node, args) => {
const filePath = path.resolve(required(args.filePath ?? args.path ?? args.savePath, "Excel路径", node.id, node.componentId)); return exportExcel(table(args.df), filePath);
register(["Bivartable_DfWriteToFile", "Bivartable_DoSave"], async (ctx, node, args) => {
const filePath = path.resolve(required(args.filePath ?? args.path ?? args.savePath, "Excel路径", node.id, node.componentId));
try {
return await exportExcel(table(args.df), filePath);
} catch (error) {
// 目标文件被占用(正被 WPS/Excel 打开等)时,自动另存为带时间戳的文件,不让流程失败
const code = (error as NodeJS.ErrnoException | undefined)?.code;
const message = error instanceof Error ? error.message : String(error);
if (code === "EBUSY" || code === "EPERM" || /EBUSY|resource busy|being used by another/i.test(message)) {
const info = path.parse(filePath);
const stamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, "").replace(/^(\d{8})(\d{6})$/, "$1_$2");
const fallback = path.join(info.dir, `${info.name}_${stamp}${info.ext}`);
await exportExcel(table(args.df), fallback);
ctx.log(`目标文件被占用(可能正被打开),已另存为:${fallback}`, "warn", node.id);
return fallback;
}
throw error;
}
});
register("Bivartable_DoOpenExcel", async (_ctx, node, args) => {
const filePath = path.resolve(required(args.filePath, "Excel路径", node.id, node.componentId)); const workbook = new ExcelJS.Workbook(); await workbook.xlsx.readFile(filePath); const sheet = workbook.getWorksheet(String(args.sheetName || "Sheet1")) || workbook.worksheets[0]; return sheet.getSheetValues().slice(1).map((row) => Array.isArray(row) ? row.slice(1).map((cell) => cell ?? "") : []);