playwright 1.55.0-alpha-2025-08-07 → 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.

@@ -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 = [];
@@ -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, box: previous.box };
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
  }
@@ -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;
@@ -364,8 +364,8 @@ const playwrightFixtures = {
364
364
  });
365
365
  await Promise.all([...contexts.values()].map((data) => data.close()));
366
366
  }, { scope: "test", title: "context", box: true }],
367
- _optionContextReuseMode: ["none", { scope: "worker", option: true }],
368
- _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 }],
369
369
  _reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
370
370
  let mode = _optionContextReuseMode;
371
371
  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)` }],
@@ -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
- this._config.lastFailedTestIdMatcher = (id) => lastRunInfo.failedTests.includes(id);
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
- const failedTests = this._suite?.allTests().filter((t) => !t.ok()).map((t) => t.id);
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:
@@ -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, additionalFileMatcher) {
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, testIdMatcher: config.testIdMatcher, additionalFileMatcher });
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.lastFailedTestIdMatcher)
170
- (0, import_suiteUtils.filterByTestIds)(rootSuite, config.lastFailedTestIdMatcher);
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.testIdMatcher && !options.additionalFileMatcher)
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.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitleWithTags()))
201
+ if (!options.testFilters.every((filter) => filter(test)))
204
202
  return false;
205
- if (options.additionalFileMatcher && !options.additionalFileMatcher(test.location.file))
203
+ if (options.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitleWithTags()))
206
204
  return false;
207
205
  return true;
208
206
  });
@@ -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
- cliOnlyChangedMatcher = (file) => changedFiles.has(file);
248
+ testRun.config.preOnlyTestFilters.push((test) => changedFiles.has(test.location.file));
250
249
  }
251
- testRun.rootSuite = await (0, import_loadUtils.createRootSuite)(testRun, options.failOnLoadErrors ? errors : softErrors, !!options.filterOnly, cliOnlyChangedMatcher);
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) {
@@ -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.testIdMatcher = testIdSet ? (id) => testIdSet.has(id) : void 0;
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 = shouldGenerateStep ? { title, category: "fixture", location } : void 0;
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
- const run = () => testInfo._runWithTimeout({ ...runnable, fixture: this._setupDescription }, () => this._setupInternal(testInfo));
58
- if (this._stepInfo)
59
- await testInfo._runAsStep(this._stepInfo, run);
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
- const run = () => testInfo._runWithTimeout(fixtureRunnable, () => this._teardownInternal());
111
- if (this._stepInfo)
112
- await testInfo._runAsStep(this._stepInfo, run);
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)
@@ -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
- data.boxedStack = parentStep?.boxedStack;
189
- if (!data.boxedStack && data.box) {
190
- data.boxedStack = filteredStack.slice(1);
191
- data.location = data.location || data.boxedStack[0];
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
- data.location = data.location || filteredStack[0];
194
- const attachmentIndices = [];
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 (data.boxedStack)
213
+ if (step.boxedStack)
210
214
  error.stack = `${error.message}
211
- ${(0, import_utils.stringifyStackFrames)(data.boxedStack).join("\n")}`;
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
- const payload2 = {
224
- testId: this.testId,
225
- stepId,
226
- wallTime: step.endWallTime,
227
- error: step.error,
228
- suggestedRebaseline: result.suggestedRebaseline,
229
- annotations: step.info.annotations
230
- };
231
- this._onStepEnd(payload2);
232
- const errorForTrace = step.error ? { name: "", message: step.error.message || "", stack: step.error.stack } : void 0;
233
- const attachments = attachmentIndices.map((i) => this.attachments[i]);
234
- this._tracing.appendAfterActionForStep(stepId, errorForTrace, attachments, step.info.annotations);
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
- const payload = {
241
- testId: this.testId,
242
- stepId,
243
- parentStepId: parentStep ? parentStep.stepId : void 0,
244
- title: data.title,
245
- category: data.category,
246
- wallTime: Date.now(),
247
- location: data.location
248
- };
249
- this._onStepBegin(payload);
250
- this._tracing.appendBeforeActionForStep(stepId, parentStep?.stepId, {
251
- title: data.title,
252
- category: data.category,
253
- params: data.params,
254
- stack: data.location ? [data.location] : []
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 callId = `attach@${(0, import_utils.createGuid)()}`;
331
- this._tracing.appendBeforeActionForStep(callId, void 0, { title: attachment.name, category: "test.attach", stack: [] });
332
- this._tracing.appendAfterActionForStep(callId, void 0, [attachment]);
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(callId, parentId, options) {
224
+ appendBeforeActionForStep(options) {
225
225
  this._appendTraceEvent({
226
226
  type: "before",
227
- callId,
228
- stepId: callId,
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: "step",
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-07",
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-07"
59
+ "playwright-core": "1.55.0-alpha-2025-08-08"
60
60
  },
61
61
  "optionalDependencies": {
62
62
  "fsevents": "2.3.2"