diff --git a/src/reusedBrowser.ts b/src/reusedBrowser.ts index 2508d0140..b39b6d18b 100644 --- a/src/reusedBrowser.ts +++ b/src/reusedBrowser.ts @@ -32,6 +32,7 @@ export class ReusedBrowser implements vscodeTypes.Disposable { private _cancelRecording: (() => void) | undefined; private _isRunningTests?: 'run' | 'debug'; private _insertedEditActionCount = 0; + private _keepBrowserAfterDebugRun = false; private _envProvider: (configFile: string) => NodeJS.ProcessEnv; private _disposables: vscodeTypes.Disposable[] = []; private _pageCount = 0; @@ -220,6 +221,10 @@ export class ReusedBrowser implements vscodeTypes.Disposable { return; if (pageCount) return; + // The page may have been torn down with the debugged test process - + // keep the browser around so that the user can inspect and record it. + if (this._keepBrowserAfterDebugRun) + return; this._stop(); } @@ -365,6 +370,7 @@ export class ReusedBrowser implements vscodeTypes.Disposable { } async onWillRunTests(config: TestConfig, debug: boolean) { + this._keepBrowserAfterDebugRun = false; if (!this._settingsModel.showBrowser.get() && !debug) return; if (!this._checkVersion(config, 'Show & reuse browser')) @@ -376,10 +382,14 @@ export class ReusedBrowser implements vscodeTypes.Disposable { } async onDidRunTests() { + const wasDebug = this._isRunningTests === 'debug'; if (!this._settingsModel.showBrowser.get()) { this._stop(); } else { - if (!this._pageCount) + // Keep the browser around after debugging, so that the user can + // inspect and record it, e.g. after stopping at a breakpoint. + this._keepBrowserAfterDebugRun = wasDebug; + if (!this._pageCount && !this._keepBrowserAfterDebugRun) this._stop(); } this._isRunningTests = undefined; @@ -398,6 +408,7 @@ export class ReusedBrowser implements vscodeTypes.Disposable { private _resetExtensionState() { this._insertedEditActionCount = 0; + this._keepBrowserAfterDebugRun = false; this._cancelRecording?.(); this._cancelRecording = undefined; } diff --git a/tests/debug-tests.spec.ts b/tests/debug-tests.spec.ts index f726bb96e..c8d176f60 100644 --- a/tests/debug-tests.spec.ts +++ b/tests/debug-tests.spec.ts @@ -175,6 +175,47 @@ test('should end test run when stopping the debugging', async ({ activate }, tes testRun.token.source.cancel(); }); +test('should keep reused browser when stopping the debugging', async ({ activate, showBrowser }, testInfo) => { + test.skip(!showBrowser); + + 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 ({ page }) => { + // Simulate breakpoint via stalling. + console.log('READY TO BREAK'); + await page.setContent(''); + 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)); + void profile.run(testItems); + const testRun = await testRunPromise; + await expect.poll(() => vscode.debug.output, { timeout: 10000 }).toContain('READY TO BREAK'); + + // The debug run has a page in the reused browser. + const extension = vscode.extensions[0]; + await expect.poll(() => extension.reusedBrowserForTest().pageCount()).toBe(1); + + const endPromise = new Promise(f => testRun.onDidEnd(f)); + vscode.debug.stopDebugging(); + await endPromise; + + // Stopping the debugging should not tear down the reused browser backend, + // so that the user can inspect and record the browser afterwards. + await new Promise(f => setTimeout(f, 1000)); + await expect.poll(() => extension.browserServerWSForTest()).toBeTruthy(); + + testRun.token.source.cancel(); +}); + test('should end test run when stopping the debugging during config parsing', async ({ activate }) => { const { vscode, testController } = await activate({ 'package.json': JSON.stringify({ type: 'module' }),