@testdino/playwright 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -6
- package/dist/cli/index.js +46 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +46 -3
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +254 -3
- package/dist/index.d.ts +254 -3
- package/dist/index.js +591 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +583 -52
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -1
package/dist/index.js
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
3
5
|
var crypto = require('crypto');
|
|
4
6
|
var fs = require('fs');
|
|
5
7
|
var WebSocket = require('ws');
|
|
6
8
|
var axios = require('axios');
|
|
9
|
+
var path = require('path');
|
|
7
10
|
var promises = require('fs/promises');
|
|
8
11
|
var execa = require('execa');
|
|
9
12
|
var os = require('os');
|
|
10
13
|
var process$1 = require('process');
|
|
11
|
-
var
|
|
14
|
+
var istanbulCoverage = require('istanbul-lib-coverage');
|
|
15
|
+
var picomatch = require('picomatch');
|
|
16
|
+
var istanbulLibReport = require('istanbul-lib-report');
|
|
17
|
+
var istanbulReports = require('istanbul-reports');
|
|
18
|
+
var test$1 = require('@playwright/test');
|
|
19
|
+
var chalk = require('chalk');
|
|
12
20
|
|
|
13
21
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
22
|
|
|
15
23
|
var WebSocket__default = /*#__PURE__*/_interopDefault(WebSocket);
|
|
16
24
|
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
25
|
+
var istanbulCoverage__default = /*#__PURE__*/_interopDefault(istanbulCoverage);
|
|
26
|
+
var picomatch__default = /*#__PURE__*/_interopDefault(picomatch);
|
|
27
|
+
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
17
28
|
|
|
18
29
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
19
30
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -373,8 +384,12 @@ var WebSocketClient = class {
|
|
|
373
384
|
}
|
|
374
385
|
}
|
|
375
386
|
};
|
|
376
|
-
|
|
377
|
-
|
|
387
|
+
function normalizePath(filePath, rootDir) {
|
|
388
|
+
if (rootDir && filePath.startsWith(rootDir)) {
|
|
389
|
+
return path.relative(rootDir, filePath);
|
|
390
|
+
}
|
|
391
|
+
return filePath;
|
|
392
|
+
}
|
|
378
393
|
function sleep(ms) {
|
|
379
394
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
380
395
|
}
|
|
@@ -1521,7 +1536,7 @@ var PlaywrightMetadataCollector = class extends BaseMetadataCollector {
|
|
|
1521
1536
|
const skeletonSuite = {
|
|
1522
1537
|
title: childSuite.title,
|
|
1523
1538
|
type: childSuite.type === "file" ? "file" : "describe",
|
|
1524
|
-
tests: childSuite.tests.map((
|
|
1539
|
+
tests: childSuite.tests.map((test2) => this.buildSkeletonTest(test2))
|
|
1525
1540
|
};
|
|
1526
1541
|
if (childSuite.type === "file" && childSuite.location) {
|
|
1527
1542
|
skeletonSuite.file = childSuite.location.file;
|
|
@@ -1543,24 +1558,24 @@ var PlaywrightMetadataCollector = class extends BaseMetadataCollector {
|
|
|
1543
1558
|
/**
|
|
1544
1559
|
* Build skeleton test from TestCase
|
|
1545
1560
|
*/
|
|
1546
|
-
buildSkeletonTest(
|
|
1561
|
+
buildSkeletonTest(test2) {
|
|
1547
1562
|
const skeletonTest = {
|
|
1548
|
-
testId:
|
|
1549
|
-
title:
|
|
1563
|
+
testId: test2.id,
|
|
1564
|
+
title: test2.title,
|
|
1550
1565
|
location: {
|
|
1551
|
-
file:
|
|
1552
|
-
line:
|
|
1553
|
-
column:
|
|
1566
|
+
file: test2.location.file,
|
|
1567
|
+
line: test2.location.line,
|
|
1568
|
+
column: test2.location.column
|
|
1554
1569
|
}
|
|
1555
1570
|
};
|
|
1556
|
-
if (
|
|
1557
|
-
skeletonTest.tags =
|
|
1571
|
+
if (test2.tags && test2.tags.length > 0) {
|
|
1572
|
+
skeletonTest.tags = test2.tags;
|
|
1558
1573
|
}
|
|
1559
|
-
if (
|
|
1560
|
-
skeletonTest.expectedStatus =
|
|
1574
|
+
if (test2.expectedStatus) {
|
|
1575
|
+
skeletonTest.expectedStatus = test2.expectedStatus;
|
|
1561
1576
|
}
|
|
1562
|
-
if (
|
|
1563
|
-
skeletonTest.annotations =
|
|
1577
|
+
if (test2.annotations && test2.annotations.length > 0) {
|
|
1578
|
+
skeletonTest.annotations = test2.annotations.map((ann) => ({
|
|
1564
1579
|
type: ann.type,
|
|
1565
1580
|
description: ann.description
|
|
1566
1581
|
}));
|
|
@@ -1931,8 +1946,326 @@ var ArtifactUploader = class {
|
|
|
1931
1946
|
return this.sasToken.uniqueId;
|
|
1932
1947
|
}
|
|
1933
1948
|
};
|
|
1934
|
-
|
|
1935
|
-
|
|
1949
|
+
function toIstanbulMapData(map) {
|
|
1950
|
+
return map;
|
|
1951
|
+
}
|
|
1952
|
+
function fromIstanbulMapData(data) {
|
|
1953
|
+
return data;
|
|
1954
|
+
}
|
|
1955
|
+
var CoverageMerger = class {
|
|
1956
|
+
coverageMap = istanbulCoverage__default.default.createCoverageMap({});
|
|
1957
|
+
hasData = false;
|
|
1958
|
+
includePatterns;
|
|
1959
|
+
excludePatterns;
|
|
1960
|
+
onError;
|
|
1961
|
+
constructor(options) {
|
|
1962
|
+
this.includePatterns = options?.include;
|
|
1963
|
+
this.excludePatterns = options?.exclude;
|
|
1964
|
+
this.onError = options?.onError;
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Merge a coverage fragment from a completed test.
|
|
1968
|
+
* The fragment is merged directly and can be GC'd immediately.
|
|
1969
|
+
*/
|
|
1970
|
+
addFragment(fragment) {
|
|
1971
|
+
if (!fragment.istanbul) return;
|
|
1972
|
+
try {
|
|
1973
|
+
const filtered = this.filterCoverageMap(fragment.istanbul);
|
|
1974
|
+
this.coverageMap.merge(toIstanbulMapData(filtered));
|
|
1975
|
+
if (this.coverageMap.files().length > 0) {
|
|
1976
|
+
this.hasData = true;
|
|
1977
|
+
}
|
|
1978
|
+
} catch (error) {
|
|
1979
|
+
const msg = `[TestDino] Failed to merge coverage fragment: ${error instanceof Error ? error.message : String(error)}`;
|
|
1980
|
+
if (this.onError) {
|
|
1981
|
+
this.onError(msg);
|
|
1982
|
+
} else {
|
|
1983
|
+
console.warn(msg);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Filter files from a coverage map based on include/exclude glob patterns.
|
|
1989
|
+
*/
|
|
1990
|
+
filterCoverageMap(coverageMap) {
|
|
1991
|
+
const hasInclude = this.includePatterns && this.includePatterns.length > 0;
|
|
1992
|
+
const hasExclude = this.excludePatterns && this.excludePatterns.length > 0;
|
|
1993
|
+
if (!hasInclude && !hasExclude) return coverageMap;
|
|
1994
|
+
const isExcluded = hasExclude ? picomatch__default.default(this.excludePatterns) : void 0;
|
|
1995
|
+
const isIncluded = hasInclude ? picomatch__default.default(this.includePatterns) : void 0;
|
|
1996
|
+
const filtered = {};
|
|
1997
|
+
for (const [filePath, fileCoverage] of Object.entries(coverageMap)) {
|
|
1998
|
+
if (isExcluded && isExcluded(filePath)) {
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
if (isIncluded && !isIncluded(filePath)) {
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
2004
|
+
filtered[filePath] = fileCoverage;
|
|
2005
|
+
}
|
|
2006
|
+
return filtered;
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Whether any coverage data has been collected.
|
|
2010
|
+
*/
|
|
2011
|
+
get hasCoverage() {
|
|
2012
|
+
return this.hasData;
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* Compute aggregate summary metrics from the merged coverage map.
|
|
2016
|
+
*/
|
|
2017
|
+
computeSummary() {
|
|
2018
|
+
const globalSummary = this.coverageMap.getCoverageSummary();
|
|
2019
|
+
return {
|
|
2020
|
+
lines: extractMetric(globalSummary.lines),
|
|
2021
|
+
branches: extractMetric(globalSummary.branches),
|
|
2022
|
+
functions: extractMetric(globalSummary.functions),
|
|
2023
|
+
statements: extractMetric(globalSummary.statements)
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Compute per-file coverage metrics.
|
|
2028
|
+
* Normalizes file paths to be relative to git root.
|
|
2029
|
+
*/
|
|
2030
|
+
computeFileCoverage(gitRoot) {
|
|
2031
|
+
const root = gitRoot || process.cwd();
|
|
2032
|
+
return this.coverageMap.files().map((filePath) => {
|
|
2033
|
+
const fileCoverage = this.coverageMap.fileCoverageFor(filePath);
|
|
2034
|
+
const fileSummary = fileCoverage.toSummary();
|
|
2035
|
+
const normalizedPath = normalizePath(filePath, root);
|
|
2036
|
+
return {
|
|
2037
|
+
path: normalizedPath,
|
|
2038
|
+
lines: extractMetric(fileSummary.lines),
|
|
2039
|
+
branches: extractMetric(fileSummary.branches),
|
|
2040
|
+
functions: extractMetric(fileSummary.functions),
|
|
2041
|
+
statements: extractMetric(fileSummary.statements)
|
|
2042
|
+
};
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Get the raw merged coverage map (for local report generation or compact extraction).
|
|
2047
|
+
*/
|
|
2048
|
+
getRawCoverageMap() {
|
|
2049
|
+
return this.coverageMap;
|
|
2050
|
+
}
|
|
2051
|
+
/**
|
|
2052
|
+
* Get the merged coverage map as a plain JSON object.
|
|
2053
|
+
*/
|
|
2054
|
+
toJSON() {
|
|
2055
|
+
return fromIstanbulMapData(this.coverageMap.toJSON());
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
function extractMetric(metric) {
|
|
2059
|
+
return {
|
|
2060
|
+
total: metric.total,
|
|
2061
|
+
covered: metric.covered,
|
|
2062
|
+
pct: metric.pct
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
function extractCompactCounts(coverageMapJSON, gitRoot) {
|
|
2066
|
+
const files = {};
|
|
2067
|
+
let fileCount = 0;
|
|
2068
|
+
for (const [filePath, fileCoverage] of Object.entries(coverageMapJSON)) {
|
|
2069
|
+
const normalizedPath = normalizePath(filePath, gitRoot);
|
|
2070
|
+
files[normalizedPath] = {
|
|
2071
|
+
s: fileCoverage.s,
|
|
2072
|
+
f: fileCoverage.f,
|
|
2073
|
+
b: fileCoverage.b,
|
|
2074
|
+
totals: {
|
|
2075
|
+
s: Object.keys(fileCoverage.statementMap || {}).length,
|
|
2076
|
+
f: Object.keys(fileCoverage.fnMap || {}).length,
|
|
2077
|
+
b: countBranchPaths(fileCoverage.branchMap || {})
|
|
2078
|
+
},
|
|
2079
|
+
shapeHash: computeShapeHash(fileCoverage)
|
|
2080
|
+
};
|
|
2081
|
+
fileCount++;
|
|
2082
|
+
}
|
|
2083
|
+
return { files, fileCount };
|
|
2084
|
+
}
|
|
2085
|
+
function countBranchPaths(branchMap) {
|
|
2086
|
+
let total = 0;
|
|
2087
|
+
for (const branch of Object.values(branchMap)) {
|
|
2088
|
+
total += (branch.locations || []).length;
|
|
2089
|
+
}
|
|
2090
|
+
return total;
|
|
2091
|
+
}
|
|
2092
|
+
function computeShapeHash(fileCoverage) {
|
|
2093
|
+
const branchMap = fileCoverage.branchMap || {};
|
|
2094
|
+
const shape = {
|
|
2095
|
+
s: Object.keys(fileCoverage.statementMap || {}).length,
|
|
2096
|
+
f: Object.keys(fileCoverage.fnMap || {}).length,
|
|
2097
|
+
b: countBranchPaths(branchMap),
|
|
2098
|
+
bp: Object.values(branchMap).map((b) => (b.locations || []).length)
|
|
2099
|
+
};
|
|
2100
|
+
return crypto.createHash("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 12);
|
|
2101
|
+
}
|
|
2102
|
+
async function generateIstanbulHtmlReport(coverageMerger, options) {
|
|
2103
|
+
await promises.rm(options.outputDir, { recursive: true, force: true }).catch(() => {
|
|
2104
|
+
});
|
|
2105
|
+
await promises.mkdir(options.outputDir, { recursive: true });
|
|
2106
|
+
const coverageMap = coverageMerger.getRawCoverageMap();
|
|
2107
|
+
const context = istanbulLibReport.createContext({
|
|
2108
|
+
dir: options.outputDir,
|
|
2109
|
+
watermarks: {
|
|
2110
|
+
statements: [50, 80],
|
|
2111
|
+
functions: [50, 80],
|
|
2112
|
+
branches: [50, 80],
|
|
2113
|
+
lines: [50, 80]
|
|
2114
|
+
},
|
|
2115
|
+
coverageMap
|
|
2116
|
+
});
|
|
2117
|
+
const reporter = istanbulReports.create("html", {
|
|
2118
|
+
skipEmpty: false,
|
|
2119
|
+
subdir: ""
|
|
2120
|
+
});
|
|
2121
|
+
reporter.execute(context);
|
|
2122
|
+
return `${options.outputDir}/index.html`;
|
|
2123
|
+
}
|
|
2124
|
+
var COVERAGE_EXTRACT_TIMEOUT_MS = 3e4;
|
|
2125
|
+
async function extractCoverageFromPage(page, timeoutMs = COVERAGE_EXTRACT_TIMEOUT_MS) {
|
|
2126
|
+
return Promise.race([
|
|
2127
|
+
page.evaluate(() => globalThis.__coverage__ ?? null),
|
|
2128
|
+
new Promise((resolve) => setTimeout(() => resolve(null), timeoutMs))
|
|
2129
|
+
]).catch(() => null);
|
|
2130
|
+
}
|
|
2131
|
+
async function attachCoverageToTestInfo(testInfo, coverage) {
|
|
2132
|
+
const fragment = {
|
|
2133
|
+
istanbul: coverage
|
|
2134
|
+
};
|
|
2135
|
+
await testInfo.attach("testdino-coverage", {
|
|
2136
|
+
body: JSON.stringify(fragment),
|
|
2137
|
+
contentType: "application/json"
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
var coverageFixtures = {
|
|
2141
|
+
_testdinoCoverage: [
|
|
2142
|
+
async ({ page }, use, testInfo) => {
|
|
2143
|
+
await use();
|
|
2144
|
+
const istanbulCoverage2 = await extractCoverageFromPage(page);
|
|
2145
|
+
if (istanbulCoverage2) {
|
|
2146
|
+
await attachCoverageToTestInfo(testInfo, istanbulCoverage2);
|
|
2147
|
+
}
|
|
2148
|
+
},
|
|
2149
|
+
{ auto: true }
|
|
2150
|
+
]
|
|
2151
|
+
};
|
|
2152
|
+
var test = test$1.test.extend(
|
|
2153
|
+
coverageFixtures
|
|
2154
|
+
);
|
|
2155
|
+
function stripAnsi(str) {
|
|
2156
|
+
return str.replace(/\u001b\[[0-9;]*m/g, "");
|
|
2157
|
+
}
|
|
2158
|
+
function pad(str, len) {
|
|
2159
|
+
const vLen = stripAnsi(str).length;
|
|
2160
|
+
return vLen >= len ? str : str + " ".repeat(len - vLen);
|
|
2161
|
+
}
|
|
2162
|
+
function padStart(str, len) {
|
|
2163
|
+
const vLen = stripAnsi(str).length;
|
|
2164
|
+
return vLen >= len ? str : " ".repeat(len - vLen) + str;
|
|
2165
|
+
}
|
|
2166
|
+
function formatDuration(ms) {
|
|
2167
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
2168
|
+
const seconds = ms / 1e3;
|
|
2169
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
2170
|
+
const minutes = Math.floor(seconds / 60);
|
|
2171
|
+
const remainingSeconds = (seconds % 60).toFixed(0);
|
|
2172
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
2173
|
+
}
|
|
2174
|
+
function shortenPath(filePath) {
|
|
2175
|
+
const markers = ["/src/", "/lib/", "/app/"];
|
|
2176
|
+
for (const marker of markers) {
|
|
2177
|
+
const idx = filePath.indexOf(marker);
|
|
2178
|
+
if (idx !== -1) return filePath.slice(idx + 1);
|
|
2179
|
+
}
|
|
2180
|
+
const parts = filePath.split("/");
|
|
2181
|
+
return parts.length > 2 ? parts.slice(-2).join("/") : filePath;
|
|
2182
|
+
}
|
|
2183
|
+
function colorPct(pct) {
|
|
2184
|
+
const color = pct >= 80 ? chalk__default.default.green : pct >= 50 ? chalk__default.default.yellow : chalk__default.default.red;
|
|
2185
|
+
return color(pct === 100 ? "100%" : `${pct.toFixed(1)}%`);
|
|
2186
|
+
}
|
|
2187
|
+
function printCoverageTable(event) {
|
|
2188
|
+
const { summary, files } = event;
|
|
2189
|
+
const row = (content) => ` ${chalk__default.default.dim("\u2502")} ${content}`;
|
|
2190
|
+
const nameW = 30;
|
|
2191
|
+
const colW = 10;
|
|
2192
|
+
console.log(row(`${chalk__default.default.bold("Coverage")} ${chalk__default.default.dim(`${files.length} files`)}`));
|
|
2193
|
+
console.log(row(""));
|
|
2194
|
+
console.log(
|
|
2195
|
+
row(
|
|
2196
|
+
` ${chalk__default.default.dim(`${pad("File", nameW)}${padStart("Stmts", colW)}${padStart("Branch", colW)}${padStart("Funcs", colW)}${padStart("Lines", colW)}`)}`
|
|
2197
|
+
)
|
|
2198
|
+
);
|
|
2199
|
+
for (const file of files) {
|
|
2200
|
+
const name = shortenPath(file.path);
|
|
2201
|
+
const short = name.length > nameW ? name.slice(0, nameW - 1) + "~" : name;
|
|
2202
|
+
console.log(
|
|
2203
|
+
row(
|
|
2204
|
+
` ${pad(short, nameW)}` + padStart(colorPct(file.statements.pct), colW) + padStart(colorPct(file.branches.pct), colW) + padStart(colorPct(file.functions.pct), colW) + padStart(colorPct(file.lines.pct), colW)
|
|
2205
|
+
)
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
console.log(row(` ${chalk__default.default.dim("\u2500".repeat(nameW + colW * 4))}`));
|
|
2209
|
+
console.log(
|
|
2210
|
+
row(
|
|
2211
|
+
` ${chalk__default.default.bold(pad("All files", nameW))}` + padStart(colorPct(summary.statements.pct), colW) + padStart(colorPct(summary.branches.pct), colW) + padStart(colorPct(summary.functions.pct), colW) + padStart(colorPct(summary.lines.pct), colW)
|
|
2212
|
+
)
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2215
|
+
function printRunSummary(result, streamingSuccess, data) {
|
|
2216
|
+
const W = 72;
|
|
2217
|
+
const topBorder = ` ${chalk__default.default.dim(`\u250C${"\u2500".repeat(W)}\u2510`)}`;
|
|
2218
|
+
const bottomBorder = ` ${chalk__default.default.dim(`\u2514${"\u2500".repeat(W)}\u2518`)}`;
|
|
2219
|
+
const divider = ` ${chalk__default.default.dim(`\u251C${"\u2500".repeat(W)}\u2524`)}`;
|
|
2220
|
+
const row = (content) => ` ${chalk__default.default.dim("\u2502")} ${content}`;
|
|
2221
|
+
console.log("");
|
|
2222
|
+
console.log(topBorder);
|
|
2223
|
+
console.log(row(chalk__default.default.bold("TestDino Run Summary")));
|
|
2224
|
+
console.log(divider);
|
|
2225
|
+
console.log(row(`${chalk__default.default.dim("Run")} ${data.runId}`));
|
|
2226
|
+
const git = data.runMetadata?.git;
|
|
2227
|
+
if (git?.branch || git?.commit?.hash) {
|
|
2228
|
+
const branch = git.branch ? chalk__default.default.cyan(git.branch) : "";
|
|
2229
|
+
const sha = git.commit?.hash ? chalk__default.default.dim(git.commit.hash.slice(0, 7)) : "";
|
|
2230
|
+
const sep = branch && sha ? ` ${chalk__default.default.dim("@")} ` : "";
|
|
2231
|
+
const msg = git.commit?.message ? ` ${chalk__default.default.dim(git.commit.message.split("\n")[0].slice(0, 50))}` : "";
|
|
2232
|
+
console.log(row(`${chalk__default.default.dim("Git")} ${branch}${sep}${sha}${msg}`));
|
|
2233
|
+
}
|
|
2234
|
+
console.log(divider);
|
|
2235
|
+
const statusColor = result.status === "passed" ? chalk__default.default.green : result.status === "failed" ? chalk__default.default.red : chalk__default.default.yellow;
|
|
2236
|
+
const statusLabel = result.status === "timedout" ? "timed out" : result.status;
|
|
2237
|
+
console.log(
|
|
2238
|
+
row(
|
|
2239
|
+
`${chalk__default.default.bold("Tests")} ${statusColor(statusLabel.toUpperCase())} ${chalk__default.default.dim(formatDuration(result.duration))}`
|
|
2240
|
+
)
|
|
2241
|
+
);
|
|
2242
|
+
const counts = [];
|
|
2243
|
+
if (data.testCounts.passed > 0) counts.push(chalk__default.default.green(`${data.testCounts.passed} passed`));
|
|
2244
|
+
if (data.testCounts.failed > 0) counts.push(chalk__default.default.red(`${data.testCounts.failed} failed`));
|
|
2245
|
+
if (data.testCounts.flaky > 0) counts.push(chalk__default.default.yellow(`${data.testCounts.flaky} flaky`));
|
|
2246
|
+
if (data.testCounts.skipped > 0) counts.push(chalk__default.default.dim(`${data.testCounts.skipped} skipped`));
|
|
2247
|
+
if (data.testCounts.timedOut > 0) counts.push(chalk__default.default.red(`${data.testCounts.timedOut} timed out`));
|
|
2248
|
+
if (data.testCounts.interrupted > 0) counts.push(chalk__default.default.yellow(`${data.testCounts.interrupted} interrupted`));
|
|
2249
|
+
const retriedStr = data.testCounts.retried > 0 ? ` ${chalk__default.default.dim(`(${data.testCounts.retried} retries)`)}` : "";
|
|
2250
|
+
console.log(row(` ${counts.join(chalk__default.default.dim(" \xB7 "))} ${chalk__default.default.dim(`of ${data.totalTests}`)}${retriedStr}`));
|
|
2251
|
+
console.log(divider);
|
|
2252
|
+
const shardStr = data.shardInfo ? `${data.shardInfo.current}/${data.shardInfo.total}` : "\u2014";
|
|
2253
|
+
console.log(
|
|
2254
|
+
row(
|
|
2255
|
+
`${pad(`${chalk__default.default.dim("Workers")} ${data.workerCount > 0 ? data.workerCount : "\u2014"}`, 28)}${pad(`${chalk__default.default.dim("Shard")} ${shardStr}`, 28)}${chalk__default.default.dim("Projects")} ${data.projectNames.size > 0 ? Array.from(data.projectNames).join(", ") : "\u2014"}`
|
|
2256
|
+
)
|
|
2257
|
+
);
|
|
2258
|
+
console.log(divider);
|
|
2259
|
+
const transport = data.useHttpFallback ? "HTTP" : "WebSocket";
|
|
2260
|
+
const streamIcon = streamingSuccess ? chalk__default.default.green("sent") : chalk__default.default.red("failed");
|
|
2261
|
+
console.log(row(`${chalk__default.default.bold("Stream")} ${streamIcon} ${chalk__default.default.dim(`via ${transport}`)}`));
|
|
2262
|
+
if (data.lastCoverageEvent) {
|
|
2263
|
+
console.log(divider);
|
|
2264
|
+
printCoverageTable(data.lastCoverageEvent);
|
|
2265
|
+
}
|
|
2266
|
+
console.log(bottomBorder);
|
|
2267
|
+
console.log("");
|
|
2268
|
+
}
|
|
1936
2269
|
var createReporterLog = (options) => ({
|
|
1937
2270
|
success: (msg) => console.log(`\u2705 TestDino: ${msg}`),
|
|
1938
2271
|
warn: (msg) => console.warn(`\u26A0\uFE0F TestDino: ${msg}`),
|
|
@@ -1942,12 +2275,15 @@ var createReporterLog = (options) => ({
|
|
|
1942
2275
|
if (options.debug) {
|
|
1943
2276
|
console.log(`\u{1F50D} TestDino: ${msg}`);
|
|
1944
2277
|
}
|
|
1945
|
-
}
|
|
2278
|
+
},
|
|
2279
|
+
printRunSummary,
|
|
2280
|
+
printCoverageTable
|
|
1946
2281
|
});
|
|
1947
2282
|
|
|
1948
2283
|
// src/reporter/index.ts
|
|
1949
2284
|
var MAX_CONSOLE_CHUNK_SIZE = 1e4;
|
|
1950
2285
|
var MAX_BUFFER_SIZE = 10;
|
|
2286
|
+
var COVERAGE_FILE_COUNT_WARNING = 500;
|
|
1951
2287
|
var TestdinoReporter = class {
|
|
1952
2288
|
config;
|
|
1953
2289
|
wsClient = null;
|
|
@@ -1978,11 +2314,32 @@ var TestdinoReporter = class {
|
|
|
1978
2314
|
pendingTestEndPromises = /* @__PURE__ */ new Set();
|
|
1979
2315
|
// Logger for consistent output
|
|
1980
2316
|
log;
|
|
2317
|
+
// Coverage collection
|
|
2318
|
+
coverageEnabled = false;
|
|
2319
|
+
coverageMerger = null;
|
|
2320
|
+
warnedCoverageDisconnect = false;
|
|
2321
|
+
coverageThresholdFailed = false;
|
|
2322
|
+
// Test result tracking for summary
|
|
2323
|
+
testCounts = { passed: 0, failed: 0, skipped: 0, timedOut: 0, interrupted: 0, flaky: 0, retried: 0 };
|
|
2324
|
+
totalTests = 0;
|
|
2325
|
+
lastCoverageEvent = null;
|
|
2326
|
+
// Detailed tracking for summary output
|
|
2327
|
+
projectNames = /* @__PURE__ */ new Set();
|
|
2328
|
+
runMetadata = null;
|
|
2329
|
+
workerCount = 0;
|
|
1981
2330
|
constructor(config = {}) {
|
|
1982
2331
|
const cliConfig = this.loadCliConfig();
|
|
1983
2332
|
this.config = { ...config, ...cliConfig };
|
|
1984
2333
|
this.runId = crypto.randomUUID();
|
|
1985
2334
|
this.log = createReporterLog({ debug: this.config.debug ?? false });
|
|
2335
|
+
this.coverageEnabled = this.config.coverage?.enabled ?? false;
|
|
2336
|
+
if (this.coverageEnabled) {
|
|
2337
|
+
this.coverageMerger = new CoverageMerger({
|
|
2338
|
+
include: this.config.coverage?.include,
|
|
2339
|
+
exclude: this.config.coverage?.exclude,
|
|
2340
|
+
onError: (msg) => this.log.warn(msg)
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
1986
2343
|
this.buffer = new EventBuffer({
|
|
1987
2344
|
maxSize: MAX_BUFFER_SIZE,
|
|
1988
2345
|
onFlush: async (events) => {
|
|
@@ -2024,6 +2381,9 @@ var TestdinoReporter = class {
|
|
|
2024
2381
|
if (cliConfig.artifacts !== void 0 && typeof cliConfig.artifacts === "boolean") {
|
|
2025
2382
|
mappedConfig.artifacts = cliConfig.artifacts;
|
|
2026
2383
|
}
|
|
2384
|
+
if (typeof cliConfig.coverage === "object" && cliConfig.coverage !== null) {
|
|
2385
|
+
mappedConfig.coverage = cliConfig.coverage;
|
|
2386
|
+
}
|
|
2027
2387
|
return mappedConfig;
|
|
2028
2388
|
} catch (error) {
|
|
2029
2389
|
if (isDebugEnabled()) {
|
|
@@ -2078,6 +2438,8 @@ var TestdinoReporter = class {
|
|
|
2078
2438
|
const serverUrl = this.getServerUrl();
|
|
2079
2439
|
try {
|
|
2080
2440
|
const metadata = await this.collectMetadata(config, suite);
|
|
2441
|
+
this.runMetadata = metadata;
|
|
2442
|
+
this.workerCount = config.workers;
|
|
2081
2443
|
this.httpClient = new HttpClient({ token, serverUrl });
|
|
2082
2444
|
const auth = await this.httpClient.authenticate();
|
|
2083
2445
|
this.sessionId = auth.sessionId;
|
|
@@ -2163,35 +2525,35 @@ var TestdinoReporter = class {
|
|
|
2163
2525
|
/**
|
|
2164
2526
|
* Called for each test before it starts
|
|
2165
2527
|
*/
|
|
2166
|
-
async onTestBegin(
|
|
2528
|
+
async onTestBegin(test2, result) {
|
|
2167
2529
|
if (!this.initPromise || this.initFailed) return;
|
|
2168
2530
|
const event = {
|
|
2169
2531
|
type: "test:begin",
|
|
2170
2532
|
runId: this.runId,
|
|
2171
2533
|
...this.getEventMetadata(),
|
|
2172
2534
|
// Test identification
|
|
2173
|
-
testId:
|
|
2174
|
-
title:
|
|
2175
|
-
titlePath:
|
|
2535
|
+
testId: test2.id,
|
|
2536
|
+
title: test2.title,
|
|
2537
|
+
titlePath: test2.titlePath(),
|
|
2176
2538
|
// Location information
|
|
2177
2539
|
location: {
|
|
2178
|
-
file:
|
|
2179
|
-
line:
|
|
2180
|
-
column:
|
|
2540
|
+
file: test2.location.file,
|
|
2541
|
+
line: test2.location.line,
|
|
2542
|
+
column: test2.location.column
|
|
2181
2543
|
},
|
|
2182
2544
|
// Test configuration
|
|
2183
|
-
tags:
|
|
2184
|
-
expectedStatus:
|
|
2185
|
-
timeout:
|
|
2186
|
-
retries:
|
|
2187
|
-
annotations: this.extractAnnotations(
|
|
2545
|
+
tags: test2.tags,
|
|
2546
|
+
expectedStatus: test2.expectedStatus,
|
|
2547
|
+
timeout: test2.timeout,
|
|
2548
|
+
retries: test2.retries,
|
|
2549
|
+
annotations: this.extractAnnotations(test2.annotations),
|
|
2188
2550
|
// Execution context
|
|
2189
2551
|
retry: result.retry,
|
|
2190
2552
|
workerIndex: result.workerIndex,
|
|
2191
2553
|
parallelIndex: result.parallelIndex,
|
|
2192
|
-
repeatEachIndex:
|
|
2554
|
+
repeatEachIndex: test2.repeatEachIndex,
|
|
2193
2555
|
// Hierarchy information
|
|
2194
|
-
parentSuite: this.extractParentSuite(
|
|
2556
|
+
parentSuite: this.extractParentSuite(test2.parent),
|
|
2195
2557
|
// Timing
|
|
2196
2558
|
startTime: result.startTime.getTime()
|
|
2197
2559
|
};
|
|
@@ -2200,15 +2562,15 @@ var TestdinoReporter = class {
|
|
|
2200
2562
|
/**
|
|
2201
2563
|
* Called when a test step begins
|
|
2202
2564
|
*/
|
|
2203
|
-
async onStepBegin(
|
|
2565
|
+
async onStepBegin(test2, result, step) {
|
|
2204
2566
|
if (!this.initPromise || this.initFailed) return;
|
|
2205
2567
|
const event = {
|
|
2206
2568
|
type: "step:begin",
|
|
2207
2569
|
runId: this.runId,
|
|
2208
2570
|
...this.getEventMetadata(),
|
|
2209
2571
|
// Step Identification
|
|
2210
|
-
testId:
|
|
2211
|
-
stepId: `${
|
|
2572
|
+
testId: test2.id,
|
|
2573
|
+
stepId: `${test2.id}-${step.titlePath().join("-")}`,
|
|
2212
2574
|
title: step.title,
|
|
2213
2575
|
titlePath: step.titlePath(),
|
|
2214
2576
|
// Step Classification
|
|
@@ -2236,7 +2598,7 @@ var TestdinoReporter = class {
|
|
|
2236
2598
|
/**
|
|
2237
2599
|
* Called when a test step ends
|
|
2238
2600
|
*/
|
|
2239
|
-
async onStepEnd(
|
|
2601
|
+
async onStepEnd(test2, result, step) {
|
|
2240
2602
|
if (!this.initPromise || this.initFailed) return;
|
|
2241
2603
|
const status = step.error ? "failed" : "passed";
|
|
2242
2604
|
const event = {
|
|
@@ -2244,8 +2606,8 @@ var TestdinoReporter = class {
|
|
|
2244
2606
|
runId: this.runId,
|
|
2245
2607
|
...this.getEventMetadata(),
|
|
2246
2608
|
// Step Identification
|
|
2247
|
-
testId:
|
|
2248
|
-
stepId: `${
|
|
2609
|
+
testId: test2.id,
|
|
2610
|
+
stepId: `${test2.id}-${step.titlePath().join("-")}`,
|
|
2249
2611
|
title: step.title,
|
|
2250
2612
|
titlePath: step.titlePath(),
|
|
2251
2613
|
// Timing
|
|
@@ -2272,9 +2634,46 @@ var TestdinoReporter = class {
|
|
|
2272
2634
|
* Called after each test.
|
|
2273
2635
|
* Playwright does not await onTestEnd promises—pending work is awaited in onEnd.
|
|
2274
2636
|
*/
|
|
2275
|
-
onTestEnd(
|
|
2637
|
+
onTestEnd(test2, result) {
|
|
2276
2638
|
if (!this.initPromise || this.initFailed) return;
|
|
2277
|
-
|
|
2639
|
+
if (!this.coverageEnabled && !this.warnedCoverageDisconnect) {
|
|
2640
|
+
const hasCoverageAttachment = result.attachments.some((a) => a.name === "testdino-coverage");
|
|
2641
|
+
if (hasCoverageAttachment) {
|
|
2642
|
+
this.log.warn(
|
|
2643
|
+
"Coverage data detected but coverage.enabled is false \u2014 set coverage: { enabled: true } to collect coverage"
|
|
2644
|
+
);
|
|
2645
|
+
this.warnedCoverageDisconnect = true;
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
if (this.coverageEnabled && this.coverageMerger) {
|
|
2649
|
+
this.extractCoverageFromResult(result, this.coverageMerger);
|
|
2650
|
+
}
|
|
2651
|
+
const projectName = this.getProjectName(test2);
|
|
2652
|
+
if (projectName) {
|
|
2653
|
+
this.projectNames.add(projectName);
|
|
2654
|
+
}
|
|
2655
|
+
if (result.retry > 0) {
|
|
2656
|
+
this.testCounts.retried++;
|
|
2657
|
+
}
|
|
2658
|
+
const isFinalAttempt = result.status === "passed" || result.retry === test2.retries;
|
|
2659
|
+
if (isFinalAttempt) {
|
|
2660
|
+
this.totalTests++;
|
|
2661
|
+
const outcome = test2.outcome();
|
|
2662
|
+
if (outcome === "flaky") {
|
|
2663
|
+
this.testCounts.flaky++;
|
|
2664
|
+
} else if (result.status === "passed") {
|
|
2665
|
+
this.testCounts.passed++;
|
|
2666
|
+
} else if (result.status === "failed") {
|
|
2667
|
+
this.testCounts.failed++;
|
|
2668
|
+
} else if (result.status === "skipped") {
|
|
2669
|
+
this.testCounts.skipped++;
|
|
2670
|
+
} else if (result.status === "timedOut") {
|
|
2671
|
+
this.testCounts.timedOut++;
|
|
2672
|
+
} else if (result.status === "interrupted") {
|
|
2673
|
+
this.testCounts.interrupted++;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
const workPromise = this.processTestEnd(test2, result);
|
|
2278
2677
|
this.pendingTestEndPromises.add(workPromise);
|
|
2279
2678
|
workPromise.finally(() => {
|
|
2280
2679
|
this.pendingTestEndPromises.delete(workPromise);
|
|
@@ -2284,18 +2683,18 @@ var TestdinoReporter = class {
|
|
|
2284
2683
|
* Process test end event asynchronously
|
|
2285
2684
|
* Uploads attachments and adds test:end event to buffer
|
|
2286
2685
|
*/
|
|
2287
|
-
async processTestEnd(
|
|
2686
|
+
async processTestEnd(test2, result) {
|
|
2288
2687
|
try {
|
|
2289
|
-
const attachmentsWithUrls = await this.uploadAttachments(result.attachments,
|
|
2688
|
+
const attachmentsWithUrls = await this.uploadAttachments(result.attachments, test2.id);
|
|
2290
2689
|
const event = {
|
|
2291
2690
|
type: "test:end",
|
|
2292
2691
|
runId: this.runId,
|
|
2293
2692
|
...this.getEventMetadata(),
|
|
2294
2693
|
// Test Identification
|
|
2295
|
-
testId:
|
|
2694
|
+
testId: test2.id,
|
|
2296
2695
|
// Status Information
|
|
2297
2696
|
status: result.status,
|
|
2298
|
-
outcome:
|
|
2697
|
+
outcome: test2.outcome(),
|
|
2299
2698
|
// Timing
|
|
2300
2699
|
duration: result.duration,
|
|
2301
2700
|
// Execution Context
|
|
@@ -2346,6 +2745,36 @@ var TestdinoReporter = class {
|
|
|
2346
2745
|
this.log.debug(`Waiting for ${this.pendingTestEndPromises.size} pending test:end events...`);
|
|
2347
2746
|
await Promise.allSettled(Array.from(this.pendingTestEndPromises));
|
|
2348
2747
|
}
|
|
2748
|
+
if (this.coverageEnabled && this.coverageMerger?.hasCoverage) {
|
|
2749
|
+
try {
|
|
2750
|
+
const coverageEvent = this.buildCoverageEvent(this.coverageMerger);
|
|
2751
|
+
await this.buffer.add(coverageEvent);
|
|
2752
|
+
this.lastCoverageEvent = coverageEvent;
|
|
2753
|
+
const thresholdFailures = this.checkCoverageThresholds(coverageEvent.summary);
|
|
2754
|
+
if (thresholdFailures.length > 0) {
|
|
2755
|
+
this.coverageThresholdFailed = true;
|
|
2756
|
+
}
|
|
2757
|
+
} catch (error) {
|
|
2758
|
+
this.log.warn(`Failed to build coverage event: ${error instanceof Error ? error.message : String(error)}`);
|
|
2759
|
+
}
|
|
2760
|
+
if (this.coverageMerger?.hasCoverage) {
|
|
2761
|
+
try {
|
|
2762
|
+
const outputDir = "./coverage";
|
|
2763
|
+
const reportPath = await generateIstanbulHtmlReport(this.coverageMerger, {
|
|
2764
|
+
outputDir
|
|
2765
|
+
});
|
|
2766
|
+
this.log.info(`Coverage Report: ${reportPath}`);
|
|
2767
|
+
} catch (error) {
|
|
2768
|
+
this.log.warn(
|
|
2769
|
+
`Failed to generate local HTML report: ${error instanceof Error ? error.message : String(error)}`
|
|
2770
|
+
);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
} else if (this.coverageEnabled && !this.coverageMerger?.hasCoverage) {
|
|
2774
|
+
this.log.warn(
|
|
2775
|
+
"Coverage enabled but no data was collected. Ensure your app is instrumented with Istanbul (babel-plugin-istanbul) and tests use { test } from @testdino/playwright"
|
|
2776
|
+
);
|
|
2777
|
+
}
|
|
2349
2778
|
const event = {
|
|
2350
2779
|
type: "run:end",
|
|
2351
2780
|
runId: this.runId,
|
|
@@ -2396,6 +2825,28 @@ var TestdinoReporter = class {
|
|
|
2396
2825
|
this.log.error(`Failed to send run:end event: ${errorMessage}`);
|
|
2397
2826
|
}
|
|
2398
2827
|
}
|
|
2828
|
+
const summaryData = {
|
|
2829
|
+
runId: this.runId,
|
|
2830
|
+
runMetadata: this.runMetadata,
|
|
2831
|
+
testCounts: this.testCounts,
|
|
2832
|
+
totalTests: this.totalTests,
|
|
2833
|
+
workerCount: this.workerCount,
|
|
2834
|
+
projectNames: this.projectNames,
|
|
2835
|
+
shardInfo: this.shardInfo,
|
|
2836
|
+
useHttpFallback: this.useHttpFallback,
|
|
2837
|
+
lastCoverageEvent: this.lastCoverageEvent
|
|
2838
|
+
};
|
|
2839
|
+
this.log.printRunSummary(result, delivered, summaryData);
|
|
2840
|
+
if (this.coverageThresholdFailed && this.lastCoverageEvent) {
|
|
2841
|
+
const failures = this.checkCoverageThresholds(this.lastCoverageEvent.summary);
|
|
2842
|
+
this.log.error("Coverage thresholds not met:");
|
|
2843
|
+
for (const msg of failures) {
|
|
2844
|
+
this.log.error(` ${msg}`);
|
|
2845
|
+
}
|
|
2846
|
+
this.wsClient?.close();
|
|
2847
|
+
this.removeSignalHandlers();
|
|
2848
|
+
return { status: "failed" };
|
|
2849
|
+
}
|
|
2399
2850
|
this.wsClient?.close();
|
|
2400
2851
|
this.removeSignalHandlers();
|
|
2401
2852
|
}
|
|
@@ -2416,7 +2867,7 @@ var TestdinoReporter = class {
|
|
|
2416
2867
|
/**
|
|
2417
2868
|
* Called when standard output is produced in worker process
|
|
2418
2869
|
*/
|
|
2419
|
-
async onStdOut(chunk,
|
|
2870
|
+
async onStdOut(chunk, test2, result) {
|
|
2420
2871
|
if (!this.initPromise || this.initFailed) return;
|
|
2421
2872
|
const { text, truncated } = this.truncateChunk(chunk);
|
|
2422
2873
|
const event = {
|
|
@@ -2426,7 +2877,7 @@ var TestdinoReporter = class {
|
|
|
2426
2877
|
// Console Output
|
|
2427
2878
|
text,
|
|
2428
2879
|
// Test Association (optional)
|
|
2429
|
-
testId:
|
|
2880
|
+
testId: test2?.id,
|
|
2430
2881
|
retry: result?.retry,
|
|
2431
2882
|
// Truncation Indicator
|
|
2432
2883
|
truncated
|
|
@@ -2436,7 +2887,7 @@ var TestdinoReporter = class {
|
|
|
2436
2887
|
/**
|
|
2437
2888
|
* Called when standard error is produced in worker process
|
|
2438
2889
|
*/
|
|
2439
|
-
async onStdErr(chunk,
|
|
2890
|
+
async onStdErr(chunk, test2, result) {
|
|
2440
2891
|
if (!this.initPromise || this.initFailed) return;
|
|
2441
2892
|
const { text, truncated } = this.truncateChunk(chunk);
|
|
2442
2893
|
const event = {
|
|
@@ -2446,7 +2897,7 @@ var TestdinoReporter = class {
|
|
|
2446
2897
|
// Console Error Output
|
|
2447
2898
|
text,
|
|
2448
2899
|
// Test Association (optional)
|
|
2449
|
-
testId:
|
|
2900
|
+
testId: test2?.id,
|
|
2450
2901
|
retry: result?.retry,
|
|
2451
2902
|
// Truncation Indicator
|
|
2452
2903
|
truncated
|
|
@@ -3005,8 +3456,98 @@ var TestdinoReporter = class {
|
|
|
3005
3456
|
getBaseServerUrl(serverUrl) {
|
|
3006
3457
|
return serverUrl.replace(/\/api\/reporter$/, "");
|
|
3007
3458
|
}
|
|
3459
|
+
/**
|
|
3460
|
+
* Walk up the suite hierarchy to find the project name for a test.
|
|
3461
|
+
*/
|
|
3462
|
+
getProjectName(test2) {
|
|
3463
|
+
let suite = test2.parent;
|
|
3464
|
+
while (suite) {
|
|
3465
|
+
const project = suite.project();
|
|
3466
|
+
if (project) {
|
|
3467
|
+
return project.name || project.use?.defaultBrowserType || "default";
|
|
3468
|
+
}
|
|
3469
|
+
suite = suite.parent;
|
|
3470
|
+
}
|
|
3471
|
+
return void 0;
|
|
3472
|
+
}
|
|
3473
|
+
/**
|
|
3474
|
+
* Extract coverage fragment from test result attachments and merge incrementally.
|
|
3475
|
+
* The fixture attaches coverage as an in-memory JSON attachment named 'testdino-coverage'.
|
|
3476
|
+
*/
|
|
3477
|
+
extractCoverageFromResult(result, merger) {
|
|
3478
|
+
const coverageAttachment = result.attachments.find((a) => a.name === "testdino-coverage");
|
|
3479
|
+
if (!coverageAttachment?.body) return;
|
|
3480
|
+
try {
|
|
3481
|
+
const fragment = JSON.parse(coverageAttachment.body.toString());
|
|
3482
|
+
merger.addFragment(fragment);
|
|
3483
|
+
} catch (error) {
|
|
3484
|
+
this.log.debug(
|
|
3485
|
+
`Malformed coverage attachment, skipping: ${error instanceof Error ? error.message : String(error)}`
|
|
3486
|
+
);
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
/**
|
|
3490
|
+
* Build a coverage:data event from the merged coverage data.
|
|
3491
|
+
*
|
|
3492
|
+
* Non-sharded runs: summary + per-file metrics only (small payload).
|
|
3493
|
+
* Sharded runs: also includes compact hit counts for server-side cross-shard merging.
|
|
3494
|
+
*/
|
|
3495
|
+
buildCoverageEvent(merger) {
|
|
3496
|
+
const rootDir = this.runMetadata?.playwright?.rootDir || process.cwd();
|
|
3497
|
+
const summary = merger.computeSummary();
|
|
3498
|
+
const files = merger.computeFileCoverage(rootDir);
|
|
3499
|
+
const isSharded = !!this.shardInfo;
|
|
3500
|
+
if (files.length > COVERAGE_FILE_COUNT_WARNING) {
|
|
3501
|
+
this.log.warn(
|
|
3502
|
+
`Coverage includes ${files.length} files \u2014 consider using coverage.include/exclude to reduce scope`
|
|
3503
|
+
);
|
|
3504
|
+
}
|
|
3505
|
+
const event = {
|
|
3506
|
+
type: "coverage:data",
|
|
3507
|
+
runId: this.runId,
|
|
3508
|
+
...this.getEventMetadata(),
|
|
3509
|
+
summary,
|
|
3510
|
+
files,
|
|
3511
|
+
metadata: {
|
|
3512
|
+
instrumentationType: "istanbul",
|
|
3513
|
+
fileCount: files.length,
|
|
3514
|
+
sharded: isSharded
|
|
3515
|
+
}
|
|
3516
|
+
};
|
|
3517
|
+
if (isSharded) {
|
|
3518
|
+
const coverageMapJSON = merger.toJSON();
|
|
3519
|
+
event.compactCounts = extractCompactCounts(coverageMapJSON, rootDir);
|
|
3520
|
+
event.shard = this.shardInfo;
|
|
3521
|
+
}
|
|
3522
|
+
return event;
|
|
3523
|
+
}
|
|
3524
|
+
/**
|
|
3525
|
+
* Check coverage against configured thresholds.
|
|
3526
|
+
* Returns an array of failure messages (empty if all thresholds pass).
|
|
3527
|
+
*/
|
|
3528
|
+
checkCoverageThresholds(summary) {
|
|
3529
|
+
const thresholds = this.config.coverage?.thresholds;
|
|
3530
|
+
if (!thresholds) return [];
|
|
3531
|
+
const failures = [];
|
|
3532
|
+
const check = (name, actual, threshold) => {
|
|
3533
|
+
if (threshold !== void 0 && actual < threshold) {
|
|
3534
|
+
failures.push(`${name}: ${actual.toFixed(2)}% < ${threshold}%`);
|
|
3535
|
+
}
|
|
3536
|
+
};
|
|
3537
|
+
check("Statements", summary.statements.pct, thresholds.statements);
|
|
3538
|
+
check("Branches", summary.branches.pct, thresholds.branches);
|
|
3539
|
+
check("Functions", summary.functions.pct, thresholds.functions);
|
|
3540
|
+
check("Lines", summary.lines.pct, thresholds.lines);
|
|
3541
|
+
return failures;
|
|
3542
|
+
}
|
|
3008
3543
|
};
|
|
3009
3544
|
|
|
3010
|
-
|
|
3545
|
+
Object.defineProperty(exports, "expect", {
|
|
3546
|
+
enumerable: true,
|
|
3547
|
+
get: function () { return test$1.expect; }
|
|
3548
|
+
});
|
|
3549
|
+
exports.coverageFixtures = coverageFixtures;
|
|
3550
|
+
exports.default = TestdinoReporter;
|
|
3551
|
+
exports.test = test;
|
|
3011
3552
|
//# sourceMappingURL=index.js.map
|
|
3012
3553
|
//# sourceMappingURL=index.js.map
|