diff --git a/src/playwrightTestServer.ts b/src/playwrightTestServer.ts index a4ccd37f2..1f98fd1d4 100644 --- a/src/playwrightTestServer.ts +++ b/src/playwrightTestServer.ts @@ -120,6 +120,7 @@ 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'); for (const message of report) teleReceiver.dispatch(message); return status; @@ -189,7 +190,6 @@ export class PlaywrightTestServer { } async debugTests(items: vscodeTypes.TestItem[], runOptions: PlaywrightTestRunOptions, reporter: reporterTypes.ReporterV2, token: vscodeTypes.CancellationToken): Promise { - const configFolder = path.dirname(this._model.config.configFile); const configFile = path.basename(this._model.config.configFile); const args = ['test-server', '-c', configFile]; @@ -212,7 +212,7 @@ export class PlaywrightTestServer { type: 'pwa-node', name: debugSessionName, request: 'launch', - cwd: configFolder, + cwd: this._model.config.workspaceFolder, env: { ...process.env, CI: this._options.isUnderTest ? undefined : process.env.CI, @@ -330,15 +330,21 @@ export class PlaywrightTestServer { resolvePath: (rootDir: string, relativePath: string) => this._vscode.Uri.file(path.join(rootDir, relativePath)).fsPath, }); return new Promise(resolve => { - const disposable = testServer.onReport(message => { - if (token.isCancellationRequested && message.method !== 'onEnd') - return; - teleReceiver.dispatch(message); - if (message.method === 'onEnd') { - disposable.dispose(); + const disposables = [ + testServer.onReport(message => { + if (token.isCancellationRequested && message.method !== 'onEnd') + return; + teleReceiver.dispatch(message); + if (message.method === 'onEnd') { + disposables.forEach(d => d.dispose()); + resolve(); + } + }), + testServer.onClose(() => { + disposables.forEach(d => d.dispose()); resolve(); - } - }); + }), + ]; }); } diff --git a/src/playwrightTestTypes.ts b/src/playwrightTestTypes.ts index 71972a5fd..cab742cc1 100644 --- a/src/playwrightTestTypes.ts +++ b/src/playwrightTestTypes.ts @@ -29,6 +29,7 @@ export type PlaywrightTestRunOptions = { headed?: boolean; workers?: string | number; trace?: 'on' | 'off'; + video?: 'on' | 'off'; reuseContext?: boolean; connectWsEndpoint?: string; }; diff --git a/src/testModel.ts b/src/testModel.ts index 1786c65b7..a785355d6 100644 --- a/src/testModel.ts +++ b/src/testModel.ts @@ -468,19 +468,24 @@ export class TestModel { const showBrowser = this._options.settingsModel.showBrowser.get() && !!externalOptions.connectWsEndpoint; let trace: 'on' | 'off' | undefined; + let video: 'on' | 'off' | undefined; + if (this._options.settingsModel.showTrace.get()) trace = 'on'; // "Show browser" mode forces context reuse that survives over multiple test runs. // Playwright Test sets up `tracesDir` inside the `test-results` folder, so it will be removed between runs. // When context is reused, its ongoing tracing will fail with ENOENT because trace files // were suddenly removed. So we disable tracing in this case. - if (this._options.settingsModel.showBrowser.get()) + if (this._options.settingsModel.showBrowser.get()) { trace = 'off'; + video = 'off'; + } const options: PlaywrightTestRunOptions = { headed: showBrowser && !this._options.isUnderTest, workers: showBrowser ? 1 : undefined, trace, + video, reuseContext: showBrowser, connectWsEndpoint: showBrowser ? externalOptions.connectWsEndpoint : undefined, }; @@ -501,6 +506,7 @@ export class TestModel { const options: PlaywrightTestRunOptions = { headed: !this._options.isUnderTest, workers: 1, + video: 'off', trace: 'off', reuseContext: false, connectWsEndpoint: externalOptions.connectWsEndpoint, diff --git a/src/upstream/testServerInterface.ts b/src/upstream/testServerInterface.ts index 6c7d5c86c..71eccdaac 100644 --- a/src/upstream/testServerInterface.ts +++ b/src/upstream/testServerInterface.ts @@ -94,6 +94,7 @@ export interface TestServerInterface { timeout?: number, reporters?: string[], trace?: 'on' | 'off'; + video?: 'on' | 'off'; projects?: string[]; reuseContext?: boolean; connectWsEndpoint?: string; diff --git a/tests/debug-tests.spec.ts b/tests/debug-tests.spec.ts index a2c6fe8f0..d194d5f4c 100644 --- a/tests/debug-tests.spec.ts +++ b/tests/debug-tests.spec.ts @@ -141,3 +141,39 @@ test('should debug error', async ({ activate }, testInfo) => { testRun.token.source.cancel(); }); + +test('should end test run when stopping the debugging', async ({ activate }, testInfo) => { + const { vscode, testController } = await activate({ + 'playwright.config.js': `module.exports = { testDir: 'tests' }`, + 'tests/test.spec.ts': ` + import { test } from '@playwright/test'; + test('should fail', async () => { + // Simulate breakpoint via stalling. + console.log('READY TO BREAK'); + await new Promise(() => {}); + }); + `, + }); + + await testController.expandTestItems(/test.spec/); + const testItems = testController.findTestItems(/fail/); + + const profile = testController.debugProfile(); + const testRunPromise = new Promise(f => testController.onDidCreateTestRun(f)); + profile.run(testItems); + const testRun = await testRunPromise; + await expect.poll(() => vscode.debug.output).toContain('READY TO BREAK'); + + const endPromise = new Promise(f => testRun.onDidEnd(f)); + vscode.debug.stopDebugging(); + await endPromise; + + expect(testRun.renderLog({ messages: true })).toBe(` + tests > test.spec.ts > should fail [2:0] + enqueued + enqueued + started + `); + + testRun.token.source.cancel(); +}); diff --git a/tests/mock/vscode.ts b/tests/mock/vscode.ts index 8e4e7902a..df4d36aa0 100644 --- a/tests/mock/vscode.ts +++ b/tests/mock/vscode.ts @@ -19,7 +19,7 @@ import glob from 'glob'; import path from 'path'; import { Disposable, EventEmitter, Event } from '../../src/upstream/events'; import minimatch from 'minimatch'; -import { spawn } from 'child_process'; +import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; import which from 'which'; import { Browser, Page } from '@playwright/test'; import { CancellationToken } from '../../src/vscodeTypes'; @@ -640,6 +640,7 @@ class Debug { output = ''; dapFactories: any[] = []; private _dapSniffer: any; + private _debuggerProcess: ChildProcessWithoutNullStreams; constructor() { } @@ -654,13 +655,13 @@ class Debug { this._dapSniffer = factory.createDebugAdapterTracker(session); this._didStartDebugSession.fire(session); const node = await which('node'); - const subprocess = spawn(node, [configuration.program, ...configuration.args], { + this._debuggerProcess = spawn(node, [configuration.program, ...configuration.args], { cwd: configuration.cwd, stdio: 'pipe', env: configuration.env, }); - subprocess.stdout.on('data', data => { + this._debuggerProcess.stdout.on('data', data => { this.output += data.toString(); this._dapSniffer.onDidSendMessage({ type: 'event', @@ -671,10 +672,14 @@ class Debug { } }); }); - subprocess.stderr.on('data', data => this.output += data.toString()); + this._debuggerProcess.stderr.on('data', data => this.output += data.toString()); return true; } + stopDebugging() { + this._debuggerProcess.kill(); + } + simulateStoppedOnError(error: string, location: { file: string; line: number; }) { this._dapSniffer.onDidSendMessage({ success: true, diff --git a/tests/run-tests.spec.ts b/tests/run-tests.spec.ts index 4ac96919c..89c70cd8e 100644 --- a/tests/run-tests.spec.ts +++ b/tests/run-tests.spec.ts @@ -1150,7 +1150,7 @@ test('should run tests for folders above root', async ({ activate }) => { `); }); -test('should produce output twice', async ({ activate }) => { +test('should produce output twice', async ({ activate, overridePlaywrightVersion }) => { const { testController } = await activate({ 'playwright.config.js': `module.exports = { testDir: 'tests', @@ -1168,12 +1168,13 @@ test('should produce output twice', async ({ activate }) => { expect(testItems.length).toBe(1); const testRun1 = await testController.run(testItems); + const runningGlobalSetup = overridePlaywrightVersion ? '' : '\n Running global setup if any…'; expect(testRun1.renderLog({ output: true })).toBe(` tests > test.spec.ts > one [2:0] enqueued started passed - Output: + Output:${runningGlobalSetup} Running 1 test using 1 worker