@checkly/playwright-reporter 1.0.0 → 1.0.2
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/CHANGELOG.md +55 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +135 -25
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@checkly/playwright-reporter` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## 1.0.2 (2026-01-19)
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Test session creation is now properly awaited before uploading results, fixing intermittent upload failures in CI environments.
|
|
12
|
+
|
|
13
|
+
## 1.0.1 (2026-01-19)
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Sharded test execution now works correctly when using `playwright merge-reports` command. Due to a Playwright limitation, `config.projects` is empty after merging blob reports - the reporter now reconstructs project information from the test suite structure.
|
|
18
|
+
- Default projects (when no explicit projects are defined in playwright.config.ts) now preserve their empty name correctly instead of being renamed to "default".
|
|
19
|
+
|
|
20
|
+
## 1.0.0 (2025-01-19)
|
|
21
|
+
|
|
22
|
+
First stable release of the rewritten Playwright reporter with extension-based architecture.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Extension-based architecture** - Modular design allows for composable functionality
|
|
27
|
+
- **Broad Playwright support** - Compatible with Playwright versions 1.40 through 1.57+
|
|
28
|
+
- **Drop-in JSON replacement** - Fully compatible with Playwright's native JSON reporter output format
|
|
29
|
+
- **Trace extraction** - Automatically extracts console messages and network requests from Playwright trace files
|
|
30
|
+
- **Checkly integration** - Automatic upload to Checkly Test Sessions when credentials are configured
|
|
31
|
+
- **New `createChecklyReporter()` function** - Better intellisense support in `playwright.config.ts`
|
|
32
|
+
- **Unified `outputDir` option** - Single option for all output files (JSON report, ZIP assets)
|
|
33
|
+
- **Verbose logging** - Debug mode for troubleshooting report generation
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- `outputFile` option is deprecated - use `outputDir` instead (JSON written to `{outputDir}/checkly-report.json`)
|
|
38
|
+
- `testResultsDir` option is deprecated - use `outputDir` instead
|
|
39
|
+
- `outputPath` option is deprecated - ZIP written to `{outputDir}/checkly-report.zip`
|
|
40
|
+
|
|
41
|
+
### Migration from 0.x
|
|
42
|
+
|
|
43
|
+
```diff
|
|
44
|
+
// playwright.config.ts
|
|
45
|
+
+import { createChecklyReporter } from '@checkly/playwright-reporter'
|
|
46
|
+
|
|
47
|
+
export default defineConfig({
|
|
48
|
+
reporter: [
|
|
49
|
+
- ['@checkly/playwright-reporter', { outputFile: 'results/report.json' }],
|
|
50
|
+
+ createChecklyReporter({ outputDir: 'results' }),
|
|
51
|
+
],
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The legacy 0.x reporter remains available via `npm install @checkly/playwright-reporter@legacy`.
|
package/dist/index.d.ts
CHANGED
|
@@ -247,6 +247,7 @@ declare class BaseReporter implements Reporter {
|
|
|
247
247
|
private _report;
|
|
248
248
|
private extensions;
|
|
249
249
|
private summaryLines;
|
|
250
|
+
private reconstructedProjects;
|
|
250
251
|
constructor(options?: BaseReporterOptions);
|
|
251
252
|
protected use(extension: Extension$1): this;
|
|
252
253
|
private log;
|
|
@@ -264,6 +265,17 @@ declare class BaseReporter implements Reporter {
|
|
|
264
265
|
printsToStdio(): boolean;
|
|
265
266
|
getReport(): JSONReport;
|
|
266
267
|
private buildReport;
|
|
268
|
+
/**
|
|
269
|
+
* Reconstructs projects from the suite structure when config.projects is empty.
|
|
270
|
+
* This handles the merge-reports scenario where Playwright doesn't populate
|
|
271
|
+
* config.projects but still provides project information via suite.project().
|
|
272
|
+
*/
|
|
273
|
+
private reconstructProjectsIfNeeded;
|
|
274
|
+
/**
|
|
275
|
+
* Recursively collects project information from test cases in the suite.
|
|
276
|
+
* Fallback for when suite.project() doesn't provide project info.
|
|
277
|
+
*/
|
|
278
|
+
private collectProjectsFromTests;
|
|
267
279
|
private serializeConfig;
|
|
268
280
|
private serializeProject;
|
|
269
281
|
/**
|
|
@@ -289,6 +301,11 @@ declare class BaseReporter implements Reporter {
|
|
|
289
301
|
*/
|
|
290
302
|
private extractTags;
|
|
291
303
|
private serializeTest;
|
|
304
|
+
/**
|
|
305
|
+
* Get the project name for a test case, handling merge-reports scenarios
|
|
306
|
+
* where project() may not be available in the expected way.
|
|
307
|
+
*/
|
|
308
|
+
private getProjectName;
|
|
292
309
|
private serializeTestResult;
|
|
293
310
|
private serializeStep;
|
|
294
311
|
private serializeError;
|
package/dist/index.js
CHANGED
|
@@ -1598,6 +1598,7 @@ function checklyUpload(options = {}) {
|
|
|
1598
1598
|
}
|
|
1599
1599
|
let api;
|
|
1600
1600
|
let testSession;
|
|
1601
|
+
let sessionCreationPromise;
|
|
1601
1602
|
let startTime;
|
|
1602
1603
|
let resolvedOutputDir;
|
|
1603
1604
|
let assetCollector;
|
|
@@ -1616,6 +1617,26 @@ function checklyUpload(options = {}) {
|
|
|
1616
1617
|
if (typeof sessionName === "string") return sessionName;
|
|
1617
1618
|
return `Playwright Test Session: ${ctx.directoryName}`;
|
|
1618
1619
|
}
|
|
1620
|
+
async function createSession(config, suite, log) {
|
|
1621
|
+
if (!api) return;
|
|
1622
|
+
try {
|
|
1623
|
+
const directoryName = getDirectoryName();
|
|
1624
|
+
const sessionName = resolveSessionName({ directoryName, config, suite });
|
|
1625
|
+
const repoInfo = getGitHubRepoInfo();
|
|
1626
|
+
log("\u{1F517} Creating test session", { name: sessionName, environment });
|
|
1627
|
+
testSession = await api.testSessions.create({
|
|
1628
|
+
name: sessionName,
|
|
1629
|
+
environment: process.env.NODE_ENV || "test",
|
|
1630
|
+
repoInfo,
|
|
1631
|
+
startedAt: startTime.getTime(),
|
|
1632
|
+
testResults: [{ name: directoryName }],
|
|
1633
|
+
provider: "PW_REPORTER"
|
|
1634
|
+
});
|
|
1635
|
+
log("\u2705 Session created", { id: testSession.testSessionId });
|
|
1636
|
+
} catch (err2) {
|
|
1637
|
+
console.error("[Checkly] Failed to create test session:", err2);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1619
1640
|
function checkTraceAttachment(test, result) {
|
|
1620
1641
|
const key = `${test.id}:${result.retry}`;
|
|
1621
1642
|
const traceAttachment = result.attachments?.find((a) => a.name === "trace" || a.contentType === "application/zip");
|
|
@@ -1740,7 +1761,7 @@ function checklyUpload(options = {}) {
|
|
|
1740
1761
|
}
|
|
1741
1762
|
return {
|
|
1742
1763
|
name: "checkly-upload",
|
|
1743
|
-
onBegin:
|
|
1764
|
+
onBegin: ({ config, suite, log }) => {
|
|
1744
1765
|
startTime = /* @__PURE__ */ new Date();
|
|
1745
1766
|
resolvedOutputDir = options.outputDir ?? options.testResultsDir ?? config.projects[0]?.outputDir ?? "test-results";
|
|
1746
1767
|
const zipPath = options.outputPath ?? path3.join(resolvedOutputDir, "checkly-report.zip");
|
|
@@ -1752,23 +1773,7 @@ function checklyUpload(options = {}) {
|
|
|
1752
1773
|
}
|
|
1753
1774
|
return;
|
|
1754
1775
|
}
|
|
1755
|
-
|
|
1756
|
-
const directoryName = getDirectoryName();
|
|
1757
|
-
const sessionName = resolveSessionName({ directoryName, config, suite });
|
|
1758
|
-
const repoInfo = getGitHubRepoInfo();
|
|
1759
|
-
log("\u{1F517} Creating test session", { name: sessionName, environment });
|
|
1760
|
-
testSession = await api.testSessions.create({
|
|
1761
|
-
name: sessionName,
|
|
1762
|
-
environment: process.env.NODE_ENV || "test",
|
|
1763
|
-
repoInfo,
|
|
1764
|
-
startedAt: startTime.getTime(),
|
|
1765
|
-
testResults: [{ name: directoryName }],
|
|
1766
|
-
provider: "PW_REPORTER"
|
|
1767
|
-
});
|
|
1768
|
-
log("\u2705 Session created", { id: testSession.testSessionId });
|
|
1769
|
-
} catch (err2) {
|
|
1770
|
-
console.error("[Checkly] Failed to create test session:", err2);
|
|
1771
|
-
}
|
|
1776
|
+
sessionCreationPromise = createSession(config, suite, log);
|
|
1772
1777
|
},
|
|
1773
1778
|
onTestEnd: ({ test, result }) => {
|
|
1774
1779
|
checkTraceAttachment(test, result);
|
|
@@ -1786,6 +1791,9 @@ function checklyUpload(options = {}) {
|
|
|
1786
1791
|
},
|
|
1787
1792
|
onEnd: async ({ report, log, addSummaryLine }) => {
|
|
1788
1793
|
try {
|
|
1794
|
+
if (sessionCreationPromise) {
|
|
1795
|
+
await sessionCreationPromise;
|
|
1796
|
+
}
|
|
1789
1797
|
await extractDataFromTraces(log);
|
|
1790
1798
|
injectDataIntoReport(report);
|
|
1791
1799
|
fs4.mkdirSync(resolvedOutputDir, { recursive: true });
|
|
@@ -1855,6 +1863,8 @@ var BaseReporter = class {
|
|
|
1855
1863
|
_report = null;
|
|
1856
1864
|
extensions = [];
|
|
1857
1865
|
summaryLines = [];
|
|
1866
|
+
// Reconstructed projects for merge-reports scenarios
|
|
1867
|
+
reconstructedProjects = /* @__PURE__ */ new Map();
|
|
1858
1868
|
constructor(options = {}) {
|
|
1859
1869
|
this.options = options;
|
|
1860
1870
|
this.verbose = options.verbose ?? process.env.CHECKLY_REPORTER_VERBOSE === "true";
|
|
@@ -1889,8 +1899,10 @@ var BaseReporter = class {
|
|
|
1889
1899
|
this.flakyCount = 0;
|
|
1890
1900
|
this.skippedCount = 0;
|
|
1891
1901
|
this._report = null;
|
|
1902
|
+
this.reconstructedProjects.clear();
|
|
1903
|
+
this.reconstructProjectsIfNeeded();
|
|
1892
1904
|
const testCount = this.countTests(suite);
|
|
1893
|
-
const projectNames = config.projects.map((p) => p.name).join(", ");
|
|
1905
|
+
const projectNames = config.projects.length > 0 ? config.projects.map((p) => p.name).join(", ") : Array.from(this.reconstructedProjects.keys()).join(", ") || "default";
|
|
1894
1906
|
this.log(`\u{1F3AC} Starting test run`, { tests: testCount, projects: projectNames, workers: config.workers });
|
|
1895
1907
|
for (const ext of this.extensions) {
|
|
1896
1908
|
ext.onBegin?.({ config, suite, log: this.createExtensionLogger(ext.name) });
|
|
@@ -2022,8 +2034,86 @@ var BaseReporter = class {
|
|
|
2022
2034
|
}
|
|
2023
2035
|
};
|
|
2024
2036
|
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Reconstructs projects from the suite structure when config.projects is empty.
|
|
2039
|
+
* This handles the merge-reports scenario where Playwright doesn't populate
|
|
2040
|
+
* config.projects but still provides project information via suite.project().
|
|
2041
|
+
*/
|
|
2042
|
+
reconstructProjectsIfNeeded() {
|
|
2043
|
+
if (this.config.projects.length > 0) {
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
this.log("\u{1F504} Detected merge-reports scenario (empty config.projects), reconstructing projects");
|
|
2047
|
+
for (const projectSuite of this.suite.suites) {
|
|
2048
|
+
const project = projectSuite.project();
|
|
2049
|
+
if (project) {
|
|
2050
|
+
const name = project.name || "default";
|
|
2051
|
+
if (!this.reconstructedProjects.has(name)) {
|
|
2052
|
+
this.reconstructedProjects.set(name, {
|
|
2053
|
+
id: name,
|
|
2054
|
+
name,
|
|
2055
|
+
testDir: project.testDir,
|
|
2056
|
+
outputDir: project.outputDir,
|
|
2057
|
+
timeout: project.timeout,
|
|
2058
|
+
retries: project.retries,
|
|
2059
|
+
repeatEach: project.repeatEach,
|
|
2060
|
+
metadata: project.metadata ?? {},
|
|
2061
|
+
testMatch: Array.isArray(project.testMatch) ? project.testMatch.map(String) : [String(project.testMatch)],
|
|
2062
|
+
testIgnore: Array.isArray(project.testIgnore) ? project.testIgnore.map(String) : [String(project.testIgnore)]
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
if (this.reconstructedProjects.size === 0) {
|
|
2068
|
+
this.collectProjectsFromTests(this.suite);
|
|
2069
|
+
}
|
|
2070
|
+
this.log("\u{1F504} Reconstructed projects", {
|
|
2071
|
+
count: this.reconstructedProjects.size,
|
|
2072
|
+
names: Array.from(this.reconstructedProjects.keys())
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Recursively collects project information from test cases in the suite.
|
|
2077
|
+
* Fallback for when suite.project() doesn't provide project info.
|
|
2078
|
+
*/
|
|
2079
|
+
collectProjectsFromTests(suite) {
|
|
2080
|
+
for (const test of suite.allTests()) {
|
|
2081
|
+
const project = test.parent.project();
|
|
2082
|
+
const projectName = project?.name || "default";
|
|
2083
|
+
if (!this.reconstructedProjects.has(projectName)) {
|
|
2084
|
+
if (project) {
|
|
2085
|
+
this.reconstructedProjects.set(projectName, {
|
|
2086
|
+
id: projectName,
|
|
2087
|
+
name: projectName,
|
|
2088
|
+
testDir: project.testDir,
|
|
2089
|
+
outputDir: project.outputDir,
|
|
2090
|
+
timeout: project.timeout,
|
|
2091
|
+
retries: project.retries,
|
|
2092
|
+
repeatEach: project.repeatEach,
|
|
2093
|
+
metadata: project.metadata ?? {},
|
|
2094
|
+
testMatch: Array.isArray(project.testMatch) ? project.testMatch.map(String) : [String(project.testMatch)],
|
|
2095
|
+
testIgnore: Array.isArray(project.testIgnore) ? project.testIgnore.map(String) : [String(project.testIgnore)]
|
|
2096
|
+
});
|
|
2097
|
+
} else {
|
|
2098
|
+
this.reconstructedProjects.set(projectName, {
|
|
2099
|
+
id: projectName,
|
|
2100
|
+
name: projectName,
|
|
2101
|
+
testDir: "",
|
|
2102
|
+
outputDir: "",
|
|
2103
|
+
timeout: 0,
|
|
2104
|
+
retries: 0,
|
|
2105
|
+
repeatEach: 1,
|
|
2106
|
+
metadata: {},
|
|
2107
|
+
testMatch: [],
|
|
2108
|
+
testIgnore: []
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2025
2114
|
serializeConfig() {
|
|
2026
2115
|
const c = this.config;
|
|
2116
|
+
const projects = c.projects.length > 0 ? c.projects.map((p) => this.serializeProject(p)) : Array.from(this.reconstructedProjects.values());
|
|
2027
2117
|
return {
|
|
2028
2118
|
rootDir: c.rootDir,
|
|
2029
2119
|
configFile: c.configFile ?? void 0,
|
|
@@ -2034,7 +2124,7 @@ var BaseReporter = class {
|
|
|
2034
2124
|
globalTimeout: c.globalTimeout,
|
|
2035
2125
|
maxFailures: c.maxFailures,
|
|
2036
2126
|
metadata: c.metadata ?? {},
|
|
2037
|
-
projects
|
|
2127
|
+
projects,
|
|
2038
2128
|
shard: c.shard ?? null,
|
|
2039
2129
|
tags: c.tags ?? [],
|
|
2040
2130
|
updateSourceMethod: c.updateSourceMethod,
|
|
@@ -2162,9 +2252,10 @@ var BaseReporter = class {
|
|
|
2162
2252
|
}
|
|
2163
2253
|
serializeTest(data) {
|
|
2164
2254
|
const { testCase, results } = data;
|
|
2255
|
+
const projectName = this.getProjectName(testCase);
|
|
2165
2256
|
return {
|
|
2166
|
-
projectId:
|
|
2167
|
-
projectName
|
|
2257
|
+
projectId: projectName,
|
|
2258
|
+
projectName,
|
|
2168
2259
|
timeout: testCase.timeout,
|
|
2169
2260
|
expectedStatus: testCase.expectedStatus,
|
|
2170
2261
|
annotations: this.serializeAnnotations(testCase.annotations),
|
|
@@ -2172,6 +2263,25 @@ var BaseReporter = class {
|
|
|
2172
2263
|
status: this.mapOutcome(testCase.outcome())
|
|
2173
2264
|
};
|
|
2174
2265
|
}
|
|
2266
|
+
/**
|
|
2267
|
+
* Get the project name for a test case, handling merge-reports scenarios
|
|
2268
|
+
* where project() may not be available in the expected way.
|
|
2269
|
+
*/
|
|
2270
|
+
getProjectName(testCase) {
|
|
2271
|
+
const project = testCase.parent.project();
|
|
2272
|
+
if (project && typeof project.name === "string") {
|
|
2273
|
+
return project.name;
|
|
2274
|
+
}
|
|
2275
|
+
let current = testCase.parent;
|
|
2276
|
+
while (current) {
|
|
2277
|
+
const suiteProject = current.project();
|
|
2278
|
+
if (suiteProject && typeof suiteProject.name === "string") {
|
|
2279
|
+
return suiteProject.name;
|
|
2280
|
+
}
|
|
2281
|
+
current = current.parent;
|
|
2282
|
+
}
|
|
2283
|
+
return "default";
|
|
2284
|
+
}
|
|
2175
2285
|
serializeTestResult(r) {
|
|
2176
2286
|
return {
|
|
2177
2287
|
workerIndex: r.workerIndex,
|
|
@@ -2255,9 +2365,9 @@ var BaseReporter = class {
|
|
|
2255
2365
|
printSummary() {
|
|
2256
2366
|
const pkgVersion = getPackageVersion();
|
|
2257
2367
|
const playwrightVersion = this.config.version;
|
|
2258
|
-
const
|
|
2259
|
-
const
|
|
2260
|
-
const rule = pluralRules.select(
|
|
2368
|
+
const projectNames = this.config.projects.length > 0 ? this.config.projects.map((p) => p.name).join(", ") : Array.from(this.reconstructedProjects.keys()).join(", ") || "default";
|
|
2369
|
+
const projectCount = this.config.projects.length > 0 ? this.config.projects.length : this.reconstructedProjects.size || 1;
|
|
2370
|
+
const rule = pluralRules.select(projectCount);
|
|
2261
2371
|
console.log("\n======================================================\n");
|
|
2262
2372
|
console.log(`\u{1F99D} Checkly reporter: ${pkgVersion}`);
|
|
2263
2373
|
console.log(`\u{1F3AD} Playwright: ${playwrightVersion}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkly/playwright-reporter",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Standalone Playwright reporter for Checkly - compatible with Playwright 1.40-1.57+",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
10
|
"README.md",
|
|
11
|
+
"CHANGELOG.md",
|
|
11
12
|
"LICENSE"
|
|
12
13
|
],
|
|
13
14
|
"keywords": [
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"tsup": "8.5.1",
|
|
36
37
|
"tsx": "4.19.0",
|
|
37
38
|
"typescript": "5.9.3",
|
|
38
|
-
"@checkly/reporter-utils": "1.0.
|
|
39
|
+
"@checkly/reporter-utils": "1.0.2"
|
|
39
40
|
},
|
|
40
41
|
"scripts": {
|
|
41
42
|
"build": "tsup",
|