cents/tests/e2e/报告器/中文失败报告器.ts

64 lines
2.9 KiB
TypeScript

import type { FullConfig, FullResult, Reporter, TestCase, TestResult } from "@playwright/test/reporter";
import fs from "node:fs";
import path from "node:path";
type Options = { runId: string };
function safeName(value: string) {
return value.replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, "-").slice(0, 100);
}
function relativeTestPath(test: TestCase) {
const file = test.location.file.replace(/\\/g, "/");
const marker = "/tests/e2e/";
const index = file.lastIndexOf(marker);
return index >= 0 ? file.slice(index + marker.length) : path.basename(file);
}
export default class ChineseFailureReporter implements Reporter {
private readonly options: Options;
private config!: FullConfig;
private failed = 0;
constructor(options: Options) {
this.options = options;
}
onConfigure(config: FullConfig) {
this.config = config;
fs.mkdirSync(path.join(projectRoot(config), "test-reports", this.options.runId, "failures"), { recursive: true });
}
onBegin(config: FullConfig) {
this.config = config;
fs.mkdirSync(path.join(projectRoot(config), "test-reports", this.options.runId, "failures"), { recursive: true });
}
onTestEnd(test: TestCase, result: TestResult) {
if (result.status === "passed" || result.status === "skipped") return;
this.failed += 1;
const testFile = relativeTestPath(test).replace(/\.ts$/, "");
const target = path.join(projectRoot(this.config), "test-reports", this.options.runId, "failures", testFile, safeName(test.title));
fs.mkdirSync(target, { recursive: true });
const attachments: string[] = [];
for (const attachment of result.attachments) {
if (!attachment.path) continue;
const targetPath = path.join(target, `${safeName(attachment.name)}${path.extname(attachment.path)}`);
fs.copyFileSync(attachment.path, targetPath);
attachments.push(path.relative(projectRoot(this.config), targetPath));
}
const errors = result.errors.map((error) => error.message ?? error.stack ?? "未知错误").join("\n\n");
fs.writeFileSync(path.join(target, "失败说明.md"), `# ${test.title}\n\n- 文件:\`${relativeTestPath(test)}\`\n- 项目:${test.parent.project()?.name ?? "未知"}\n- 重试次数:${result.retry}\n- 状态:${result.status}\n\n## 失败原因\n\n${errors || "未捕获到错误文本,请查看 trace。"}\n\n## 附件\n\n${attachments.length ? attachments.map((item) => `- \`${item}\``).join("\n") : "无"}\n`, "utf8");
}
onEnd(result: FullResult) {
const root = path.join(projectRoot(this.config), "test-reports", this.options.runId);
fs.mkdirSync(root, { recursive: true });
fs.writeFileSync(path.join(root, "SUMMARY.md"), `# 第 ${this.options.runId} 轮测试\n\n- 总体状态:${result.status}\n- 失败测试数:${this.failed}\n- 失败详情:${this.failed ? "见 failures/ 目录" : "无"}\n`, "utf8");
}
}
function projectRoot(config: FullConfig) {
return path.resolve(config.rootDir, "../..");
}