playwright-archive
Version:
A lightweight CLI tool to archive and serve your Playwright test run history with a web interface. Useful for CI environments and local development.
168 lines (142 loc) • 5.98 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const RunResultsGenerator = require('./RunResultsGenerator');
class JsonReportParser {
constructor(runPath = process.cwd()) {
this.runPath = runPath;
this.data = null;
}
async getData(config) {
try {
// Try these locations in order:
// 1. metadata.json in the run directory
// 2. pw-archive.json in test-results relative to cwd
const metadataPath = path.join(this.runPath, "metadata.json");
const archivePath = path.resolve("test-results", "pw-archive.json");
try {
if (await fs.pathExists(metadataPath)) {
this.data = await fs.readJson(metadataPath);
console.log("Successfully loaded metadata from:", metadataPath);
} else if (await fs.pathExists(archivePath)) {
this.data = await fs.readJson(archivePath);
console.log("Successfully loaded archive from:", archivePath);
} else {
throw new Error(`No metadata found in ${metadataPath} or ${archivePath}`);
}
} catch (error) {
console.warn(`Warning: Could not load metadata:`, error.message);
this.data = {
config: {
metadata: {},
projects: []
},
stats: {},
suites: []
};
}
// Ensure config is an object
const safeConfig = config || {};
// Generate results file if configured
if (safeConfig?.runResults?.enabled) {
try {
const generator = new RunResultsGenerator(this.data, safeConfig);
await generator.generate();
} catch (error) {
console.warn("Warning: Failed to generate run results:", error.message);
}
}
const result = {};
const hideMetrics = Array.isArray(safeConfig?.display?.hideMetrics) ? safeConfig.display.hideMetrics : [];
const hideTestNames = Boolean(safeConfig?.display?.hideTestNames);
// Workers count
if (!hideMetrics.includes('workers')) {
result.actualWorkers = this.data?.config?.metadata?.actualWorkers || "Not defined";
}
// Projects info
if (!hideMetrics.includes('projects')) {
// Ensure projects is an array
const projects = Array.isArray(this.data?.config?.projects) ? this.data.config.projects : [];
result.projects = {
quantity: projects.length,
names: projects
.map(p => (p && typeof p.name === 'string') ? p.name : "Unknown")
.filter(Boolean)
};
// Only include projects section if we have actual data
if (result.projects.quantity === 0 && result.projects.names.length === 0) {
delete result.projects;
}
}
// Test names
if (!hideTestNames) {
const suites = Array.isArray(this.data?.suites) ? this.data.suites : [];
result.suites = {
failedNames: [],
passedNames: []
};
suites.forEach(suite => {
if (!suite || !Array.isArray(suite.specs)) return;
const title = (suite.title && typeof suite.title === 'string') ? suite.title : "Unknown Suite";
const specs = Array.isArray(suite.specs) ? suite.specs : [];
const hasFailedSpecs = specs.some(spec => spec && spec.ok === false);
const allSpecsPassed = specs.length > 0 && specs.every(spec => spec && spec.ok === true);
if (hasFailedSpecs) {
result.suites.failedNames.push(title);
}
if (allSpecsPassed) {
result.suites.passedNames.push(title);
}
});
// Only include suites if we have any names
if (result.suites.failedNames.length === 0 && result.suites.passedNames.length === 0) {
delete result.suites;
}
}
// Test statistics
const stats = this.data?.stats || {};
result.stats = {};
if (!hideMetrics.includes('duration')) {
result.stats.duration = typeof stats.duration === 'number' ? stats.duration : 0;
}
if (!hideMetrics.includes('totalTests')) {
// Ensure we're dealing with numbers
const expected = typeof stats.expected === 'number' ? stats.expected : 0;
const unexpected = typeof stats.unexpected === 'number' ? stats.unexpected : 0;
const skipped = typeof stats.skipped === 'number' ? stats.skipped : 0;
result.stats.quantity = expected + unexpected + skipped;
}
if (!hideMetrics.includes('passed')) {
result.stats.passedQuantity = typeof stats.expected === 'number' ? stats.expected : 0;
}
if (!hideMetrics.includes('failed')) {
result.stats.failedQuantity = typeof stats.unexpected === 'number' ? stats.unexpected : 0;
}
if (!hideMetrics.includes('skipped')) {
result.stats.skippedQuantity = typeof stats.skipped === 'number' ? stats.skipped : 0;
}
if (!hideMetrics.includes('flaky')) {
result.stats.flakyQuantity = typeof stats.flaky === 'number' ? stats.flaky : 0;
}
// Only include stats if we have any non-zero values
if (Object.values(result.stats).every(value => value === 0)) {
delete result.stats;
}
return result;
} catch (error) {
console.error("Error processing test metadata:", error);
// Return minimal valid structure in case of errors
return {
actualWorkers: "Not available",
stats: {
duration: 0,
quantity: 0,
passedQuantity: 0,
failedQuantity: 0,
skippedQuantity: 0,
flakyQuantity: 0
}
};
}
}
}
module.exports = JsonReportParser;