playwright 1.54.0-alpha-2025-07-07 → 1.54.0-alpha-2025-07-09
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.
Potentially problematic release.
This version of playwright might be problematic. Click here for more details.
- package/lib/common/test.js +10 -2
- package/lib/common/testType.js +11 -10
- package/lib/isomorphic/teleReceiver.js +9 -1
- package/lib/matchers/toMatchSnapshot.js +5 -5
- package/lib/reporters/base.js +58 -11
- package/lib/reporters/html.js +19 -5
- package/lib/reporters/merge.js +11 -2
- package/lib/reporters/teleEmitter.js +9 -3
- package/lib/runner/loadUtils.js +2 -2
- package/lib/worker/testInfo.js +16 -23
- package/lib/worker/workerMain.js +1 -1
- package/package.json +2 -2
- package/types/test.d.ts +9 -2
- package/types/testReporter.d.ts +15 -0
package/lib/common/test.js
CHANGED
|
@@ -237,7 +237,11 @@ class TestCase extends Base {
|
|
|
237
237
|
return status === "expected" || status === "flaky" || status === "skipped";
|
|
238
238
|
}
|
|
239
239
|
get tags() {
|
|
240
|
-
|
|
240
|
+
const titleTags = this._grepBaseTitlePath().join(" ").match(/@[\S]+/g) || [];
|
|
241
|
+
return [
|
|
242
|
+
...titleTags,
|
|
243
|
+
...this._tags
|
|
244
|
+
];
|
|
241
245
|
}
|
|
242
246
|
_serialize() {
|
|
243
247
|
return {
|
|
@@ -298,10 +302,14 @@ class TestCase extends Base {
|
|
|
298
302
|
this.results.push(result);
|
|
299
303
|
return result;
|
|
300
304
|
}
|
|
301
|
-
|
|
305
|
+
_grepBaseTitlePath() {
|
|
302
306
|
const path = [];
|
|
303
307
|
this.parent._collectGrepTitlePath(path);
|
|
304
308
|
path.push(this.title);
|
|
309
|
+
return path;
|
|
310
|
+
}
|
|
311
|
+
_grepTitleWithTags() {
|
|
312
|
+
const path = this._grepBaseTitlePath();
|
|
305
313
|
path.push(...this._tags);
|
|
306
314
|
return path.join(" ");
|
|
307
315
|
}
|
package/lib/common/testType.js
CHANGED
|
@@ -96,7 +96,7 @@ class TestTypeImpl {
|
|
|
96
96
|
body = fn;
|
|
97
97
|
details = fnOrDetails;
|
|
98
98
|
}
|
|
99
|
-
const validatedDetails = validateTestDetails(details);
|
|
99
|
+
const validatedDetails = validateTestDetails(details, location);
|
|
100
100
|
const test = new import_test.TestCase(title, body, this, location);
|
|
101
101
|
test._requireFile = suite._requireFile;
|
|
102
102
|
test.annotations.push(...validatedDetails.annotations);
|
|
@@ -105,9 +105,9 @@ class TestTypeImpl {
|
|
|
105
105
|
if (type === "only" || type === "fail.only")
|
|
106
106
|
test._only = true;
|
|
107
107
|
if (type === "skip" || type === "fixme" || type === "fail")
|
|
108
|
-
test.annotations.push({ type });
|
|
108
|
+
test.annotations.push({ type, location });
|
|
109
109
|
else if (type === "fail.only")
|
|
110
|
-
test.annotations.push({ type: "fail" });
|
|
110
|
+
test.annotations.push({ type: "fail", location });
|
|
111
111
|
}
|
|
112
112
|
_describe(type, location, titleOrFn, fnOrDetails, fn) {
|
|
113
113
|
throwIfRunningInsideJest();
|
|
@@ -130,7 +130,7 @@ class TestTypeImpl {
|
|
|
130
130
|
details = fnOrDetails;
|
|
131
131
|
body = fn;
|
|
132
132
|
}
|
|
133
|
-
const validatedDetails = validateTestDetails(details);
|
|
133
|
+
const validatedDetails = validateTestDetails(details, location);
|
|
134
134
|
const child = new import_test.Suite(title, "describe");
|
|
135
135
|
child._requireFile = suite._requireFile;
|
|
136
136
|
child.location = location;
|
|
@@ -144,7 +144,7 @@ class TestTypeImpl {
|
|
|
144
144
|
if (type === "parallel" || type === "parallel.only")
|
|
145
145
|
child._parallelMode = "parallel";
|
|
146
146
|
if (type === "skip" || type === "fixme")
|
|
147
|
-
child._staticAnnotations.push({ type });
|
|
147
|
+
child._staticAnnotations.push({ type, location });
|
|
148
148
|
for (let parent = suite; parent; parent = parent.parent) {
|
|
149
149
|
if (parent._parallelMode === "serial" && child._parallelMode === "parallel")
|
|
150
150
|
throw new Error("describe.parallel cannot be nested inside describe.serial");
|
|
@@ -203,7 +203,7 @@ class TestTypeImpl {
|
|
|
203
203
|
if (modifierArgs.length >= 1 && !modifierArgs[0])
|
|
204
204
|
return;
|
|
205
205
|
const description = modifierArgs[1];
|
|
206
|
-
suite._staticAnnotations.push({ type, description });
|
|
206
|
+
suite._staticAnnotations.push({ type, description, location });
|
|
207
207
|
}
|
|
208
208
|
return;
|
|
209
209
|
}
|
|
@@ -212,7 +212,7 @@ class TestTypeImpl {
|
|
|
212
212
|
throw new Error(`test.${type}() can only be called inside test, describe block or fixture`);
|
|
213
213
|
if (typeof modifierArgs[0] === "function")
|
|
214
214
|
throw new Error(`test.${type}() with a function can only be called inside describe block`);
|
|
215
|
-
testInfo
|
|
215
|
+
testInfo._modifier(type, location, modifierArgs);
|
|
216
216
|
}
|
|
217
217
|
_setTimeout(location, timeout) {
|
|
218
218
|
const suite = (0, import_globals.currentlyLoadingFileSuite)();
|
|
@@ -241,7 +241,7 @@ class TestTypeImpl {
|
|
|
241
241
|
let result = void 0;
|
|
242
242
|
result = await (0, import_utils.raceAgainstDeadline)(async () => {
|
|
243
243
|
try {
|
|
244
|
-
return await step.info._runStepBody(expectation === "skip", body);
|
|
244
|
+
return await step.info._runStepBody(expectation === "skip", body, step.location);
|
|
245
245
|
} catch (e) {
|
|
246
246
|
if (result?.timedOut)
|
|
247
247
|
testInfo._failWithError(e);
|
|
@@ -276,8 +276,9 @@ See https://playwright.dev/docs/intro for more information about Playwright Test
|
|
|
276
276
|
);
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
-
function validateTestDetails(details) {
|
|
280
|
-
const
|
|
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 }));
|
|
281
282
|
const tags = Array.isArray(details.tag) ? details.tag : details.tag ? [details.tag] : [];
|
|
282
283
|
for (const tag of tags) {
|
|
283
284
|
if (tag[0] !== "@")
|
|
@@ -128,8 +128,9 @@ class TeleReporterReceiver {
|
|
|
128
128
|
if (!!payload.attachments)
|
|
129
129
|
result.attachments = this._parseAttachments(payload.attachments);
|
|
130
130
|
if (payload.annotations) {
|
|
131
|
+
this._absoluteAnnotationLocationsInplace(payload.annotations);
|
|
131
132
|
result.annotations = payload.annotations;
|
|
132
|
-
test.annotations =
|
|
133
|
+
test.annotations = payload.annotations;
|
|
133
134
|
}
|
|
134
135
|
this._reporter.onTestEnd?.(test, result);
|
|
135
136
|
result._stepMap = /* @__PURE__ */ new Map();
|
|
@@ -257,8 +258,15 @@ class TeleReporterReceiver {
|
|
|
257
258
|
test.retries = payload.retries;
|
|
258
259
|
test.tags = payload.tags ?? [];
|
|
259
260
|
test.annotations = payload.annotations ?? [];
|
|
261
|
+
this._absoluteAnnotationLocationsInplace(test.annotations);
|
|
260
262
|
return test;
|
|
261
263
|
}
|
|
264
|
+
_absoluteAnnotationLocationsInplace(annotations) {
|
|
265
|
+
for (const annotation of annotations) {
|
|
266
|
+
if (annotation.location)
|
|
267
|
+
annotation.location = this._absoluteLocation(annotation.location);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
262
270
|
_absoluteLocation(location) {
|
|
263
271
|
if (!location)
|
|
264
272
|
return location;
|
|
@@ -60,6 +60,7 @@ class SnapshotHelper {
|
|
|
60
60
|
this.options = options;
|
|
61
61
|
name = nameFromOptions;
|
|
62
62
|
}
|
|
63
|
+
this.name = Array.isArray(name) ? name.join(import_path.default.sep) : name || "";
|
|
63
64
|
const resolvedPaths = testInfo._resolveSnapshotPaths(matcherName === "toHaveScreenshot" ? "screenshot" : "snapshot", name, "updateSnapshotIndex", anonymousSnapshotExtension);
|
|
64
65
|
this.expectedPath = resolvedPaths.absoluteSnapshotPath;
|
|
65
66
|
this.attachmentBaseName = resolvedPaths.relativeOutputPath;
|
|
@@ -140,26 +141,25 @@ class SnapshotHelper {
|
|
|
140
141
|
}
|
|
141
142
|
handleDifferent(actual, expected, previous, diff, header, diffError, log, step) {
|
|
142
143
|
const output = [`${header}${indent(diffError, " ")}`];
|
|
144
|
+
if (this.name) {
|
|
145
|
+
output.push("");
|
|
146
|
+
output.push(` Snapshot: ${this.name}`);
|
|
147
|
+
}
|
|
143
148
|
if (expected !== void 0) {
|
|
144
149
|
writeFileSync(this.legacyExpectedPath, expected);
|
|
145
150
|
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-expected"), contentType: this.mimeType, path: this.expectedPath });
|
|
146
|
-
output.push(`
|
|
147
|
-
Expected: ${import_utils2.colors.yellow(this.expectedPath)}`);
|
|
148
151
|
}
|
|
149
152
|
if (previous !== void 0) {
|
|
150
153
|
writeFileSync(this.previousPath, previous);
|
|
151
154
|
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-previous"), contentType: this.mimeType, path: this.previousPath });
|
|
152
|
-
output.push(`Previous: ${import_utils2.colors.yellow(this.previousPath)}`);
|
|
153
155
|
}
|
|
154
156
|
if (actual !== void 0) {
|
|
155
157
|
writeFileSync(this.actualPath, actual);
|
|
156
158
|
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-actual"), contentType: this.mimeType, path: this.actualPath });
|
|
157
|
-
output.push(`Received: ${import_utils2.colors.yellow(this.actualPath)}`);
|
|
158
159
|
}
|
|
159
160
|
if (diff !== void 0) {
|
|
160
161
|
writeFileSync(this.diffPath, diff);
|
|
161
162
|
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-diff"), contentType: this.mimeType, path: this.diffPath });
|
|
162
|
-
output.push(` Diff: ${import_utils2.colors.yellow(this.diffPath)}`);
|
|
163
163
|
}
|
|
164
164
|
if (log?.length)
|
|
165
165
|
output.push((0, import_util.callLogText)(log));
|
package/lib/reporters/base.js
CHANGED
|
@@ -307,8 +307,9 @@ function formatFailure(screen, config, test, index) {
|
|
|
307
307
|
resultLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
|
308
308
|
}
|
|
309
309
|
resultLines.push(...errors.map((error) => "\n" + error.message));
|
|
310
|
-
|
|
311
|
-
|
|
310
|
+
const attachmentGroups = groupAttachments(result.attachments);
|
|
311
|
+
for (let i = 0; i < attachmentGroups.length; ++i) {
|
|
312
|
+
const attachment = attachmentGroups[i];
|
|
312
313
|
if (attachment.name === "error-context" && attachment.path) {
|
|
313
314
|
resultLines.push("");
|
|
314
315
|
resultLines.push(screen.colors.dim(` Error Context: ${relativeFilePath(screen, config, attachment.path)}`));
|
|
@@ -320,15 +321,30 @@ function formatFailure(screen, config, test, index) {
|
|
|
320
321
|
if (!attachment.path && !hasPrintableContent)
|
|
321
322
|
continue;
|
|
322
323
|
resultLines.push("");
|
|
323
|
-
resultLines.push(screen.colors.
|
|
324
|
-
if (attachment.path) {
|
|
325
|
-
|
|
326
|
-
|
|
324
|
+
resultLines.push(screen.colors.dim(separator(screen, ` attachment #${i + 1}: ${screen.colors.bold(attachment.name)} (${attachment.contentType})`)));
|
|
325
|
+
if (attachment.actual?.path) {
|
|
326
|
+
if (attachment.expected?.path) {
|
|
327
|
+
const expectedPath = relativeFilePath(screen, config, attachment.expected.path);
|
|
328
|
+
resultLines.push(screen.colors.dim(` Expected: ${expectedPath}`));
|
|
329
|
+
}
|
|
330
|
+
const actualPath = relativeFilePath(screen, config, attachment.actual.path);
|
|
331
|
+
resultLines.push(screen.colors.dim(` Received: ${actualPath}`));
|
|
332
|
+
if (attachment.previous?.path) {
|
|
333
|
+
const previousPath = relativeFilePath(screen, config, attachment.previous.path);
|
|
334
|
+
resultLines.push(screen.colors.dim(` Previous: ${previousPath}`));
|
|
335
|
+
}
|
|
336
|
+
if (attachment.diff?.path) {
|
|
337
|
+
const diffPath = relativeFilePath(screen, config, attachment.diff.path);
|
|
338
|
+
resultLines.push(screen.colors.dim(` Diff: ${diffPath}`));
|
|
339
|
+
}
|
|
340
|
+
} else if (attachment.path) {
|
|
341
|
+
const relativePath = relativeFilePath(screen, config, attachment.path);
|
|
342
|
+
resultLines.push(screen.colors.dim(` ${relativePath}`));
|
|
327
343
|
if (attachment.name === "trace") {
|
|
328
344
|
const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)();
|
|
329
|
-
resultLines.push(screen.colors.
|
|
345
|
+
resultLines.push(screen.colors.dim(` Usage:`));
|
|
330
346
|
resultLines.push("");
|
|
331
|
-
resultLines.push(screen.colors.
|
|
347
|
+
resultLines.push(screen.colors.dim(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`));
|
|
332
348
|
resultLines.push("");
|
|
333
349
|
}
|
|
334
350
|
} else {
|
|
@@ -337,10 +353,10 @@ function formatFailure(screen, config, test, index) {
|
|
|
337
353
|
if (text.length > 300)
|
|
338
354
|
text = text.slice(0, 300) + "...";
|
|
339
355
|
for (const line of text.split("\n"))
|
|
340
|
-
resultLines.push(screen.colors.
|
|
356
|
+
resultLines.push(screen.colors.dim(` ${line}`));
|
|
341
357
|
}
|
|
342
358
|
}
|
|
343
|
-
resultLines.push(screen.colors.
|
|
359
|
+
resultLines.push(screen.colors.dim(separator(screen, " ")));
|
|
344
360
|
}
|
|
345
361
|
lines.push(...resultLines);
|
|
346
362
|
}
|
|
@@ -460,7 +476,7 @@ function separator(screen, text = "") {
|
|
|
460
476
|
if (text)
|
|
461
477
|
text += " ";
|
|
462
478
|
const columns = Math.min(100, screen.ttyWidth || 100);
|
|
463
|
-
return text + screen.colors.dim("\u2500".repeat(Math.max(0, columns - text.length)));
|
|
479
|
+
return text + screen.colors.dim("\u2500".repeat(Math.max(0, columns - (0, import_util.stripAnsiEscapes)(text).length)));
|
|
464
480
|
}
|
|
465
481
|
function indent(lines, tab) {
|
|
466
482
|
return lines.replace(/^(?=.+$)/gm, tab);
|
|
@@ -537,6 +553,37 @@ function resolveOutputFile(reporterName, options) {
|
|
|
537
553
|
outputFile = import_path.default.resolve(outputDir, reportName);
|
|
538
554
|
return { outputFile, outputDir };
|
|
539
555
|
}
|
|
556
|
+
function groupAttachments(attachments) {
|
|
557
|
+
const result = [];
|
|
558
|
+
const attachmentsByPrefix = /* @__PURE__ */ new Map();
|
|
559
|
+
for (const attachment of attachments) {
|
|
560
|
+
if (!attachment.path) {
|
|
561
|
+
result.push(attachment);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/);
|
|
565
|
+
if (!match) {
|
|
566
|
+
result.push(attachment);
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
const [, name, category] = match;
|
|
570
|
+
let group = attachmentsByPrefix.get(name);
|
|
571
|
+
if (!group) {
|
|
572
|
+
group = { ...attachment, name };
|
|
573
|
+
attachmentsByPrefix.set(name, group);
|
|
574
|
+
result.push(group);
|
|
575
|
+
}
|
|
576
|
+
if (category === "expected")
|
|
577
|
+
group.expected = attachment;
|
|
578
|
+
else if (category === "actual")
|
|
579
|
+
group.actual = attachment;
|
|
580
|
+
else if (category === "diff")
|
|
581
|
+
group.diff = attachment;
|
|
582
|
+
else if (category === "previous")
|
|
583
|
+
group.previous = attachment;
|
|
584
|
+
}
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
540
587
|
// Annotate the CommonJS export names for ESM import in node:
|
|
541
588
|
0 && (module.exports = {
|
|
542
589
|
TerminalReporter,
|
package/lib/reporters/html.js
CHANGED
|
@@ -107,7 +107,13 @@ class HtmlReporter {
|
|
|
107
107
|
async onEnd(result) {
|
|
108
108
|
const projectSuites = this.suite.suites;
|
|
109
109
|
await (0, import_utils.removeFolders)([this._outputFolder]);
|
|
110
|
-
|
|
110
|
+
let noSnippets;
|
|
111
|
+
if (process.env.PLAYWRIGHT_HTML_NO_SNIPPETS === "false" || process.env.PLAYWRIGHT_HTML_NO_SNIPPETS === "0")
|
|
112
|
+
noSnippets = false;
|
|
113
|
+
else if (process.env.PLAYWRIGHT_HTML_NO_SNIPPETS)
|
|
114
|
+
noSnippets = true;
|
|
115
|
+
noSnippets = noSnippets || this._options.noSnippets;
|
|
116
|
+
const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL, process.env.PLAYWRIGHT_HTML_TITLE || this._options.title, noSnippets);
|
|
111
117
|
this._buildResult = await builder.build(this.config.metadata, projectSuites, result, this._topLevelErrors);
|
|
112
118
|
}
|
|
113
119
|
async onExit() {
|
|
@@ -191,12 +197,12 @@ function startHtmlReportServer(folder) {
|
|
|
191
197
|
return server;
|
|
192
198
|
}
|
|
193
199
|
class HtmlBuilder {
|
|
194
|
-
constructor(config, outputDir, attachmentsBaseURL, title,
|
|
200
|
+
constructor(config, outputDir, attachmentsBaseURL, title, noSnippets = false) {
|
|
195
201
|
this._stepsInFile = new import_utils.MultiMap();
|
|
196
202
|
this._hasTraces = false;
|
|
197
203
|
this._config = config;
|
|
198
204
|
this._reportFolder = outputDir;
|
|
199
|
-
this.
|
|
205
|
+
this._noSnippets = noSnippets;
|
|
200
206
|
import_fs.default.mkdirSync(this._reportFolder, { recursive: true });
|
|
201
207
|
this._dataZipFile = new import_zipBundle.yazl.ZipFile();
|
|
202
208
|
this._attachmentsBaseURL = attachmentsBaseURL;
|
|
@@ -225,7 +231,7 @@ class HtmlBuilder {
|
|
|
225
231
|
}
|
|
226
232
|
}
|
|
227
233
|
}
|
|
228
|
-
if (this.
|
|
234
|
+
if (!this._noSnippets)
|
|
229
235
|
createSnippets(this._stepsInFile);
|
|
230
236
|
let ok = true;
|
|
231
237
|
for (const [fileId, { testFile, testFileSummary }] of data) {
|
|
@@ -431,7 +437,15 @@ class HtmlBuilder {
|
|
|
431
437
|
}).filter(Boolean);
|
|
432
438
|
}
|
|
433
439
|
_serializeAnnotations(annotations) {
|
|
434
|
-
return annotations.map((a) => ({
|
|
440
|
+
return annotations.map((a) => ({
|
|
441
|
+
type: a.type,
|
|
442
|
+
description: a.description === void 0 ? void 0 : String(a.description),
|
|
443
|
+
location: a.location ? {
|
|
444
|
+
file: a.location.file,
|
|
445
|
+
line: a.location.line,
|
|
446
|
+
column: a.location.column
|
|
447
|
+
} : void 0
|
|
448
|
+
}));
|
|
435
449
|
}
|
|
436
450
|
_createTestResult(test, result) {
|
|
437
451
|
return {
|
package/lib/reporters/merge.js
CHANGED
|
@@ -406,7 +406,10 @@ class PathSeparatorPatcher {
|
|
|
406
406
|
return;
|
|
407
407
|
}
|
|
408
408
|
if (jsonEvent.method === "onTestEnd") {
|
|
409
|
+
const test = jsonEvent.params.test;
|
|
410
|
+
test.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
|
409
411
|
const testResult = jsonEvent.params.result;
|
|
412
|
+
testResult.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
|
410
413
|
testResult.errors.forEach((error) => this._updateErrorLocations(error));
|
|
411
414
|
(testResult.attachments ?? []).forEach((attachment) => {
|
|
412
415
|
if (attachment.path)
|
|
@@ -422,6 +425,7 @@ class PathSeparatorPatcher {
|
|
|
422
425
|
if (jsonEvent.method === "onStepEnd") {
|
|
423
426
|
const step = jsonEvent.params.step;
|
|
424
427
|
this._updateErrorLocations(step.error);
|
|
428
|
+
step.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
|
425
429
|
return;
|
|
426
430
|
}
|
|
427
431
|
if (jsonEvent.method === "onAttach") {
|
|
@@ -444,10 +448,12 @@ class PathSeparatorPatcher {
|
|
|
444
448
|
if (isFileSuite)
|
|
445
449
|
suite.title = this._updatePath(suite.title);
|
|
446
450
|
for (const entry of suite.entries) {
|
|
447
|
-
if ("testId" in entry)
|
|
451
|
+
if ("testId" in entry) {
|
|
448
452
|
this._updateLocation(entry.location);
|
|
449
|
-
|
|
453
|
+
entry.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
|
454
|
+
} else {
|
|
450
455
|
this._updateSuite(entry);
|
|
456
|
+
}
|
|
451
457
|
}
|
|
452
458
|
}
|
|
453
459
|
_updateErrorLocations(error) {
|
|
@@ -456,6 +462,9 @@ class PathSeparatorPatcher {
|
|
|
456
462
|
error = error.cause;
|
|
457
463
|
}
|
|
458
464
|
}
|
|
465
|
+
_updateAnnotationLocation(annotation) {
|
|
466
|
+
this._updateLocation(annotation.location);
|
|
467
|
+
}
|
|
459
468
|
_updateLocation(location) {
|
|
460
469
|
if (location)
|
|
461
470
|
location.file = this._updatePath(location.file);
|
|
@@ -204,7 +204,7 @@ class TeleReporterEmitter {
|
|
|
204
204
|
retries: test.retries,
|
|
205
205
|
tags: test.tags,
|
|
206
206
|
repeatEachIndex: test.repeatEachIndex,
|
|
207
|
-
annotations: test.annotations
|
|
207
|
+
annotations: this._relativeAnnotationLocations(test.annotations)
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
210
|
_serializeResultStart(result) {
|
|
@@ -222,7 +222,7 @@ class TeleReporterEmitter {
|
|
|
222
222
|
duration: result.duration,
|
|
223
223
|
status: result.status,
|
|
224
224
|
errors: result.errors,
|
|
225
|
-
annotations: result.annotations?.length ? result.annotations : void 0
|
|
225
|
+
annotations: result.annotations?.length ? this._relativeAnnotationLocations(result.annotations) : void 0
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
228
|
_sendNewAttachments(result, testId) {
|
|
@@ -266,9 +266,15 @@ class TeleReporterEmitter {
|
|
|
266
266
|
duration: step.duration,
|
|
267
267
|
error: step.error,
|
|
268
268
|
attachments: step.attachments.length ? step.attachments.map((a) => result.attachments.indexOf(a)) : void 0,
|
|
269
|
-
annotations: step.annotations.length ? step.annotations : void 0
|
|
269
|
+
annotations: step.annotations.length ? this._relativeAnnotationLocations(step.annotations) : void 0
|
|
270
270
|
};
|
|
271
271
|
}
|
|
272
|
+
_relativeAnnotationLocations(annotations) {
|
|
273
|
+
return annotations.map((annotation) => ({
|
|
274
|
+
...annotation,
|
|
275
|
+
location: annotation.location ? this._relativeLocation(annotation.location) : void 0
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
272
278
|
_relativeLocation(location) {
|
|
273
279
|
if (!location)
|
|
274
280
|
return location;
|
package/lib/runner/loadUtils.js
CHANGED
|
@@ -184,7 +184,7 @@ function createProjectSuite(project, fileSuites) {
|
|
|
184
184
|
const grepMatcher = (0, import_util.createTitleMatcher)(project.project.grep);
|
|
185
185
|
const grepInvertMatcher = project.project.grepInvert ? (0, import_util.createTitleMatcher)(project.project.grepInvert) : null;
|
|
186
186
|
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(projectSuite, (test) => {
|
|
187
|
-
const grepTitle = test.
|
|
187
|
+
const grepTitle = test._grepTitleWithTags();
|
|
188
188
|
if (grepInvertMatcher?.(grepTitle))
|
|
189
189
|
return false;
|
|
190
190
|
return grepMatcher(grepTitle);
|
|
@@ -200,7 +200,7 @@ function filterProjectSuite(projectSuite, options) {
|
|
|
200
200
|
if (options.testIdMatcher)
|
|
201
201
|
(0, import_suiteUtils.filterByTestIds)(result, options.testIdMatcher);
|
|
202
202
|
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(result, (test) => {
|
|
203
|
-
if (options.cliTitleMatcher && !options.cliTitleMatcher(test.
|
|
203
|
+
if (options.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitleWithTags()))
|
|
204
204
|
return false;
|
|
205
205
|
if (options.additionalFileMatcher && !options.additionalFileMatcher(test.location.file))
|
|
206
206
|
return false;
|
package/lib/worker/testInfo.js
CHANGED
|
@@ -41,6 +41,7 @@ var import_timeoutManager = require("./timeoutManager");
|
|
|
41
41
|
var import_util = require("../util");
|
|
42
42
|
var import_testTracing = require("./testTracing");
|
|
43
43
|
var import_util2 = require("./util");
|
|
44
|
+
var import_transform = require("../transform/transform");
|
|
44
45
|
class TestInfoImpl {
|
|
45
46
|
constructor(configInternal, projectInternal, workerParams, test, retry, onStepBegin, onStepEnd, onAttach) {
|
|
46
47
|
this._snapshotNames = { lastAnonymousSnapshotIndex: 0, lastNamedSnapshotIndex: {} };
|
|
@@ -110,6 +111,10 @@ class TestInfoImpl {
|
|
|
110
111
|
return this.attachments.length;
|
|
111
112
|
};
|
|
112
113
|
this._tracing = new import_testTracing.TestTracing(this, workerParams.artifactsDir);
|
|
114
|
+
this.skip = (0, import_transform.wrapFunctionWithLocation)((location, ...args) => this._modifier("skip", location, args));
|
|
115
|
+
this.fixme = (0, import_transform.wrapFunctionWithLocation)((location, ...args) => this._modifier("fixme", location, args));
|
|
116
|
+
this.fail = (0, import_transform.wrapFunctionWithLocation)((location, ...args) => this._modifier("fail", location, args));
|
|
117
|
+
this.slow = (0, import_transform.wrapFunctionWithLocation)((location, ...args) => this._modifier("slow", location, args));
|
|
113
118
|
}
|
|
114
119
|
get error() {
|
|
115
120
|
return this.errors[0];
|
|
@@ -135,7 +140,7 @@ class TestInfoImpl {
|
|
|
135
140
|
static _defaultDeadlineForMatcher(timeout) {
|
|
136
141
|
return { deadline: timeout ? (0, import_utils.monotonicTime)() + timeout : 0, timeoutMessage: `Timeout ${timeout}ms exceeded while waiting on the predicate` };
|
|
137
142
|
}
|
|
138
|
-
_modifier(type, modifierArgs) {
|
|
143
|
+
_modifier(type, location, modifierArgs) {
|
|
139
144
|
if (typeof modifierArgs[1] === "function") {
|
|
140
145
|
throw new Error([
|
|
141
146
|
"It looks like you are calling test.skip() inside the test and pass a callback.",
|
|
@@ -148,7 +153,7 @@ class TestInfoImpl {
|
|
|
148
153
|
if (modifierArgs.length >= 1 && !modifierArgs[0])
|
|
149
154
|
return;
|
|
150
155
|
const description = modifierArgs[1];
|
|
151
|
-
this.annotations.push({ type, description });
|
|
156
|
+
this.annotations.push({ type, description, location });
|
|
152
157
|
if (type === "slow") {
|
|
153
158
|
this._timeoutManager.slow();
|
|
154
159
|
} else if (type === "skip" || type === "fixme") {
|
|
@@ -418,18 +423,6 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
|
|
|
418
423
|
throw new Error(`testInfo.snapshotPath: unknown kind "${kind}", must be one of "snapshot", "screenshot" or "aria"`);
|
|
419
424
|
return this._resolveSnapshotPaths(kind, name.length <= 1 ? name[0] : name, "dontUpdateSnapshotIndex").absoluteSnapshotPath;
|
|
420
425
|
}
|
|
421
|
-
skip(...args) {
|
|
422
|
-
this._modifier("skip", args);
|
|
423
|
-
}
|
|
424
|
-
fixme(...args) {
|
|
425
|
-
this._modifier("fixme", args);
|
|
426
|
-
}
|
|
427
|
-
fail(...args) {
|
|
428
|
-
this._modifier("fail", args);
|
|
429
|
-
}
|
|
430
|
-
slow(...args) {
|
|
431
|
-
this._modifier("slow", args);
|
|
432
|
-
}
|
|
433
426
|
setTimeout(timeout) {
|
|
434
427
|
this._timeoutManager.setTimeout(timeout);
|
|
435
428
|
}
|
|
@@ -439,10 +432,17 @@ class TestStepInfoImpl {
|
|
|
439
432
|
this.annotations = [];
|
|
440
433
|
this._testInfo = testInfo;
|
|
441
434
|
this._stepId = stepId;
|
|
435
|
+
this.skip = (0, import_transform.wrapFunctionWithLocation)((location, ...args) => {
|
|
436
|
+
if (args.length > 0 && !args[0])
|
|
437
|
+
return;
|
|
438
|
+
const description = args[1];
|
|
439
|
+
this.annotations.push({ type: "skip", description, location });
|
|
440
|
+
throw new StepSkipError(description);
|
|
441
|
+
});
|
|
442
442
|
}
|
|
443
|
-
async _runStepBody(skip, body) {
|
|
443
|
+
async _runStepBody(skip, body, location) {
|
|
444
444
|
if (skip) {
|
|
445
|
-
this.annotations.push({ type: "skip" });
|
|
445
|
+
this.annotations.push({ type: "skip", location });
|
|
446
446
|
return void 0;
|
|
447
447
|
}
|
|
448
448
|
try {
|
|
@@ -459,13 +459,6 @@ class TestStepInfoImpl {
|
|
|
459
459
|
async attach(name, options) {
|
|
460
460
|
this._attachToStep(await (0, import_util.normalizeAndSaveAttachment)(this._testInfo.outputPath(), name, options));
|
|
461
461
|
}
|
|
462
|
-
skip(...args) {
|
|
463
|
-
if (args.length > 0 && !args[0])
|
|
464
|
-
return;
|
|
465
|
-
const description = args[1];
|
|
466
|
-
this.annotations.push({ type: "skip", description });
|
|
467
|
-
throw new StepSkipError(description);
|
|
468
|
-
}
|
|
469
462
|
}
|
|
470
463
|
class TestSkipError extends Error {
|
|
471
464
|
}
|
package/lib/worker/workerMain.js
CHANGED
|
@@ -391,7 +391,7 @@ class WorkerMain extends import_process.ProcessRunner {
|
|
|
391
391
|
continue;
|
|
392
392
|
const fn = async (fixtures) => {
|
|
393
393
|
const result = await modifier.fn(fixtures);
|
|
394
|
-
testInfo
|
|
394
|
+
testInfo._modifier(modifier.type, modifier.location, [!!result, modifier.description]);
|
|
395
395
|
};
|
|
396
396
|
(0, import_fixtures.inheritFixtureNames)(modifier.fn, fn);
|
|
397
397
|
runnables.push({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright",
|
|
3
|
-
"version": "1.54.0-alpha-2025-07-
|
|
3
|
+
"version": "1.54.0-alpha-2025-07-09",
|
|
4
4
|
"description": "A high-level API to automate web browsers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"license": "Apache-2.0",
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"playwright-core": "1.54.0-alpha-2025-07-
|
|
59
|
+
"playwright-core": "1.54.0-alpha-2025-07-09"
|
|
60
60
|
},
|
|
61
61
|
"optionalDependencies": {
|
|
62
62
|
"fsevents": "2.3.2"
|
package/types/test.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type BlobReporterOptions = { outputDir?: string, fileName?: string };
|
|
|
22
22
|
export type ListReporterOptions = { printSteps?: boolean };
|
|
23
23
|
export type JUnitReporterOptions = { outputFile?: string, stripANSIControlSequences?: boolean, includeProjectInTestName?: boolean };
|
|
24
24
|
export type JsonReporterOptions = { outputFile?: string };
|
|
25
|
-
export type HtmlReporterOptions = { outputFolder?: string, open?: 'always' | 'never' | 'on-failure', host?: string, port?: number, attachmentsBaseURL?: string, title?: string,
|
|
25
|
+
export type HtmlReporterOptions = { outputFolder?: string, open?: 'always' | 'never' | 'on-failure', host?: string, port?: number, attachmentsBaseURL?: string, title?: string, noSnippets?: boolean };
|
|
26
26
|
|
|
27
27
|
export type ReporterDescription = Readonly<
|
|
28
28
|
['blob'] | ['blob', BlobReporterOptions] |
|
|
@@ -2325,6 +2325,11 @@ export interface TestInfo {
|
|
|
2325
2325
|
* Optional description.
|
|
2326
2326
|
*/
|
|
2327
2327
|
description?: string;
|
|
2328
|
+
|
|
2329
|
+
/**
|
|
2330
|
+
* Optional location in the source where the annotation is added.
|
|
2331
|
+
*/
|
|
2332
|
+
location?: Location;
|
|
2328
2333
|
}>;
|
|
2329
2334
|
|
|
2330
2335
|
/**
|
|
@@ -2575,7 +2580,9 @@ export type TestDetailsAnnotation = {
|
|
|
2575
2580
|
description?: string;
|
|
2576
2581
|
};
|
|
2577
2582
|
|
|
2578
|
-
export type TestAnnotation = TestDetailsAnnotation
|
|
2583
|
+
export type TestAnnotation = TestDetailsAnnotation & {
|
|
2584
|
+
location?: Location;
|
|
2585
|
+
};
|
|
2579
2586
|
|
|
2580
2587
|
export type TestDetails = {
|
|
2581
2588
|
tag?: string | string[];
|
package/types/testReporter.d.ts
CHANGED
|
@@ -451,6 +451,11 @@ export interface TestCase {
|
|
|
451
451
|
* Optional description.
|
|
452
452
|
*/
|
|
453
453
|
description?: string;
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Optional location in the source where the annotation is added.
|
|
457
|
+
*/
|
|
458
|
+
location?: Location;
|
|
454
459
|
}>;
|
|
455
460
|
|
|
456
461
|
/**
|
|
@@ -607,6 +612,11 @@ export interface TestResult {
|
|
|
607
612
|
* Optional description.
|
|
608
613
|
*/
|
|
609
614
|
description?: string;
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Optional location in the source where the annotation is added.
|
|
618
|
+
*/
|
|
619
|
+
location?: Location;
|
|
610
620
|
}>;
|
|
611
621
|
|
|
612
622
|
/**
|
|
@@ -722,6 +732,11 @@ export interface TestStep {
|
|
|
722
732
|
* Optional description.
|
|
723
733
|
*/
|
|
724
734
|
description?: string;
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Optional location in the source where the annotation is added.
|
|
738
|
+
*/
|
|
739
|
+
location?: Location;
|
|
725
740
|
}>;
|
|
726
741
|
|
|
727
742
|
/**
|