playwright 1.57.0-alpha-2025-10-28 → 1.57.0-alpha-2025-10-30

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.
@@ -0,0 +1,34 @@
1
+ name: "Copilot Setup Steps"
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ paths:
7
+ - .github/workflows/copilot-setup-steps.yml
8
+ pull_request:
9
+ paths:
10
+ - .github/workflows/copilot-setup-steps.yml
11
+
12
+ jobs:
13
+ copilot-setup-steps:
14
+ runs-on: ubuntu-latest
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+
22
+ - uses: actions/setup-node@v4
23
+ with:
24
+ node-version: lts/*
25
+
26
+ - name: Install dependencies
27
+ run: npm ci
28
+
29
+ - name: Install Playwright Browsers
30
+ run: npx playwright install --with-deps
31
+
32
+ # Customize this step as needed
33
+ - name: Build application
34
+ run: npx run build
@@ -100,9 +100,6 @@ class ClaudeGenerator {
100
100
  await import_fs.default.promises.mkdir(".claude/agents", { recursive: true });
101
101
  for (const agent of agents)
102
102
  await writeFile(`.claude/agents/${agent.header.name}.md`, ClaudeGenerator.agentSpec(agent), "\u{1F916}", "agent definition");
103
- await deleteFile(`.claude/agents/playwright-test-planner.md`, "legacy planner agent");
104
- await deleteFile(`.claude/agents/playwright-test-generator.md`, "legacy generator agent");
105
- await deleteFile(`.claude/agents/playwright-test-healer.md`, "legacy healer agent");
106
103
  await writeFile(".mcp.json", JSON.stringify({
107
104
  mcpServers: {
108
105
  "playwright-test": {
@@ -115,7 +112,7 @@ class ClaudeGenerator {
115
112
  }
116
113
  static agentSpec(agent) {
117
114
  const claudeToolMap = /* @__PURE__ */ new Map([
118
- ["search", ["Glob", "Grep", "Read"]],
115
+ ["search", ["Glob", "Grep", "Read", "LS"]],
119
116
  ["edit", ["Edit", "MultiEdit", "Write"]]
120
117
  ]);
121
118
  function asClaudeTool(tool) {
@@ -126,13 +123,15 @@ class ClaudeGenerator {
126
123
  }
127
124
  const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : "";
128
125
  const lines = [];
126
+ const header = {
127
+ name: agent.header.name,
128
+ description: agent.header.description + examples,
129
+ tools: agent.header.tools.map((tool) => asClaudeTool(tool)).join(", "),
130
+ model: agent.header.model,
131
+ color: agent.header.color
132
+ };
129
133
  lines.push(`---`);
130
- lines.push(`name: ${agent.header.name}`);
131
- lines.push(`description: ${agent.header.description}.${examples}`);
132
- lines.push(`tools: ${agent.header.tools.map((tool) => asClaudeTool(tool)).join(", ")}`);
133
- lines.push(`model: ${agent.header.model}`);
134
- lines.push(`color: ${agent.header.color}`);
135
- lines.push(`---`);
134
+ lines.push(import_utilsBundle.yaml.stringify(header, { lineWidth: 1e5 }) + `---`);
136
135
  lines.push("");
137
136
  lines.push(agent.instructions);
138
137
  return lines.join("\n");
@@ -151,9 +150,6 @@ class OpencodeGenerator {
151
150
  prompt.push(...agent.examples.map((example) => `<example>${example}</example>`));
152
151
  await writeFile(`.opencode/prompts/${agent.header.name}.md`, prompt.join("\n"), "\u{1F916}", "agent definition");
153
152
  }
154
- await deleteFile(`.opencode/prompts/playwright-test-planner.md`, "legacy planner agent");
155
- await deleteFile(`.opencode/prompts/playwright-test-generator.md`, "legacy generator agent");
156
- await deleteFile(`.opencode/prompts/playwright-test-healer.md`, "legacy healer agent");
157
153
  await writeFile("opencode.json", OpencodeGenerator.configuration(agents), "\u{1F527}", "opencode configuration");
158
154
  initRepoDone();
159
155
  }
@@ -212,18 +208,46 @@ class CopilotGenerator {
212
208
  await deleteFile(`.github/chatmodes/\u{1F3AD} generator.chatmode.md`, "legacy generator chatmode");
213
209
  await deleteFile(`.github/chatmodes/\u{1F3AD} healer.chatmode.md`, "legacy healer chatmode");
214
210
  await VSCodeGenerator.appendToMCPJson();
211
+ const cwdFolder = import_path.default.basename(process.cwd());
212
+ const mcpConfig = {
213
+ "mcpServers": {
214
+ "playwright-test": {
215
+ "type": "stdio",
216
+ "command": "npx",
217
+ "args": [
218
+ `--prefix=/home/runner/work/${cwdFolder}/${cwdFolder}`,
219
+ "playwright",
220
+ "run-test-mcp-server",
221
+ "--headless",
222
+ `--config=/home/runner/work/${cwdFolder}/${cwdFolder}`
223
+ ],
224
+ "tools": ["*"]
225
+ }
226
+ }
227
+ };
228
+ if (!import_fs.default.existsSync(".github/copilot-setup-steps.yml")) {
229
+ const yaml2 = import_fs.default.readFileSync(import_path.default.join(__dirname, "copilot-setup-steps.yml"), "utf-8");
230
+ await writeFile(".github/workflows/copilot-setup-steps.yml", yaml2, "\u{1F527}", "GitHub Copilot setup steps");
231
+ }
232
+ console.log("");
233
+ console.log("");
234
+ console.log(" \u{1F527} TODO: GitHub > Settings > Copilot > Coding agent > MCP configuration");
235
+ console.log("------------------------------------------------------------------");
236
+ console.log(JSON.stringify(mcpConfig, null, 2));
237
+ console.log("------------------------------------------------------------------");
215
238
  initRepoDone();
216
239
  }
217
240
  static agentSpec(agent) {
218
241
  const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : "";
219
242
  const lines = [];
243
+ const header = {
244
+ name: agent.header.name,
245
+ description: agent.header.description + examples,
246
+ tools: agent.header.tools,
247
+ model: "Claude Sonnet 4"
248
+ };
220
249
  lines.push(`---`);
221
- lines.push(`name: ${agent.header.name}`);
222
- lines.push(`description: ${agent.header.description}.${examples}`);
223
- lines.push(`tools:
224
- ${agent.header.tools.map((tool) => ` - ${tool}`).join("\n")}`);
225
- lines.push(`model: Claude Sonnet 4`);
226
- lines.push(`---`);
250
+ lines.push(import_utilsBundle.yaml.stringify(header) + `---`);
227
251
  lines.push("");
228
252
  lines.push(agent.instructions);
229
253
  lines.push("");
@@ -307,7 +331,7 @@ class VSCodeGenerator {
307
331
  }
308
332
  }
309
333
  async function writeFile(filePath, content, icon, description) {
310
- console.log(`- ${icon} ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
334
+ console.log(` ${icon} ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
311
335
  await (0, import_utils.mkdirIfNeeded)(filePath);
312
336
  await import_fs.default.promises.writeFile(filePath, content, "utf-8");
313
337
  }
@@ -318,12 +342,12 @@ async function deleteFile(filePath, description) {
318
342
  } catch {
319
343
  return;
320
344
  }
321
- console.log(`- \u2702\uFE0F ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
345
+ console.log(` \u2702\uFE0F ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
322
346
  await import_fs.default.promises.unlink(filePath);
323
347
  }
324
348
  async function initRepo(config, projectName, options) {
325
349
  const project = (0, import_seed.seedProject)(config, projectName);
326
- console.log(`- \u{1F3AD} Using project "${project.project.name}" as a primary project`);
350
+ console.log(` \u{1F3AD} Using project "${project.project.name}" as a primary project`);
327
351
  if (!import_fs.default.existsSync("specs")) {
328
352
  await import_fs.default.promises.mkdir("specs");
329
353
  await writeFile(import_path.default.join("specs", "README.md"), `# Specs
@@ -353,7 +377,7 @@ This is a directory for test plans.
353
377
  }
354
378
  }
355
379
  function initRepoDone() {
356
- console.log("\u2705 Done.");
380
+ console.log(" \u2705 Done.");
357
381
  }
358
382
  async function loadPrompt(file, params) {
359
383
  const content = await import_fs.default.promises.readFile(import_path.default.join(__dirname, file), "utf-8");
@@ -8,7 +8,7 @@ Parameters:
8
8
  - Seed file (optional): the seed file to use, defaults to `${seedFile}`
9
9
  - Test plan file (optional): the test plan file to write, under `specs/` folder.
10
10
 
11
- 1. Call #pwt-planner subagent with prompt:
11
+ 1. Call #playwright-test-planner subagent with prompt:
12
12
 
13
13
  <plan>
14
14
  <task-text><!-- the task --></task-text>
@@ -16,7 +16,7 @@ Parameters:
16
16
  <plan-file><!-- path to test plan file to generate --></plan-file>
17
17
  </plan>
18
18
 
19
- 2. For each test case from the test plan file (1.1, 1.2, ...), one after another, not in parallel, call #pwt-generator subagent with prompt:
19
+ 2. For each test case from the test plan file (1.1, 1.2, ...), one after another, not in parallel, call #playwright-test-generator subagent with prompt:
20
20
 
21
21
  <generate>
22
22
  <test-suite><!-- Verbatim name of the test spec group w/o ordinal like "Multiplication tests" --></test-suite>
@@ -26,6 +26,6 @@ Parameters:
26
26
  <body><!-- Test case content including steps and expectations --></body>
27
27
  </generate>
28
28
 
29
- 3. Call #pwt-healer subagent with prompt:
29
+ 3. Call #playwright-test-healer subagent with prompt:
30
30
 
31
31
  <heal>Run all tests and fix the failing ones one after another.</heal>
@@ -1,5 +1,5 @@
1
1
  ---
2
- agent: pwt-generator
2
+ agent: playwright-test-generator
3
3
  description: Generate test plan
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: pwt-generator
2
+ name: playwright-test-generator
3
3
  description: Use this agent when you need to create automated browser tests using Playwright
4
4
  model: sonnet
5
5
  color: blue
@@ -1,5 +1,5 @@
1
1
  ---
2
- agent: pwt-healer
2
+ agent: playwright-test-healer
3
3
  description: Fix tests
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: pwt-healer
2
+ name: playwright-test-healer
3
3
  description: Use this agent when you need to debug and fix failing Playwright tests
4
4
  model: sonnet
5
5
  color: red
@@ -1,5 +1,5 @@
1
1
  ---
2
- agent: pwt-planner
2
+ agent: playwright-test-planner
3
3
  description: Create test plan
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: pwt-planner
2
+ name: playwright-test-planner
3
3
  description: Use this agent when you need to create comprehensive test plan for a web application or website
4
4
  model: sonnet
5
5
  color: green
@@ -22,14 +22,16 @@ __export(streams_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(streams_exports);
24
24
  var import_stream = require("stream");
25
+ var import_util = require("../../util");
25
26
  class StringWriteStream extends import_stream.Writable {
26
- constructor(progress) {
27
+ constructor(progress, stdio) {
27
28
  super();
28
29
  this._progress = progress;
30
+ this._prefix = stdio === "stdout" ? "" : "[err] ";
29
31
  }
30
32
  _write(chunk, encoding, callback) {
31
- const text = chunk.toString();
32
- this._progress({ message: text.endsWith("\n") ? text.slice(0, -1) : text });
33
+ const text = (0, import_util.stripAnsiEscapes)(chunk.toString());
34
+ this._progress({ message: `${this._prefix}${text.endsWith("\n") ? text.slice(0, -1) : text}` });
33
35
  callback();
34
36
  }
35
37
  }
@@ -29,7 +29,8 @@ 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"));
@@ -124,39 +125,77 @@ class TestContext {
124
125
  };
125
126
  }
126
127
  async runSeedTest(seedFile, projectName, progress) {
127
- const { screen } = this.createScreen(progress);
128
+ await this.runWithGlobalSetup(async (testRunner, reporter) => {
129
+ const result = await testRunner.runTests(reporter, {
130
+ headed: !this.options?.headless,
131
+ locations: ["/" + (0, import_utils.escapeRegExp)(seedFile) + "/"],
132
+ projects: [projectName],
133
+ timeout: 0,
134
+ workers: 1,
135
+ pauseAtEnd: true,
136
+ disableConfigReporters: true,
137
+ failOnLoadErrors: true
138
+ });
139
+ if (result.status === "passed" && !reporter.suite?.allTests().length)
140
+ throw new Error("seed test not found.");
141
+ if (result.status !== "passed")
142
+ throw new Error("Errors while running the seed test.");
143
+ }, progress);
144
+ }
145
+ async runWithGlobalSetup(callback, progress) {
146
+ const { screen, claimStdio, releaseStdio } = createScreen(progress);
128
147
  const configDir = this.configLocation.configDir;
129
- const reporter = new import_list.default({ configDir, screen });
130
148
  const testRunner = await this.createTestRunner();
131
- const result = await testRunner.runTests(reporter, {
132
- headed: !this.options?.headless,
133
- locations: ["/" + (0, import_utils.escapeRegExp)(seedFile) + "/"],
134
- projects: [projectName],
135
- timeout: 0,
136
- workers: 1,
137
- pauseAtEnd: true,
138
- disableConfigReporters: true,
139
- failOnLoadErrors: true
140
- });
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.");
145
- }
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
154
- };
155
- return { screen, stream };
149
+ claimStdio();
150
+ try {
151
+ const setupReporter = new import_list.default({ configDir, screen, includeTestId: true });
152
+ const { status } = await testRunner.runGlobalSetup([setupReporter]);
153
+ if (status !== "passed")
154
+ throw new Error("Failed to run global setup");
155
+ } finally {
156
+ releaseStdio();
157
+ }
158
+ try {
159
+ const reporter = new import_list.default({ configDir, screen, includeTestId: true });
160
+ return await callback(testRunner, reporter);
161
+ } finally {
162
+ claimStdio();
163
+ await testRunner.runGlobalTeardown().finally(() => {
164
+ releaseStdio();
165
+ });
166
+ }
156
167
  }
157
168
  async close() {
158
169
  }
159
170
  }
171
+ function createScreen(progress) {
172
+ const stdout = new import_streams.StringWriteStream(progress, "stdout");
173
+ const stderr = new import_streams.StringWriteStream(progress, "stderr");
174
+ const screen = {
175
+ ...import_base.terminalScreen,
176
+ isTTY: false,
177
+ colors: import_utils.noColors,
178
+ stdout,
179
+ stderr
180
+ };
181
+ const originalStdoutWrite = process.stdout.write;
182
+ const originalStderrWrite = process.stderr.write;
183
+ const claimStdio = () => {
184
+ process.stdout.write = (chunk) => {
185
+ stdout.write(chunk);
186
+ return true;
187
+ };
188
+ process.stderr.write = (chunk) => {
189
+ stderr.write(chunk);
190
+ return true;
191
+ };
192
+ };
193
+ const releaseStdio = () => {
194
+ process.stdout.write = originalStdoutWrite;
195
+ process.stderr.write = originalStderrWrite;
196
+ };
197
+ return { screen, claimStdio, releaseStdio };
198
+ }
160
199
  const bestPracticesMarkdown = `
161
200
  # Best practices
162
201
  - Do not improvise, do not add directives that were not asked for
@@ -172,5 +211,6 @@ const bestPracticesMarkdown = `
172
211
  // Annotate the CommonJS export names for ESM import in node:
173
212
  0 && (module.exports = {
174
213
  GeneratorJournal,
175
- TestContext
214
+ TestContext,
215
+ createScreen
176
216
  });
@@ -34,9 +34,9 @@ __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");
39
+ var import_testContext = require("./testContext");
40
40
  const listTests = (0, import_testTool.defineTestTool)({
41
41
  schema: {
42
42
  name: "test_list",
@@ -46,7 +46,7 @@ const listTests = (0, import_testTool.defineTestTool)({
46
46
  type: "readOnly"
47
47
  },
48
48
  handle: async (context, _, progress) => {
49
- const { screen } = context.createScreen(progress);
49
+ const { screen } = (0, import_testContext.createScreen)(progress);
50
50
  const reporter = new import_listModeReporter.default({ screen, includeTestId: true });
51
51
  const testRunner = await context.createTestRunner();
52
52
  await testRunner.listTests(reporter, {});
@@ -65,15 +65,13 @@ const runTests = (0, import_testTool.defineTestTool)({
65
65
  type: "readOnly"
66
66
  },
67
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, {
73
- locations: params.locations,
74
- projects: params.projects,
75
- disableConfigReporters: true
76
- });
68
+ await context.runWithGlobalSetup(async (testRunner, reporter) => {
69
+ await testRunner.runTests(reporter, {
70
+ locations: params.locations,
71
+ projects: params.projects,
72
+ disableConfigReporters: true
73
+ });
74
+ }, progress);
77
75
  return { content: [] };
78
76
  }
79
77
  });
@@ -91,19 +89,17 @@ const debugTest = (0, import_testTool.defineTestTool)({
91
89
  type: "readOnly"
92
90
  },
93
91
  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,
100
- testIds: [params.test.id],
101
- // For automatic recovery
102
- timeout: 0,
103
- workers: 1,
104
- pauseOnError: true,
105
- disableConfigReporters: true
106
- });
92
+ await context.runWithGlobalSetup(async (testRunner, reporter) => {
93
+ await testRunner.runTests(reporter, {
94
+ headed: !context.options?.headless,
95
+ testIds: [params.test.id],
96
+ // For automatic recovery
97
+ timeout: 0,
98
+ workers: 1,
99
+ pauseOnError: true,
100
+ disableConfigReporters: true
101
+ });
102
+ }, progress);
107
103
  return { content: [] };
108
104
  }
109
105
  });
@@ -115,17 +115,38 @@ class WebServerPlugin {
115
115
  });
116
116
  this._killProcess = gracefullyClose;
117
117
  debugWebServer(`Process started`);
118
+ if (this._options.wait?.stdout || this._options.wait?.stderr)
119
+ this._waitForStdioPromise = new import_utils.ManualPromise();
120
+ let stdoutWaitCollector = this._options.wait?.stdout ? "" : void 0;
121
+ let stderrWaitCollector = this._options.wait?.stderr ? "" : void 0;
122
+ const resolveStdioPromise = () => {
123
+ stderrWaitCollector = void 0;
124
+ stdoutWaitCollector = void 0;
125
+ this._waitForStdioPromise?.resolve();
126
+ };
118
127
  launchedProcess.stderr.on("data", (data) => {
128
+ if (stderrWaitCollector !== void 0) {
129
+ stderrWaitCollector += data.toString();
130
+ if (this._options.wait?.stderr?.test(stderrWaitCollector))
131
+ resolveStdioPromise();
132
+ }
119
133
  if (debugWebServer.enabled || (this._options.stderr === "pipe" || !this._options.stderr))
120
134
  this._reporter.onStdErr?.(prefixOutputLines(data.toString(), this._options.name));
121
135
  });
122
136
  launchedProcess.stdout.on("data", (data) => {
137
+ if (stdoutWaitCollector !== void 0) {
138
+ stdoutWaitCollector += data.toString();
139
+ if (this._options.wait?.stdout?.test(stdoutWaitCollector))
140
+ resolveStdioPromise();
141
+ }
123
142
  if (debugWebServer.enabled || this._options.stdout === "pipe")
124
143
  this._reporter.onStdOut?.(prefixOutputLines(data.toString(), this._options.name));
125
144
  });
126
145
  }
127
146
  async _waitForProcess() {
128
- if (!this._isAvailableCallback) {
147
+ if (this._options.wait?.time)
148
+ await new Promise((resolve) => setTimeout(resolve, this._options.wait.time));
149
+ if (!this._isAvailableCallback && !this._waitForStdioPromise) {
129
150
  this._processExitedPromise.catch(() => {
130
151
  });
131
152
  return;
@@ -133,10 +154,13 @@ class WebServerPlugin {
133
154
  debugWebServer(`Waiting for availability...`);
134
155
  const launchTimeout = this._options.timeout || 60 * 1e3;
135
156
  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
- ]);
157
+ const deadline = (0, import_utils.monotonicTime)() + launchTimeout;
158
+ const racingPromises = [this._processExitedPromise];
159
+ if (this._isAvailableCallback)
160
+ racingPromises.push((0, import_utils.raceAgainstDeadline)(() => waitFor(this._isAvailableCallback, cancellationToken), deadline));
161
+ if (this._waitForStdioPromise)
162
+ racingPromises.push((0, import_utils.raceAgainstDeadline)(() => this._waitForStdioPromise, deadline));
163
+ const { timedOut } = await Promise.race(racingPromises);
140
164
  cancellationToken.canceled = true;
141
165
  if (timedOut)
142
166
  throw new Error(`Timed out waiting ${launchTimeout}ms from config.webServer.`);
@@ -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}` : "";
@@ -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 = () => {
@@ -47,7 +47,7 @@ var import_line = __toESM(require("../reporters/line"));
47
47
  var import_list = __toESM(require("../reporters/list"));
48
48
  var import_listModeReporter = __toESM(require("../reporters/listModeReporter"));
49
49
  var import_reporterV2 = require("../reporters/reporterV2");
50
- async function createReporters(config, mode, isTestServer, descriptions) {
50
+ async function createReporters(config, mode, descriptions) {
51
51
  const defaultReporters = {
52
52
  blob: import_blob.BlobReporter,
53
53
  dot: mode === "list" ? import_listModeReporter.default : import_dot.default,
@@ -63,7 +63,7 @@ async function createReporters(config, mode, isTestServer, descriptions) {
63
63
  descriptions ??= config.config.reporter;
64
64
  if (config.configCLIOverrides.additionalReporters)
65
65
  descriptions = [...descriptions, ...config.configCLIOverrides.additionalReporters];
66
- const runOptions = reporterOptions(config, mode, isTestServer);
66
+ const runOptions = reporterOptions(config, mode);
67
67
  for (const r of descriptions) {
68
68
  const [name, arg] = r;
69
69
  const options = { ...runOptions, ...arg };
@@ -104,11 +104,10 @@ function createErrorCollectingReporter(screen) {
104
104
  errors: () => errors
105
105
  };
106
106
  }
107
- function reporterOptions(config, mode, isTestServer) {
107
+ function reporterOptions(config, mode) {
108
108
  return {
109
109
  configDir: config.configDir,
110
110
  _mode: mode,
111
- _isTestServer: isTestServer,
112
111
  _commandHash: computeCommandHash(config)
113
112
  };
114
113
  }
@@ -269,7 +269,7 @@ class TestRunner extends import_events.default {
269
269
  const testIdSet = new Set(params.testIds);
270
270
  config.preOnlyTestFilters.push((test) => testIdSet.has(test.id));
271
271
  }
272
- const configReporters = params.disableConfigReporters ? [] : await (0, import_reporters.createReporters)(config, "test", true);
272
+ const configReporters = params.disableConfigReporters ? [] : await (0, import_reporters.createReporters)(config, "test");
273
273
  const reporter = new import_internalReporter.InternalReporter([...configReporters, userReporter]);
274
274
  const stop = new import_utils.ManualPromise();
275
275
  const tasks = [
@@ -363,7 +363,7 @@ async function runAllTestsWithConfig(config) {
363
363
  const listOnly = config.cliListOnly;
364
364
  (0, import_gitCommitInfoPlugin.addGitCommitInfoPlugin)(config);
365
365
  (0, import_webServerPlugin.webServerPluginsForConfig)(config).forEach((p) => config.plugins.push({ factory: p }));
366
- const reporters = await (0, import_reporters.createReporters)(config, listOnly ? "list" : "test", false);
366
+ const reporters = await (0, import_reporters.createReporters)(config, listOnly ? "list" : "test");
367
367
  const lastRun = new import_lastRun.LastRunReporter(config);
368
368
  if (config.cliLastFailed)
369
369
  await lastRun.filterLastFailed();
@@ -45,6 +45,7 @@ var import_testRunner = require("./testRunner");
45
45
  const originalDebugLog = import_utilsBundle.debug.log;
46
46
  const originalStdoutWrite = process.stdout.write;
47
47
  const originalStderrWrite = process.stderr.write;
48
+ const originalStdinIsTTY = process.stdin.isTTY;
48
49
  class TestServer {
49
50
  constructor(configLocation, configCLIOverrides) {
50
51
  this._configLocation = configLocation;
@@ -191,10 +192,12 @@ class TestServerDispatcher {
191
192
  };
192
193
  process.stdout.write = stdoutWrite;
193
194
  process.stderr.write = stderrWrite;
195
+ process.stdin.isTTY = void 0;
194
196
  } else {
195
197
  import_utilsBundle.debug.log = originalDebugLog;
196
198
  process.stdout.write = originalStdoutWrite;
197
199
  process.stderr.write = originalStderrWrite;
200
+ process.stdin.isTTY = originalStdinIsTTY;
198
201
  }
199
202
  }
200
203
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright",
3
- "version": "1.57.0-alpha-2025-10-28",
3
+ "version": "1.57.0-alpha-2025-10-30",
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-28"
67
+ "playwright-core": "1.57.0-alpha-2025-10-30"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "fsevents": "2.3.2"
package/types/test.d.ts CHANGED
@@ -10170,6 +10170,17 @@ interface TestConfigWebServer {
10170
10170
  */
10171
10171
  stdout?: "pipe"|"ignore";
10172
10172
 
10173
+ /**
10174
+ * Consider command started only when given output has been produced or a time in milliseconds has passed.
10175
+ */
10176
+ wait?: {
10177
+ stdout?: RegExp;
10178
+
10179
+ stderr?: RegExp;
10180
+
10181
+ time?: number;
10182
+ };
10183
+
10173
10184
  /**
10174
10185
  * How long to wait for the process to start up and be available in milliseconds. Defaults to 60000.
10175
10186
  */