playwright 1.55.0-alpha-2025-08-06 → 1.55.0-alpha-2025-08-08
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/config.js +2 -0
- package/lib/common/fixtures.js +1 -1
- package/lib/common/suiteUtils.js +0 -9
- package/lib/index.js +115 -109
- package/lib/program.js +2 -0
- package/lib/runner/lastRun.js +7 -4
- package/lib/runner/loadUtils.js +7 -9
- package/lib/runner/tasks.js +11 -3
- package/lib/runner/testServer.js +5 -2
- package/lib/util.js +18 -0
- package/lib/worker/fixtureRunner.js +9 -12
- package/lib/worker/testInfo.js +56 -41
- package/lib/worker/testTracing.js +7 -6
- package/package.json +2 -2
package/lib/common/config.js
CHANGED
|
@@ -49,6 +49,8 @@ class FullConfigInternal {
|
|
|
49
49
|
this.projects = [];
|
|
50
50
|
this.cliArgs = [];
|
|
51
51
|
this.cliListOnly = false;
|
|
52
|
+
this.preOnlyTestFilters = [];
|
|
53
|
+
this.postShardTestFilters = [];
|
|
52
54
|
this.defineConfigWasUsed = false;
|
|
53
55
|
this.globalSetups = [];
|
|
54
56
|
this.globalTeardowns = [];
|
package/lib/common/fixtures.js
CHANGED
|
@@ -90,7 +90,7 @@ class FixturePool {
|
|
|
90
90
|
continue;
|
|
91
91
|
}
|
|
92
92
|
} else if (previous) {
|
|
93
|
-
options = { auto: previous.auto, scope: previous.scope, option: previous.option, timeout: previous.timeout, customTitle: previous.customTitle
|
|
93
|
+
options = { auto: previous.auto, scope: previous.scope, option: previous.option, timeout: previous.timeout, customTitle: previous.customTitle };
|
|
94
94
|
} else if (!options) {
|
|
95
95
|
options = { auto: false, scope: "test", option: false, timeout: void 0 };
|
|
96
96
|
}
|
package/lib/common/suiteUtils.js
CHANGED
|
@@ -31,10 +31,8 @@ __export(suiteUtils_exports, {
|
|
|
31
31
|
applyRepeatEachIndex: () => applyRepeatEachIndex,
|
|
32
32
|
bindFileSuiteToProject: () => bindFileSuiteToProject,
|
|
33
33
|
filterByFocusedLine: () => filterByFocusedLine,
|
|
34
|
-
filterByTestIds: () => filterByTestIds,
|
|
35
34
|
filterOnly: () => filterOnly,
|
|
36
35
|
filterSuite: () => filterSuite,
|
|
37
|
-
filterSuiteWithOnlySemantics: () => filterSuiteWithOnlySemantics,
|
|
38
36
|
filterTestsRemoveEmptySuites: () => filterTestsRemoveEmptySuites
|
|
39
37
|
});
|
|
40
38
|
module.exports = __toCommonJS(suiteUtils_exports);
|
|
@@ -127,11 +125,6 @@ function filterByFocusedLine(suite, focusedTestFileLines) {
|
|
|
127
125
|
const testFilter = (test) => testFileLineMatches(test.location.file, test.location.line, test.location.column);
|
|
128
126
|
return filterSuite(suite, suiteFilter, testFilter);
|
|
129
127
|
}
|
|
130
|
-
function filterByTestIds(suite, testIdMatcher) {
|
|
131
|
-
if (!testIdMatcher)
|
|
132
|
-
return;
|
|
133
|
-
filterTestsRemoveEmptySuites(suite, (test) => testIdMatcher(test.id));
|
|
134
|
-
}
|
|
135
128
|
function createFileMatcherFromFilter(filter) {
|
|
136
129
|
const fileMatcher = (0, import_util.createFileMatcher)(filter.re || filter.exact || "");
|
|
137
130
|
return (testFileName, testLine, testColumn) => fileMatcher(testFileName) && (filter.line === testLine || filter.line === null) && (filter.column === testColumn || filter.column === null);
|
|
@@ -141,9 +134,7 @@ function createFileMatcherFromFilter(filter) {
|
|
|
141
134
|
applyRepeatEachIndex,
|
|
142
135
|
bindFileSuiteToProject,
|
|
143
136
|
filterByFocusedLine,
|
|
144
|
-
filterByTestIds,
|
|
145
137
|
filterOnly,
|
|
146
138
|
filterSuite,
|
|
147
|
-
filterSuiteWithOnlySemantics,
|
|
148
139
|
filterTestsRemoveEmptySuites
|
|
149
140
|
});
|
package/lib/index.js
CHANGED
|
@@ -61,20 +61,20 @@ if (process["__pw_initiator__"]) {
|
|
|
61
61
|
process["__pw_initiator__"] = new Error().stack;
|
|
62
62
|
}
|
|
63
63
|
const playwrightFixtures = {
|
|
64
|
-
defaultBrowserType: ["chromium", { scope: "worker", option: true }],
|
|
65
|
-
browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true }],
|
|
64
|
+
defaultBrowserType: ["chromium", { scope: "worker", option: true, box: true }],
|
|
65
|
+
browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true, box: true }],
|
|
66
66
|
playwright: [async ({}, use) => {
|
|
67
67
|
await use(require("playwright-core"));
|
|
68
68
|
}, { scope: "worker", box: true }],
|
|
69
|
-
headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: "worker", option: true }],
|
|
70
|
-
channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: "worker", option: true }],
|
|
71
|
-
launchOptions: [{}, { scope: "worker", option: true }],
|
|
69
|
+
headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: "worker", option: true, box: true }],
|
|
70
|
+
channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: "worker", option: true, box: true }],
|
|
71
|
+
launchOptions: [{}, { scope: "worker", option: true, box: true }],
|
|
72
72
|
connectOptions: [async ({ _optionConnectOptions }, use) => {
|
|
73
73
|
await use(connectOptionsFromEnv() || _optionConnectOptions);
|
|
74
|
-
}, { scope: "worker", option: true }],
|
|
75
|
-
screenshot: ["off", { scope: "worker", option: true }],
|
|
76
|
-
video: ["off", { scope: "worker", option: true }],
|
|
77
|
-
trace: ["off", { scope: "worker", option: true }],
|
|
74
|
+
}, { scope: "worker", option: true, box: true }],
|
|
75
|
+
screenshot: ["off", { scope: "worker", option: true, box: true }],
|
|
76
|
+
video: ["off", { scope: "worker", option: true, box: true }],
|
|
77
|
+
trace: ["off", { scope: "worker", option: true, box: true }],
|
|
78
78
|
_browserOptions: [async ({ playwright, headless, channel, launchOptions }, use) => {
|
|
79
79
|
const options = {
|
|
80
80
|
handleSIGINT: false,
|
|
@@ -103,45 +103,41 @@ const playwrightFixtures = {
|
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
105
|
await use(browser2);
|
|
106
|
-
await browser2.
|
|
107
|
-
await browser2.close({ reason: "Test ended." });
|
|
108
|
-
}, { internal: true });
|
|
106
|
+
await browser2.close({ reason: "Test ended." });
|
|
109
107
|
return;
|
|
110
108
|
}
|
|
111
109
|
const browser = await playwright[browserName].launch();
|
|
112
110
|
await use(browser);
|
|
113
|
-
await browser.
|
|
114
|
-
await browser.close({ reason: "Test ended." });
|
|
115
|
-
}, { internal: true });
|
|
111
|
+
await browser.close({ reason: "Test ended." });
|
|
116
112
|
}, { scope: "worker", timeout: 0 }],
|
|
117
|
-
acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true }],
|
|
118
|
-
bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true }],
|
|
119
|
-
colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true }],
|
|
120
|
-
deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true }],
|
|
121
|
-
extraHTTPHeaders: [({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true }],
|
|
122
|
-
geolocation: [({ contextOptions }, use) => use(contextOptions.geolocation), { option: true }],
|
|
123
|
-
hasTouch: [({ contextOptions }, use) => use(contextOptions.hasTouch ?? false), { option: true }],
|
|
124
|
-
httpCredentials: [({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true }],
|
|
125
|
-
ignoreHTTPSErrors: [({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors ?? false), { option: true }],
|
|
126
|
-
isMobile: [({ contextOptions }, use) => use(contextOptions.isMobile ?? false), { option: true }],
|
|
127
|
-
javaScriptEnabled: [({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true }],
|
|
128
|
-
locale: [({ contextOptions }, use) => use(contextOptions.locale ?? "en-US"), { option: true }],
|
|
129
|
-
offline: [({ contextOptions }, use) => use(contextOptions.offline ?? false), { option: true }],
|
|
130
|
-
permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true }],
|
|
131
|
-
proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true }],
|
|
132
|
-
storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true }],
|
|
133
|
-
clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true }],
|
|
134
|
-
timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true }],
|
|
135
|
-
userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true }],
|
|
136
|
-
viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true }],
|
|
137
|
-
actionTimeout: [0, { option: true }],
|
|
138
|
-
testIdAttribute: ["data-testid", { option: true }],
|
|
139
|
-
navigationTimeout: [0, { option: true }],
|
|
113
|
+
acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true, box: true }],
|
|
114
|
+
bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true, box: true }],
|
|
115
|
+
colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true, box: true }],
|
|
116
|
+
deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true, box: true }],
|
|
117
|
+
extraHTTPHeaders: [({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true, box: true }],
|
|
118
|
+
geolocation: [({ contextOptions }, use) => use(contextOptions.geolocation), { option: true, box: true }],
|
|
119
|
+
hasTouch: [({ contextOptions }, use) => use(contextOptions.hasTouch ?? false), { option: true, box: true }],
|
|
120
|
+
httpCredentials: [({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true, box: true }],
|
|
121
|
+
ignoreHTTPSErrors: [({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors ?? false), { option: true, box: true }],
|
|
122
|
+
isMobile: [({ contextOptions }, use) => use(contextOptions.isMobile ?? false), { option: true, box: true }],
|
|
123
|
+
javaScriptEnabled: [({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true, box: true }],
|
|
124
|
+
locale: [({ contextOptions }, use) => use(contextOptions.locale ?? "en-US"), { option: true, box: true }],
|
|
125
|
+
offline: [({ contextOptions }, use) => use(contextOptions.offline ?? false), { option: true, box: true }],
|
|
126
|
+
permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true, box: true }],
|
|
127
|
+
proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true, box: true }],
|
|
128
|
+
storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true, box: true }],
|
|
129
|
+
clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true, box: true }],
|
|
130
|
+
timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true, box: true }],
|
|
131
|
+
userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true, box: true }],
|
|
132
|
+
viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true, box: true }],
|
|
133
|
+
actionTimeout: [0, { option: true, box: true }],
|
|
134
|
+
testIdAttribute: ["data-testid", { option: true, box: true }],
|
|
135
|
+
navigationTimeout: [0, { option: true, box: true }],
|
|
140
136
|
baseURL: [async ({}, use) => {
|
|
141
137
|
await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
|
|
142
|
-
}, { option: true }],
|
|
143
|
-
serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true }],
|
|
144
|
-
contextOptions: [{}, { option: true }],
|
|
138
|
+
}, { option: true, box: true }],
|
|
139
|
+
serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true, box: true }],
|
|
140
|
+
contextOptions: [{}, { option: true, box: true }],
|
|
145
141
|
_combinedContextOptions: [async ({
|
|
146
142
|
acceptDownloads,
|
|
147
143
|
bypassCSP,
|
|
@@ -221,7 +217,7 @@ const playwrightFixtures = {
|
|
|
221
217
|
if (testIdAttribute)
|
|
222
218
|
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
|
|
223
219
|
testInfo.snapshotSuffix = process.platform;
|
|
224
|
-
if ((0, import_utils.debugMode)())
|
|
220
|
+
if ((0, import_utils.debugMode)() === "inspector")
|
|
225
221
|
testInfo._setDebugMode();
|
|
226
222
|
playwright._defaultContextOptions = _combinedContextOptions;
|
|
227
223
|
playwright._defaultContextTimeout = actionTimeout || 0;
|
|
@@ -310,6 +306,7 @@ const playwrightFixtures = {
|
|
|
310
306
|
const videoMode = normalizeVideoMode(video);
|
|
311
307
|
const captureVideo = shouldCaptureVideo(videoMode, testInfo) && !_reuseContext;
|
|
312
308
|
const contexts = /* @__PURE__ */ new Map();
|
|
309
|
+
let counter = 0;
|
|
313
310
|
await use(async (options) => {
|
|
314
311
|
const hook = testInfoImpl._currentHookType();
|
|
315
312
|
if (hook === "beforeAll" || hook === "afterAll") {
|
|
@@ -326,10 +323,6 @@ const playwrightFixtures = {
|
|
|
326
323
|
}
|
|
327
324
|
} : {};
|
|
328
325
|
const context = await browser.newContext({ ...videoOptions, ...options });
|
|
329
|
-
const contextData = { pagesWithVideo: [] };
|
|
330
|
-
contexts.set(context, contextData);
|
|
331
|
-
if (captureVideo)
|
|
332
|
-
context.on("page", (page) => contextData.pagesWithVideo.push(page));
|
|
333
326
|
if (process.env.PW_CLOCK === "frozen") {
|
|
334
327
|
await context._wrapApiCall(async () => {
|
|
335
328
|
await context.clock.install({ time: 0 });
|
|
@@ -340,33 +333,39 @@ const playwrightFixtures = {
|
|
|
340
333
|
await context.clock.install({ time: 0 });
|
|
341
334
|
}, { internal: true });
|
|
342
335
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
336
|
+
let closed = false;
|
|
337
|
+
const close = async () => {
|
|
338
|
+
if (closed)
|
|
339
|
+
return;
|
|
340
|
+
closed = true;
|
|
341
|
+
const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
|
|
349
342
|
await context.close({ reason: closeReason });
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
343
|
+
const testFailed = testInfo.status !== testInfo.expectedStatus;
|
|
344
|
+
const preserveVideo = captureVideo && (videoMode === "on" || testFailed && videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1);
|
|
345
|
+
if (preserveVideo) {
|
|
346
|
+
const { pagesWithVideo: pagesForVideo } = contexts.get(context);
|
|
347
|
+
const videos = pagesForVideo.map((p) => p.video()).filter((video2) => !!video2);
|
|
348
|
+
await Promise.all(videos.map(async (v) => {
|
|
349
|
+
try {
|
|
350
|
+
const savedPath = testInfo.outputPath(`video${counter ? "-" + counter : ""}.webm`);
|
|
351
|
+
++counter;
|
|
352
|
+
await v.saveAs(savedPath);
|
|
353
|
+
testInfo.attachments.push({ name: "video", path: savedPath, contentType: "video/webm" });
|
|
354
|
+
} catch (e) {
|
|
355
|
+
}
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
const contextData = { close, pagesWithVideo: [] };
|
|
360
|
+
if (captureVideo)
|
|
361
|
+
context.on("page", (page) => contextData.pagesWithVideo.push(page));
|
|
362
|
+
contexts.set(context, contextData);
|
|
363
|
+
return { context, close };
|
|
364
|
+
});
|
|
365
|
+
await Promise.all([...contexts.values()].map((data) => data.close()));
|
|
367
366
|
}, { scope: "test", title: "context", box: true }],
|
|
368
|
-
_optionContextReuseMode: ["none", { scope: "worker", option: true }],
|
|
369
|
-
_optionConnectOptions: [void 0, { scope: "worker", option: true }],
|
|
367
|
+
_optionContextReuseMode: ["none", { scope: "worker", option: true, box: true }],
|
|
368
|
+
_optionConnectOptions: [void 0, { scope: "worker", option: true, box: true }],
|
|
370
369
|
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
|
|
371
370
|
let mode = _optionContextReuseMode;
|
|
372
371
|
if (process.env.PW_TEST_REUSE_CONTEXT)
|
|
@@ -374,17 +373,19 @@ const playwrightFixtures = {
|
|
|
374
373
|
const reuse = mode === "when-possible" && normalizeVideoMode(video) === "off";
|
|
375
374
|
await use(reuse);
|
|
376
375
|
}, { scope: "worker", title: "context", box: true }],
|
|
377
|
-
context: async ({
|
|
378
|
-
|
|
376
|
+
context: async ({ browser, _reuseContext, _contextFactory }, use, testInfo) => {
|
|
377
|
+
const browserImpl = browser;
|
|
378
|
+
attachConnectedHeaderIfNeeded(testInfo, browserImpl);
|
|
379
379
|
if (!_reuseContext) {
|
|
380
|
-
|
|
380
|
+
const { context: context2, close } = await _contextFactory();
|
|
381
|
+
await use(context2);
|
|
382
|
+
await close();
|
|
381
383
|
return;
|
|
382
384
|
}
|
|
383
|
-
const
|
|
384
|
-
const context = await browser._newContextForReuse(defaultContextOptions);
|
|
385
|
+
const context = await browserImpl._wrapApiCall(() => browserImpl._newContextForReuse(), { internal: true });
|
|
385
386
|
await use(context);
|
|
386
387
|
const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
|
|
387
|
-
await
|
|
388
|
+
await browserImpl._wrapApiCall(() => browserImpl._disconnectFromReusedContext(closeReason), { internal: true });
|
|
388
389
|
},
|
|
389
390
|
page: async ({ context, _reuseContext }, use) => {
|
|
390
391
|
if (!_reuseContext) {
|
|
@@ -548,7 +549,9 @@ class ArtifactsRecorder {
|
|
|
548
549
|
const screenshotOptions = typeof screenshot === "string" ? void 0 : screenshot;
|
|
549
550
|
this._startedCollectingArtifacts = Symbol("startedCollectingArtifacts");
|
|
550
551
|
this._screenshotRecorder = new SnapshotRecorder(this, normalizeScreenshotMode(screenshot), "screenshot", "image/png", ".png", async (page, path2) => {
|
|
551
|
-
await page.
|
|
552
|
+
await page._wrapApiCall(async () => {
|
|
553
|
+
await page.screenshot({ ...screenshotOptions, timeout: 5e3, path: path2, caret: "initial" });
|
|
554
|
+
}, { internal: true });
|
|
552
555
|
});
|
|
553
556
|
}
|
|
554
557
|
async willStartTest(testInfo) {
|
|
@@ -560,10 +563,10 @@ class ArtifactsRecorder {
|
|
|
560
563
|
await Promise.all(existingApiRequests.map((c) => this.didCreateRequestContext(c)));
|
|
561
564
|
}
|
|
562
565
|
async didCreateBrowserContext(context) {
|
|
563
|
-
await this._startTraceChunkOnContextCreation(context.tracing);
|
|
566
|
+
await this._startTraceChunkOnContextCreation(context, context.tracing);
|
|
564
567
|
}
|
|
565
568
|
async willCloseBrowserContext(context) {
|
|
566
|
-
await this._stopTracing(context.tracing);
|
|
569
|
+
await this._stopTracing(context, context.tracing);
|
|
567
570
|
await this._screenshotRecorder.captureTemporary(context);
|
|
568
571
|
await this._takePageSnapshot(context);
|
|
569
572
|
}
|
|
@@ -578,17 +581,17 @@ class ArtifactsRecorder {
|
|
|
578
581
|
if (!page)
|
|
579
582
|
return;
|
|
580
583
|
try {
|
|
581
|
-
|
|
584
|
+
await page._wrapApiCall(async () => {
|
|
585
|
+
this._pageSnapshot = await page._snapshotForAI({ timeout: 5e3 });
|
|
586
|
+
}, { internal: true });
|
|
582
587
|
} catch {
|
|
583
588
|
}
|
|
584
589
|
}
|
|
585
590
|
async didCreateRequestContext(context) {
|
|
586
|
-
|
|
587
|
-
await this._startTraceChunkOnContextCreation(tracing2);
|
|
591
|
+
await this._startTraceChunkOnContextCreation(context, context._tracing);
|
|
588
592
|
}
|
|
589
593
|
async willCloseRequestContext(context) {
|
|
590
|
-
|
|
591
|
-
await this._stopTracing(tracing2);
|
|
594
|
+
await this._stopTracing(context, context._tracing);
|
|
592
595
|
}
|
|
593
596
|
async didFinishTestFunction() {
|
|
594
597
|
await this._screenshotRecorder.maybeCapture();
|
|
@@ -598,10 +601,9 @@ class ArtifactsRecorder {
|
|
|
598
601
|
const leftoverContexts = this._playwright._allContexts();
|
|
599
602
|
const leftoverApiRequests = Array.from(this._playwright.request._contexts);
|
|
600
603
|
await Promise.all(leftoverContexts.map(async (context2) => {
|
|
601
|
-
await this._stopTracing(context2.tracing);
|
|
604
|
+
await this._stopTracing(context2, context2.tracing);
|
|
602
605
|
}).concat(leftoverApiRequests.map(async (context2) => {
|
|
603
|
-
|
|
604
|
-
await this._stopTracing(tracing2);
|
|
606
|
+
await this._stopTracing(context2, context2._tracing);
|
|
605
607
|
})));
|
|
606
608
|
await this._screenshotRecorder.persistTemporary();
|
|
607
609
|
const context = leftoverContexts[0];
|
|
@@ -624,30 +626,34 @@ class ArtifactsRecorder {
|
|
|
624
626
|
}, void 0);
|
|
625
627
|
}
|
|
626
628
|
}
|
|
627
|
-
async _startTraceChunkOnContextCreation(tracing2) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
629
|
+
async _startTraceChunkOnContextCreation(channelOwner, tracing2) {
|
|
630
|
+
await channelOwner._wrapApiCall(async () => {
|
|
631
|
+
const options = this._testInfo._tracing.traceOptions();
|
|
632
|
+
if (options) {
|
|
633
|
+
const title = this._testInfo._tracing.traceTitle();
|
|
634
|
+
const name = this._testInfo._tracing.generateNextTraceRecordingName();
|
|
635
|
+
if (!tracing2[kTracingStarted]) {
|
|
636
|
+
await tracing2.start({ ...options, title, name });
|
|
637
|
+
tracing2[kTracingStarted] = true;
|
|
638
|
+
} else {
|
|
639
|
+
await tracing2.startChunk({ title, name });
|
|
640
|
+
}
|
|
635
641
|
} else {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
tracing2[kTracingStarted] = false;
|
|
641
|
-
await tracing2.stop();
|
|
642
|
+
if (tracing2[kTracingStarted]) {
|
|
643
|
+
tracing2[kTracingStarted] = false;
|
|
644
|
+
await tracing2.stop();
|
|
645
|
+
}
|
|
642
646
|
}
|
|
643
|
-
}
|
|
647
|
+
}, { internal: true });
|
|
644
648
|
}
|
|
645
|
-
async _stopTracing(tracing2) {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
649
|
+
async _stopTracing(channelOwner, tracing2) {
|
|
650
|
+
await channelOwner._wrapApiCall(async () => {
|
|
651
|
+
if (tracing2[this._startedCollectingArtifacts])
|
|
652
|
+
return;
|
|
653
|
+
tracing2[this._startedCollectingArtifacts] = true;
|
|
654
|
+
if (this._testInfo._tracing.traceOptions() && tracing2[kTracingStarted])
|
|
655
|
+
await tracing2.stopChunk({ path: this._testInfo._tracing.maybeGenerateNextTraceRecordingPath() });
|
|
656
|
+
}, { internal: true });
|
|
651
657
|
}
|
|
652
658
|
}
|
|
653
659
|
function renderTitle(type, method, params, title) {
|
package/lib/program.js
CHANGED
|
@@ -172,6 +172,7 @@ async function runTests(args, opts) {
|
|
|
172
172
|
config.cliProjectFilter = opts.project || void 0;
|
|
173
173
|
config.cliPassWithNoTests = !!opts.passWithNoTests;
|
|
174
174
|
config.cliLastFailed = !!opts.lastFailed;
|
|
175
|
+
config.cliFilterFile = opts.filter ? import_path.default.resolve(process.cwd(), opts.filter) : void 0;
|
|
175
176
|
(0, import_projectUtils.filterProjects)(config.projects, config.cliProjectFilter);
|
|
176
177
|
if (opts.ui || opts.uiHost || opts.uiPort) {
|
|
177
178
|
if (opts.onlyChanged)
|
|
@@ -346,6 +347,7 @@ const testOptions = [
|
|
|
346
347
|
["-c, --config <file>", { description: `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"` }],
|
|
347
348
|
["--debug", { description: `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --max-failures=1 --headed --workers=1" options` }],
|
|
348
349
|
["--fail-on-flaky-tests", { description: `Fail if any test is flagged as flaky (default: false)` }],
|
|
350
|
+
["--filter <file>", { description: `Path to a test filter file. See documentation for details.` }],
|
|
349
351
|
["--forbid-only", { description: `Fail if test.only is called (default: false)` }],
|
|
350
352
|
["--fully-parallel", { description: `Run all tests in parallel (default: false)` }],
|
|
351
353
|
["--global-timeout <timeout>", { description: `Maximum time this test suite can run in milliseconds (default: unlimited)` }],
|
package/lib/runner/lastRun.js
CHANGED
|
@@ -46,7 +46,8 @@ class LastRunReporter {
|
|
|
46
46
|
return;
|
|
47
47
|
try {
|
|
48
48
|
const lastRunInfo = JSON.parse(await import_fs.default.promises.readFile(this._lastRunFile, "utf8"));
|
|
49
|
-
|
|
49
|
+
const failedTestIds = new Set(lastRunInfo.failedTests);
|
|
50
|
+
this._config.postShardTestFilters.push((test) => failedTestIds.has(test.id));
|
|
50
51
|
} catch {
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -62,10 +63,12 @@ class LastRunReporter {
|
|
|
62
63
|
async onEnd(result) {
|
|
63
64
|
if (!this._lastRunFile || this._config.cliListOnly)
|
|
64
65
|
return;
|
|
66
|
+
const lastRunInfo = {
|
|
67
|
+
status: result.status,
|
|
68
|
+
failedTests: this._suite?.allTests().filter((t) => !t.ok()).map((t) => t.id) || []
|
|
69
|
+
};
|
|
65
70
|
await import_fs.default.promises.mkdir(import_path.default.dirname(this._lastRunFile), { recursive: true });
|
|
66
|
-
|
|
67
|
-
const lastRunReport = JSON.stringify({ status: result.status, failedTests }, void 0, 2);
|
|
68
|
-
await import_fs.default.promises.writeFile(this._lastRunFile, lastRunReport);
|
|
71
|
+
await import_fs.default.promises.writeFile(this._lastRunFile, JSON.stringify(lastRunInfo, void 0, 2));
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
// Annotate the CommonJS export names for ESM import in node:
|
package/lib/runner/loadUtils.js
CHANGED
|
@@ -112,7 +112,7 @@ async function loadFileSuites(testRun, mode, errors) {
|
|
|
112
112
|
testRun.projectSuites.set(project, suites);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
async function createRootSuite(testRun, errors, shouldFilterOnly
|
|
115
|
+
async function createRootSuite(testRun, errors, shouldFilterOnly) {
|
|
116
116
|
const config = testRun.config;
|
|
117
117
|
const rootSuite = new import_test.Suite("", "root");
|
|
118
118
|
const projectSuites = /* @__PURE__ */ new Map();
|
|
@@ -125,7 +125,7 @@ async function createRootSuite(testRun, errors, shouldFilterOnly, additionalFile
|
|
|
125
125
|
for (const [project, fileSuites] of testRun.projectSuites) {
|
|
126
126
|
const projectSuite = createProjectSuite(project, fileSuites);
|
|
127
127
|
projectSuites.set(project, projectSuite);
|
|
128
|
-
const filteredProjectSuite = filterProjectSuite(projectSuite, { cliFileFilters, cliTitleMatcher,
|
|
128
|
+
const filteredProjectSuite = filterProjectSuite(projectSuite, { cliFileFilters, cliTitleMatcher, testFilters: config.preOnlyTestFilters });
|
|
129
129
|
filteredProjectSuites.set(project, filteredProjectSuite);
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -166,8 +166,8 @@ async function createRootSuite(testRun, errors, shouldFilterOnly, additionalFile
|
|
|
166
166
|
}
|
|
167
167
|
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => testsInThisShard.has(test));
|
|
168
168
|
}
|
|
169
|
-
if (config.
|
|
170
|
-
(0, import_suiteUtils.
|
|
169
|
+
if (config.postShardTestFilters.length)
|
|
170
|
+
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => config.postShardTestFilters.every((filter) => filter(test)));
|
|
171
171
|
{
|
|
172
172
|
const projectClosure2 = new Map((0, import_projectUtils.buildProjectsClosure)(rootSuite.suites.map((suite) => suite._fullProject)));
|
|
173
173
|
for (const [project, level] of projectClosure2.entries()) {
|
|
@@ -192,17 +192,15 @@ function createProjectSuite(project, fileSuites) {
|
|
|
192
192
|
return projectSuite;
|
|
193
193
|
}
|
|
194
194
|
function filterProjectSuite(projectSuite, options) {
|
|
195
|
-
if (!options.cliFileFilters.length && !options.cliTitleMatcher && !options.
|
|
195
|
+
if (!options.cliFileFilters.length && !options.cliTitleMatcher && !options.testFilters.length)
|
|
196
196
|
return projectSuite;
|
|
197
197
|
const result = projectSuite._deepClone();
|
|
198
198
|
if (options.cliFileFilters.length)
|
|
199
199
|
(0, import_suiteUtils.filterByFocusedLine)(result, options.cliFileFilters);
|
|
200
|
-
if (options.testIdMatcher)
|
|
201
|
-
(0, import_suiteUtils.filterByTestIds)(result, options.testIdMatcher);
|
|
202
200
|
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(result, (test) => {
|
|
203
|
-
if (options.
|
|
201
|
+
if (!options.testFilters.every((filter) => filter(test)))
|
|
204
202
|
return false;
|
|
205
|
-
if (options.
|
|
203
|
+
if (options.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitleWithTags()))
|
|
206
204
|
return false;
|
|
207
205
|
return true;
|
|
208
206
|
});
|
package/lib/runner/tasks.js
CHANGED
|
@@ -243,12 +243,20 @@ function createLoadTask(mode, options) {
|
|
|
243
243
|
for (const plugin of testRun.config.plugins)
|
|
244
244
|
await plugin.instance?.populateDependencies?.();
|
|
245
245
|
}
|
|
246
|
-
let cliOnlyChangedMatcher = void 0;
|
|
247
246
|
if (testRun.config.cliOnlyChanged) {
|
|
248
247
|
const changedFiles = await (0, import_vcs.detectChangedTestFiles)(testRun.config.cliOnlyChanged, testRun.config.configDir);
|
|
249
|
-
|
|
248
|
+
testRun.config.preOnlyTestFilters.push((test) => changedFiles.has(test.location.file));
|
|
250
249
|
}
|
|
251
|
-
|
|
250
|
+
if (testRun.config.cliFilterFile) {
|
|
251
|
+
try {
|
|
252
|
+
testRun.config.preOnlyTestFilters.push(await (0, import_util2.loadTestFilterFile)(testRun.config.cliFilterFile));
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (options.failOnLoadErrors)
|
|
255
|
+
throw error;
|
|
256
|
+
softErrors.push(error);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
testRun.rootSuite = await (0, import_loadUtils.createRootSuite)(testRun, options.failOnLoadErrors ? errors : softErrors, !!options.filterOnly);
|
|
252
260
|
testRun.failureTracker.onRootSuite(testRun.rootSuite);
|
|
253
261
|
if (options.failOnLoadErrors && !testRun.rootSuite.allTests().length && !testRun.config.cliPassWithNoTests && !testRun.config.config.shard && !testRun.config.cliOnlyChanged) {
|
|
254
262
|
if (testRun.config.cliArgs.length) {
|
package/lib/runner/testServer.js
CHANGED
|
@@ -286,14 +286,17 @@ class TestServerDispatcher {
|
|
|
286
286
|
const config = await this._loadConfigOrReportError(new import_internalReporter.InternalReporter([wireReporter]), overrides);
|
|
287
287
|
if (!config)
|
|
288
288
|
return { status: "failed" };
|
|
289
|
-
const testIdSet = params.testIds ? new Set(params.testIds) : null;
|
|
290
289
|
config.cliListOnly = false;
|
|
291
290
|
config.cliPassWithNoTests = true;
|
|
292
291
|
config.cliArgs = params.locations || [];
|
|
293
292
|
config.cliGrep = params.grep;
|
|
294
293
|
config.cliGrepInvert = params.grepInvert;
|
|
295
294
|
config.cliProjectFilter = params.projects?.length ? params.projects : void 0;
|
|
296
|
-
config.
|
|
295
|
+
config.preOnlyTestFilters = [];
|
|
296
|
+
if (params.testIds) {
|
|
297
|
+
const testIdSet = new Set(params.testIds);
|
|
298
|
+
config.preOnlyTestFilters.push((test) => testIdSet.has(test.id));
|
|
299
|
+
}
|
|
297
300
|
const configReporters = await (0, import_reporters.createReporters)(config, "test", true);
|
|
298
301
|
const reporter = new import_internalReporter.InternalReporter([...configReporters, wireReporter]);
|
|
299
302
|
const stop = new import_utils.ManualPromise();
|
package/lib/util.js
CHANGED
|
@@ -47,6 +47,7 @@ __export(util_exports, {
|
|
|
47
47
|
formatLocation: () => formatLocation,
|
|
48
48
|
getContainedPath: () => getContainedPath,
|
|
49
49
|
getPackageJsonPath: () => getPackageJsonPath,
|
|
50
|
+
loadTestFilterFile: () => loadTestFilterFile,
|
|
50
51
|
mergeObjects: () => mergeObjects,
|
|
51
52
|
normalizeAndSaveAttachment: () => normalizeAndSaveAttachment,
|
|
52
53
|
relativeFilePath: () => relativeFilePath,
|
|
@@ -390,6 +391,22 @@ function stepTitle(category, title) {
|
|
|
390
391
|
return `[${category}] ${title}`;
|
|
391
392
|
}
|
|
392
393
|
}
|
|
394
|
+
async function loadTestFilterFile(filePath) {
|
|
395
|
+
try {
|
|
396
|
+
const data = JSON.parse(await import_fs.default.promises.readFile(filePath, "utf8"));
|
|
397
|
+
if (!data || typeof data !== "object" || !Array.isArray(data.titlePath) || !data.titlePath.every((path2) => Array.isArray(path2)))
|
|
398
|
+
throw new Error(`Wrong test filter file format`);
|
|
399
|
+
const toId = (titlePath) => {
|
|
400
|
+
const [project, file, ...titles] = titlePath;
|
|
401
|
+
return `${project}${(0, import_utils.toPosixPath)(file)}${titles.join("")}`;
|
|
402
|
+
};
|
|
403
|
+
const ids = new Set(data.titlePath.map((titlePath) => toId(titlePath)));
|
|
404
|
+
return (test) => ids.has(toId(test.titlePath()));
|
|
405
|
+
} catch (error) {
|
|
406
|
+
(0, import_utils.rewriteErrorMessage)(error, `Failed to read test filter file "${filePath}": ${error.message}`);
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
393
410
|
// Annotate the CommonJS export names for ESM import in node:
|
|
394
411
|
0 && (module.exports = {
|
|
395
412
|
addSuffixToFilePath,
|
|
@@ -411,6 +428,7 @@ function stepTitle(category, title) {
|
|
|
411
428
|
formatLocation,
|
|
412
429
|
getContainedPath,
|
|
413
430
|
getPackageJsonPath,
|
|
431
|
+
loadTestFilterFile,
|
|
414
432
|
mergeObjects,
|
|
415
433
|
normalizeAndSaveAttachment,
|
|
416
434
|
relativeFilePath,
|
|
@@ -32,11 +32,12 @@ class Fixture {
|
|
|
32
32
|
this.runner = runner;
|
|
33
33
|
this.registration = registration;
|
|
34
34
|
this.value = null;
|
|
35
|
-
const shouldGenerateStep = !this.registration.box && !this.registration.option;
|
|
36
35
|
const isUserFixture = this.registration.location && (0, import_util.filterStackFile)(this.registration.location.file);
|
|
37
36
|
const title = this.registration.customTitle || this.registration.name;
|
|
38
37
|
const location = isUserFixture ? this.registration.location : void 0;
|
|
39
|
-
this._stepInfo =
|
|
38
|
+
this._stepInfo = { title, category: "fixture", location };
|
|
39
|
+
if (this.registration.box)
|
|
40
|
+
this._stepInfo.visibility = isUserFixture ? "hidden" : "internal";
|
|
40
41
|
this._setupDescription = {
|
|
41
42
|
title,
|
|
42
43
|
phase: "setup",
|
|
@@ -54,11 +55,9 @@ class Fixture {
|
|
|
54
55
|
this.value = this.registration.fn;
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
else
|
|
61
|
-
await run();
|
|
58
|
+
await testInfo._runAsStep(this._stepInfo, async () => {
|
|
59
|
+
await testInfo._runWithTimeout({ ...runnable, fixture: this._setupDescription }, () => this._setupInternal(testInfo));
|
|
60
|
+
});
|
|
62
61
|
}
|
|
63
62
|
async _setupInternal(testInfo) {
|
|
64
63
|
const params = {};
|
|
@@ -107,11 +106,9 @@ class Fixture {
|
|
|
107
106
|
try {
|
|
108
107
|
const fixtureRunnable = { ...runnable, fixture: this._teardownDescription };
|
|
109
108
|
if (!testInfo._timeoutManager.isTimeExhaustedFor(fixtureRunnable)) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
else
|
|
114
|
-
await run();
|
|
109
|
+
await testInfo._runAsStep(this._stepInfo, async () => {
|
|
110
|
+
await testInfo._runWithTimeout(fixtureRunnable, () => this._teardownInternal());
|
|
111
|
+
});
|
|
115
112
|
}
|
|
116
113
|
} finally {
|
|
117
114
|
for (const dep of this._deps)
|
package/lib/worker/testInfo.js
CHANGED
|
@@ -185,18 +185,22 @@ class TestInfoImpl {
|
|
|
185
185
|
parentStep = this._parentStep();
|
|
186
186
|
}
|
|
187
187
|
const filteredStack = (0, import_util.filteredStackTrace)((0, import_utils.captureRawStack)());
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
let boxedStack = parentStep?.boxedStack;
|
|
189
|
+
let location = data.location;
|
|
190
|
+
if (!boxedStack && data.box) {
|
|
191
|
+
boxedStack = filteredStack.slice(1);
|
|
192
|
+
location = location || boxedStack[0];
|
|
192
193
|
}
|
|
193
|
-
|
|
194
|
-
const
|
|
194
|
+
location = location || filteredStack[0];
|
|
195
|
+
const visibility = parentStep?.visibility === "internal" || data.visibility === "internal" ? "internal" : parentStep?.visibility === "hidden" || data.visibility === "hidden" ? "hidden" : "default";
|
|
195
196
|
const step = {
|
|
196
|
-
stepId,
|
|
197
197
|
...data,
|
|
198
|
+
stepId,
|
|
199
|
+
visibility,
|
|
200
|
+
boxedStack,
|
|
201
|
+
location,
|
|
198
202
|
steps: [],
|
|
199
|
-
attachmentIndices,
|
|
203
|
+
attachmentIndices: [],
|
|
200
204
|
info: new TestStepInfoImpl(this, stepId, data.title, parentStep?.info),
|
|
201
205
|
complete: (result) => {
|
|
202
206
|
if (step.endWallTime)
|
|
@@ -206,9 +210,9 @@ class TestInfoImpl {
|
|
|
206
210
|
if (typeof result.error === "object" && !result.error?.[stepSymbol])
|
|
207
211
|
result.error[stepSymbol] = step;
|
|
208
212
|
const error = (0, import_util2.testInfoError)(result.error);
|
|
209
|
-
if (
|
|
213
|
+
if (step.boxedStack)
|
|
210
214
|
error.stack = `${error.message}
|
|
211
|
-
${(0, import_utils.stringifyStackFrames)(
|
|
215
|
+
${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
|
|
212
216
|
step.error = error;
|
|
213
217
|
}
|
|
214
218
|
if (!step.error) {
|
|
@@ -220,39 +224,50 @@ ${(0, import_utils.stringifyStackFrames)(data.boxedStack).join("\n")}`;
|
|
|
220
224
|
}
|
|
221
225
|
}
|
|
222
226
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
227
|
+
if (visibility === "default") {
|
|
228
|
+
const payload = {
|
|
229
|
+
testId: this.testId,
|
|
230
|
+
stepId,
|
|
231
|
+
wallTime: step.endWallTime,
|
|
232
|
+
error: step.error,
|
|
233
|
+
suggestedRebaseline: result.suggestedRebaseline,
|
|
234
|
+
annotations: step.info.annotations
|
|
235
|
+
};
|
|
236
|
+
this._onStepEnd(payload);
|
|
237
|
+
}
|
|
238
|
+
if (visibility !== "internal") {
|
|
239
|
+
const errorForTrace = step.error ? { name: "", message: step.error.message || "", stack: step.error.stack } : void 0;
|
|
240
|
+
const attachments = step.attachmentIndices.map((i) => this.attachments[i]);
|
|
241
|
+
this._tracing.appendAfterActionForStep(stepId, errorForTrace, attachments, step.info.annotations);
|
|
242
|
+
}
|
|
235
243
|
}
|
|
236
244
|
};
|
|
237
245
|
const parentStepList = parentStep ? parentStep.steps : this._steps;
|
|
238
246
|
parentStepList.push(step);
|
|
239
247
|
this._stepMap.set(stepId, step);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
248
|
+
if (visibility === "default") {
|
|
249
|
+
const payload = {
|
|
250
|
+
testId: this.testId,
|
|
251
|
+
stepId,
|
|
252
|
+
parentStepId: parentStep ? parentStep.stepId : void 0,
|
|
253
|
+
title: step.title,
|
|
254
|
+
category: step.category,
|
|
255
|
+
wallTime: Date.now(),
|
|
256
|
+
location: step.location
|
|
257
|
+
};
|
|
258
|
+
this._onStepBegin(payload);
|
|
259
|
+
}
|
|
260
|
+
if (visibility !== "internal") {
|
|
261
|
+
this._tracing.appendBeforeActionForStep({
|
|
262
|
+
stepId,
|
|
263
|
+
parentId: parentStep?.stepId,
|
|
264
|
+
title: step.title,
|
|
265
|
+
category: step.category,
|
|
266
|
+
params: step.params,
|
|
267
|
+
stack: step.location ? [step.location] : [],
|
|
268
|
+
visibility: visibility === "hidden" ? "hidden" : void 0
|
|
269
|
+
});
|
|
270
|
+
}
|
|
256
271
|
return step;
|
|
257
272
|
}
|
|
258
273
|
_interrupt() {
|
|
@@ -327,9 +342,9 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
|
|
|
327
342
|
if (stepId) {
|
|
328
343
|
this._stepMap.get(stepId).attachmentIndices.push(index);
|
|
329
344
|
} else {
|
|
330
|
-
const
|
|
331
|
-
this._tracing.appendBeforeActionForStep(
|
|
332
|
-
this._tracing.appendAfterActionForStep(
|
|
345
|
+
const stepId2 = `attach@${(0, import_utils.createGuid)()}`;
|
|
346
|
+
this._tracing.appendBeforeActionForStep({ stepId: stepId2, title: attachment.name, category: "test.attach", stack: [] });
|
|
347
|
+
this._tracing.appendAfterActionForStep(stepId2, void 0, [attachment]);
|
|
333
348
|
}
|
|
334
349
|
this._onAttach({
|
|
335
350
|
testId: this.testId,
|
|
@@ -221,18 +221,19 @@ class TestTracing {
|
|
|
221
221
|
base64: typeof chunk === "string" ? void 0 : chunk.toString("base64")
|
|
222
222
|
});
|
|
223
223
|
}
|
|
224
|
-
appendBeforeActionForStep(
|
|
224
|
+
appendBeforeActionForStep(options) {
|
|
225
225
|
this._appendTraceEvent({
|
|
226
226
|
type: "before",
|
|
227
|
-
callId,
|
|
228
|
-
stepId:
|
|
229
|
-
parentId,
|
|
227
|
+
callId: options.stepId,
|
|
228
|
+
stepId: options.stepId,
|
|
229
|
+
parentId: options.parentId,
|
|
230
230
|
startTime: (0, import_utils.monotonicTime)(),
|
|
231
231
|
class: "Test",
|
|
232
|
-
method:
|
|
232
|
+
method: options.category,
|
|
233
233
|
title: (0, import_util.stepTitle)(options.category, options.title),
|
|
234
234
|
params: Object.fromEntries(Object.entries(options.params || {}).map(([name, value]) => [name, generatePreview(value)])),
|
|
235
|
-
stack: options.stack
|
|
235
|
+
stack: options.stack,
|
|
236
|
+
visibility: options.visibility
|
|
236
237
|
});
|
|
237
238
|
}
|
|
238
239
|
appendAfterActionForStep(callId, error, attachments = [], annotations) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright",
|
|
3
|
-
"version": "1.55.0-alpha-2025-08-
|
|
3
|
+
"version": "1.55.0-alpha-2025-08-08",
|
|
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.55.0-alpha-2025-08-
|
|
59
|
+
"playwright-core": "1.55.0-alpha-2025-08-08"
|
|
60
60
|
},
|
|
61
61
|
"optionalDependencies": {
|
|
62
62
|
"fsevents": "2.3.2"
|