106 行
4.1 KiB
TypeScript
106 行
4.1 KiB
TypeScript
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
|
|
import { expect, test } from "../helpers/fixtures";
|
|
import { EMPLOYEE, login } from "../helpers/auth";
|
|
import { openInvoicePool, resolveLiveOcrUser, uploadLiveInvoiceAndWait, type LiveInvoiceOcrResult } from "../helpers/liveInvoiceOcr";
|
|
|
|
type BatchResult = LiveInvoiceOcrResult & {
|
|
filePath: string;
|
|
fileName: string;
|
|
};
|
|
|
|
const rootDir = path.resolve(process.cwd(), "..");
|
|
const reportPath = path.join(rootDir, "artifacts/r70/live-ocr-batch-report.md");
|
|
|
|
function resolveInvoiceFiles() {
|
|
const files = new Set<string>();
|
|
const directFile = process.env.PLAYWRIGHT_OCR_LIVE_FILE?.trim();
|
|
const batchFiles = process.env.PLAYWRIGHT_OCR_LIVE_FILES?.split(/[\n,]/).map((item) => item.trim()).filter(Boolean) ?? [];
|
|
const batchDir = process.env.PLAYWRIGHT_OCR_LIVE_DIR?.trim() ?? "";
|
|
|
|
if (directFile && /\.pdf$/i.test(directFile)) {
|
|
files.add(path.resolve(directFile));
|
|
}
|
|
|
|
for (const file of batchFiles) {
|
|
if (!/\.pdf$/i.test(file)) continue;
|
|
files.add(path.resolve(file));
|
|
}
|
|
|
|
if (batchDir && existsSync(batchDir) && statSync(batchDir).isDirectory()) {
|
|
for (const entry of readdirSync(batchDir).sort((left, right) => left.localeCompare(right, "zh-CN"))) {
|
|
const fullPath = path.join(batchDir, entry);
|
|
if (!statSync(fullPath).isFile()) continue;
|
|
if (!/\.pdf$/i.test(entry)) continue;
|
|
files.add(path.resolve(fullPath));
|
|
}
|
|
}
|
|
|
|
return [...files].filter((filePath) => existsSync(filePath));
|
|
}
|
|
|
|
function writeReport(results: BatchResult[]) {
|
|
mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
|
|
const lines = [
|
|
"# R70 批量 live OCR 辅助复核报告",
|
|
"",
|
|
`生成时间:${new Date().toISOString()}`,
|
|
"",
|
|
"## 结果",
|
|
"",
|
|
`- 总文件数:${results.length}`,
|
|
`- 成功:${results.filter((item) => item.status === "available").length}`,
|
|
`- 失败:${results.filter((item) => item.status === "ocr_failed").length}`,
|
|
"",
|
|
"## 明细",
|
|
"",
|
|
"| 文件 | 状态 | 耗时(ms) | 票据ID | 发票代码 | 发票号码 | 销售方 | 开票日期 | 备注 |",
|
|
"| --- | --- | ---: | --- | --- | --- | --- | --- | --- |",
|
|
...results.map((item) => {
|
|
const remark = item.status === "ocr_failed" ? (item.bannerText || item.detailText).replaceAll("|", "\\|") : "ok";
|
|
return `| ${item.fileName} | ${item.status} | ${item.durationMs} | ${item.invoiceId ?? "-"} | ${item.invoiceCode ?? "-"} | ${item.invoiceNumber ?? "-"} | ${item.sellerName ?? "-"} | ${item.invoiceDate ?? "-"} | ${remark} |`;
|
|
}),
|
|
"",
|
|
"## 说明",
|
|
"",
|
|
"- 本报告用于真实腾讯 OCR 的本地辅助复核,不进入常规本地回归。",
|
|
"- 如某张票失败,会继续处理后续文件,并在本报告统一汇总。",
|
|
];
|
|
|
|
writeFileSync(reportPath, `${lines.join("\n")}\n`, "utf-8");
|
|
}
|
|
|
|
test("手工 live 腾讯 OCR 批量辅助复核:真实票样逐张上传并输出报告", async ({ page }) => {
|
|
test.skip(process.env.PLAYWRIGHT_MANUAL !== "1", "仅在显式手工 Playwright 模式下执行");
|
|
test.skip(process.env.PLAYWRIGHT_OCR_LIVE !== "1", "仅在显式 live OCR 辅助复核模式下执行");
|
|
|
|
const invoiceFiles = resolveInvoiceFiles();
|
|
test.skip(!invoiceFiles.length, "需要提供 PDF 格式的 PLAYWRIGHT_OCR_LIVE_FILE / PLAYWRIGHT_OCR_LIVE_FILES / PLAYWRIGHT_OCR_LIVE_DIR");
|
|
|
|
const results: BatchResult[] = [];
|
|
const liveOcrUser = resolveLiveOcrUser(EMPLOYEE);
|
|
await login(page, liveOcrUser);
|
|
|
|
try {
|
|
await openInvoicePool(page);
|
|
|
|
for (const invoiceFilePath of invoiceFiles) {
|
|
await openInvoicePool(page);
|
|
const result = await uploadLiveInvoiceAndWait(page, invoiceFilePath);
|
|
results.push({
|
|
...result,
|
|
filePath: invoiceFilePath,
|
|
fileName: path.basename(invoiceFilePath),
|
|
});
|
|
}
|
|
} finally {
|
|
writeReport(results);
|
|
await page.context().clearCookies().catch(() => undefined);
|
|
}
|
|
|
|
const failures = results.filter((item) => item.status === "ocr_failed");
|
|
expect(failures, JSON.stringify(failures, null, 2)).toHaveLength(0);
|
|
});
|