diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 320b0e2f8a..49fb561b75 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -180,10 +180,13 @@ export abstract class BrowserContext extends SdkObject { page = undefined; } - // Unless I do this early, setting extra http headers below does not respond. + // Unless dialogs are dismissed, setting extra http headers below does not respond. + page?._frameManager.setCloseAllOpeningDialogs(true); await page?._frameManager.closeOpenDialogs(); // Navigate to about:blank first to ensure no page scripts are running after this point. await page?.mainFrame().goto(metadata, 'about:blank', { timeout: 0 }); + page?._frameManager.setCloseAllOpeningDialogs(false); + await this._resetStorage(); await this._removeExposedBindings(); await this._removeInitScripts(); diff --git a/packages/playwright-core/src/server/dialog.ts b/packages/playwright-core/src/server/dialog.ts index 8d863fa9cb..dd1683e9aa 100644 --- a/packages/playwright-core/src/server/dialog.ts +++ b/packages/playwright-core/src/server/dialog.ts @@ -25,13 +25,13 @@ export type DialogType = 'alert' | 'beforeunload' | 'confirm' | 'prompt'; export class Dialog extends SdkObject { private _page: Page; - private _type: string; + private _type: DialogType; private _message: string; private _onHandle: OnHandle; private _handled = false; private _defaultValue: string; - constructor(page: Page, type: string, message: string, onHandle: OnHandle, defaultValue?: string) { + constructor(page: Page, type: DialogType, message: string, onHandle: OnHandle, defaultValue?: string) { super(page, 'dialog'); this._page = page; this._type = type; @@ -53,7 +53,7 @@ export class Dialog extends SdkObject { return this._defaultValue; } - async accept(promptText: string | undefined) { + async accept(promptText?: string) { assert(!this._handled, 'Cannot accept dialog which is already handled!'); this._handled = true; this._page._frameManager.dialogWillClose(this); @@ -66,4 +66,11 @@ export class Dialog extends SdkObject { this._page._frameManager.dialogWillClose(this); await this._onHandle(false); } + + async close() { + if (this._type === 'beforeunload') + await this.accept(); + else + await this.dismiss(); + } } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 90ad50dc4f..94c8028161 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -108,6 +108,7 @@ export class FrameManager { readonly _signalBarriers = new Set(); private _webSockets = new Map(); _openedDialogs: Set = new Set(); + private _closeAllOpeningDialogs = false; constructor(page: Page) { this._page = page; @@ -352,7 +353,10 @@ export class FrameManager { // Any ongoing evaluations will be stalled until the dialog is closed. for (const frame of this._frames.values()) frame._invalidateNonStallingEvaluations('JavaScript dialog interrupted evaluation'); - this._openedDialogs.add(dialog); + if (this._closeAllOpeningDialogs) + dialog.close().then(() => {}); + else + this._openedDialogs.add(dialog); } dialogWillClose(dialog: Dialog) { @@ -360,10 +364,14 @@ export class FrameManager { } async closeOpenDialogs() { - await Promise.all([...this._openedDialogs].map(dialog => dialog.dismiss())).catch(() => {}); + await Promise.all([...this._openedDialogs].map(dialog => dialog.close())).catch(() => {}); this._openedDialogs.clear(); } + setCloseAllOpeningDialogs(closeDialogs: boolean) { + this._closeAllOpeningDialogs = closeDialogs; + } + removeChildFramesRecursively(frame: Frame) { for (const child of frame.childFrames()) this._removeFramesRecursively(child); diff --git a/tests/playwright-test/playwright.reuse.spec.ts b/tests/playwright-test/playwright.reuse.spec.ts index 16827a6c5e..1036e81229 100644 --- a/tests/playwright-test/playwright.reuse.spec.ts +++ b/tests/playwright-test/playwright.reuse.spec.ts @@ -349,3 +349,28 @@ test('should restore cookies', async ({ runInlineTest }) => { expect(result.exitCode).toBe(0); expect(result.passed).toBe(3); }); + +test('should reuse context with beforeunload', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'src/reuse.test.ts': ` + const { test } = pwt; + let lastContextGuid; + test('one', async ({ page, context }) => { + lastContextGuid = context._guid; + await page.evaluate(() => { + window.addEventListener('beforeunload', event => { + event.preventDefault(); + return event.returnValue = "Are you sure you want to exit?"; + }); + }); + }); + + test('two', async ({ context }) => { + expect(context._guid).toBe(lastContextGuid); + }); + `, + }, { workers: 1 }, { PW_TEST_REUSE_CONTEXT: '1' }); + + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(2); +});