diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts index 9df049a641..06672cc64e 100644 --- a/packages/playwright-core/src/client/browserType.ts +++ b/packages/playwright-core/src/client/browserType.ts @@ -151,8 +151,14 @@ export class BrowserType extends ChannelOwner imple page._onClose(); context._onClose(); } - browser?._didClose(); connection.close(reason || closeError); + // Give a chance to any API call promises to reject upon page/context closure. + // This happens naturally when we receive page.onClose and browser.onClose from the server + // in separate tasks. However, upon pipe closure we used to dispatch them all synchronously + // here and promises did not have a chance to reject. + // The order of rejects vs closure is a part of the API contract and our test runner + // relies on it to attribute rejections to the right test. + setTimeout(() => browser?._didClose(), 0); }; pipe.on('closed', params => onPipeClosed(params.reason)); connection.onmessage = message => this._wrapApiCall(() => pipe.send({ message }).catch(() => onPipeClosed()), /* isInternal */ true); diff --git a/tests/library/browsertype-connect.spec.ts b/tests/library/browsertype-connect.spec.ts index a78814d326..4a3cdb1efa 100644 --- a/tests/library/browsertype-connect.spec.ts +++ b/tests/library/browsertype-connect.spec.ts @@ -408,6 +408,29 @@ for (const kind of ['launchServer', 'run-server'] as const) { } }); + test('should reject waitForEvent before browser.close finishes', async ({ connect, startRemoteServer, server }) => { + const remoteServer = await startRemoteServer(kind); + const browser = await connect(remoteServer.wsEndpoint()); + const newPage = await browser.newPage(); + let rejected = false; + const promise = newPage.waitForEvent('download').catch(() => rejected = true); + await browser.close(); + expect(rejected).toBe(true); + await promise; + }); + + test('should reject waitForEvent before browser.onDisconnect fires', async ({ connect, startRemoteServer, server }) => { + const remoteServer = await startRemoteServer(kind); + const browser = await connect(remoteServer.wsEndpoint()); + const newPage = await browser.newPage(); + const log: string[] = []; + const promise = newPage.waitForEvent('download').catch(() => log.push('rejected')); + browser.on('disconnected', () => log.push('disconnected')); + await remoteServer.close(); + await promise; + await expect.poll(() => log).toEqual(['rejected', 'disconnected']); + }); + test('should respect selectors', async ({ playwright, connect, startRemoteServer }) => { const remoteServer = await startRemoteServer(kind);