playwright 1.57.0-alpha-2025-10-20 → 1.57.0-alpha-2025-10-22
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/lib/agents/generateAgents.js +1 -1
- package/lib/common/config.js +6 -0
- package/lib/common/testLoader.js +3 -2
- package/lib/common/testType.js +3 -12
- package/lib/common/validators.js +68 -0
- package/lib/isomorphic/teleReceiver.js +1 -0
- package/lib/loader/loaderMain.js +1 -1
- package/lib/reporters/junit.js +4 -2
- package/lib/reporters/merge.js +11 -5
- package/lib/reporters/teleEmitter.js +2 -1
- package/lib/runner/loadUtils.js +2 -1
- package/lib/runner/loaderHost.js +1 -1
- package/lib/runner/reporters.js +2 -0
- package/lib/worker/workerMain.js +1 -1
- package/package.json +2 -2
- package/types/test.d.ts +27 -2
- package/types/testReporter.d.ts +5 -0
|
@@ -207,7 +207,7 @@ class AgentGenerator {
|
|
|
207
207
|
const agents = await AgentParser.loadAgents();
|
|
208
208
|
await import_fs.default.promises.mkdir(agentsFolder, { recursive: true });
|
|
209
209
|
for (const agent of agents)
|
|
210
|
-
await writeFile(`${agentsFolder}/${agent.header.name}.md`, agent.source, "\u{1F916}", "agent definition");
|
|
210
|
+
await writeFile(`${agentsFolder}/agents/${agent.header.name}.md`, agent.source, "\u{1F916}", "agent definition");
|
|
211
211
|
console.log("\u{1F527} MCP configuration");
|
|
212
212
|
console.log(JSON.stringify({
|
|
213
213
|
mcpServers: {
|
package/lib/common/config.js
CHANGED
|
@@ -69,6 +69,11 @@ class FullConfigInternal {
|
|
|
69
69
|
this.globalSetups = (Array.isArray(userConfig.globalSetup) ? userConfig.globalSetup : [userConfig.globalSetup]).map((s) => resolveScript(s, configDir)).filter((script) => script !== void 0);
|
|
70
70
|
this.globalTeardowns = (Array.isArray(userConfig.globalTeardown) ? userConfig.globalTeardown : [userConfig.globalTeardown]).map((s) => resolveScript(s, configDir)).filter((script) => script !== void 0);
|
|
71
71
|
userConfig.metadata = userConfig.metadata || {};
|
|
72
|
+
const globalTags = Array.isArray(userConfig.tag) ? userConfig.tag : userConfig.tag ? [userConfig.tag] : [];
|
|
73
|
+
for (const tag of globalTags) {
|
|
74
|
+
if (tag[0] !== "@")
|
|
75
|
+
throw new Error(`Tag must start with "@" symbol, got "${tag}" instead.`);
|
|
76
|
+
}
|
|
72
77
|
this.config = {
|
|
73
78
|
configFile: resolvedConfigFile,
|
|
74
79
|
rootDir: pathResolve(configDir, userConfig.testDir) || configDir,
|
|
@@ -91,6 +96,7 @@ class FullConfigInternal {
|
|
|
91
96
|
quiet: takeFirst(configCLIOverrides.quiet, userConfig.quiet, false),
|
|
92
97
|
projects: [],
|
|
93
98
|
shard: takeFirst(configCLIOverrides.shard, userConfig.shard, null),
|
|
99
|
+
tags: globalTags,
|
|
94
100
|
updateSnapshots: takeFirst(configCLIOverrides.updateSnapshots, userConfig.updateSnapshots, "missing"),
|
|
95
101
|
updateSourceMethod: takeFirst(configCLIOverrides.updateSourceMethod, userConfig.updateSourceMethod, "patch"),
|
|
96
102
|
version: require("../../package.json").version,
|
package/lib/common/testLoader.js
CHANGED
|
@@ -42,12 +42,13 @@ var import_transform = require("../transform/transform");
|
|
|
42
42
|
var import_util2 = require("../util");
|
|
43
43
|
const defaultTimeout = 3e4;
|
|
44
44
|
const cachedFileSuites = /* @__PURE__ */ new Map();
|
|
45
|
-
async function loadTestFile(file,
|
|
45
|
+
async function loadTestFile(file, config, testErrors) {
|
|
46
46
|
if (cachedFileSuites.has(file))
|
|
47
47
|
return cachedFileSuites.get(file);
|
|
48
|
-
const suite = new import_test.Suite(import_path.default.relative(rootDir, file) || import_path.default.basename(file), "file");
|
|
48
|
+
const suite = new import_test.Suite(import_path.default.relative(config.config.rootDir, file) || import_path.default.basename(file), "file");
|
|
49
49
|
suite._requireFile = file;
|
|
50
50
|
suite.location = { file, line: 0, column: 0 };
|
|
51
|
+
suite._tags = [...config.config.tags];
|
|
51
52
|
(0, import_globals.setCurrentlyLoadingFileSuite)(suite);
|
|
52
53
|
if (!(0, import_globals.isWorkerProcess)()) {
|
|
53
54
|
(0, import_compilationCache.startCollectingFileDeps)();
|
package/lib/common/testType.js
CHANGED
|
@@ -29,6 +29,7 @@ var import_globals = require("./globals");
|
|
|
29
29
|
var import_test = require("./test");
|
|
30
30
|
var import_expect = require("../matchers/expect");
|
|
31
31
|
var import_transform = require("../transform/transform");
|
|
32
|
+
var import_validators = require("./validators");
|
|
32
33
|
const testTypeSymbol = Symbol("testType");
|
|
33
34
|
class TestTypeImpl {
|
|
34
35
|
constructor(fixtures) {
|
|
@@ -96,7 +97,7 @@ class TestTypeImpl {
|
|
|
96
97
|
body = fn;
|
|
97
98
|
details = fnOrDetails;
|
|
98
99
|
}
|
|
99
|
-
const validatedDetails = validateTestDetails(details, location);
|
|
100
|
+
const validatedDetails = (0, import_validators.validateTestDetails)(details, location);
|
|
100
101
|
const test = new import_test.TestCase(title, body, this, location);
|
|
101
102
|
test._requireFile = suite._requireFile;
|
|
102
103
|
test.annotations.push(...validatedDetails.annotations);
|
|
@@ -130,7 +131,7 @@ class TestTypeImpl {
|
|
|
130
131
|
details = fnOrDetails;
|
|
131
132
|
body = fn;
|
|
132
133
|
}
|
|
133
|
-
const validatedDetails = validateTestDetails(details, location);
|
|
134
|
+
const validatedDetails = (0, import_validators.validateTestDetails)(details, location);
|
|
134
135
|
const child = new import_test.Suite(title, "describe");
|
|
135
136
|
child._requireFile = suite._requireFile;
|
|
136
137
|
child.location = location;
|
|
@@ -276,16 +277,6 @@ See https://playwright.dev/docs/intro for more information about Playwright Test
|
|
|
276
277
|
);
|
|
277
278
|
}
|
|
278
279
|
}
|
|
279
|
-
function validateTestDetails(details, location) {
|
|
280
|
-
const originalAnnotations = Array.isArray(details.annotation) ? details.annotation : details.annotation ? [details.annotation] : [];
|
|
281
|
-
const annotations = originalAnnotations.map((annotation) => ({ ...annotation, location }));
|
|
282
|
-
const tags = Array.isArray(details.tag) ? details.tag : details.tag ? [details.tag] : [];
|
|
283
|
-
for (const tag of tags) {
|
|
284
|
-
if (tag[0] !== "@")
|
|
285
|
-
throw new Error(`Tag must start with "@" symbol, got "${tag}" instead.`);
|
|
286
|
-
}
|
|
287
|
-
return { annotations, tags };
|
|
288
|
-
}
|
|
289
280
|
const rootTestType = new TestTypeImpl([]);
|
|
290
281
|
function mergeTests(...tests) {
|
|
291
282
|
let result = rootTestType;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var validators_exports = {};
|
|
20
|
+
__export(validators_exports, {
|
|
21
|
+
validateTestAnnotation: () => validateTestAnnotation,
|
|
22
|
+
validateTestDetails: () => validateTestDetails
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(validators_exports);
|
|
25
|
+
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
26
|
+
const testAnnotationSchema = import_utilsBundle.zod.object({
|
|
27
|
+
type: import_utilsBundle.zod.string(),
|
|
28
|
+
description: import_utilsBundle.zod.string().optional()
|
|
29
|
+
});
|
|
30
|
+
const testDetailsSchema = import_utilsBundle.zod.object({
|
|
31
|
+
tag: import_utilsBundle.zod.union([
|
|
32
|
+
import_utilsBundle.zod.string().optional(),
|
|
33
|
+
import_utilsBundle.zod.array(import_utilsBundle.zod.string())
|
|
34
|
+
]).transform((val) => Array.isArray(val) ? val : val !== void 0 ? [val] : []).refine((val) => val.every((v) => v.startsWith("@")), {
|
|
35
|
+
message: "Tag must start with '@'"
|
|
36
|
+
}),
|
|
37
|
+
annotation: import_utilsBundle.zod.union([
|
|
38
|
+
testAnnotationSchema,
|
|
39
|
+
import_utilsBundle.zod.array(testAnnotationSchema).optional()
|
|
40
|
+
]).transform((val) => Array.isArray(val) ? val : val !== void 0 ? [val] : [])
|
|
41
|
+
});
|
|
42
|
+
function validateTestAnnotation(annotation) {
|
|
43
|
+
try {
|
|
44
|
+
return testAnnotationSchema.parse(annotation);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throwZodError(error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function validateTestDetails(details, location) {
|
|
50
|
+
try {
|
|
51
|
+
const parsedDetails = testDetailsSchema.parse(details);
|
|
52
|
+
return {
|
|
53
|
+
annotations: parsedDetails.annotation.map((a) => ({ ...a, location })),
|
|
54
|
+
tags: parsedDetails.tag,
|
|
55
|
+
location
|
|
56
|
+
};
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throwZodError(error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function throwZodError(error) {
|
|
62
|
+
throw new Error(error.issues.map((i) => i.message).join("\n"));
|
|
63
|
+
}
|
|
64
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
65
|
+
0 && (module.exports = {
|
|
66
|
+
validateTestAnnotation,
|
|
67
|
+
validateTestDetails
|
|
68
|
+
});
|
package/lib/loader/loaderMain.js
CHANGED
|
@@ -42,7 +42,7 @@ class LoaderMain extends import_process.ProcessRunner {
|
|
|
42
42
|
async loadTestFile(params) {
|
|
43
43
|
const testErrors = [];
|
|
44
44
|
const config = await this._config();
|
|
45
|
-
const fileSuite = await (0, import_testLoader.loadTestFile)(params.file, config
|
|
45
|
+
const fileSuite = await (0, import_testLoader.loadTestFile)(params.file, config, testErrors);
|
|
46
46
|
this._poolBuilder.buildPools(fileSuite);
|
|
47
47
|
return { fileSuite: fileSuite._deepSerialize(), testErrors };
|
|
48
48
|
}
|
package/lib/reporters/junit.js
CHANGED
|
@@ -173,8 +173,10 @@ class JUnitReporter {
|
|
|
173
173
|
const systemOut = [];
|
|
174
174
|
const systemErr = [];
|
|
175
175
|
for (const result of test.results) {
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
for (const item of result.stdout)
|
|
177
|
+
systemOut.push(item.toString());
|
|
178
|
+
for (const item of result.stderr)
|
|
179
|
+
systemErr.push(item.toString());
|
|
178
180
|
for (const attachment of result.attachments) {
|
|
179
181
|
if (!attachment.path)
|
|
180
182
|
continue;
|
package/lib/reporters/merge.js
CHANGED
|
@@ -72,13 +72,15 @@ async function createMergedReport(config, dir, reporterDescriptions, rootDirOver
|
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
await dispatchEvents(eventData.prologue);
|
|
75
|
-
for (const { reportFile, eventPatchers, metadata } of eventData.reports) {
|
|
75
|
+
for (const { reportFile, eventPatchers, metadata, tags } of eventData.reports) {
|
|
76
76
|
const reportJsonl = await import_fs.default.promises.readFile(reportFile);
|
|
77
77
|
const events = parseTestEvents(reportJsonl);
|
|
78
78
|
new import_stringInternPool.JsonStringInternalizer(stringPool).traverse(events);
|
|
79
79
|
eventPatchers.patchers.push(new AttachmentPathPatcher(dir));
|
|
80
80
|
if (metadata.name)
|
|
81
81
|
eventPatchers.patchers.push(new GlobalErrorPatcher(metadata.name));
|
|
82
|
+
if (tags.length)
|
|
83
|
+
eventPatchers.patchers.push(new GlobalErrorPatcher(tags.join(" ")));
|
|
82
84
|
eventPatchers.patchEvents(events);
|
|
83
85
|
await dispatchEvents(events);
|
|
84
86
|
}
|
|
@@ -178,18 +180,22 @@ async function mergeEvents(dir, shardReportFiles, stringPool, printStatus, rootD
|
|
|
178
180
|
if (rootDirOverride)
|
|
179
181
|
eventPatchers.patchers.push(new PathSeparatorPatcher(metadata.pathSeparator));
|
|
180
182
|
eventPatchers.patchEvents(parsedEvents);
|
|
183
|
+
let tags = [];
|
|
181
184
|
for (const event of parsedEvents) {
|
|
182
|
-
if (event.method === "onConfigure")
|
|
185
|
+
if (event.method === "onConfigure") {
|
|
183
186
|
configureEvents.push(event);
|
|
184
|
-
|
|
187
|
+
tags = event.params.config.tags || [];
|
|
188
|
+
} else if (event.method === "onProject") {
|
|
185
189
|
projectEvents.push(event);
|
|
186
|
-
else if (event.method === "onEnd")
|
|
190
|
+
} else if (event.method === "onEnd") {
|
|
187
191
|
endEvents.push(event);
|
|
192
|
+
}
|
|
188
193
|
}
|
|
189
194
|
reports.push({
|
|
190
195
|
eventPatchers,
|
|
191
196
|
reportFile: localPath,
|
|
192
|
-
metadata
|
|
197
|
+
metadata,
|
|
198
|
+
tags
|
|
193
199
|
});
|
|
194
200
|
}
|
|
195
201
|
return {
|
|
@@ -154,7 +154,8 @@ class TeleReporterEmitter {
|
|
|
154
154
|
version: config.version,
|
|
155
155
|
workers: config.workers,
|
|
156
156
|
globalSetup: config.globalSetup,
|
|
157
|
-
globalTeardown: config.globalTeardown
|
|
157
|
+
globalTeardown: config.globalTeardown,
|
|
158
|
+
tags: config.tags
|
|
158
159
|
};
|
|
159
160
|
}
|
|
160
161
|
_serializeProject(suite) {
|
package/lib/runner/loadUtils.js
CHANGED
|
@@ -159,7 +159,8 @@ async function createRootSuite(testRun, errors, shouldFilterOnly) {
|
|
|
159
159
|
if (config.config.shard) {
|
|
160
160
|
const testGroups = [];
|
|
161
161
|
for (const projectSuite of rootSuite.suites) {
|
|
162
|
-
|
|
162
|
+
for (const group of (0, import_testGroups.createTestGroups)(projectSuite, config.config.shard.total))
|
|
163
|
+
testGroups.push(group);
|
|
163
164
|
}
|
|
164
165
|
const testGroupsInThisShard = (0, import_testGroups.filterForShard)(config.config.shard, testGroups);
|
|
165
166
|
const testsInThisShard = /* @__PURE__ */ new Set();
|
package/lib/runner/loaderHost.js
CHANGED
|
@@ -48,7 +48,7 @@ class InProcessLoaderHost {
|
|
|
48
48
|
return true;
|
|
49
49
|
}
|
|
50
50
|
async loadTestFile(file, testErrors) {
|
|
51
|
-
const result = await (0, import_testLoader.loadTestFile)(file, this._config
|
|
51
|
+
const result = await (0, import_testLoader.loadTestFile)(file, this._config, testErrors);
|
|
52
52
|
this._poolBuilder.buildPools(result, testErrors);
|
|
53
53
|
return result;
|
|
54
54
|
}
|
package/lib/runner/reporters.js
CHANGED
|
@@ -125,6 +125,8 @@ function computeCommandHash(config) {
|
|
|
125
125
|
command.cliGrepInvert = config.cliGrepInvert;
|
|
126
126
|
if (config.cliOnlyChanged)
|
|
127
127
|
command.cliOnlyChanged = config.cliOnlyChanged;
|
|
128
|
+
if (config.config.tags.length)
|
|
129
|
+
command.tags = config.config.tags.join(" ");
|
|
128
130
|
if (Object.keys(command).length)
|
|
129
131
|
parts.push((0, import_utils.calculateSha1)(JSON.stringify(command)).substring(0, 7));
|
|
130
132
|
return parts.join("-");
|
package/lib/worker/workerMain.js
CHANGED
|
@@ -179,7 +179,7 @@ class WorkerMain extends import_process.ProcessRunner {
|
|
|
179
179
|
let fatalUnknownTestIds;
|
|
180
180
|
try {
|
|
181
181
|
await this._loadIfNeeded();
|
|
182
|
-
const fileSuite = await (0, import_testLoader.loadTestFile)(runPayload.file, this._config
|
|
182
|
+
const fileSuite = await (0, import_testLoader.loadTestFile)(runPayload.file, this._config);
|
|
183
183
|
const suite = (0, import_suiteUtils.bindFileSuiteToProject)(this._project, fileSuite);
|
|
184
184
|
if (this._params.repeatEachIndex)
|
|
185
185
|
(0, import_suiteUtils.applyRepeatEachIndex)(this._project, suite, this._params.repeatEachIndex);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright",
|
|
3
|
-
"version": "1.57.0-alpha-2025-10-
|
|
3
|
+
"version": "1.57.0-alpha-2025-10-22",
|
|
4
4
|
"description": "A high-level API to automate web browsers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"license": "Apache-2.0",
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"playwright-core": "1.57.0-alpha-2025-10-
|
|
67
|
+
"playwright-core": "1.57.0-alpha-2025-10-22"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
70
|
"fsevents": "2.3.2"
|
package/types/test.d.ts
CHANGED
|
@@ -1758,6 +1758,26 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
|
|
|
1758
1758
|
*/
|
|
1759
1759
|
snapshotPathTemplate?: string;
|
|
1760
1760
|
|
|
1761
|
+
/**
|
|
1762
|
+
* Tag or tags prepended to each test in the report. Useful for tagging your test run to differentiate between
|
|
1763
|
+
* [CI environments](https://playwright.dev/docs/test-sharding#merging-reports-from-multiple-environments).
|
|
1764
|
+
*
|
|
1765
|
+
* Note that each tag must start with `@` symbol. Learn more about [tagging](https://playwright.dev/docs/test-annotations#tag-tests).
|
|
1766
|
+
*
|
|
1767
|
+
* **Usage**
|
|
1768
|
+
*
|
|
1769
|
+
* ```js
|
|
1770
|
+
* // playwright.config.ts
|
|
1771
|
+
* import { defineConfig } from '@playwright/test';
|
|
1772
|
+
*
|
|
1773
|
+
* export default defineConfig({
|
|
1774
|
+
* tag: process.env.CI_ENVIRONMENT_NAME, // for example "@APIv2"
|
|
1775
|
+
* });
|
|
1776
|
+
* ```
|
|
1777
|
+
*
|
|
1778
|
+
*/
|
|
1779
|
+
tag?: string|Array<string>;
|
|
1780
|
+
|
|
1761
1781
|
/**
|
|
1762
1782
|
* Directory that will be recursively scanned for test files. Defaults to the directory of the configuration file.
|
|
1763
1783
|
*
|
|
@@ -2035,6 +2055,11 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
|
|
|
2035
2055
|
current: number;
|
|
2036
2056
|
};
|
|
2037
2057
|
|
|
2058
|
+
/**
|
|
2059
|
+
* Resolved global tags. See [testConfig.tag](https://playwright.dev/docs/api/class-testconfig#test-config-tag).
|
|
2060
|
+
*/
|
|
2061
|
+
tags: Array<string>;
|
|
2062
|
+
|
|
2038
2063
|
/**
|
|
2039
2064
|
* See [testConfig.updateSnapshots](https://playwright.dev/docs/api/class-testconfig#test-config-update-snapshots).
|
|
2040
2065
|
*/
|
|
@@ -6180,7 +6205,7 @@ export interface TestType<TestArgs extends {}, WorkerArgs extends {}> {
|
|
|
6180
6205
|
* const name = this.constructor.name + '.' + (context.name as string);
|
|
6181
6206
|
* return test.step(name, async () => {
|
|
6182
6207
|
* return await target.call(this, ...args);
|
|
6183
|
-
* });
|
|
6208
|
+
* }, { box: true });
|
|
6184
6209
|
* };
|
|
6185
6210
|
* }
|
|
6186
6211
|
*
|
|
@@ -6339,7 +6364,7 @@ export interface TestType<TestArgs extends {}, WorkerArgs extends {}> {
|
|
|
6339
6364
|
* const name = this.constructor.name + '.' + (context.name as string);
|
|
6340
6365
|
* return test.step(name, async () => {
|
|
6341
6366
|
* return await target.call(this, ...args);
|
|
6342
|
-
* });
|
|
6367
|
+
* }, { box: true });
|
|
6343
6368
|
* };
|
|
6344
6369
|
* }
|
|
6345
6370
|
*
|
package/types/testReporter.d.ts
CHANGED
|
@@ -127,6 +127,11 @@ export interface FullResult {
|
|
|
127
127
|
* `false`. This way, Playwright will use one of the standard terminal reporters in addition to your custom reporter
|
|
128
128
|
* to enhance user experience.
|
|
129
129
|
*
|
|
130
|
+
* **Reporter errors**
|
|
131
|
+
*
|
|
132
|
+
* Playwright will swallow any errors thrown in your custom reporter methods. If you need to detect or fail on
|
|
133
|
+
* reporter errors, you must wrap and handle them yourself.
|
|
134
|
+
*
|
|
130
135
|
* **Merged report API notes**
|
|
131
136
|
*
|
|
132
137
|
* When merging multiple [`blob`](https://playwright.dev/docs/test-reporters#blob-reporter) reports via
|