diff --git a/packages/playwright-core/src/remote/playwrightServer.ts b/packages/playwright-core/src/remote/playwrightServer.ts
index c1928ce1e6b5e..d0bc9be5a017a 100644
--- a/packages/playwright-core/src/remote/playwrightServer.ts
+++ b/packages/playwright-core/src/remote/playwrightServer.ts
@@ -222,6 +222,10 @@ export class PlaywrightServer {
return {
preLaunchedBrowser: browser,
denyLaunch: true,
+ // The browser is shared between sequential connections, so that
+ // pages created by one connection can be kept alive after it drops
+ // (e.g. to debug or record them) and picked up by the next one.
+ sharedBrowser: true,
dispose: async () => {
// Don't close the pages so that user could debug them,
// but close all the empty contexts to clean up.
diff --git a/packages/trace-viewer/src/ui/actionList.css b/packages/trace-viewer/src/ui/actionList.css
index 7161559c62af6..eccb93b24b8de 100644
--- a/packages/trace-viewer/src/ui/actionList.css
+++ b/packages/trace-viewer/src/ui/actionList.css
@@ -49,10 +49,15 @@
color: var(--vscode-foreground);
}
-.action-location > span {
+.action-location > button {
margin: 0 4px;
cursor: pointer;
text-decoration: underline;
+ background: none;
+ border: none;
+ padding: 0;
+ color: inherit;
+ font: inherit;
}
.action-duration {
diff --git a/packages/trace-viewer/src/ui/errorsTab.tsx b/packages/trace-viewer/src/ui/errorsTab.tsx
index 36d65c4b7cc97..b8615bb583120 100644
--- a/packages/trace-viewer/src/ui/errorsTab.tsx
+++ b/packages/trace-viewer/src/ui/errorsTab.tsx
@@ -73,7 +73,7 @@ function ErrorView({ message, error, sdkLanguage, revealInSource }: { message: s
}}>
{error.action && renderAction(error.action, { sdkLanguage })}
{location &&
- @ revealInSource(error)}>{location}
+ @
}
diff --git a/packages/trace-viewer/src/ui/networkFilters.tsx b/packages/trace-viewer/src/ui/networkFilters.tsx
index dedb250dae895..a08f9a99cf815 100644
--- a/packages/trace-viewer/src/ui/networkFilters.tsx
+++ b/packages/trace-viewer/src/ui/networkFilters.tsx
@@ -35,6 +35,7 @@ export const NetworkFilters = ({ filterState, onFilterStateChange }: {
onFilterStateChange({ ...filterState, searchValue: e.target.value })}
diff --git a/packages/trace-viewer/src/ui/uiModeFiltersView.tsx b/packages/trace-viewer/src/ui/uiModeFiltersView.tsx
index 0579a741e1d89..6bf4fdcdaef1f 100644
--- a/packages/trace-viewer/src/ui/uiModeFiltersView.tsx
+++ b/packages/trace-viewer/src/ui/uiModeFiltersView.tsx
@@ -46,7 +46,7 @@ export const FiltersView: React.FC<{
{
setFilterText(e.target.value);
}}
diff --git a/tests/library/debug-controller.spec.ts b/tests/library/debug-controller.spec.ts
index 784b5e00253c1..9a63778ca28d0 100644
--- a/tests/library/debug-controller.spec.ts
+++ b/tests/library/debug-controller.spec.ts
@@ -296,6 +296,38 @@ test('should reset routes before reuse', async ({ server, connectedBrowserFactor
await browser2.close();
});
+test('should keep pages alive when the test connection drops', async ({ backend, connectedBrowserFactory }, testInfo) => {
+ testInfo.annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/37822' });
+
+ const pageCounts: number[] = [];
+ backend.on('stateChanged', params => pageCounts.push(params.pageCount));
+ await backend.setReportStateChanged({ enabled: true }, undefined);
+
+ // Emulates "Debug Test": the test process creates its own context and page.
+ const browser1 = await connectedBrowserFactory();
+ const context1 = await browser1.newContext();
+ const page1 = await context1.newPage();
+ await page1.setContent('');
+ const contextEmpty = await browser1.newContext();
+ expect(contextEmpty.pages()).toHaveLength(0);
+ await expect.poll(() => pageCounts[pageCounts.length - 1]).toBe(1);
+
+ // Emulates the user hitting "Stop" during debugging: the test process is
+ // killed and its connection drops without a graceful close.
+ await browser1.close();
+
+ // The next connection (e.g. "Record at cursor") still sees the page alive:
+ // contexts with pages are kept around for debugging, empty ones are cleaned up.
+ const browser2 = await connectedBrowserFactory();
+ const contexts = browser2.contexts();
+ expect(contexts).toHaveLength(1);
+ const page = contexts[0].pages()[0];
+ await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
+
+ // The backend never reported the debugged page as closed.
+ expect(pageCounts).not.toContain(0);
+});
+
test('should highlight inside iframe', async ({ backend, connectedBrowser }, testInfo) => {
testInfo.annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33146' });