donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
146 lines • 6.03 kB
JavaScript
;
/**
* @fileoverview Donobu HTML Reporter for Playwright
*
* A Playwright reporter that generates a Donobu-branded HTML report directly,
* without requiring a separate JSON post-processing step.
*
* @usage
* ```ts
* // playwright.config.ts
* import { defineConfig } from 'donobu';
* export default defineConfig({
* reporter: [
* ['donobu/reporter/html', { outputFile: 'test-results/index.html' }],
* ],
* });
* ```
*
* Optionally enrich the report with Donobu AI triage data:
* ```ts
* reporter: [
* ['donobu/reporter/html', {
* outputFile: 'test-results/index.html',
* triageDir: process.env.DONOBU_TRIAGE_DIR,
* }],
* ],
* ```
*
* During an auto-heal rerun (`DONOBU_AUTO_HEAL_ACTIVE=1`) the reporter skips
* HTML generation. It always serializes its `DonobuReport` to a state file in
* the Playwright JSON output directory so the orchestrator can pick it up,
* merge it with the initial run's state, and re-render the HTML once.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = require("fs");
const path_1 = require("path");
const envVars_1 = require("../envVars");
const Logger_1 = require("../utils/Logger");
const buildReport_1 = require("./buildReport");
const render_1 = require("./render");
const stateFile_1 = require("./stateFile");
class DonobuHtmlReporter {
constructor(options = {}) {
/** Accumulates all TestResult objects per TestCase (one per retry attempt). */
this.resultsByTest = new Map();
this.options = options;
}
onBegin(config, _suite) {
this.rootDir = config.rootDir;
}
onTestEnd(test, result) {
const existing = this.resultsByTest.get(test);
if (existing) {
existing.push(result);
}
else {
this.resultsByTest.set(test, [result]);
}
}
async onEnd(_result) {
const outputFile = (0, path_1.resolve)(this.options.outputFile ?? 'test-results/index.html');
const outputDir = (0, path_1.dirname)(outputFile);
const autoHealActive = envVars_1.env.data.DONOBU_AUTO_HEAL_ACTIVE === '1';
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest, this.rootDir);
// Persist the full report + this reporter's output entry to the shared
// state file so other reporters and the auto-heal orchestrator can find
// it. When multiple reporters run in the same process this merges each
// entry in without clobbering siblings.
(0, stateFile_1.mergeStateFileEntry)(envVars_1.env.data.PLAYWRIGHT_JSON_OUTPUT_DIR, report, {
html: { outputFile },
});
// During an auto-heal rerun the orchestrator re-renders the HTML from the
// merged report; writing it here would overwrite the initial HTML with
// incomplete data.
if (autoHealActive) {
return;
}
const triage = this.options.triageDir
? (0, render_1.loadTriageData)((0, path_1.resolve)(this.options.triageDir))
: { plans: [], evidence: [] };
const html = (0, render_1.renderHtml)(report, triage, outputDir);
(0, fs_1.mkdirSync)(outputDir, { recursive: true });
(0, fs_1.writeFileSync)(outputFile, html, 'utf8');
Logger_1.appLogger.info(`Donobu report written to ${outputFile}`);
this.writePerTestStubs(outputFile, outputDir);
}
printsToStdio() {
return false;
}
/**
* Drop a tiny redirect `index.html` into each Playwright-managed per-test
* directory under `outputDir`. The stub points back at the combined report's
* `#?testId=<id>` deep link, giving every test a stable URL inside its own
* directory without duplicating any of the rendered HTML.
*
* Directories are discovered from `dirname(attachment.path)` — Donobu does
* not invent any directory naming or layout. Tests with no attachments (and
* therefore no Playwright-created directory) get no stub.
*/
writePerTestStubs(outputFile, outputDir) {
for (const [test, results] of this.resultsByTest) {
if (!test.id) {
continue;
}
const testDirs = new Set();
for (const result of results) {
for (const att of result.attachments) {
if (!att.path) {
continue;
}
const attDir = (0, path_1.dirname)((0, path_1.resolve)(att.path));
const rel = (0, path_1.relative)(outputDir, attDir);
if (!rel || rel.startsWith('..')) {
// Attachment lives outside the report's output directory — skip;
// we shouldn't be writing into arbitrary paths.
continue;
}
testDirs.add(attDir);
}
}
if (testDirs.size === 0) {
continue;
}
const fileBase = test.location.file.split('/').pop() ?? test.location.file;
const title = `${fileBase} › ${test.title}`;
for (const testDir of testDirs) {
const stubPath = (0, path_1.resolve)(testDir, 'index.html');
const relPathToReport = (0, path_1.relative)(testDir, outputFile);
const stub = (0, render_1.renderPerTestStub)({
testId: test.id,
title,
relPathToReport,
});
try {
(0, fs_1.mkdirSync)(testDir, { recursive: true });
(0, fs_1.writeFileSync)(stubPath, stub, 'utf8');
}
catch (err) {
Logger_1.appLogger.warn(`Failed to write per-test redirect stub at ${stubPath}: ${err.message}`);
}
}
}
}
}
exports.default = DonobuHtmlReporter;
//# sourceMappingURL=html.js.map