diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index b8463cbd16..c13c30d962 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -215,99 +215,7 @@ export class Request extends ChannelOwner { - return null; - } - - async serverAddr(): Promise<{ ipAddress: string; port: number; } | null> { - return null; - } - - async finished(): Promise { - const response = await this._request.response(); - if (!response) - return null; - return await response.finished(); - } - - frame(): api.Frame { - return this._request.frame(); - } - - ok(): boolean { - return this._initializer.status === 0 || (this._initializer.status >= 200 && this._initializer.status <= 299); - } - - url(): string { - return this._request.url(); - } - - status(): number { - return this._initializer.status; - } - - statusText(): string { - return this._initializer.statusText; - } - - headers(): Headers { - return this._headers.headers(); - } - - async allHeaders(): Promise { - return this.headers(); - } - - async headersArray(): Promise { - return this._headers.headersArray(); - } - - async headerValue(name: string): Promise { - return this._headers.get(name); - } - - async headerValues(name: string): Promise { - return this._headers.getAll(name); - } - - async body(): Promise { - return this._route._responseBody(); - } - - async text(): Promise { - const content = await this.body(); - return content.toString('utf8'); - } - - async json(): Promise { - const content = await this.text(); - return JSON.parse(content); - } - - request(): Request { - return this._request; - } -} - -type InterceptResponse = true; -type NotInterceptResponse = false; - export class Route extends ChannelOwner implements api.Route { - private _interceptedResponse: api.Response | undefined; - static from(route: channels.RouteChannel): Route { return (route as any)._object; } @@ -326,25 +234,17 @@ export class Route extends ChannelOwner { - let useInterceptedResponseBody; let fetchResponseUid; - let { status: statusOption, headers: headersOption, body: bodyOption } = options; + let { status: statusOption, headers: headersOption, body } = options; if (options.response) { statusOption ||= options.response.status(); headersOption ||= options.response.headers(); - if (options.body === undefined && options.path === undefined) { - if (options.response instanceof FetchResponse) - fetchResponseUid = (options.response as FetchResponse)._fetchUid(); - else if (options.response === this._interceptedResponse) - useInterceptedResponseBody = true; - else - bodyOption = await options.response.body(); - } + if (options.body === undefined && options.path === undefined && options.response instanceof FetchResponse) + fetchResponseUid = (options.response as FetchResponse)._fetchUid(); } - let body = undefined; let isBase64 = false; let length = 0; if (options.path) { @@ -352,14 +252,13 @@ export class Route extends ChannelOwner { - this._interceptedResponse = await this._continue(options, true); - return this._interceptedResponse; - } - async continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer } = {}) { - await this._continue(options, false); + await this._continue(options); } async _internalContinue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer } = {}) { - await this._continue(options, false, true).catch(() => {}); + await this._continue(options, true).catch(() => {}); } - async _continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, interceptResponse: NotInterceptResponse, isInternal?: boolean): Promise; - async _continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, interceptResponse: InterceptResponse, isInternal?: boolean): Promise; - async _continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, interceptResponse: boolean, isInternal?: boolean): Promise { + private async _continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, isInternal?: boolean) { return await this._wrapApiCall(async (channel: channels.RouteChannel) => { const postDataBuffer = isString(options.postData) ? Buffer.from(options.postData, 'utf8') : options.postData; - const result = await channel.continue({ + await channel.continue({ url: options.url, method: options.method, headers: options.headers ? headersObjectToArray(options.headers) : undefined, postData: postDataBuffer ? postDataBuffer.toString('base64') : undefined, - interceptResponse, }); - if (result.response) - return new InterceptedResponse(this, result.response); - return null; }, undefined, isInternal); } - - async _responseBody(): Promise { - return this._wrapApiCall(async (channel: channels.RouteChannel) => { - return Buffer.from((await channel.responseBody()).binary, 'base64'); - }); - } } export type RouteHandlerCallback = (route: Route, request: Request) => void; diff --git a/packages/playwright-core/src/dispatchers/networkDispatchers.ts b/packages/playwright-core/src/dispatchers/networkDispatchers.ts index b7fe2dc5ac..15bd5d217e 100644 --- a/packages/playwright-core/src/dispatchers/networkDispatchers.ts +++ b/packages/playwright-core/src/dispatchers/networkDispatchers.ts @@ -113,28 +113,13 @@ export class RouteDispatcher extends Dispatcher { - return { binary: (await this._object.responseBody()).toString('base64') }; - } - async continue(params: channels.RouteContinueParams, metadata: CallMetadata): Promise { - const response = await this._object.continue({ + await this._object.continue({ url: params.url, method: params.method, headers: params.headers, postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined, - interceptResponse: params.interceptResponse }); - const result: channels.RouteContinueResult = {}; - if (response) { - result.response = { - request: RequestDispatcher.from(this._scope, response.request()), - status: response.status(), - statusText: response.statusText(), - headers: response.headers(), - }; - } - return result; } async fulfill(params: channels.RouteFulfillParams): Promise { diff --git a/packages/playwright-core/src/protocol/channels.ts b/packages/playwright-core/src/protocol/channels.ts index 82ef354922..c61b336a0e 100644 --- a/packages/playwright-core/src/protocol/channels.ts +++ b/packages/playwright-core/src/protocol/channels.ts @@ -161,13 +161,6 @@ export type FormField = { }, }; -export type InterceptedResponse = { - request: RequestChannel, - status: number, - statusText: string, - headers: NameValue[], -}; - // ----------- FetchRequest ----------- export type FetchRequestInitializer = {}; export interface FetchRequestChannel extends Channel { @@ -2797,7 +2790,6 @@ export interface RouteChannel extends Channel { abort(params: RouteAbortParams, metadata?: Metadata): Promise; continue(params: RouteContinueParams, metadata?: Metadata): Promise; fulfill(params: RouteFulfillParams, metadata?: Metadata): Promise; - responseBody(params?: RouteResponseBodyParams, metadata?: Metadata): Promise; } export type RouteAbortParams = { errorCode?: string, @@ -2811,24 +2803,19 @@ export type RouteContinueParams = { method?: string, headers?: NameValue[], postData?: Binary, - interceptResponse?: boolean, }; export type RouteContinueOptions = { url?: string, method?: string, headers?: NameValue[], postData?: Binary, - interceptResponse?: boolean, -}; -export type RouteContinueResult = { - response?: InterceptedResponse, }; +export type RouteContinueResult = void; export type RouteFulfillParams = { status?: number, headers?: NameValue[], body?: string, isBase64?: boolean, - useInterceptedResponseBody?: boolean, fetchResponseUid?: string, }; export type RouteFulfillOptions = { @@ -2836,15 +2823,9 @@ export type RouteFulfillOptions = { headers?: NameValue[], body?: string, isBase64?: boolean, - useInterceptedResponseBody?: boolean, fetchResponseUid?: string, }; export type RouteFulfillResult = void; -export type RouteResponseBodyParams = {}; -export type RouteResponseBodyOptions = {}; -export type RouteResponseBodyResult = { - binary: Binary, -}; export interface RouteEvents { } diff --git a/packages/playwright-core/src/protocol/protocol.yml b/packages/playwright-core/src/protocol/protocol.yml index 0832ecff6e..70079c3771 100644 --- a/packages/playwright-core/src/protocol/protocol.yml +++ b/packages/playwright-core/src/protocol/protocol.yml @@ -227,17 +227,6 @@ FormField: mimeType: string buffer: binary -InterceptedResponse: - type: object - properties: - request: Request - status: number - statusText: string - headers: - type: array - items: NameValue - - FetchRequest: type: interface @@ -2327,9 +2316,6 @@ Route: type: array? items: NameValue postData: binary? - interceptResponse: boolean? - returns: - response: InterceptedResponse? fulfill: parameters: @@ -2340,13 +2326,8 @@ Route: items: NameValue body: string? isBase64: boolean? - useInterceptedResponseBody: boolean? fetchResponseUid: string? - responseBody: - returns: - binary: binary - ResourceTiming: type: object diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 9dced3cbb6..f71601a36a 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -157,12 +157,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { buffer: tBinary, })), }); - scheme.InterceptedResponse = tObject({ - request: tChannel('Request'), - status: tNumber, - statusText: tString, - headers: tArray(tType('NameValue')), - }); scheme.FetchRequestFetchParams = tObject({ url: tString, params: tOptional(tArray(tType('NameValue'))), @@ -1108,17 +1102,14 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { method: tOptional(tString), headers: tOptional(tArray(tType('NameValue'))), postData: tOptional(tBinary), - interceptResponse: tOptional(tBoolean), }); scheme.RouteFulfillParams = tObject({ status: tOptional(tNumber), headers: tOptional(tArray(tType('NameValue'))), body: tOptional(tString), isBase64: tOptional(tBoolean), - useInterceptedResponseBody: tOptional(tBoolean), fetchResponseUid: tOptional(tString), }); - scheme.RouteResponseBodyParams = tOptional(tObject({})); scheme.ResourceTiming = tObject({ startTime: tNumber, domainLookupStart: tNumber, diff --git a/packages/playwright-core/src/server/chromium/crNetworkManager.ts b/packages/playwright-core/src/server/chromium/crNetworkManager.ts index 695003ecfb..5943c69082 100644 --- a/packages/playwright-core/src/server/chromium/crNetworkManager.ts +++ b/packages/playwright-core/src/server/chromium/crNetworkManager.ts @@ -105,7 +105,7 @@ export class CRNetworkManager { this._client.send('Network.setCacheDisabled', { cacheDisabled: true }), this._client.send('Fetch.enable', { handleAuthRequests: true, - patterns: [{ urlPattern: '*', requestStage: 'Request' }, { urlPattern: '*', requestStage: 'Response' }], + patterns: [{ urlPattern: '*', requestStage: 'Request' }], }), ]); } else { @@ -179,20 +179,6 @@ export class CRNetworkManager { if (event.request.url.startsWith('data:')) return; - if (event.responseStatusCode || event.responseErrorReason) { - const isRedirect = event.responseStatusCode && event.responseStatusCode >= 300 && event.responseStatusCode < 400; - const request = this._requestIdToRequest.get(event.networkId!); - const route = request?._routeForRedirectChain(); - if (isRedirect || !route || !route._interceptingResponse) { - this._client._sendMayFail('Fetch.continueRequest', { - requestId: event.requestId - }); - return; - } - route._responseInterceptedCallback(event); - return; - } - const requestId = event.networkId; const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId); if (requestWillBeSentEvent) { @@ -476,24 +462,14 @@ class InterceptableRequest { class RouteImpl implements network.RouteDelegate { private readonly _client: CRSession; private _interceptionId: string; - private _responseInterceptedPromise: Promise; - _responseInterceptedCallback: ((event: Protocol.Fetch.requestPausedPayload) => void) = () => {}; - _interceptingResponse: boolean = false; _wasFulfilled = false; constructor(client: CRSession, interceptionId: string) { this._client = client; this._interceptionId = interceptionId; - this._responseInterceptedPromise = new Promise(resolve => this._responseInterceptedCallback = resolve); } - async responseBody(): Promise { - const response = await this._client.send('Fetch.getResponseBody', { requestId: this._interceptionId! }); - return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8'); - } - - async continue(request: network.Request, overrides: types.NormalizedContinueOverrides): Promise { - this._interceptingResponse = !!overrides.interceptResponse; + async continue(request: network.Request, overrides: types.NormalizedContinueOverrides): Promise { // In certain cases, protocol will return error if the request was already canceled // or the page was closed. We should tolerate these errors. await this._client._sendMayFail('Fetch.continueRequest', { @@ -503,18 +479,6 @@ class RouteImpl implements network.RouteDelegate { method: overrides.method, postData: overrides.postData ? overrides.postData.toString('base64') : undefined }); - if (!this._interceptingResponse) - return null; - const event = await this._responseInterceptedPromise; - this._interceptionId = event.requestId; - // FIXME: plumb status text from browser - if (event.responseErrorReason) { - this._client._sendMayFail('Fetch.continueRequest', { - requestId: event.requestId - }); - throw new Error(`Request failed: ${event.responseErrorReason}`); - } - return new network.InterceptedResponse(request, event.responseStatusCode!, '', event.responseHeaders!); } async fulfill(response: types.NormalizedFulfillResponse) { diff --git a/packages/playwright-core/src/server/firefox/ffNetworkManager.ts b/packages/playwright-core/src/server/firefox/ffNetworkManager.ts index ee12f5a5a6..d2bac24fef 100644 --- a/packages/playwright-core/src/server/firefox/ffNetworkManager.ts +++ b/packages/playwright-core/src/server/firefox/ffNetworkManager.ts @@ -22,7 +22,6 @@ import * as network from '../network'; import * as frames from '../frames'; import * as types from '../types'; import { Protocol } from './protocol'; -import { InterceptedResponse } from '../network'; import { HeadersArray } from '../../server/types'; export class FFNetworkManager { @@ -212,27 +211,14 @@ class FFRouteImpl implements network.RouteDelegate { this._request = request; } - async responseBody(): Promise { - const response = await this._session.send('Network.getResponseBody', { - requestId: this._request._finalRequest()._id - }); - return Buffer.from(response.base64body, 'base64'); - } - - async continue(request: network.Request, overrides: types.NormalizedContinueOverrides): Promise { - const result = await this._session.sendMayFail('Network.resumeInterceptedRequest', { + async continue(request: network.Request, overrides: types.NormalizedContinueOverrides) { + await this._session.sendMayFail('Network.resumeInterceptedRequest', { requestId: this._request._id, url: overrides.url, method: overrides.method, headers: overrides.headers, postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined, - interceptResponse: overrides.interceptResponse, - }) as any; - if (!overrides.interceptResponse) - return null; - if (result.error) - throw new Error(`Request failed: ${result.error}`); - return new InterceptedResponse(request, result.response.status, result.response.statusText, result.response.headers); + }); } async fulfill(response: types.NormalizedFulfillResponse) { diff --git a/packages/playwright-core/src/server/network.ts b/packages/playwright-core/src/server/network.ts index 7fb64283fb..ea4d271b95 100644 --- a/packages/playwright-core/src/server/network.ts +++ b/packages/playwright-core/src/server/network.ts @@ -226,7 +226,6 @@ export class Route extends SdkObject { private readonly _request: Request; private readonly _delegate: RouteDelegate; private _handled = false; - private _response: InterceptedResponse | null = null; constructor(request: Request, delegate: RouteDelegate) { super(request.frame(), 'route'); @@ -256,9 +255,6 @@ export class Route extends SdkObject { assert(buffer, 'Fetch response has been disposed'); body = buffer.toString('base64'); isBase64 = true; - } else if (this._response && overrides.useInterceptedResponseBody) { - body = (await this._delegate.responseBody()).toString('base64'); - isBase64 = true; } else { body = ''; isBase64 = false; @@ -272,22 +268,15 @@ export class Route extends SdkObject { }); } - async continue(overrides: types.NormalizedContinueOverrides = {}): Promise { + async continue(overrides: types.NormalizedContinueOverrides = {}) { assert(!this._handled, 'Route is already handled!'); - assert(!this._response, 'Cannot call continue after response interception!'); if (overrides.url) { const newUrl = new URL(overrides.url); const oldUrl = new URL(this._request.url()); if (oldUrl.protocol !== newUrl.protocol) throw new Error('New URL must have same protocol as overridden URL'); } - this._response = await this._delegate.continue(this._request, overrides); - return this._response; - } - - async responseBody(): Promise { - assert(!this._handled, 'Route is already handled!'); - return this._delegate.responseBody(); + await this._delegate.continue(this._request, overrides); } } @@ -485,37 +474,6 @@ export class Response extends SdkObject { } } -export class InterceptedResponse extends SdkObject { - private readonly _request: Request; - private readonly _status: number; - private readonly _statusText: string; - private readonly _headers: types.HeadersArray; - - constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray) { - super(request.frame(), 'interceptedResponse'); - this._request = request._finalRequest(); - this._status = status; - this._statusText = statusText; - this._headers = headers; - } - - status(): number { - return this._status; - } - - statusText(): string { - return this._statusText; - } - - headers(): types.HeadersArray { - return this._headers; - } - - request(): Request { - return this._request; - } -} - export class WebSocket extends SdkObject { private _url: string; @@ -555,8 +513,7 @@ export class WebSocket extends SdkObject { export interface RouteDelegate { abort(errorCode: string): Promise; fulfill(response: types.NormalizedFulfillResponse): Promise; - continue(request: Request, overrides: types.NormalizedContinueOverrides): Promise; - responseBody(): Promise; + continue(request: Request, overrides: types.NormalizedContinueOverrides): Promise; } // List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes. diff --git a/packages/playwright-core/src/server/types.ts b/packages/playwright-core/src/server/types.ts index 8e99ff60cf..dcaca66482 100644 --- a/packages/playwright-core/src/server/types.ts +++ b/packages/playwright-core/src/server/types.ts @@ -211,7 +211,6 @@ export type NormalizedContinueOverrides = { method?: string, headers?: HeadersArray, postData?: Buffer, - interceptResponse?: boolean, }; export type NetworkCookie = { diff --git a/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts b/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts index 2ea1c2a359..ba681c434c 100644 --- a/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts +++ b/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts @@ -21,8 +21,6 @@ import * as types from '../types'; import { Protocol } from './protocol'; import { WKSession } from './wkConnection'; import { assert, headersObjectToArray, headersArrayToObject } from '../../utils/utils'; -import { InterceptedResponse } from '../network'; -import { WKPage } from './wkPage'; import { ManualPromise } from '../../utils/async'; const errorReasons: { [reason: string]: Protocol.Network.ResourceErrorType } = { @@ -98,28 +96,19 @@ export class WKRouteImpl implements network.RouteDelegate { private readonly _session: WKSession; private readonly _requestId: string; readonly _requestInterceptedPromise = new ManualPromise(); - _responseInterceptedPromise: ManualPromise<{ response?: Protocol.Network.Response, error?: Protocol.Network.loadingFailedPayload }> | undefined; - private readonly _page: WKPage; - constructor(session: WKSession, page: WKPage, requestId: string) { + constructor(session: WKSession, requestId: string) { this._session = session; - this._page = page; this._requestId = requestId; } - async responseBody(): Promise { - const response = await this._session.send('Network.getInterceptedResponseBody', { requestId: this._requestId }); - return Buffer.from(response.body, 'base64'); - } - async abort(errorCode: string) { const errorType = errorReasons[errorCode]; assert(errorType, 'Unknown error code: ' + errorCode); await this._requestInterceptedPromise; - const isResponseIntercepted = await this._responseInterceptedPromise; // In certain cases, protocol will return error if the request was already canceled // or the page was closed. We should tolerate these errors. - await this._session.sendMayFail(isResponseIntercepted ? 'Network.interceptResponseWithError' : 'Network.interceptRequestWithError', { requestId: this._requestId, errorType }); + await this._session.sendMayFail('Network.interceptRequestWithError', { requestId: this._requestId, errorType }); } async fulfill(response: types.NormalizedFulfillResponse) { @@ -135,8 +124,7 @@ export class WKRouteImpl implements network.RouteDelegate { if (contentType) mimeType = contentType.split(';')[0].trim(); - const isResponseIntercepted = await this._responseInterceptedPromise; - await this._session.sendMayFail(isResponseIntercepted ? 'Network.interceptWithResponse' : 'Network.interceptRequestWithResponse', { + await this._session.sendMayFail('Network.interceptRequestWithResponse', { requestId: this._requestId, status: response.status, statusText: network.STATUS_TEXTS[String(response.status)], @@ -147,11 +135,7 @@ export class WKRouteImpl implements network.RouteDelegate { }); } - async continue(request: network.Request, overrides: types.NormalizedContinueOverrides): Promise { - if (overrides.interceptResponse) { - await this._page._ensureResponseInterceptionEnabled(); - this._responseInterceptedPromise = new ManualPromise(); - } + async continue(request: network.Request, overrides: types.NormalizedContinueOverrides) { await this._requestInterceptedPromise; // In certain cases, protocol will return error if the request was already canceled // or the page was closed. We should tolerate these errors. @@ -162,12 +146,6 @@ export class WKRouteImpl implements network.RouteDelegate { headers: overrides.headers ? headersArrayToObject(overrides.headers, false /* lowerCase */) : undefined, postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined }); - if (!this._responseInterceptedPromise) - return null; - const { response, error } = await this._responseInterceptedPromise; - if (error) - throw new Error(`Request failed: ${error.errorText}`); - return new InterceptedResponse(request, response!.status, response!.statusText, headersObjectToArray(response!.headers)); } } diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index b94b9eb5f7..a771d20b46 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -70,7 +70,6 @@ export class WKPage implements PageDelegate { private _lastConsoleMessage: { derivedType: string, text: string, handles: JSHandle[]; count: number, location: types.ConsoleMessageLocation; } | null = null; private readonly _requestIdToResponseReceivedPayloadEvent = new Map(); - _needsResponseInterception: boolean = false; // Holds window features for the next popup being opened via window.open, // until the popup page proxy arrives. private _nextWindowOpenPopupFeatures?: string[]; @@ -178,8 +177,6 @@ export class WKPage implements PageDelegate { if (this._page._needsRequestInterception()) { promises.push(session.send('Network.setInterceptionEnabled', { enabled: true })); promises.push(session.send('Network.addInterception', { url: '.*', stage: 'request', isRegex: true })); - if (this._needsResponseInterception) - promises.push(session.send('Network.addInterception', { url: '.*', stage: 'response', isRegex: true })); } const contextOptions = this._browserContext._options; @@ -375,7 +372,6 @@ export class WKPage implements PageDelegate { eventsHelper.addEventListener(this._session, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)), eventsHelper.addEventListener(this._session, 'Network.requestWillBeSent', e => this._onRequestWillBeSent(this._session, e)), eventsHelper.addEventListener(this._session, 'Network.requestIntercepted', e => this._onRequestIntercepted(this._session, e)), - eventsHelper.addEventListener(this._session, 'Network.responseIntercepted', e => this._onResponseIntercepted(this._session, e)), eventsHelper.addEventListener(this._session, 'Network.responseReceived', e => this._onResponseReceived(e)), eventsHelper.addEventListener(this._session, 'Network.loadingFinished', e => this._onLoadingFinished(e)), eventsHelper.addEventListener(this._session, 'Network.loadingFailed', e => this._onLoadingFailed(e)), @@ -671,22 +667,12 @@ export class WKPage implements PageDelegate { await Promise.all(promises); } - async _ensureResponseInterceptionEnabled() { - if (this._needsResponseInterception) - return; - this._needsResponseInterception = true; - await this.updateRequestInterception(); - } - async updateRequestInterception(): Promise { const enabled = this._page._needsRequestInterception(); - const promises = [ + await Promise.all([ this._updateState('Network.setInterceptionEnabled', { enabled }), this._updateState('Network.addInterception', { url: '.*', stage: 'request', isRegex: true }), - ]; - if (this._needsResponseInterception) - this._updateState('Network.addInterception', { url: '.*', stage: 'response', isRegex: true }); - await Promise.all(promises); + ]); } async updateOffline() { @@ -970,7 +956,7 @@ export class WKPage implements PageDelegate { let route = null; // We do not support intercepting redirects. if (this._page._needsRequestInterception() && !redirectedFrom) - route = new WKRouteImpl(session, this, event.requestId); + route = new WKRouteImpl(session, event.requestId); const request = new WKInterceptableRequest(session, route, frame, event, redirectedFrom, documentId); this._requestIdToRequest.set(event.requestId, request); this._page._frameManager.requestStarted(request.request, route || undefined); @@ -1001,16 +987,6 @@ export class WKPage implements PageDelegate { } } - _onResponseIntercepted(session: WKSession, event: Protocol.Network.responseInterceptedPayload) { - const request = this._requestIdToRequest.get(event.requestId); - const route = request?._routeForRedirectChain(); - if (!route?._responseInterceptedPromise) { - session.sendMayFail('Network.interceptContinue', { requestId: event.requestId, stage: 'response' }); - return; - } - route._responseInterceptedPromise.resolve({ response: event.response }); - } - _onResponseReceived(event: Protocol.Network.responseReceivedPayload) { const request = this._requestIdToRequest.get(event.requestId); // FileUpload sends a response without a matching request. @@ -1075,9 +1051,6 @@ export class WKPage implements PageDelegate { // @see https://crbug.com/750469 if (!request) return; - const route = request._routeForRedirectChain(); - if (route?._responseInterceptedPromise) - route._responseInterceptedPromise.resolve({ error: event }); const response = request.request._existingResponse(); if (response) { response._serverAddrFinished(); diff --git a/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts b/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts index cccb8080ed..67f3c4d26d 100644 --- a/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts +++ b/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts @@ -44,7 +44,6 @@ export class WKProvisionalPage { this._sessionListeners = [ eventsHelper.addEventListener(session, 'Network.requestWillBeSent', overrideFrameId(e => wkPage._onRequestWillBeSent(session, e))), eventsHelper.addEventListener(session, 'Network.requestIntercepted', overrideFrameId(e => wkPage._onRequestIntercepted(session, e))), - eventsHelper.addEventListener(session, 'Network.responseIntercepted', overrideFrameId(e => wkPage._onResponseIntercepted(session, e))), eventsHelper.addEventListener(session, 'Network.responseReceived', overrideFrameId(e => wkPage._onResponseReceived(e))), eventsHelper.addEventListener(session, 'Network.loadingFinished', overrideFrameId(e => wkPage._onLoadingFinished(e))), eventsHelper.addEventListener(session, 'Network.loadingFailed', overrideFrameId(e => wkPage._onLoadingFailed(e))), diff --git a/tests/page/page-request-intercept.spec.ts b/tests/page/page-request-intercept.spec.ts index 770a8853c5..92dd54fc89 100644 --- a/tests/page/page-request-intercept.spec.ts +++ b/tests/page/page-request-intercept.spec.ts @@ -15,16 +15,15 @@ * limitations under the License. */ -import { fail } from 'assert'; import os from 'os'; -import type { Route, Response } from 'playwright-core'; +import type { Route } from 'playwright-core'; import { expect, test as it } from './pageTest'; -it('should fulfill intercepted response', async ({ page, server, browserName }) => { +it('should fulfill intercepted response', async ({ page, server }) => { await page.route('**/*', async route => { - // @ts-expect-error - await route._continueToResponse({}); + const response = await page.request.fetch(route.request()); await route.fulfill({ + response, status: 201, headers: { foo: 'bar' @@ -43,8 +42,7 @@ it('should fulfill intercepted response', async ({ page, server, browserName }) it('should fulfill response with empty body', async ({ page, server, browserName, browserMajorVersion }) => { it.skip(browserName === 'chromium' && browserMajorVersion <= 91, 'Fails in Electron that uses old Chromium'); await page.route('**/*', async route => { - // @ts-expect-error - const response = await route._continueToResponse({}); + const response = await page.request.fetch(route.request()); await route.fulfill({ response, status: 201, @@ -63,8 +61,7 @@ it('should override with defaults when intercepted response not provided', async res.end('my content'); }); await page.route('**/*', async route => { - // @ts-expect-error - await route._continueToResponse({}); + await page.request.fetch(route.request()); await route.fulfill({ status: 201, }); @@ -85,14 +82,10 @@ it('should fulfill with any response', async ({ page, server, browserName, brows res.setHeader('foo', 'bar'); res.end('Woo-hoo'); }); - const page2 = await page.context().newPage(); - const sampleResponse = await page2.goto(`${server.PREFIX}/sample`); + const sampleResponse = await page.request.get(`${server.PREFIX}/sample`); await page.route('**/*', async route => { - // @ts-expect-error - await route._continueToResponse({}); await route.fulfill({ - // @ts-expect-error response: sampleResponse, status: 201, contentType: 'text/plain' @@ -104,28 +97,10 @@ it('should fulfill with any response', async ({ page, server, browserName, brows expect(response.headers()['foo']).toBe('bar'); }); -it('should throw on continue after intercept', async ({ page, server, browserName }) => { - let routeCallback; - const routePromise = new Promise(f => routeCallback = f); - await page.route('**', routeCallback); - - page.goto(server.EMPTY_PAGE).catch(e => {}); - const route = await routePromise; - // @ts-expect-error - await route._continueToResponse(); - try { - await route.continue(); - fail('did not throw'); - } catch (e) { - expect(e.message).toContain('Cannot call continue after response interception!'); - } -}); - it('should support fulfill after intercept', async ({ page, server }) => { const requestPromise = server.waitForRequest('/title.html'); await page.route('**', async route => { - // @ts-expect-error - const response = await route._continueToResponse(); + const response = await page.request.fetch(route.request()); await route.fulfill({ response }); }); const response = await page.goto(server.PREFIX + '/title.html'); @@ -134,52 +109,6 @@ it('should support fulfill after intercept', async ({ page, server }) => { expect(await response.text()).toBe('Woof-Woof' + os.EOL); }); -it('should intercept failures', async ({ page, browserName, browserMajorVersion, server }) => { - it.skip(browserName === 'chromium' && browserMajorVersion <= 91, 'Fails in Electron that uses old Chromium'); - server.setRoute('/title.html', (req, res) => { - req.destroy(); - }); - const requestPromise = server.waitForRequest('/title.html'); - let error; - await page.route('**', async route => { - try { - // @ts-expect-error - const response = await route._continueToResponse(); - await route.fulfill({ response }); - } catch (e) { - error = e; - } - }); - const [request] = await Promise.all([ - requestPromise, - page.goto(server.PREFIX + '/title.html').catch(e => {}) - ]); - expect(error).toBeTruthy(); - expect(error.message).toContain('Request failed'); - expect(request.url).toBe('/title.html'); -}); - -it('should support request overrides', async ({ page, server, browserName, browserMajorVersion }) => { - it.skip(browserName === 'chromium' && browserMajorVersion <= 91, 'Fails in Electron that uses old Chromium'); - const requestPromise = server.waitForRequest('/empty.html'); - await page.route('**/foo', async route => { - // @ts-expect-error - const response = await route._continueToResponse({ - url: server.EMPTY_PAGE, - method: 'POST', - headers: { 'foo': 'bar' }, - postData: 'my data', - }); - await route.fulfill({ response }); - }); - await page.goto(server.PREFIX + '/foo'); - const request = await requestPromise; - expect(request.method).toBe('POST'); - expect(request.url).toBe('/empty.html'); - expect(request.headers['foo']).toBe('bar'); - expect((await request.postBody).toString('utf8')).toBe('my data'); -}); - it('should give access to the intercepted response', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); @@ -190,42 +119,18 @@ it('should give access to the intercepted response', async ({ page, server }) => const evalPromise = page.evaluate(url => fetch(url), server.PREFIX + '/title.html'); const route = await routePromise; - // @ts-expect-error - const response: Response = await route._continueToResponse(); + const response = await page.request.fetch(route.request()); expect(response.status()).toBe(200); + expect(response.statusText()).toBe('OK'); expect(response.ok()).toBeTruthy(); expect(response.url()).toBe(server.PREFIX + '/title.html'); expect(response.headers()['content-type']).toBe('text/html; charset=utf-8'); - expect((await response.allHeaders())['content-type']).toBe('text/html; charset=utf-8'); expect(await (await response.headersArray()).filter(({ name }) => name.toLowerCase() === 'content-type')).toEqual([{ name: 'Content-Type', value: 'text/html; charset=utf-8' }]); - // @ts-expect-error await Promise.all([route.fulfill({ response }), evalPromise]); }); -it('should give access to the intercepted response status text', async ({ page, server, browserName }) => { - it.fail(browserName === 'chromium', 'Status line is not reported for intercepted responses'); - await page.goto(server.EMPTY_PAGE); - server.setRoute('/title.html', (req, res) => { - res.statusCode = 200; - res.statusMessage = 'You are awesome'; - res.setHeader('Content-Type', 'text/plain'); - res.end(); - }); - let routeCallback; - const routePromise = new Promise(f => routeCallback = f); - await page.route('**/title.html', routeCallback); - const evalPromise = page.evaluate(url => fetch(url), server.PREFIX + '/title.html'); - const route = await routePromise; - // @ts-expect-error - const response = await route._continueToResponse(); - - await Promise.all([route.fulfill({ response }), evalPromise]); - expect(response.statusText()).toBe('You are awesome'); - expect(response.url()).toBe(server.PREFIX + '/title.html'); -}); - it('should give access to the intercepted response body', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); @@ -236,139 +141,9 @@ it('should give access to the intercepted response body', async ({ page, server const evalPromise = page.evaluate(url => fetch(url), server.PREFIX + '/simple.json').catch(() => {}); const route = await routePromise; - // @ts-expect-error - const response = await route._continueToResponse(); + const response = await page.request.fetch(route.request()); expect((await response.text())).toBe('{"foo": "bar"}\n'); await Promise.all([route.fulfill({ response }), evalPromise]); }); - -it('should be abortable after interception', async ({ page, server, browserName }) => { - await page.route(/\.css$/, async route => { - // @ts-expect-error - await route._continueToResponse(); - await route.abort(); - }); - let failed = false; - page.on('requestfailed', request => { - if (request.url().includes('.css')) - failed = true; - }); - const response = await page.goto(server.PREFIX + '/one-style.html'); - expect(response.ok()).toBe(true); - expect(response.request().failure()).toBe(null); - expect(failed).toBe(true); -}); - -it('should fulfill after redirects', async ({ page, server, browserName }) => { - server.setRedirect('/redirect/1.html', '/redirect/2.html'); - server.setRedirect('/redirect/2.html', '/empty.html'); - const expectedUrls = ['/redirect/1.html', '/redirect/2.html', '/empty.html'].map(s => server.PREFIX + s); - const requestUrls = []; - const responseUrls = []; - const requestFinishedUrls = []; - page.on('request', request => requestUrls.push(request.url())); - page.on('response', response => responseUrls.push(response.url())); - page.on('requestfinished', request => requestFinishedUrls.push(request.url())); - let routeCalls = 0; - await page.route('**/*', async route => { - ++routeCalls; - // @ts-expect-error - await route._continueToResponse({}); - await route.fulfill({ - status: 201, - headers: { - foo: 'bar' - }, - contentType: 'text/plain', - body: 'Yo, page!' - }); - }); - const response = await page.goto(server.PREFIX + '/redirect/1.html'); - expect(requestUrls).toEqual(expectedUrls); - expect(responseUrls).toEqual(expectedUrls); - await response.finished(); - expect(requestFinishedUrls).toEqual(expectedUrls); - expect(routeCalls).toBe(1); - - const redirectChain = []; - for (let req = response.request(); req; req = req.redirectedFrom()) - redirectChain.unshift(req.url()); - expect(redirectChain).toEqual(expectedUrls); - - expect(response.status()).toBe(201); - expect(response.headers().foo).toBe('bar'); - expect(response.headers()['content-type']).toBe('text/plain'); - expect(await response.text()).toBe('Yo, page!'); -}); - -it('should fulfill original response after redirects', async ({ page, browserName, server }) => { - server.setRedirect('/redirect/1.html', '/redirect/2.html'); - server.setRedirect('/redirect/2.html', '/title.html'); - const expectedUrls = ['/redirect/1.html', '/redirect/2.html', '/title.html'].map(s => server.PREFIX + s); - const requestUrls = []; - const responseUrls = []; - const requestFinishedUrls = []; - page.on('request', request => requestUrls.push(request.url())); - page.on('response', response => responseUrls.push(response.url())); - page.on('requestfinished', request => requestFinishedUrls.push(request.url())); - let routeCalls = 0; - await page.route('**/*', async route => { - ++routeCalls; - // @ts-expect-error - const response = await route._continueToResponse({}); - await route.fulfill({ response }); - }); - const response = await page.goto(server.PREFIX + '/redirect/1.html'); - expect(requestUrls).toEqual(expectedUrls); - expect(responseUrls).toEqual(expectedUrls); - await response.finished(); - expect(requestFinishedUrls).toEqual(expectedUrls); - expect(routeCalls).toBe(1); - - const redirectChain = []; - for (let req = response.request(); req; req = req.redirectedFrom()) - redirectChain.unshift(req.url()); - expect(redirectChain).toEqual(expectedUrls); - - expect(response.status()).toBe(200); - expect(await response.text()).toBe('Woof-Woof' + os.EOL); -}); - -it('should abort after redirects', async ({ page, browserName, server }) => { - server.setRedirect('/redirect/1.html', '/redirect/2.html'); - server.setRedirect('/redirect/2.html', '/title.html'); - const expectedUrls = ['/redirect/1.html', '/redirect/2.html', '/title.html'].map(s => server.PREFIX + s); - const requestUrls = []; - const responseUrls = []; - const requestFinishedUrls = []; - const requestFailedUrls = []; - page.on('request', request => requestUrls.push(request.url())); - page.on('response', response => responseUrls.push(response.url())); - page.on('requestfinished', request => requestFinishedUrls.push(request.url())); - page.on('requestfailed', request => requestFailedUrls.push(request.url())); - let routeCalls = 0; - await page.route('**/*', async route => { - ++routeCalls; - // @ts-expect-error - await route._continueToResponse({}); - await route.abort('connectionreset'); - }); - - try { - await page.goto(server.PREFIX + '/redirect/1.html'); - } catch (e) { - if (browserName === 'webkit') - expect(e.message).toContain('Request intercepted'); - else if (browserName === 'chromium') - expect(e.message).toContain('ERR_CONNECTION_RESET'); - else - expect(e.message).toContain('NS_ERROR_NET_RESET'); - } - expect(requestUrls).toEqual(expectedUrls); - expect(responseUrls).toEqual(expectedUrls.slice(0, -1)); - expect(requestFinishedUrls).toEqual(expectedUrls.slice(0, -1)); - expect(requestFailedUrls).toEqual(expectedUrls.slice(-1)); - expect(routeCalls).toBe(1); -});