Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(debug): run global setup before debug #486

Merged
merged 1 commit into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,13 +408,7 @@ export class Extension implements RunHooks {
const result = model.narrowDownLocations(items);
if (!result.testIds && !result.locations)
continue;
let globalSetupResult: reporterTypes.FullResult['status'] = 'passed';
if (model.canRunGlobalHooks('setup')) {
const testListener = this._errorReportingListener(this._testRun, testItemForGlobalErrors);
globalSetupResult = await model.runGlobalHooks('setup', testListener);
}
if (globalSetupResult === 'passed')
await this._runTest(this._testRun, items, testItemForGlobalErrors, new Set(), model, mode === 'debug', enqueuedTests.length === 1);
await this._runTest(this._testRun, items, testItemForGlobalErrors, new Set(), model, mode === 'debug', enqueuedTests.length === 1);
}
} finally {
this._activeSteps.clear();
Expand Down
29 changes: 20 additions & 9 deletions src/playwrightTestServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ export class PlaywrightTestServer {
const testServer = await this._testServer();
if (!testServer)
return 'failed';
return await this._runGlobalHooksInServer(testServer, type, testListener);
}

private async _runGlobalHooksInServer(testServer: TestServerConnection, type: 'setup' | 'teardown', testListener: reporterTypes.ReporterV2): Promise<'passed' | 'failed' | 'interrupted' | 'timedout'> {
const teleReceiver = new TeleReporterReceiver(testListener, {
mergeProjects: true,
mergeTestCases: true,
Expand All @@ -119,8 +122,8 @@ export class PlaywrightTestServer {

try {
if (type === 'setup') {
const { report, status } = await testServer.runGlobalSetup({});
testListener.onStdOut?.('\x1b[2mRunning global setup if any\u2026\x1b[0m\n');
const { report, status } = await testServer.runGlobalSetup({});
for (const message of report)
teleReceiver.dispatch(message);
return status;
Expand Down Expand Up @@ -207,7 +210,7 @@ export class PlaywrightTestServer {

const testDirs = this._model.enabledProjects().map(project => project.project.testDir);

let testServer: TestServerConnection | undefined;
let debugTestServer: TestServerConnection | undefined;
let disposable: vscodeTypes.Disposable | undefined;
try {
await this._vscode.debug.startDebugging(undefined, {
Expand All @@ -233,8 +236,8 @@ export class PlaywrightTestServer {
if (token?.isCancellationRequested)
return;
const address = await addressPromise;
testServer = new TestServerConnection(address);
await testServer.initialize({
debugTestServer = new TestServerConnection(address);
await debugTestServer.initialize({
serializer: require.resolve('./oopReporter'),
closeOnDisconnect: true,
});
Expand All @@ -245,6 +248,12 @@ export class PlaywrightTestServer {
if (!locations && !testIds)
return;

const result = await this._runGlobalHooksInServer(debugTestServer, 'setup', reporter);
if (result !== 'passed')
return;
if (token?.isCancellationRequested)
return;

// Locations are regular expressions.
const locationPatterns = locations ? locations.map(escapeRegex) : undefined;
const options: Parameters<TestServerInterface['runTests']>['0'] = {
Expand All @@ -253,22 +262,24 @@ export class PlaywrightTestServer {
testIds,
...runOptions,
};
testServer.runTests(options);
debugTestServer.runTests(options);

token.onCancellationRequested(() => {
testServer!.stopTestsNoReply({});
debugTestServer!.stopTestsNoReply({});
});
disposable = testServer.onStdio(params => {
disposable = debugTestServer.onStdio(params => {
if (params.type === 'stdout')
reporter.onStdOut?.(unwrapString(params));
if (params.type === 'stderr')
reporter.onStdErr?.(unwrapString(params));
});
const testEndPromise = this._wireTestServer(testServer, reporter, token);
const testEndPromise = this._wireTestServer(debugTestServer, reporter, token);
await testEndPromise;
} finally {
disposable?.dispose();
testServer?.close();
if (!token.isCancellationRequested && debugTestServer && !debugTestServer.isClosed())
await this._runGlobalHooksInServer(debugTestServer, 'teardown', reporter);
debugTestServer?.close();
await this._options.runHooks.onDidRunTests(true);
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/testModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ export class TestModel {
}

async runGlobalHooks(type: 'setup' | 'teardown', testListener: reporterTypes.ReporterV2): Promise<reporterTypes.FullResult['status']> {
if (!this.canRunGlobalHooks(type))
return 'passed';
if (type === 'setup') {
if (this._ranGlobalSetup)
return 'passed';
Expand Down Expand Up @@ -465,6 +467,14 @@ export class TestModel {
async runTests(items: vscodeTypes.TestItem[], reporter: reporterTypes.ReporterV2, token: vscodeTypes.CancellationToken) {
if (token?.isCancellationRequested)
return;

// Run global setup with the first test.
let globalSetupResult: reporterTypes.FullResult['status'] = 'passed';
if (this.canRunGlobalHooks('setup'))
globalSetupResult = await this.runGlobalHooks('setup', reporter);
if (globalSetupResult !== 'passed')
return;

const externalOptions = await this._options.runHooks.onWillRunTests(this.config, false);
const showBrowser = this._options.settingsModel.showBrowser.get() && !!externalOptions.connectWsEndpoint;

Expand Down Expand Up @@ -503,6 +513,12 @@ export class TestModel {
async debugTests(items: vscodeTypes.TestItem[], reporter: reporterTypes.ReporterV2, token: vscodeTypes.CancellationToken) {
if (token?.isCancellationRequested)
return;

// Underlying debugTest implementation will run the global setup.
await this.runGlobalHooks('teardown', reporter);
if (token?.isCancellationRequested)
return;

const externalOptions = await this._options.runHooks.onWillRunTests(this.config, true);
const options: PlaywrightTestRunOptions = {
headed: !this._options.isUnderTest,
Expand Down
6 changes: 6 additions & 0 deletions src/upstream/testServerConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
private _ws: WebSocket;
private _callbacks = new Map<number, { resolve: (arg: any) => void, reject: (arg: Error) => void }>();
private _connectedPromise: Promise<void>;
private _isClosed = false;

constructor(wsURL: string) {
this.onClose = this._onCloseEmitter.event;
Expand Down Expand Up @@ -72,11 +73,16 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
this._ws.addEventListener('error', r);
});
this._ws.addEventListener('close', () => {
this._isClosed = true;
this._onCloseEmitter.fire();
clearInterval(pingInterval);
});
}

isClosed(): boolean {
return this._isClosed;
}

private async _sendMessage(method: string, params?: any): Promise<any> {
const logForTest = (globalThis as any).__logForTest;
logForTest?.({ method, params });
Expand Down
53 changes: 52 additions & 1 deletion tests/debug-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { expect, test, escapedPathSep } from './utils';
import { TestRun, DebugSession } from './mock/vscode';
import { TestRun, DebugSession, stripAnsi } from './mock/vscode';

test('should debug all tests', async ({ activate }) => {
const { vscode } = await activate({
Expand Down Expand Up @@ -56,6 +56,7 @@ test('should debug all tests', async ({ activate }) => {
testIds: undefined
})
},
{ method: 'runGlobalTeardown', params: {} },
]);
});

Expand Down Expand Up @@ -101,6 +102,7 @@ test('should debug one test', async ({ activate }) => {
testIds: [expect.any(String)]
})
},
{ method: 'runGlobalTeardown', params: {} },
]);
});

Expand Down Expand Up @@ -199,3 +201,52 @@ test('should pass all args as string[] when debugging', async ({ activate }) =>
expect(session.configuration.args.filter(arg => typeof arg !== 'string')).toEqual([]);
await onDidTerminateDebugSession;
});

test('should run global setup before debugging', async ({ activate }, testInfo) => {
const { vscode, testController } = await activate({
'playwright.config.js': `module.exports = {
testDir: 'tests',
globalSetup: 'globalSetup.ts',
globalTeardown: 'globalTeardown.ts',
}`,
'globalSetup.ts': `
async function globalSetup(config: FullConfig) {
console.log('RUN GLOBAL SETUP');
process.env.MAGIC_NUMBER = '42';
}
export default globalSetup;
`,
'globalTeardown.ts': `
async function globalTeardown(config: FullConfig) {
console.log('RUN GLOBAL TEARDOWN');
delete process.env.MAGIC_NUMBER;
}
export default globalTeardown;
`,
'tests/test.spec.ts': `
import { test, expect } from '@playwright/test';
test('should pass', async () => {
console.log('MAGIC NUMBER: ' + process.env.MAGIC_NUMBER);
expect(process.env.MAGIC_NUMBER).toBe('42');
});
`
});

const testRunPromise = new Promise<TestRun>(f => testController.onDidCreateTestRun(f));
await testController.expandTestItems(/test.spec/);
const testItems = testController.findTestItems(/pass/);
const profile = testController.debugProfile();
await profile.run(testItems);
const testRun = await testRunPromise;
expect(testRun.renderLog({ messages: true })).toBe(`
tests > test.spec.ts > should pass [2:0]
enqueued
enqueued
started
passed
`);

await expect.poll(() => stripAnsi(vscode.debug.output)).toContain(`RUN GLOBAL SETUP`);
await expect.poll(() => stripAnsi(vscode.debug.output)).toContain(`MAGIC NUMBER: 42`);
await expect.poll(() => stripAnsi(vscode.debug.output)).toContain(`RUN GLOBAL TEARDOWN`);
});
2 changes: 1 addition & 1 deletion tests/mock/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,6 @@ function trimLog(log: string) {
}

const ansiRegex = new RegExp('[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', 'g');
function stripAnsi(str: string): string {
export function stripAnsi(str: string): string {
return str.replace(ansiRegex, '');
}