playwright 1.56.1 → 1.57.0

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.
Files changed (76) hide show
  1. package/README.md +3 -3
  2. package/ThirdPartyNotices.txt +202 -282
  3. package/lib/agents/copilot-setup-steps.yml +34 -0
  4. package/lib/agents/generateAgents.js +292 -160
  5. package/lib/agents/playwright-test-coverage.prompt.md +31 -0
  6. package/lib/agents/playwright-test-generate.prompt.md +8 -0
  7. package/lib/agents/{generator.md → playwright-test-generator.agent.md} +8 -22
  8. package/lib/agents/playwright-test-heal.prompt.md +6 -0
  9. package/lib/agents/{healer.md → playwright-test-healer.agent.md} +5 -28
  10. package/lib/agents/playwright-test-plan.prompt.md +9 -0
  11. package/lib/agents/{planner.md → playwright-test-planner.agent.md} +5 -68
  12. package/lib/common/config.js +6 -0
  13. package/lib/common/expectBundle.js +0 -9
  14. package/lib/common/expectBundleImpl.js +267 -249
  15. package/lib/common/testLoader.js +3 -2
  16. package/lib/common/testType.js +3 -12
  17. package/lib/common/validators.js +68 -0
  18. package/lib/index.js +9 -9
  19. package/lib/isomorphic/teleReceiver.js +1 -0
  20. package/lib/isomorphic/testServerConnection.js +14 -0
  21. package/lib/loader/loaderMain.js +1 -1
  22. package/lib/matchers/expect.js +12 -13
  23. package/lib/matchers/matchers.js +16 -0
  24. package/lib/mcp/browser/browserServerBackend.js +1 -1
  25. package/lib/mcp/browser/config.js +9 -24
  26. package/lib/mcp/browser/context.js +4 -21
  27. package/lib/mcp/browser/response.js +20 -11
  28. package/lib/mcp/browser/tab.js +25 -10
  29. package/lib/mcp/browser/tools/evaluate.js +2 -3
  30. package/lib/mcp/browser/tools/form.js +2 -3
  31. package/lib/mcp/browser/tools/keyboard.js +4 -5
  32. package/lib/mcp/browser/tools/pdf.js +1 -1
  33. package/lib/mcp/browser/tools/runCode.js +75 -0
  34. package/lib/mcp/browser/tools/screenshot.js +33 -15
  35. package/lib/mcp/browser/tools/snapshot.js +13 -14
  36. package/lib/mcp/browser/tools/tabs.js +2 -2
  37. package/lib/mcp/browser/tools/utils.js +0 -11
  38. package/lib/mcp/browser/tools/verify.js +3 -4
  39. package/lib/mcp/browser/tools.js +2 -0
  40. package/lib/mcp/program.js +21 -1
  41. package/lib/mcp/sdk/exports.js +1 -3
  42. package/lib/mcp/sdk/http.js +9 -2
  43. package/lib/mcp/sdk/proxyBackend.js +1 -1
  44. package/lib/mcp/sdk/server.js +13 -5
  45. package/lib/mcp/sdk/tool.js +2 -6
  46. package/lib/mcp/test/browserBackend.js +43 -33
  47. package/lib/mcp/test/generatorTools.js +3 -3
  48. package/lib/mcp/test/plannerTools.js +103 -5
  49. package/lib/mcp/test/seed.js +25 -15
  50. package/lib/mcp/test/streams.js +9 -4
  51. package/lib/mcp/test/testBackend.js +31 -29
  52. package/lib/mcp/test/testContext.js +143 -40
  53. package/lib/mcp/test/testTools.js +12 -21
  54. package/lib/plugins/webServerPlugin.js +37 -9
  55. package/lib/program.js +11 -20
  56. package/lib/reporters/html.js +2 -23
  57. package/lib/reporters/internalReporter.js +4 -2
  58. package/lib/reporters/junit.js +4 -2
  59. package/lib/reporters/list.js +1 -5
  60. package/lib/reporters/merge.js +12 -6
  61. package/lib/reporters/teleEmitter.js +3 -1
  62. package/lib/runner/dispatcher.js +26 -2
  63. package/lib/runner/failureTracker.js +5 -5
  64. package/lib/runner/loadUtils.js +2 -1
  65. package/lib/runner/loaderHost.js +1 -1
  66. package/lib/runner/reporters.js +5 -4
  67. package/lib/runner/testRunner.js +8 -9
  68. package/lib/runner/testServer.js +8 -3
  69. package/lib/runner/workerHost.js +3 -0
  70. package/lib/worker/testInfo.js +28 -17
  71. package/lib/worker/testTracing.js +1 -0
  72. package/lib/worker/workerMain.js +15 -6
  73. package/package.json +2 -2
  74. package/types/test.d.ts +96 -3
  75. package/types/testReporter.d.ts +5 -0
  76. package/lib/mcp/sdk/mdb.js +0 -208
@@ -29,10 +29,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  var testContext_exports = {};
30
30
  __export(testContext_exports, {
31
31
  GeneratorJournal: () => GeneratorJournal,
32
- TestContext: () => TestContext
32
+ TestContext: () => TestContext,
33
+ createScreen: () => createScreen
33
34
  });
34
35
  module.exports = __toCommonJS(testContext_exports);
35
36
  var import_fs = __toESM(require("fs"));
37
+ var import_os = __toESM(require("os"));
36
38
  var import_path = __toESM(require("path"));
37
39
  var import_utils = require("playwright-core/lib/utils");
38
40
  var import_base = require("../../reporters/base");
@@ -41,6 +43,10 @@ var import_streams = require("./streams");
41
43
  var import_util = require("../../util");
42
44
  var import_testRunner = require("../../runner/testRunner");
43
45
  var import_seed = require("./seed");
46
+ var import_exports = require("../sdk/exports");
47
+ var import_configLoader = require("../../common/configLoader");
48
+ var import_response = require("../browser/response");
49
+ var import_log = require("../log");
44
50
  class GeneratorJournal {
45
51
  constructor(rootPath, plan, seed) {
46
52
  this._rootPath = rootPath;
@@ -70,35 +76,55 @@ ${step.code}
70
76
  }
71
77
  }
72
78
  class TestContext {
73
- constructor(options) {
74
- this.options = options;
75
- }
76
- initialize(rootPath, configLocation) {
77
- this.configLocation = configLocation;
78
- this.rootPath = rootPath || configLocation.configDir;
79
+ constructor(clientInfo, configPath, options) {
80
+ this._clientInfo = clientInfo;
81
+ const rootPath = (0, import_exports.firstRootPath)(clientInfo);
82
+ this._configLocation = (0, import_configLoader.resolveConfigLocation)(configPath || rootPath);
83
+ this.rootPath = rootPath || this._configLocation.configDir;
84
+ if (options?.headless !== void 0)
85
+ this.computedHeaded = !options.headless;
86
+ else
87
+ this.computedHeaded = !process.env.CI && !(import_os.default.platform() === "linux" && !process.env.DISPLAY);
79
88
  }
80
89
  existingTestRunner() {
81
- return this._testRunner;
90
+ return this._testRunnerAndScreen?.testRunner;
91
+ }
92
+ async _cleanupTestRunner() {
93
+ if (!this._testRunnerAndScreen)
94
+ return;
95
+ await this._testRunnerAndScreen.testRunner.stopTests();
96
+ this._testRunnerAndScreen.claimStdio();
97
+ try {
98
+ await this._testRunnerAndScreen.testRunner.runGlobalTeardown();
99
+ } finally {
100
+ this._testRunnerAndScreen.releaseStdio();
101
+ this._testRunnerAndScreen = void 0;
102
+ }
82
103
  }
83
104
  async createTestRunner() {
84
- if (this._testRunner)
85
- await this._testRunner.stopTests();
86
- const testRunner = new import_testRunner.TestRunner(this.configLocation, {});
105
+ await this._cleanupTestRunner();
106
+ const testRunner = new import_testRunner.TestRunner(this._configLocation, {});
87
107
  await testRunner.initialize({});
88
- this._testRunner = testRunner;
89
- testRunner.on(import_testRunner.TestRunnerEvent.TestFilesChanged, (testFiles) => {
90
- this._testRunner?.emit(import_testRunner.TestRunnerEvent.TestFilesChanged, testFiles);
108
+ const testPaused = new import_utils.ManualPromise();
109
+ const testRunnerAndScreen = {
110
+ ...createScreen(),
111
+ testRunner,
112
+ waitForTestPaused: () => testPaused
113
+ };
114
+ this._testRunnerAndScreen = testRunnerAndScreen;
115
+ testRunner.on(import_testRunner.TestRunnerEvent.TestPaused, (params) => {
116
+ testRunnerAndScreen.sendMessageToPausedTest = params.sendMessage;
117
+ testPaused.resolve();
91
118
  });
92
- this._testRunner = testRunner;
93
- return testRunner;
119
+ return testRunnerAndScreen;
94
120
  }
95
121
  async getOrCreateSeedFile(seedFile, projectName) {
96
- const configDir = this.configLocation.configDir;
97
- const testRunner = await this.createTestRunner();
122
+ const configDir = this._configLocation.configDir;
123
+ const { testRunner } = await this.createTestRunner();
98
124
  const config = await testRunner.loadConfig();
99
125
  const project = (0, import_seed.seedProject)(config, projectName);
100
126
  if (!seedFile) {
101
- seedFile = await (0, import_seed.ensureSeedTest)(project, false);
127
+ seedFile = await (0, import_seed.ensureSeedFile)(project);
102
128
  } else {
103
129
  const candidateFiles = [];
104
130
  const testDir = project.project.testDir;
@@ -123,13 +149,9 @@ class TestContext {
123
149
  projectName: project.project.name
124
150
  };
125
151
  }
126
- async runSeedTest(seedFile, projectName, progress) {
127
- const { screen } = this.createScreen(progress);
128
- const configDir = this.configLocation.configDir;
129
- const reporter = new import_list.default({ configDir, screen });
130
- const testRunner = await this.createTestRunner();
131
- const result = await testRunner.runTests(reporter, {
132
- headed: !this.options?.headless,
152
+ async runSeedTest(seedFile, projectName) {
153
+ const result = await this.runTestsWithGlobalSetupAndPossiblePause({
154
+ headed: this.computedHeaded,
133
155
  locations: ["/" + (0, import_utils.escapeRegExp)(seedFile) + "/"],
134
156
  projects: [projectName],
135
157
  timeout: 0,
@@ -138,25 +160,105 @@ class TestContext {
138
160
  disableConfigReporters: true,
139
161
  failOnLoadErrors: true
140
162
  });
141
- if (result.status === "passed" && !reporter.suite?.allTests().length)
142
- throw new Error("seed test not found.");
143
- if (result.status !== "passed")
144
- throw new Error("Errors while running the seed test.");
163
+ if (result.status === "passed")
164
+ result.output += "\nError: seed test not found.";
165
+ else if (result.status !== "paused")
166
+ result.output += "\nError while running the seed test.";
167
+ return result;
145
168
  }
146
- createScreen(progress) {
147
- const stream = new import_streams.StringWriteStream(progress);
148
- const screen = {
149
- ...import_base.terminalScreen,
150
- isTTY: false,
151
- colors: import_utils.noColors,
152
- stdout: stream,
153
- stderr: stream
169
+ async runTestsWithGlobalSetupAndPossiblePause(params) {
170
+ const configDir = this._configLocation.configDir;
171
+ const testRunnerAndScreen = await this.createTestRunner();
172
+ const { testRunner, screen, claimStdio, releaseStdio } = testRunnerAndScreen;
173
+ claimStdio();
174
+ try {
175
+ const setupReporter = new import_list.default({ configDir, screen, includeTestId: true });
176
+ const { status: status2 } = await testRunner.runGlobalSetup([setupReporter]);
177
+ if (status2 !== "passed")
178
+ return { output: testRunnerAndScreen.output.join("\n"), status: status2 };
179
+ } finally {
180
+ releaseStdio();
181
+ }
182
+ let status = "passed";
183
+ const cleanup = async () => {
184
+ claimStdio();
185
+ try {
186
+ const result = await testRunner.runGlobalTeardown();
187
+ if (status === "passed")
188
+ status = result.status;
189
+ } finally {
190
+ releaseStdio();
191
+ }
154
192
  };
155
- return { screen, stream };
193
+ try {
194
+ const reporter = new import_list.default({ configDir, screen, includeTestId: true });
195
+ status = await Promise.race([
196
+ testRunner.runTests(reporter, params).then((result) => result.status),
197
+ testRunnerAndScreen.waitForTestPaused().then(() => "paused")
198
+ ]);
199
+ if (status === "paused") {
200
+ const response = await testRunnerAndScreen.sendMessageToPausedTest({ request: { initialize: { clientInfo: this._clientInfo } } });
201
+ if (response.error)
202
+ throw new Error(response.error.message);
203
+ testRunnerAndScreen.output.push(response.response.initialize.pausedMessage);
204
+ return { output: testRunnerAndScreen.output.join("\n"), status };
205
+ }
206
+ } catch (e) {
207
+ status = "failed";
208
+ testRunnerAndScreen.output.push(String(e));
209
+ await cleanup();
210
+ return { output: testRunnerAndScreen.output.join("\n"), status };
211
+ }
212
+ await cleanup();
213
+ return { output: testRunnerAndScreen.output.join("\n"), status };
156
214
  }
157
215
  async close() {
216
+ await this._cleanupTestRunner().catch(import_log.logUnhandledError);
217
+ }
218
+ async sendMessageToPausedTest(request) {
219
+ const sendMessage = this._testRunnerAndScreen?.sendMessageToPausedTest;
220
+ if (!sendMessage)
221
+ throw new Error("Must setup test before interacting with the page");
222
+ const result = await sendMessage({ request });
223
+ if (result.error)
224
+ throw new Error(result.error.message);
225
+ if (typeof request?.callTool?.arguments?.["intent"] === "string") {
226
+ const response = (0, import_response.parseResponse)(result.response.callTool);
227
+ if (response && !response.isError && response.code)
228
+ this.generatorJournal?.logStep(request.callTool.arguments["intent"], response.code);
229
+ }
230
+ return result.response;
158
231
  }
159
232
  }
233
+ function createScreen() {
234
+ const output = [];
235
+ const stdout = new import_streams.StringWriteStream(output, "stdout");
236
+ const stderr = new import_streams.StringWriteStream(output, "stderr");
237
+ const screen = {
238
+ ...import_base.terminalScreen,
239
+ isTTY: false,
240
+ colors: import_utils.noColors,
241
+ stdout,
242
+ stderr
243
+ };
244
+ const originalStdoutWrite = process.stdout.write;
245
+ const originalStderrWrite = process.stderr.write;
246
+ const claimStdio = () => {
247
+ process.stdout.write = (chunk) => {
248
+ stdout.write(chunk);
249
+ return true;
250
+ };
251
+ process.stderr.write = (chunk) => {
252
+ stderr.write(chunk);
253
+ return true;
254
+ };
255
+ };
256
+ const releaseStdio = () => {
257
+ process.stdout.write = originalStdoutWrite;
258
+ process.stderr.write = originalStderrWrite;
259
+ };
260
+ return { screen, claimStdio, releaseStdio, output };
261
+ }
160
262
  const bestPracticesMarkdown = `
161
263
  # Best practices
162
264
  - Do not improvise, do not add directives that were not asked for
@@ -172,5 +274,6 @@ const bestPracticesMarkdown = `
172
274
  // Annotate the CommonJS export names for ESM import in node:
173
275
  0 && (module.exports = {
174
276
  GeneratorJournal,
175
- TestContext
277
+ TestContext,
278
+ createScreen
176
279
  });
@@ -34,7 +34,6 @@ __export(testTools_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(testTools_exports);
36
36
  var import_bundle = require("../sdk/bundle");
37
- var import_list = __toESM(require("../../reporters/list"));
38
37
  var import_listModeReporter = __toESM(require("../../reporters/listModeReporter"));
39
38
  var import_testTool = require("./testTool");
40
39
  const listTests = (0, import_testTool.defineTestTool)({
@@ -45,12 +44,11 @@ const listTests = (0, import_testTool.defineTestTool)({
45
44
  inputSchema: import_bundle.z.object({}),
46
45
  type: "readOnly"
47
46
  },
48
- handle: async (context, _, progress) => {
49
- const { screen } = context.createScreen(progress);
47
+ handle: async (context) => {
48
+ const { testRunner, screen, output } = await context.createTestRunner();
50
49
  const reporter = new import_listModeReporter.default({ screen, includeTestId: true });
51
- const testRunner = await context.createTestRunner();
52
50
  await testRunner.listTests(reporter, {});
53
- return { content: [] };
51
+ return { content: output.map((text) => ({ type: "text", text })) };
54
52
  }
55
53
  });
56
54
  const runTests = (0, import_testTool.defineTestTool)({
@@ -64,17 +62,13 @@ const runTests = (0, import_testTool.defineTestTool)({
64
62
  }),
65
63
  type: "readOnly"
66
64
  },
67
- handle: async (context, params, progress) => {
68
- const { screen } = context.createScreen(progress);
69
- const configDir = context.configLocation.configDir;
70
- const reporter = new import_list.default({ configDir, screen, includeTestId: true, prefixStdio: "out" });
71
- const testRunner = await context.createTestRunner();
72
- await testRunner.runTests(reporter, {
65
+ handle: async (context, params) => {
66
+ const { output } = await context.runTestsWithGlobalSetupAndPossiblePause({
73
67
  locations: params.locations,
74
68
  projects: params.projects,
75
69
  disableConfigReporters: true
76
70
  });
77
- return { content: [] };
71
+ return { content: [{ type: "text", text: output }] };
78
72
  }
79
73
  });
80
74
  const debugTest = (0, import_testTool.defineTestTool)({
@@ -90,21 +84,18 @@ const debugTest = (0, import_testTool.defineTestTool)({
90
84
  }),
91
85
  type: "readOnly"
92
86
  },
93
- handle: async (context, params, progress) => {
94
- const { screen } = context.createScreen(progress);
95
- const configDir = context.configLocation.configDir;
96
- const reporter = new import_list.default({ configDir, screen, includeTestId: true, prefixStdio: "out" });
97
- const testRunner = await context.createTestRunner();
98
- await testRunner.runTests(reporter, {
99
- headed: !context.options?.headless,
87
+ handle: async (context, params) => {
88
+ const { output, status } = await context.runTestsWithGlobalSetupAndPossiblePause({
89
+ headed: context.computedHeaded,
100
90
  testIds: [params.test.id],
101
91
  // For automatic recovery
102
92
  timeout: 0,
103
93
  workers: 1,
104
94
  pauseOnError: true,
105
- disableConfigReporters: true
95
+ disableConfigReporters: true,
96
+ actionTimeout: 5e3
106
97
  });
107
- return { content: [] };
98
+ return { content: [{ type: "text", text: output }], isError: status !== "paused" && status !== "passed" };
108
99
  }
109
100
  });
110
101
  // Annotate the CommonJS export names for ESM import in node:
@@ -115,17 +115,42 @@ class WebServerPlugin {
115
115
  });
116
116
  this._killProcess = gracefullyClose;
117
117
  debugWebServer(`Process started`);
118
- launchedProcess.stderr.on("data", (data) => {
119
- if (debugWebServer.enabled || (this._options.stderr === "pipe" || !this._options.stderr))
120
- this._reporter.onStdErr?.(prefixOutputLines(data.toString(), this._options.name));
121
- });
118
+ if (this._options.wait?.stdout || this._options.wait?.stderr)
119
+ this._waitForStdioPromise = new import_utils.ManualPromise();
120
+ const stdioWaitCollectors = {
121
+ stdout: this._options.wait?.stdout ? "" : void 0,
122
+ stderr: this._options.wait?.stderr ? "" : void 0
123
+ };
122
124
  launchedProcess.stdout.on("data", (data) => {
123
125
  if (debugWebServer.enabled || this._options.stdout === "pipe")
124
126
  this._reporter.onStdOut?.(prefixOutputLines(data.toString(), this._options.name));
125
127
  });
128
+ launchedProcess.stderr.on("data", (data) => {
129
+ if (debugWebServer.enabled || (this._options.stderr === "pipe" || !this._options.stderr))
130
+ this._reporter.onStdErr?.(prefixOutputLines(data.toString(), this._options.name));
131
+ });
132
+ const resolveStdioPromise = () => {
133
+ stdioWaitCollectors.stdout = void 0;
134
+ stdioWaitCollectors.stderr = void 0;
135
+ this._waitForStdioPromise?.resolve();
136
+ };
137
+ for (const stdio of ["stdout", "stderr"]) {
138
+ launchedProcess[stdio].on("data", (data) => {
139
+ if (!this._options.wait?.[stdio] || stdioWaitCollectors[stdio] === void 0)
140
+ return;
141
+ stdioWaitCollectors[stdio] += data.toString();
142
+ this._options.wait[stdio].lastIndex = 0;
143
+ const result = this._options.wait[stdio].exec(stdioWaitCollectors[stdio]);
144
+ if (result) {
145
+ for (const [key, value] of Object.entries(result.groups || {}))
146
+ process.env[key.toUpperCase()] = value;
147
+ resolveStdioPromise();
148
+ }
149
+ });
150
+ }
126
151
  }
127
152
  async _waitForProcess() {
128
- if (!this._isAvailableCallback) {
153
+ if (!this._isAvailableCallback && !this._waitForStdioPromise) {
129
154
  this._processExitedPromise.catch(() => {
130
155
  });
131
156
  return;
@@ -133,10 +158,13 @@ class WebServerPlugin {
133
158
  debugWebServer(`Waiting for availability...`);
134
159
  const launchTimeout = this._options.timeout || 60 * 1e3;
135
160
  const cancellationToken = { canceled: false };
136
- const { timedOut } = await Promise.race([
137
- (0, import_utils.raceAgainstDeadline)(() => waitFor(this._isAvailableCallback, cancellationToken), (0, import_utils.monotonicTime)() + launchTimeout),
138
- this._processExitedPromise
139
- ]);
161
+ const deadline = (0, import_utils.monotonicTime)() + launchTimeout;
162
+ const racingPromises = [this._processExitedPromise];
163
+ if (this._isAvailableCallback)
164
+ racingPromises.push((0, import_utils.raceAgainstDeadline)(() => waitFor(this._isAvailableCallback, cancellationToken), deadline));
165
+ if (this._waitForStdioPromise)
166
+ racingPromises.push((0, import_utils.raceAgainstDeadline)(() => this._waitForStdioPromise, deadline));
167
+ const { timedOut } = await Promise.race(racingPromises);
140
168
  cancellationToken.canceled = true;
141
169
  if (timedOut)
142
170
  throw new Error(`Timed out waiting ${launchTimeout}ms from config.webServer.`);
package/lib/program.js CHANGED
@@ -46,9 +46,8 @@ var testServer = __toESM(require("./runner/testServer"));
46
46
  var import_watchMode = require("./runner/watchMode");
47
47
  var import_testRunner = require("./runner/testRunner");
48
48
  var import_reporters = require("./runner/reporters");
49
- var import_exports = require("./mcp/sdk/exports");
49
+ var mcp = __toESM(require("./mcp/sdk/exports"));
50
50
  var import_testBackend = require("./mcp/test/testBackend");
51
- var import_seed = require("./mcp/test/seed");
52
51
  var import_program3 = require("./mcp/program");
53
52
  var import_watchdog = require("./mcp/browser/watchdog");
54
53
  var import_generateAgents = require("./agents/generateAgents");
@@ -160,44 +159,36 @@ function addTestMCPServerCommand(program3) {
160
159
  command.option("--port <port>", "port to listen on for SSE transport.");
161
160
  command.action(async (options) => {
162
161
  (0, import_watchdog.setupExitWatchdog)();
163
- const backendFactory = {
162
+ const factory = {
164
163
  name: "Playwright Test Runner",
165
164
  nameInConfig: "playwright-test-runner",
166
165
  version: packageJSON.version,
167
166
  create: () => new import_testBackend.TestServerBackend(options.config, { muteConsole: options.port === void 0, headless: options.headless })
168
167
  };
169
- const mdbUrl = await (0, import_exports.runMainBackend)(
170
- backendFactory,
171
- {
172
- port: options.port === void 0 ? void 0 : +options.port
173
- }
174
- );
175
- if (mdbUrl)
176
- console.error("MCP Listening on: ", mdbUrl);
168
+ await mcp.start(factory, { port: options.port === void 0 ? void 0 : +options.port, host: options.host });
177
169
  });
178
170
  }
179
171
  function addInitAgentsCommand(program3) {
180
172
  const command = program3.command("init-agents");
181
173
  command.description("Initialize repository agents");
182
174
  const option = command.createOption("--loop <loop>", "Agentic loop provider");
183
- option.choices(["vscode", "claude", "opencode"]);
175
+ option.choices(["claude", "copilot", "opencode", "vscode", "vscode-legacy"]);
184
176
  command.addOption(option);
185
177
  command.option("-c, --config <file>", `Configuration file to find a project to use for seed test`);
186
178
  command.option("--project <project>", "Project to use for seed test");
179
+ command.option("--prompts", "Whether to include prompts in the agent initialization");
187
180
  command.action(async (opts) => {
181
+ const config = await (0, import_configLoader.loadConfigFromFile)(opts.config);
188
182
  if (opts.loop === "opencode") {
189
- await (0, import_generateAgents.initOpencodeRepo)();
190
- } else if (opts.loop === "vscode") {
191
- await (0, import_generateAgents.initVSCodeRepo)();
183
+ await import_generateAgents.OpencodeGenerator.init(config, opts.project, opts.prompts);
184
+ } else if (opts.loop === "vscode-legacy") {
185
+ await import_generateAgents.VSCodeGenerator.init(config, opts.project);
192
186
  } else if (opts.loop === "claude") {
193
- await (0, import_generateAgents.initClaudeCodeRepo)();
187
+ await import_generateAgents.ClaudeGenerator.init(config, opts.project, opts.prompts);
194
188
  } else {
195
- command.help();
189
+ await import_generateAgents.CopilotGenerator.init(config, opts.project, opts.prompts);
196
190
  return;
197
191
  }
198
- const config = await (0, import_configLoader.loadConfigFromFile)(opts.config);
199
- const project = (0, import_seed.seedProject)(config, opts.project);
200
- await (0, import_seed.ensureSeedTest)(project, true);
201
192
  });
202
193
  }
203
194
  async function runTests(args, opts) {
@@ -130,10 +130,10 @@ class HtmlReporter {
130
130
  if (process.env.CI || !this._buildResult)
131
131
  return;
132
132
  const { ok, singleTestId } = this._buildResult;
133
- const shouldOpen = !this._options._isTestServer && (this._open === "always" || !ok && this._open === "on-failure");
133
+ const shouldOpen = !!process.stdin.isTTY && (this._open === "always" || !ok && this._open === "on-failure");
134
134
  if (shouldOpen) {
135
135
  await showHTMLReport(this._outputFolder, this._host, this._port, singleTestId);
136
- } else if (this._options._mode === "test" && !this._options._isTestServer) {
136
+ } else if (this._options._mode === "test" && !!process.stdin.isTTY) {
137
137
  const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)();
138
138
  const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? "" : " " + import_path.default.relative(process.cwd(), this._outputFolder);
139
139
  const hostArg = this._host ? ` --host ${this._host}` : "";
@@ -197,8 +197,6 @@ function startHtmlReportServer(folder) {
197
197
  return false;
198
198
  }
199
199
  }
200
- if (relativePath.endsWith("/stall.js"))
201
- return true;
202
200
  if (relativePath === "/")
203
201
  relativePath = "/index.html";
204
202
  const absolutePath = import_path.default.join(folder, ...relativePath.split("/"));
@@ -274,25 +272,6 @@ class HtmlBuilder {
274
272
  const testFile = data.values().next().value.testFile;
275
273
  singleTestId = testFile.tests[0].testId;
276
274
  }
277
- if (process.env.PW_HMR === "1") {
278
- const redirectFile = import_path.default.join(this._reportFolder, "index.html");
279
- await this._writeReportData(redirectFile);
280
- async function redirect() {
281
- const hmrURL = new URL("http://localhost:44224");
282
- const popup = window.open(hmrURL);
283
- const listener = (evt) => {
284
- if (evt.source === popup && evt.data === "ready") {
285
- const element = document.getElementById("playwrightReportBase64");
286
- popup.postMessage(element?.textContent ?? "", hmrURL.origin);
287
- window.removeEventListener("message", listener);
288
- window.close();
289
- }
290
- };
291
- window.addEventListener("message", listener);
292
- }
293
- import_fs.default.appendFileSync(redirectFile, `<script>(${redirect.toString()})()</script>`);
294
- return { ok, singleTestId };
295
- }
296
275
  const appFolder = import_path.default.join(require.resolve("playwright-core"), "..", "lib", "vite", "htmlReport");
297
276
  await (0, import_utils.copyFileAndMakeWritable)(import_path.default.join(appFolder, "index.html"), import_path.default.join(this._reportFolder, "index.html"));
298
277
  if (this._hasTraces) {
@@ -28,7 +28,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var internalReporter_exports = {};
30
30
  __export(internalReporter_exports, {
31
- InternalReporter: () => InternalReporter
31
+ InternalReporter: () => InternalReporter,
32
+ addLocationAndSnippetToError: () => addLocationAndSnippetToError
32
33
  });
33
34
  module.exports = __toCommonJS(internalReporter_exports);
34
35
  var import_fs = __toESM(require("fs"));
@@ -126,5 +127,6 @@ function addLocationAndSnippetToError(config, error, file) {
126
127
  }
127
128
  // Annotate the CommonJS export names for ESM import in node:
128
129
  0 && (module.exports = {
129
- InternalReporter
130
+ InternalReporter,
131
+ addLocationAndSnippetToError
130
132
  });
@@ -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;
@@ -39,7 +39,6 @@ class ListReporter extends import_base.TerminalReporter {
39
39
  this._stepIndex = /* @__PURE__ */ new Map();
40
40
  this._needNewLine = false;
41
41
  this._printSteps = (0, import_utils.getAsBooleanFromENV)("PLAYWRIGHT_LIST_PRINT_STEPS", options?.printSteps);
42
- this._prefixStdio = options?.prefixStdio;
43
42
  }
44
43
  onBegin(suite) {
45
44
  super.onBegin(suite);
@@ -143,10 +142,7 @@ class ListReporter extends import_base.TerminalReporter {
143
142
  return;
144
143
  const text = chunk.toString("utf-8");
145
144
  this._updateLineCountAndNewLineFlagForOutput(text);
146
- if (this._prefixStdio)
147
- stream.write(`[${stdio}] ${chunk}`);
148
- else
149
- stream.write(chunk);
145
+ stream.write(chunk);
150
146
  }
151
147
  onTestEnd(test, result) {
152
148
  super.onTestEnd(test, result);
@@ -41,7 +41,7 @@ var import_teleReceiver = require("../isomorphic/teleReceiver");
41
41
  var import_reporters = require("../runner/reporters");
42
42
  var import_util = require("../util");
43
43
  async function createMergedReport(config, dir, reporterDescriptions, rootDirOverride) {
44
- const reporters = await (0, import_reporters.createReporters)(config, "merge", false, reporterDescriptions);
44
+ const reporters = await (0, import_reporters.createReporters)(config, "merge", reporterDescriptions);
45
45
  const multiplexer = new import_multiplexer.Multiplexer(reporters);
46
46
  const stringPool = new import_stringInternPool.StringInternPool();
47
47
  let printStatus = () => {
@@ -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,9 @@ 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,
159
+ webServer: config.webServer
158
160
  };
159
161
  }
160
162
  _serializeProject(suite) {