From e03fd9c2be3dabc0a5d1e88679f7c5a5949c093c Mon Sep 17 00:00:00 2001 From: Ashraf Ali Date: Sat, 29 Aug 2026 20:41:00 +0600 Subject: [PATCH] fix: keep reused browser when stopping the debugging Stopping a debug session tears down the debugged test process, which also tears down its browser page. When the page count then dropped to zero, the extension killed the reused browser backend as well, closing the browser instead of keeping it around for inspection and recording. Keep the backend alive after a debug run with "Show Browser" enabled, both when the run ends and when a late page-count update arrives. Fixes: https://github.com/microsoft/playwright/issues/37822 --- src/reusedBrowser.ts | 13 ++++++++++++- tests/debug-tests.spec.ts | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) 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' }),