diff --git a/packages/playwright-core/src/server/chromium/crNetworkManager.ts b/packages/playwright-core/src/server/chromium/crNetworkManager.ts index 0c3411d0d0..ad07d24eac 100644 --- a/packages/playwright-core/src/server/chromium/crNetworkManager.ts +++ b/packages/playwright-core/src/server/chromium/crNetworkManager.ts @@ -285,10 +285,16 @@ export class CRNetworkManager { let route = null; if (requestPausedEvent) { // We do not support intercepting redirects. - if (redirectedFrom || (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled)) - this._session._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId }); - else + if (redirectedFrom || (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled)) { + let headers = undefined; + const previousHeaderOverrides = redirectedFrom?._originalRequestRoute?._alreadyContinuedParams?.headers; + // Chromium does not preserve header overrides between redirects, so we have to do it ourselves. + if (previousHeaderOverrides) + headers = network.mergeHeaders([headersObjectToArray(requestPausedEvent.request.headers, '\n'), previousHeaderOverrides]); + this._session._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId, headers }); + } else { route = new RouteImpl(this._session, requestPausedEvent.requestId); + } } const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document'; const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined; @@ -384,8 +390,6 @@ export class CRNetworkManager { _deleteRequest(request: InterceptableRequest) { this._requestIdToRequest.delete(request._requestId); - if (request._route) - request._route._alreadyContinuedParams = undefined; if (request._interceptionId) this._attemptedAuthentications.delete(request._interceptionId); } @@ -508,6 +512,9 @@ class InterceptableRequest { readonly _timestamp: number; readonly _wallTime: number; readonly _route: RouteImpl | null; + // Only first request in the chain can be intercepted, so this will + // store the first and only Route in the chain (if any). + readonly _originalRequestRoute: RouteImpl | undefined; session: CRSession; constructor(options: { @@ -529,6 +536,7 @@ class InterceptableRequest { this._interceptionId = requestPausedEvent && requestPausedEvent.requestId; this._documentId = documentId; this._route = route; + this._originalRequestRoute = route ?? redirectedFrom?._originalRequestRoute; const { headers, diff --git a/tests/page/page-request-continue.spec.ts b/tests/page/page-request-continue.spec.ts index 30d47b0c88..1b7f78e40d 100644 --- a/tests/page/page-request-continue.spec.ts +++ b/tests/page/page-request-continue.spec.ts @@ -395,13 +395,13 @@ it('should continue preload link requests', async ({ page, server, browserName } it('continue should propagate headers to redirects', async ({ page, server, browserName }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28758' }); - it.fixme(browserName !== 'webkit'); + it.fixme(browserName === 'firefox'); await server.setRedirect('/redirect', '/empty.html'); await page.route('**/redirect', route => { void route.continue({ headers: { ...route.request().headers(), - 'custom': 'value' + custom: 'value' } }); }); @@ -412,6 +412,38 @@ it('continue should propagate headers to redirects', async ({ page, server, brow expect(serverRequest.headers['custom']).toBe('value'); }); +it('continue should delete headers on redirects', async ({ page, server, browserName }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/13106' }); + it.fixme(browserName === 'firefox'); + await page.goto(server.PREFIX + '/empty.html'); + server.setRoute('/something', (request, response) => { + response.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); + response.end('done'); + }); + await server.setRedirect('/redirect', '/something'); + await page.route('**/redirect', route => { + void route.continue({ + headers: { + ...route.request().headers(), + foo: undefined + } + }); + }); + const [text, serverRequest] = await Promise.all([ + page.evaluate(async url => { + const data = await fetch(url, { + headers: { + foo: 'a', + } + }); + return data.text(); + }, server.PREFIX + '/redirect'), + server.waitForRequest('/something') + ]); + expect(text).toBe('done'); + expect(serverRequest.headers.foo).toBeFalsy(); +}); + it('should intercept css variable with background url', async ({ page, server }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/19158' });