playwright 1.55.0-alpha-2025-08-07 → 1.55.0-alpha-2025-08-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/config.js +2 -0
- package/lib/common/fixtures.js +1 -1
- package/lib/common/suiteUtils.js +0 -9
- package/lib/index.js +40 -39
- 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,
|
|
@@ -110,34 +110,34 @@ const playwrightFixtures = {
|
|
|
110
110
|
await use(browser);
|
|
111
111
|
await browser.close({ reason: "Test ended." });
|
|
112
112
|
}, { scope: "worker", timeout: 0 }],
|
|
113
|
-
acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true }],
|
|
114
|
-
bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true }],
|
|
115
|
-
colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true }],
|
|
116
|
-
deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true }],
|
|
117
|
-
extraHTTPHeaders: [({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true }],
|
|
118
|
-
geolocation: [({ contextOptions }, use) => use(contextOptions.geolocation), { option: true }],
|
|
119
|
-
hasTouch: [({ contextOptions }, use) => use(contextOptions.hasTouch ?? false), { option: true }],
|
|
120
|
-
httpCredentials: [({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true }],
|
|
121
|
-
ignoreHTTPSErrors: [({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors ?? false), { option: true }],
|
|
122
|
-
isMobile: [({ contextOptions }, use) => use(contextOptions.isMobile ?? false), { option: true }],
|
|
123
|
-
javaScriptEnabled: [({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true }],
|
|
124
|
-
locale: [({ contextOptions }, use) => use(contextOptions.locale ?? "en-US"), { option: true }],
|
|
125
|
-
offline: [({ contextOptions }, use) => use(contextOptions.offline ?? false), { option: true }],
|
|
126
|
-
permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true }],
|
|
127
|
-
proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true }],
|
|
128
|
-
storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true }],
|
|
129
|
-
clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true }],
|
|
130
|
-
timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true }],
|
|
131
|
-
userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true }],
|
|
132
|
-
viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true }],
|
|
133
|
-
actionTimeout: [0, { option: true }],
|
|
134
|
-
testIdAttribute: ["data-testid", { option: true }],
|
|
135
|
-
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 }],
|
|
136
136
|
baseURL: [async ({}, use) => {
|
|
137
137
|
await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
|
|
138
|
-
}, { option: true }],
|
|
139
|
-
serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true }],
|
|
140
|
-
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 }],
|
|
141
141
|
_combinedContextOptions: [async ({
|
|
142
142
|
acceptDownloads,
|
|
143
143
|
bypassCSP,
|
|
@@ -217,7 +217,7 @@ const playwrightFixtures = {
|
|
|
217
217
|
if (testIdAttribute)
|
|
218
218
|
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
|
|
219
219
|
testInfo.snapshotSuffix = process.platform;
|
|
220
|
-
if ((0, import_utils.debugMode)())
|
|
220
|
+
if ((0, import_utils.debugMode)() === "inspector")
|
|
221
221
|
testInfo._setDebugMode();
|
|
222
222
|
playwright._defaultContextOptions = _combinedContextOptions;
|
|
223
223
|
playwright._defaultContextTimeout = actionTimeout || 0;
|
|
@@ -251,7 +251,8 @@ const playwrightFixtures = {
|
|
|
251
251
|
category: "pw:api",
|
|
252
252
|
title: renderTitle(channel.type, channel.method, channel.params, data.title),
|
|
253
253
|
apiName: data.apiName,
|
|
254
|
-
params: channel.params
|
|
254
|
+
params: channel.params,
|
|
255
|
+
visibility: (0, import_utils.getActionGroup)({ type: channel.type, method: channel.method }) ? "hidden" : void 0
|
|
255
256
|
}, tracingGroupSteps[tracingGroupSteps.length - 1]);
|
|
256
257
|
data.userData = step;
|
|
257
258
|
data.stepId = step.stepId;
|
|
@@ -364,8 +365,8 @@ const playwrightFixtures = {
|
|
|
364
365
|
});
|
|
365
366
|
await Promise.all([...contexts.values()].map((data) => data.close()));
|
|
366
367
|
}, { scope: "test", title: "context", box: true }],
|
|
367
|
-
_optionContextReuseMode: ["none", { scope: "worker", option: true }],
|
|
368
|
-
_optionConnectOptions: [void 0, { scope: "worker", option: true }],
|
|
368
|
+
_optionContextReuseMode: ["none", { scope: "worker", option: true, box: true }],
|
|
369
|
+
_optionConnectOptions: [void 0, { scope: "worker", option: true, box: true }],
|
|
369
370
|
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
|
|
370
371
|
let mode = _optionContextReuseMode;
|
|
371
372
|
if (process.env.PW_TEST_REUSE_CONTEXT)
|
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" : void 0;
|
|
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 (!step.visibility) {
|
|
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 (step.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 (!step.visibility) {
|
|
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 (step.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: step.visibility
|
|
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-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.55.0-alpha-2025-08-
|
|
59
|
+
"playwright-core": "1.55.0-alpha-2025-08-09"
|
|
60
60
|
},
|
|
61
61
|
"optionalDependencies": {
|
|
62
62
|
"fsevents": "2.3.2"
|