diff --git a/packages/playwright-core/src/server/chromium/crNetworkManager.ts b/packages/playwright-core/src/server/chromium/crNetworkManager.ts index 5943c69082..f4668dae54 100644 --- a/packages/playwright-core/src/server/chromium/crNetworkManager.ts +++ b/packages/playwright-core/src/server/chromium/crNetworkManager.ts @@ -56,7 +56,7 @@ export class CRNetworkManager { eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this)), eventsHelper.addEventListener(session, 'Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)), eventsHelper.addEventListener(session, 'Network.loadingFinished', this._onLoadingFinished.bind(this)), - eventsHelper.addEventListener(session, 'Network.loadingFailed', this._onLoadingFailed.bind(this)), + eventsHelper.addEventListener(session, 'Network.loadingFailed', this._onLoadingFailed.bind(this, workerFrame)), eventsHelper.addEventListener(session, 'Network.webSocketCreated', e => this._page._frameManager.onWebSocketCreated(e.requestId, e.url)), eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page._frameManager.onWebSocketRequest(e.requestId)), eventsHelper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)), @@ -368,12 +368,25 @@ export class CRNetworkManager { this._page._frameManager.reportRequestFinished(request.request, response); } - _onLoadingFailed(event: Protocol.Network.loadingFailedPayload) { + _onLoadingFailed(workerFrame: frames.Frame | undefined, event: Protocol.Network.loadingFailedPayload) { this._responseExtraInfoTracker.loadingFailed(event); let request = this._requestIdToRequest.get(event.requestId); if (!request) request = this._maybeAdoptMainRequest(event.requestId); + + if (!request) { + const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId); + if (requestWillBeSentEvent) { + // This is a case where request has failed before we had a chance to intercept it. + // We stop waiting for Fetch.requestPaused (it might never come), and dispatch request event + // right away, followed by requestfailed event. + this._requestIdToRequestWillBeSentEvent.delete(event.requestId); + this._onRequest(workerFrame, requestWillBeSentEvent, null); + request = this._requestIdToRequest.get(event.requestId); + } + } + // For certain requestIds we never receive requestWillBeSent event. // @see https://crbug.com/750469 if (!request) diff --git a/tests/page/page-event-request.spec.ts b/tests/page/page-event-request.spec.ts index 0858f1a44b..d6d4e3ca0b 100644 --- a/tests/page/page-event-request.spec.ts +++ b/tests/page/page-event-request.spec.ts @@ -69,3 +69,56 @@ it('should return response body when Cross-Origin-Opener-Policy is set', async ( expect(response.request().failure()).toBeNull(); expect(await response.text()).toBe('Hello there!'); }); + +it('should fire requestfailed when intercepting race', async ({ page, server, browserName }) => { + const promsie = new Promise(resolve => { + let counter = 0; + const failures = new Set(); + const alive = new Set(); + page.on('request', request => { + expect(alive.has(request)).toBe(false); + expect(failures.has(request)).toBe(false); + alive.add(request); + }); + page.on('requestfailed', request => { + expect(failures.has(request)).toBe(false); + expect(alive.has(request)).toBe(true); + alive.delete(request); + failures.add(request); + if (++counter === 10) + resolve(); + }); + }); + + // Stall requests to make sure we don't get requestfinished. + await page.route('**', route => {}); + + const runFunc = { + chromium: 'abortAll()', // Fast in chromium to expose the race. + webkit: 'setTimeout(abortAll, 0)', // Async in webkit to let it issue requests. + firefox: 'setTimeout(abortAll, 1000)', // Slow in firefox to give it enough time to issue requests. + }[browserName]; + + await page.setContent(` + + + + + + + + + + + + `); + + await promsie; +});