Skip to content

Commit 1dbdb27

Browse files
committed
chore: run global setup in test tools
1 parent 6664140 commit 1dbdb27

File tree

10 files changed

+257
-68
lines changed

10 files changed

+257
-68
lines changed

.github/workflows/tests_mcp.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ env:
2323
# Force terminal colors. @see https://www.npmjs.com/package/colors
2424
FORCE_COLOR: 1
2525
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
26-
DEBUG_GIT_COMMIT_INFO: 1
2726

2827
jobs:
2928
test_mcp:

.github/workflows/tests_primary.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ env:
2525
# Force terminal colors. @see https://www.npmjs.com/package/colors
2626
FORCE_COLOR: 1
2727
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
28-
DEBUG_GIT_COMMIT_INFO: 1
2928

3029
jobs:
3130
test_linux:
@@ -177,7 +176,6 @@ jobs:
177176
runs-on: ubuntu-latest
178177
env:
179178
PWTEST_BOT_NAME: "vscode-extension"
180-
DEBUG_GIT_COMMIT_INFO: ""
181179
steps:
182180
- uses: actions/checkout@v5
183181
- uses: actions/setup-node@v5

packages/playwright/src/mcp/test/streams.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,24 @@
1616

1717
import { Writable } from 'stream';
1818

19+
import { stripAnsiEscapes } from '../../util';
20+
1921
import type { ProgressCallback } from '../sdk/server';
2022

2123
export class StringWriteStream extends Writable {
2224
private _progress: ProgressCallback;
25+
private _prefix: string;
2326

24-
constructor(progress: ProgressCallback) {
27+
constructor(progress: ProgressCallback, stdio: 'stdout' | 'stderr') {
2528
super();
2629
this._progress = progress;
30+
this._prefix = stdio === 'stdout' ? '' : '[err] ';
2731
}
2832

2933
override _write(chunk: any, encoding: any, callback: any) {
30-
const text = chunk.toString();
34+
const text = stripAnsiEscapes(chunk.toString());
3135
// Progress wraps these as individual messages.
32-
this._progress({ message: text.endsWith('\n') ? text.slice(0, -1) : text });
36+
this._progress({ message: `${this._prefix}${text.endsWith('\n') ? text.slice(0, -1) : text}` });
3337
callback();
3438
}
3539
}

packages/playwright/src/mcp/test/testContext.ts

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -138,45 +138,93 @@ export class TestContext {
138138
}
139139

140140
async runSeedTest(seedFile: string, projectName: string, progress: ProgressCallback) {
141-
const { screen } = this.createScreen(progress);
141+
await this.runWithGlobalSetup(async (testRunner, reporter) => {
142+
const result = await testRunner.runTests(reporter, {
143+
headed: !this.options?.headless,
144+
locations: ['/' + escapeRegExp(seedFile) + '/'],
145+
projects: [projectName],
146+
timeout: 0,
147+
workers: 1,
148+
pauseAtEnd: true,
149+
disableConfigReporters: true,
150+
failOnLoadErrors: true,
151+
});
152+
// Ideally, we should check that page was indeed created and browser mcp has kicked in.
153+
// However, that is handled in the upper layer, so hard to check here.
154+
if (result.status === 'passed' && !reporter.suite?.allTests().length)
155+
throw new Error('seed test not found.');
156+
157+
if (result.status !== 'passed')
158+
throw new Error('Errors while running the seed test.');
159+
}, progress);
160+
}
161+
162+
async runWithGlobalSetup(
163+
callback: (testRunner: TestRunner, reporter: ListReporter) => Promise<void>,
164+
progress: ProgressCallback): Promise<void> {
165+
const { screen, claimStdio, releaseStdio } = createScreen(progress);
142166
const configDir = this.configLocation.configDir;
143-
const reporter = new ListReporter({ configDir, screen });
144167
const testRunner = await this.createTestRunner();
145168

146-
const result = await testRunner.runTests(reporter, {
147-
headed: !this.options?.headless,
148-
locations: ['/' + escapeRegExp(seedFile) + '/'],
149-
projects: [projectName],
150-
timeout: 0,
151-
workers: 1,
152-
pauseAtEnd: true,
153-
disableConfigReporters: true,
154-
failOnLoadErrors: true,
155-
});
169+
claimStdio();
170+
try {
171+
const setupReporter = new ListReporter({ configDir, screen, includeTestId: true });
172+
const { status } = await testRunner.runGlobalSetup([setupReporter]);
173+
if (status !== 'passed')
174+
throw new Error('Failed to run global setup');
175+
} finally {
176+
releaseStdio();
177+
}
156178

157-
// Ideally, we should check that page was indeed created and browser mcp has kicked in.
158-
// However, that is handled in the upper layer, so hard to check here.
159-
if (result.status === 'passed' && !reporter.suite?.allTests().length)
160-
throw new Error('seed test not found.');
179+
try {
180+
const reporter = new ListReporter({ configDir, screen, includeTestId: true });
181+
return await callback(testRunner, reporter);
182+
} finally {
183+
claimStdio();
184+
await testRunner.runGlobalTeardown().finally(() => {
185+
releaseStdio();
186+
});
187+
}
188+
}
161189

162-
if (result.status !== 'passed')
163-
throw new Error('Errors while running the seed test.');
190+
async close() {
164191
}
192+
}
165193

166-
createScreen(progress: ProgressCallback) {
167-
const stream = new StringWriteStream(progress);
168-
const screen = {
169-
...terminalScreen,
170-
isTTY: false,
171-
colors: noColors,
172-
stdout: stream as unknown as NodeJS.WriteStream,
173-
stderr: stream as unknown as NodeJS.WriteStream,
194+
export function createScreen(progress: ProgressCallback) {
195+
const stdout = new StringWriteStream(progress, 'stdout');
196+
const stderr = new StringWriteStream(progress, 'stderr');
197+
198+
const screen = {
199+
...terminalScreen,
200+
isTTY: false,
201+
colors: noColors,
202+
stdout: stdout as unknown as NodeJS.WriteStream,
203+
stderr: stderr as unknown as NodeJS.WriteStream,
204+
};
205+
206+
/* eslint-disable no-restricted-properties */
207+
const originalStdoutWrite = process.stdout.write;
208+
const originalStderrWrite = process.stderr.write;
209+
210+
const claimStdio = () => {
211+
process.stdout.write = (chunk: string | Buffer) => {
212+
stdout.write(chunk);
213+
return true;
174214
};
175-
return { screen, stream };
176-
}
215+
process.stderr.write = (chunk: string | Buffer) => {
216+
stderr.write(chunk);
217+
return true;
218+
};
219+
};
177220

178-
async close() {
179-
}
221+
const releaseStdio = () => {
222+
process.stdout.write = originalStdoutWrite;
223+
process.stderr.write = originalStderrWrite;
224+
};
225+
/* eslint-enable no-restricted-properties */
226+
227+
return { screen, claimStdio, releaseStdio };
180228
}
181229

182230
const bestPracticesMarkdown = `

packages/playwright/src/mcp/test/testTools.ts

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
*/
1616

1717
import { z } from '../sdk/bundle';
18-
import ListReporter from '../../reporters/list';
1918
import ListModeReporter from '../../reporters/listModeReporter';
2019
import { defineTestTool } from './testTool';
20+
import { createScreen } from './testContext';
2121

2222
export const listTests = defineTestTool({
2323
schema: {
@@ -29,7 +29,7 @@ export const listTests = defineTestTool({
2929
},
3030

3131
handle: async (context, _, progress) => {
32-
const { screen } = context.createScreen(progress);
32+
const { screen } = createScreen(progress);
3333
const reporter = new ListModeReporter({ screen, includeTestId: true });
3434
const testRunner = await context.createTestRunner();
3535
await testRunner.listTests(reporter, {});
@@ -51,15 +51,13 @@ export const runTests = defineTestTool({
5151
},
5252

5353
handle: async (context, params, progress) => {
54-
const { screen } = context.createScreen(progress);
55-
const configDir = context.configLocation.configDir;
56-
const reporter = new ListReporter({ configDir, screen, includeTestId: true, prefixStdio: 'out' });
57-
const testRunner = await context.createTestRunner();
58-
await testRunner.runTests(reporter, {
59-
locations: params.locations,
60-
projects: params.projects,
61-
disableConfigReporters: true,
62-
});
54+
await context.runWithGlobalSetup(async (testRunner, reporter) => {
55+
await testRunner.runTests(reporter, {
56+
locations: params.locations,
57+
projects: params.projects,
58+
disableConfigReporters: true,
59+
});
60+
}, progress);
6361

6462
return { content: [] };
6563
},
@@ -80,19 +78,17 @@ export const debugTest = defineTestTool({
8078
},
8179

8280
handle: async (context, params, progress) => {
83-
const { screen } = context.createScreen(progress);
84-
const configDir = context.configLocation.configDir;
85-
const reporter = new ListReporter({ configDir, screen, includeTestId: true, prefixStdio: 'out' });
86-
const testRunner = await context.createTestRunner();
87-
await testRunner.runTests(reporter, {
88-
headed: !context.options?.headless,
89-
testIds: [params.test.id],
90-
// For automatic recovery
91-
timeout: 0,
92-
workers: 1,
93-
pauseOnError: true,
94-
disableConfigReporters: true,
95-
});
81+
await context.runWithGlobalSetup(async (testRunner, reporter) => {
82+
await testRunner.runTests(reporter, {
83+
headed: !context.options?.headless,
84+
testIds: [params.test.id],
85+
// For automatic recovery
86+
timeout: 0,
87+
workers: 1,
88+
pauseOnError: true,
89+
disableConfigReporters: true,
90+
});
91+
}, progress);
9692

9793
return { content: [] };
9894
},

packages/playwright/src/reporters/list.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,10 @@ class ListReporter extends TerminalReporter {
3838
private _stepIndex = new Map<TestStep, string>();
3939
private _needNewLine = false;
4040
private _printSteps: boolean;
41-
private _prefixStdio?: string;
4241

43-
constructor(options?: ListReporterOptions & CommonReporterOptions & TerminalReporterOptions & { prefixStdio?: string }) {
42+
constructor(options?: ListReporterOptions & CommonReporterOptions & TerminalReporterOptions) {
4443
super(options);
4544
this._printSteps = getAsBooleanFromENV('PLAYWRIGHT_LIST_PRINT_STEPS', options?.printSteps);
46-
this._prefixStdio = options?.prefixStdio;
4745
}
4846

4947
override onBegin(suite: Suite) {
@@ -164,10 +162,7 @@ class ListReporter extends TerminalReporter {
164162
return;
165163
const text = chunk.toString('utf-8');
166164
this._updateLineCountAndNewLineFlagForOutput(text);
167-
if (this._prefixStdio)
168-
stream.write(`[${stdio}] ${chunk}`);
169-
else
170-
stream.write(chunk);
165+
stream.write(chunk);
171166
}
172167

173168
override onTestEnd(test: TestCase, result: TestResult) {

tests/mcp/planner.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ test('planner_setup_page (loading error)', async ({ startClient }) => {
183183
});
184184

185185
test('planner_setup_page (wrong test location)', async ({ startClient }) => {
186+
await writeFiles({});
186187
const { client } = await startClient();
187188
expect(await client.callTool({
188189
name: 'planner_setup_page',
@@ -196,6 +197,7 @@ test('planner_setup_page (wrong test location)', async ({ startClient }) => {
196197
});
197198

198199
test('planner_setup_page (no test location)', async ({ startClient }) => {
200+
await writeFiles({});
199201
const { client } = await startClient();
200202
expect(await client.callTool({
201203
name: 'planner_setup_page',

tests/mcp/seed-default-project.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,23 @@ test('seed test runs in first top-level project by default', async ({ startClien
4747
expect(fs.existsSync(path.join(baseDir, 'third', 'seed.spec.ts'))).toBe(false);
4848
});
4949

50+
test('respects provided seed test', async ({ startClient }) => {
51+
await writeFiles({
52+
'a.test.ts': `
53+
import { test, expect } from '@playwright/test';
54+
test('test', async ({ page }) => {});
55+
`,
56+
});
57+
58+
const { client } = await startClient();
59+
expect(await client.callTool({
60+
name: 'planner_setup_page',
61+
arguments: {
62+
seedFile: 'a.test.ts',
63+
},
64+
})).toHaveTextResponse(expect.stringContaining(`### Paused at end of test. ready for interaction`));
65+
});
66+
5067
test('seed test runs in first top-level project with dependencies', async ({ startClient }) => {
5168
const baseDir = await writeFiles({
5269
'playwright.config.ts': `

tests/mcp/test-debug.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ test('test_debug w/ console.log in test', async ({ startClient }) => {
323323
},
324324
})).toHaveTextResponse(expect.stringContaining(`
325325
Running 1 test using 1 worker
326-
[out] console.log
326+
console.log
327327
[err] console.error
328328
### Paused on error:
329329
Error: expect(locator).toBeVisible() failed`));

0 commit comments

Comments
 (0)