playwright 1.57.0-alpha-2025-10-20 → 1.57.0-alpha-2025-10-22

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