mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 05:37:20 +03:00
feat: response interception after redirects in chromium (#7910)
This commit is contained in:
parent
62a4d82b7b
commit
28fb3c776a
@ -178,16 +178,17 @@ export class CRNetworkManager {
|
|||||||
if (event.request.url.startsWith('data:'))
|
if (event.request.url.startsWith('data:'))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (event.responseStatusCode || event.responseErrorReason) {
|
||||||
if (event.responseStatusCode || event.responseHeaders || event.responseErrorReason) {
|
const isRedirect = event.responseStatusCode && event.responseStatusCode >= 300 && event.responseStatusCode < 400;
|
||||||
const request = this._requestIdToRequest.get(event.networkId);
|
const request = this._requestIdToRequest.get(event.networkId!);
|
||||||
if (!request || !request._onInterceptedResponse) {
|
const route = request?._routeForRedirectChain();
|
||||||
|
if (isRedirect || !route || !route._interceptingResponse) {
|
||||||
this._client._sendMayFail('Fetch.continueRequest', {
|
this._client._sendMayFail('Fetch.continueRequest', {
|
||||||
requestId: event.requestId
|
requestId: event.requestId
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
request._onInterceptedResponse!(event);
|
route._responseInterceptedCallback(event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,13 +205,13 @@ export class CRNetworkManager {
|
|||||||
_onRequest(workerFrame: frames.Frame | undefined, requestWillBeSentEvent: Protocol.Network.requestWillBeSentPayload, requestPausedEvent: Protocol.Fetch.requestPausedPayload | null) {
|
_onRequest(workerFrame: frames.Frame | undefined, requestWillBeSentEvent: Protocol.Network.requestWillBeSentPayload, requestPausedEvent: Protocol.Fetch.requestPausedPayload | null) {
|
||||||
if (requestWillBeSentEvent.request.url.startsWith('data:'))
|
if (requestWillBeSentEvent.request.url.startsWith('data:'))
|
||||||
return;
|
return;
|
||||||
let redirectedFrom: network.Request | null = null;
|
let redirectedFrom: InterceptableRequest | null = null;
|
||||||
if (requestWillBeSentEvent.redirectResponse) {
|
if (requestWillBeSentEvent.redirectResponse) {
|
||||||
const request = this._requestIdToRequest.get(requestWillBeSentEvent.requestId);
|
const request = this._requestIdToRequest.get(requestWillBeSentEvent.requestId);
|
||||||
// If we connect late to the target, we could have missed the requestWillBeSent event.
|
// If we connect late to the target, we could have missed the requestWillBeSent event.
|
||||||
if (request) {
|
if (request) {
|
||||||
this._handleRequestRedirect(request, requestWillBeSentEvent.redirectResponse, requestWillBeSentEvent.timestamp);
|
this._handleRequestRedirect(request, requestWillBeSentEvent.redirectResponse, requestWillBeSentEvent.timestamp);
|
||||||
redirectedFrom = request.request;
|
redirectedFrom = request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let frame = requestWillBeSentEvent.frameId ? this._page._frameManager.frame(requestWillBeSentEvent.frameId) : workerFrame;
|
let frame = requestWillBeSentEvent.frameId ? this._page._frameManager.frame(requestWillBeSentEvent.frameId) : workerFrame;
|
||||||
@ -258,26 +259,26 @@ export class CRNetworkManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let allowInterception = this._userRequestInterceptionEnabled;
|
let route = null;
|
||||||
if (redirectedFrom) {
|
if (requestPausedEvent) {
|
||||||
allowInterception = false;
|
|
||||||
// We do not support intercepting redirects.
|
// We do not support intercepting redirects.
|
||||||
if (requestPausedEvent)
|
if (redirectedFrom)
|
||||||
this._client._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId });
|
this._client._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId });
|
||||||
|
else
|
||||||
|
route = new RouteImpl(this._client, requestPausedEvent.requestId);
|
||||||
}
|
}
|
||||||
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
|
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
|
||||||
const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined;
|
const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined;
|
||||||
const request = new InterceptableRequest({
|
const request = new InterceptableRequest({
|
||||||
client: this._client,
|
|
||||||
frame,
|
frame,
|
||||||
documentId,
|
documentId,
|
||||||
allowInterception,
|
route,
|
||||||
requestWillBeSentEvent,
|
requestWillBeSentEvent,
|
||||||
requestPausedEvent,
|
requestPausedEvent,
|
||||||
redirectedFrom
|
redirectedFrom
|
||||||
});
|
});
|
||||||
this._requestIdToRequest.set(requestWillBeSentEvent.requestId, request);
|
this._requestIdToRequest.set(requestWillBeSentEvent.requestId, request);
|
||||||
this._page._frameManager.requestStarted(request.request);
|
this._page._frameManager.requestStarted(request.request, route || undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
_createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
|
_createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
|
||||||
@ -404,32 +405,32 @@ export class CRNetworkManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InterceptableRequest implements network.RouteDelegate {
|
class InterceptableRequest {
|
||||||
readonly request: network.Request;
|
readonly request: network.Request;
|
||||||
_requestId: string;
|
_requestId: string;
|
||||||
_interceptionId: string | null;
|
_interceptionId: string | null;
|
||||||
_documentId: string | undefined;
|
_documentId: string | undefined;
|
||||||
private readonly _client: CRSession;
|
|
||||||
_timestamp: number;
|
_timestamp: number;
|
||||||
_wallTime: number;
|
_wallTime: number;
|
||||||
_onInterceptedResponse: ((event: Protocol.Fetch.requestPausedPayload) => void) | null = null;
|
private _route: RouteImpl | null;
|
||||||
|
private _redirectedFrom: InterceptableRequest | null;
|
||||||
|
|
||||||
constructor(options: {
|
constructor(options: {
|
||||||
client: CRSession;
|
|
||||||
frame: frames.Frame;
|
frame: frames.Frame;
|
||||||
documentId?: string;
|
documentId?: string;
|
||||||
allowInterception: boolean;
|
route: RouteImpl | null;
|
||||||
requestWillBeSentEvent: Protocol.Network.requestWillBeSentPayload;
|
requestWillBeSentEvent: Protocol.Network.requestWillBeSentPayload;
|
||||||
requestPausedEvent: Protocol.Fetch.requestPausedPayload | null;
|
requestPausedEvent: Protocol.Fetch.requestPausedPayload | null;
|
||||||
redirectedFrom: network.Request | null;
|
redirectedFrom: InterceptableRequest | null;
|
||||||
}) {
|
}) {
|
||||||
const { client, frame, documentId, allowInterception, requestWillBeSentEvent, requestPausedEvent, redirectedFrom } = options;
|
const { frame, documentId, route, requestWillBeSentEvent, requestPausedEvent, redirectedFrom } = options;
|
||||||
this._client = client;
|
|
||||||
this._timestamp = requestWillBeSentEvent.timestamp;
|
this._timestamp = requestWillBeSentEvent.timestamp;
|
||||||
this._wallTime = requestWillBeSentEvent.wallTime;
|
this._wallTime = requestWillBeSentEvent.wallTime;
|
||||||
this._requestId = requestWillBeSentEvent.requestId;
|
this._requestId = requestWillBeSentEvent.requestId;
|
||||||
this._interceptionId = requestPausedEvent && requestPausedEvent.requestId;
|
this._interceptionId = requestPausedEvent && requestPausedEvent.requestId;
|
||||||
this._documentId = documentId;
|
this._documentId = documentId;
|
||||||
|
this._route = route;
|
||||||
|
this._redirectedFrom = redirectedFrom;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
headers,
|
headers,
|
||||||
@ -442,7 +443,28 @@ class InterceptableRequest implements network.RouteDelegate {
|
|||||||
if (postDataEntries && postDataEntries.length && postDataEntries[0].bytes)
|
if (postDataEntries && postDataEntries.length && postDataEntries[0].bytes)
|
||||||
postDataBuffer = Buffer.from(postDataEntries[0].bytes, 'base64');
|
postDataBuffer = Buffer.from(postDataEntries[0].bytes, 'base64');
|
||||||
|
|
||||||
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, url, type, method, postDataBuffer, headersObjectToArray(headers));
|
this.request = new network.Request(frame, redirectedFrom?.request || null, documentId, url, type, method, postDataBuffer, headersObjectToArray(headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
_routeForRedirectChain(): RouteImpl | null {
|
||||||
|
let request: InterceptableRequest = this;
|
||||||
|
while (request._redirectedFrom)
|
||||||
|
request = request._redirectedFrom;
|
||||||
|
return request._route;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RouteImpl implements network.RouteDelegate {
|
||||||
|
private readonly _client: CRSession;
|
||||||
|
private _interceptionId: string;
|
||||||
|
private _responseInterceptedPromise: Promise<Protocol.Fetch.requestPausedPayload>;
|
||||||
|
_responseInterceptedCallback: ((event: Protocol.Fetch.requestPausedPayload) => void) = () => {};
|
||||||
|
_interceptingResponse: boolean = false;
|
||||||
|
|
||||||
|
constructor(client: CRSession, interceptionId: string) {
|
||||||
|
this._client = client;
|
||||||
|
this._interceptionId = interceptionId;
|
||||||
|
this._responseInterceptedPromise = new Promise(resolve => this._responseInterceptedCallback = resolve);
|
||||||
}
|
}
|
||||||
|
|
||||||
async responseBody(): Promise<Buffer> {
|
async responseBody(): Promise<Buffer> {
|
||||||
@ -450,8 +472,8 @@ class InterceptableRequest implements network.RouteDelegate {
|
|||||||
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
async continue(overrides: types.NormalizedContinueOverrides): Promise<network.InterceptedResponse|null> {
|
async continue(request: network.Request, overrides: types.NormalizedContinueOverrides): Promise<network.InterceptedResponse|null> {
|
||||||
const interceptPromise = overrides.interceptResponse ? new Promise<Protocol.Fetch.requestPausedPayload>(resolve => this._onInterceptedResponse = resolve) : null;
|
this._interceptingResponse = !!overrides.interceptResponse;
|
||||||
// In certain cases, protocol will return error if the request was already canceled
|
// In certain cases, protocol will return error if the request was already canceled
|
||||||
// or the page was closed. We should tolerate these errors.
|
// or the page was closed. We should tolerate these errors.
|
||||||
await this._client._sendMayFail('Fetch.continueRequest', {
|
await this._client._sendMayFail('Fetch.continueRequest', {
|
||||||
@ -461,10 +483,11 @@ class InterceptableRequest implements network.RouteDelegate {
|
|||||||
method: overrides.method,
|
method: overrides.method,
|
||||||
postData: overrides.postData ? overrides.postData.toString('base64') : undefined
|
postData: overrides.postData ? overrides.postData.toString('base64') : undefined
|
||||||
});
|
});
|
||||||
if (!interceptPromise)
|
if (!this._interceptingResponse)
|
||||||
return null;
|
return null;
|
||||||
const event = await interceptPromise;
|
const event = await this._responseInterceptedPromise;
|
||||||
return new network.InterceptedResponse(this.request, event.responseStatusCode!, event.responseErrorReason!, event.responseHeaders!);
|
this._interceptionId = event.requestId;
|
||||||
|
return new network.InterceptedResponse(request, event.responseStatusCode!, event.responseErrorReason!, event.responseHeaders!);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: types.NormalizedFulfillResponse) {
|
async fulfill(response: types.NormalizedFulfillResponse) {
|
||||||
|
@ -60,9 +60,12 @@ export class FFNetworkManager {
|
|||||||
return;
|
return;
|
||||||
if (redirectedFrom)
|
if (redirectedFrom)
|
||||||
this._requests.delete(redirectedFrom._id);
|
this._requests.delete(redirectedFrom._id);
|
||||||
const request = new InterceptableRequest(this._session, frame, redirectedFrom, event);
|
const request = new InterceptableRequest(frame, redirectedFrom, event);
|
||||||
|
let route;
|
||||||
|
if (event.isIntercepted)
|
||||||
|
route = new FFRouteImpl(this._session, request);
|
||||||
this._requests.set(request._id, request);
|
this._requests.set(request._id, request);
|
||||||
this._page._frameManager.requestStarted(request.request);
|
this._page._frameManager.requestStarted(request.request, route);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
|
_onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
|
||||||
@ -173,34 +176,42 @@ const internalCauseToResourceType: {[key: string]: string} = {
|
|||||||
TYPE_INTERNAL_EVENTSOURCE: 'eventsource',
|
TYPE_INTERNAL_EVENTSOURCE: 'eventsource',
|
||||||
};
|
};
|
||||||
|
|
||||||
class InterceptableRequest implements network.RouteDelegate {
|
class InterceptableRequest {
|
||||||
readonly request: network.Request;
|
readonly request: network.Request;
|
||||||
_id: string;
|
readonly _id: string;
|
||||||
private _session: FFSession;
|
|
||||||
|
|
||||||
constructor(session: FFSession, frame: frames.Frame, redirectedFrom: InterceptableRequest | null, payload: Protocol.Network.requestWillBeSentPayload) {
|
constructor(frame: frames.Frame, redirectedFrom: InterceptableRequest | null, payload: Protocol.Network.requestWillBeSentPayload) {
|
||||||
this._id = payload.requestId;
|
this._id = payload.requestId;
|
||||||
this._session = session;
|
|
||||||
let postDataBuffer = null;
|
let postDataBuffer = null;
|
||||||
if (payload.postData)
|
if (payload.postData)
|
||||||
postDataBuffer = Buffer.from(payload.postData, 'base64');
|
postDataBuffer = Buffer.from(payload.postData, 'base64');
|
||||||
this.request = new network.Request(payload.isIntercepted ? this : null, frame, redirectedFrom ? redirectedFrom.request : null, payload.navigationId,
|
this.request = new network.Request(frame, redirectedFrom ? redirectedFrom.request : null, payload.navigationId,
|
||||||
payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, postDataBuffer, payload.headers);
|
payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, postDataBuffer, payload.headers);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FFRouteImpl implements network.RouteDelegate {
|
||||||
|
private _request: InterceptableRequest;
|
||||||
|
private _session: FFSession;
|
||||||
|
|
||||||
|
constructor(session: FFSession, request: InterceptableRequest) {
|
||||||
|
this._session = session;
|
||||||
|
this._request = request;
|
||||||
|
}
|
||||||
|
|
||||||
async responseBody(forFulfill: boolean): Promise<Buffer> {
|
async responseBody(forFulfill: boolean): Promise<Buffer> {
|
||||||
// Empty buffer will result in the response being used.
|
// Empty buffer will result in the response being used.
|
||||||
if (forFulfill)
|
if (forFulfill)
|
||||||
return Buffer.from('');
|
return Buffer.from('');
|
||||||
const response = await this._session.send('Network.getResponseBody', {
|
const response = await this._session.send('Network.getResponseBody', {
|
||||||
requestId: this._id
|
requestId: this._request._id
|
||||||
});
|
});
|
||||||
return Buffer.from(response.base64body, 'base64');
|
return Buffer.from(response.base64body, 'base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
async continue(overrides: types.NormalizedContinueOverrides): Promise<network.InterceptedResponse|null> {
|
async continue(request: network.Request, overrides: types.NormalizedContinueOverrides): Promise<network.InterceptedResponse|null> {
|
||||||
const result = await this._session.sendMayFail('Network.resumeInterceptedRequest', {
|
const result = await this._session.sendMayFail('Network.resumeInterceptedRequest', {
|
||||||
requestId: this._id,
|
requestId: this._request._id,
|
||||||
url: overrides.url,
|
url: overrides.url,
|
||||||
method: overrides.method,
|
method: overrides.method,
|
||||||
headers: overrides.headers,
|
headers: overrides.headers,
|
||||||
@ -209,14 +220,14 @@ class InterceptableRequest implements network.RouteDelegate {
|
|||||||
}) as any;
|
}) as any;
|
||||||
if (!overrides.interceptResponse)
|
if (!overrides.interceptResponse)
|
||||||
return null;
|
return null;
|
||||||
return new InterceptedResponse(this.request, result.response.status, result.response.statusText, result.response.headers);
|
return new InterceptedResponse(request, result.response.status, result.response.statusText, result.response.headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: types.NormalizedFulfillResponse) {
|
async fulfill(response: types.NormalizedFulfillResponse) {
|
||||||
const base64body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
|
const base64body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
|
||||||
|
|
||||||
await this._session.sendMayFail('Network.fulfillInterceptedRequest', {
|
await this._session.sendMayFail('Network.fulfillInterceptedRequest', {
|
||||||
requestId: this._id,
|
requestId: this._request._id,
|
||||||
status: response.status,
|
status: response.status,
|
||||||
statusText: network.STATUS_TEXTS[String(response.status)] || '',
|
statusText: network.STATUS_TEXTS[String(response.status)] || '',
|
||||||
headers: response.headers,
|
headers: response.headers,
|
||||||
@ -226,7 +237,7 @@ class InterceptableRequest implements network.RouteDelegate {
|
|||||||
|
|
||||||
async abort(errorCode: string) {
|
async abort(errorCode: string) {
|
||||||
await this._session.sendMayFail('Network.abortInterceptedRequest', {
|
await this._session.sendMayFail('Network.abortInterceptedRequest', {
|
||||||
requestId: this._id,
|
requestId: this._request._id,
|
||||||
errorCode,
|
errorCode,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -254,19 +254,19 @@ export class FrameManager {
|
|||||||
frame._onLifecycleEvent(event);
|
frame._onLifecycleEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestStarted(request: network.Request) {
|
requestStarted(request: network.Request, route?: network.RouteDelegate) {
|
||||||
const frame = request.frame();
|
const frame = request.frame();
|
||||||
this._inflightRequestStarted(request);
|
this._inflightRequestStarted(request);
|
||||||
if (request._documentId)
|
if (request._documentId)
|
||||||
frame.setPendingDocument({ documentId: request._documentId, request });
|
frame.setPendingDocument({ documentId: request._documentId, request });
|
||||||
if (request._isFavicon) {
|
if (request._isFavicon) {
|
||||||
const route = request._route();
|
|
||||||
if (route)
|
if (route)
|
||||||
route.continue();
|
route.continue(request, {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._page._browserContext.emit(BrowserContext.Events.Request, request);
|
this._page._browserContext.emit(BrowserContext.Events.Request, request);
|
||||||
this._page._requestStarted(request);
|
if (route)
|
||||||
|
this._page._requestStarted(request, route);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestReceivedResponse(response: network.Response) {
|
requestReceivedResponse(response: network.Response) {
|
||||||
|
@ -79,7 +79,6 @@ export function stripFragmentFromUrl(url: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Request extends SdkObject {
|
export class Request extends SdkObject {
|
||||||
readonly _routeDelegate: RouteDelegate | null;
|
|
||||||
private _response: Response | null = null;
|
private _response: Response | null = null;
|
||||||
private _redirectedFrom: Request | null;
|
private _redirectedFrom: Request | null;
|
||||||
private _redirectedTo: Request | null = null;
|
private _redirectedTo: Request | null = null;
|
||||||
@ -97,12 +96,10 @@ export class Request extends SdkObject {
|
|||||||
private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {};
|
private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {};
|
||||||
_responseEndTiming = -1;
|
_responseEndTiming = -1;
|
||||||
|
|
||||||
constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined,
|
constructor(frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined,
|
||||||
url: string, resourceType: string, method: string, postData: Buffer | null, headers: types.HeadersArray) {
|
url: string, resourceType: string, method: string, postData: Buffer | null, headers: types.HeadersArray) {
|
||||||
super(frame, 'request');
|
super(frame, 'request');
|
||||||
assert(!url.startsWith('data:'), 'Data urls should not fire requests');
|
assert(!url.startsWith('data:'), 'Data urls should not fire requests');
|
||||||
assert(!(routeDelegate && redirectedFrom), 'Should not be able to intercept redirects');
|
|
||||||
this._routeDelegate = routeDelegate;
|
|
||||||
this._frame = frame;
|
this._frame = frame;
|
||||||
this._redirectedFrom = redirectedFrom;
|
this._redirectedFrom = redirectedFrom;
|
||||||
if (redirectedFrom)
|
if (redirectedFrom)
|
||||||
@ -185,12 +182,6 @@ export class Request extends SdkObject {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_route(): Route | null {
|
|
||||||
if (!this._routeDelegate)
|
|
||||||
return null;
|
|
||||||
return new Route(this, this._routeDelegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateWithRawHeaders(headers: types.HeadersArray) {
|
updateWithRawHeaders(headers: types.HeadersArray) {
|
||||||
this._headers = headers;
|
this._headers = headers;
|
||||||
this._headersMap.clear();
|
this._headersMap.clear();
|
||||||
@ -257,7 +248,7 @@ export class Route extends SdkObject {
|
|||||||
if (oldUrl.protocol !== newUrl.protocol)
|
if (oldUrl.protocol !== newUrl.protocol)
|
||||||
throw new Error('New URL must have same protocol as overridden URL');
|
throw new Error('New URL must have same protocol as overridden URL');
|
||||||
}
|
}
|
||||||
this._response = await this._delegate.continue(overrides);
|
this._response = await this._delegate.continue(this._request, overrides);
|
||||||
return this._response;
|
return this._response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,7 +411,7 @@ export class InterceptedResponse extends SdkObject {
|
|||||||
|
|
||||||
constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray) {
|
constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray) {
|
||||||
super(request.frame(), 'interceptedResponse');
|
super(request.frame(), 'interceptedResponse');
|
||||||
this._request = request;
|
this._request = request._finalRequest();
|
||||||
this._status = status;
|
this._status = status;
|
||||||
this._statusText = statusText;
|
this._statusText = statusText;
|
||||||
this._headers = headers;
|
this._headers = headers;
|
||||||
@ -482,7 +473,7 @@ export class WebSocket extends SdkObject {
|
|||||||
export interface RouteDelegate {
|
export interface RouteDelegate {
|
||||||
abort(errorCode: string): Promise<void>;
|
abort(errorCode: string): Promise<void>;
|
||||||
fulfill(response: types.NormalizedFulfillResponse): Promise<void>;
|
fulfill(response: types.NormalizedFulfillResponse): Promise<void>;
|
||||||
continue(overrides: types.NormalizedContinueOverrides): Promise<InterceptedResponse|null>;
|
continue(request: Request, overrides: types.NormalizedContinueOverrides): Promise<InterceptedResponse|null>;
|
||||||
responseBody(forFulfill: boolean): Promise<Buffer>;
|
responseBody(forFulfill: boolean): Promise<Buffer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,10 +405,8 @@ export class Page extends SdkObject {
|
|||||||
await this._delegate.updateRequestInterception();
|
await this._delegate.updateRequestInterception();
|
||||||
}
|
}
|
||||||
|
|
||||||
_requestStarted(request: network.Request) {
|
_requestStarted(request: network.Request, routeDelegate: network.RouteDelegate) {
|
||||||
const route = request._route();
|
const route = new network.Route(request, routeDelegate);
|
||||||
if (!route)
|
|
||||||
return;
|
|
||||||
if (this._serverRequestInterceptor) {
|
if (this._serverRequestInterceptor) {
|
||||||
this._serverRequestInterceptor(route, request);
|
this._serverRequestInterceptor(route, request);
|
||||||
return;
|
return;
|
||||||
|
@ -41,95 +41,35 @@ const errorReasons: { [reason: string]: Protocol.Network.ResourceErrorType } = {
|
|||||||
'failed': 'General',
|
'failed': 'General',
|
||||||
};
|
};
|
||||||
|
|
||||||
export class WKInterceptableRequest implements network.RouteDelegate {
|
export class WKInterceptableRequest {
|
||||||
private readonly _session: WKSession;
|
private readonly _session: WKSession;
|
||||||
readonly request: network.Request;
|
readonly request: network.Request;
|
||||||
readonly _requestId: string;
|
readonly _requestId: string;
|
||||||
_interceptedCallback: () => void = () => {};
|
|
||||||
private _interceptedPromise: Promise<unknown>;
|
|
||||||
_responseInterceptedCallback: ((r: Protocol.Network.Response) => void) | undefined;
|
|
||||||
private _responseInterceptedPromise: Promise<Protocol.Network.Response> | undefined;
|
|
||||||
readonly _allowInterception: boolean;
|
|
||||||
_timestamp: number;
|
_timestamp: number;
|
||||||
_wallTime: number;
|
_wallTime: number;
|
||||||
|
readonly _route: WKRouteImpl | null;
|
||||||
|
private _redirectedFrom: WKInterceptableRequest | null;
|
||||||
|
|
||||||
constructor(session: WKSession, allowInterception: boolean, frame: frames.Frame, event: Protocol.Network.requestWillBeSentPayload, redirectedFrom: network.Request | null, documentId: string | undefined) {
|
constructor(session: WKSession, route: WKRouteImpl | null, frame: frames.Frame, event: Protocol.Network.requestWillBeSentPayload, redirectedFrom: WKInterceptableRequest | null, documentId: string | undefined) {
|
||||||
this._session = session;
|
this._session = session;
|
||||||
this._requestId = event.requestId;
|
this._requestId = event.requestId;
|
||||||
this._allowInterception = allowInterception;
|
this._route = route;
|
||||||
const resourceType = event.type ? event.type.toLowerCase() : (redirectedFrom ? redirectedFrom.resourceType() : 'other');
|
this._redirectedFrom = redirectedFrom;
|
||||||
|
const resourceType = event.type ? event.type.toLowerCase() : (redirectedFrom ? redirectedFrom.request.resourceType() : 'other');
|
||||||
let postDataBuffer = null;
|
let postDataBuffer = null;
|
||||||
this._timestamp = event.timestamp;
|
this._timestamp = event.timestamp;
|
||||||
this._wallTime = event.walltime * 1000;
|
this._wallTime = event.walltime * 1000;
|
||||||
if (event.request.postData)
|
if (event.request.postData)
|
||||||
postDataBuffer = Buffer.from(event.request.postData, 'base64');
|
postDataBuffer = Buffer.from(event.request.postData, 'base64');
|
||||||
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, event.request.url,
|
this.request = new network.Request(frame, redirectedFrom?.request || null, documentId, event.request.url,
|
||||||
resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers));
|
resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers));
|
||||||
this._interceptedPromise = new Promise<void>(f => this._interceptedCallback = f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async responseBody(forFulfill: boolean): Promise<Buffer> {
|
_routeForRedirectChain(): WKRouteImpl | null {
|
||||||
// Empty buffer will result in the response being used.
|
let request: WKInterceptableRequest = this;
|
||||||
if (forFulfill)
|
while (request._redirectedFrom)
|
||||||
return Buffer.from('');
|
request = request._redirectedFrom;
|
||||||
const response = await this._session.send('Network.getInterceptedResponseBody', { requestId: this._requestId });
|
return request._route;
|
||||||
return Buffer.from(response.body, 'base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
async abort(errorCode: string) {
|
|
||||||
const errorType = errorReasons[errorCode];
|
|
||||||
assert(errorType, 'Unknown error code: ' + errorCode);
|
|
||||||
await this._interceptedPromise;
|
|
||||||
// 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('Network.interceptRequestWithError', { requestId: this._requestId, errorType });
|
|
||||||
}
|
|
||||||
|
|
||||||
async fulfill(response: types.NormalizedFulfillResponse) {
|
|
||||||
if (300 <= response.status && response.status < 400)
|
|
||||||
throw new Error('Cannot fulfill with redirect status: ' + response.status);
|
|
||||||
|
|
||||||
await this._interceptedPromise;
|
|
||||||
|
|
||||||
// In certain cases, protocol will return error if the request was already canceled
|
|
||||||
// or the page was closed. We should tolerate these errors.
|
|
||||||
let mimeType = response.isBase64 ? 'application/octet-stream' : 'text/plain';
|
|
||||||
const headers = headersArrayToObject(response.headers, false /* lowerCase */);
|
|
||||||
const contentType = headers['content-type'];
|
|
||||||
if (contentType)
|
|
||||||
mimeType = contentType.split(';')[0].trim();
|
|
||||||
|
|
||||||
const isResponseIntercepted = await this._responseInterceptedPromise;
|
|
||||||
await this._session.sendMayFail(isResponseIntercepted ? 'Network.interceptWithResponse' : 'Network.interceptRequestWithResponse', {
|
|
||||||
requestId: this._requestId,
|
|
||||||
status: response.status,
|
|
||||||
statusText: network.STATUS_TEXTS[String(response.status)],
|
|
||||||
mimeType,
|
|
||||||
headers,
|
|
||||||
base64Encoded: response.isBase64,
|
|
||||||
content: response.body
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async continue(overrides: types.NormalizedContinueOverrides): Promise<network.InterceptedResponse|null> {
|
|
||||||
if (overrides.interceptResponse) {
|
|
||||||
await (this.request.frame()._page._delegate as WKPage)._ensureResponseInterceptionEnabled();
|
|
||||||
this._responseInterceptedPromise = new Promise(f => this._responseInterceptedCallback = f);
|
|
||||||
}
|
|
||||||
await this._interceptedPromise;
|
|
||||||
// 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('Network.interceptWithRequest', {
|
|
||||||
requestId: this._requestId,
|
|
||||||
url: overrides.url,
|
|
||||||
method: overrides.method,
|
|
||||||
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 responsePayload = await this._responseInterceptedPromise;
|
|
||||||
return new InterceptedResponse(this.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createResponse(responsePayload: Protocol.Network.Response): network.Response {
|
createResponse(responsePayload: Protocol.Network.Response): network.Response {
|
||||||
@ -152,6 +92,86 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class WKRouteImpl implements network.RouteDelegate {
|
||||||
|
private readonly _session: WKSession;
|
||||||
|
private readonly _requestId: string;
|
||||||
|
_requestInterceptedCallback: () => void = () => {};
|
||||||
|
private readonly _requestInterceptedPromise: Promise<unknown>;
|
||||||
|
_responseInterceptedCallback: ((responsePayload: Protocol.Network.Response) => void) | undefined;
|
||||||
|
private _responseInterceptedPromise: Promise<Protocol.Network.Response> | undefined;
|
||||||
|
private readonly _page: WKPage;
|
||||||
|
|
||||||
|
constructor(session: WKSession, page: WKPage, requestId: string) {
|
||||||
|
this._session = session;
|
||||||
|
this._page = page;
|
||||||
|
this._requestId = requestId;
|
||||||
|
this._requestInterceptedPromise = new Promise<void>(f => this._requestInterceptedCallback = f);
|
||||||
|
}
|
||||||
|
|
||||||
|
async responseBody(forFulfill: boolean): Promise<Buffer> {
|
||||||
|
// Empty buffer will result in the response being used.
|
||||||
|
if (forFulfill)
|
||||||
|
return Buffer.from('');
|
||||||
|
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;
|
||||||
|
// 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('Network.interceptRequestWithError', { requestId: this._requestId, errorType });
|
||||||
|
}
|
||||||
|
|
||||||
|
async fulfill(response: types.NormalizedFulfillResponse) {
|
||||||
|
if (300 <= response.status && response.status < 400)
|
||||||
|
throw new Error('Cannot fulfill with redirect status: ' + response.status);
|
||||||
|
|
||||||
|
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.
|
||||||
|
let mimeType = response.isBase64 ? 'application/octet-stream' : 'text/plain';
|
||||||
|
const headers = headersArrayToObject(response.headers, false /* lowerCase */);
|
||||||
|
const contentType = headers['content-type'];
|
||||||
|
if (contentType)
|
||||||
|
mimeType = contentType.split(';')[0].trim();
|
||||||
|
|
||||||
|
const isResponseIntercepted = await this._responseInterceptedPromise;
|
||||||
|
await this._session.sendMayFail(isResponseIntercepted ? 'Network.interceptWithResponse' : 'Network.interceptRequestWithResponse', {
|
||||||
|
requestId: this._requestId,
|
||||||
|
status: response.status,
|
||||||
|
statusText: network.STATUS_TEXTS[String(response.status)],
|
||||||
|
mimeType,
|
||||||
|
headers,
|
||||||
|
base64Encoded: response.isBase64,
|
||||||
|
content: response.body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async continue(request: network.Request, overrides: types.NormalizedContinueOverrides): Promise<network.InterceptedResponse|null> {
|
||||||
|
if (overrides.interceptResponse) {
|
||||||
|
await this._page._ensureResponseInterceptionEnabled();
|
||||||
|
this._responseInterceptedPromise = new Promise(f => this._responseInterceptedCallback = f);
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
await this._session.sendMayFail('Network.interceptWithRequest', {
|
||||||
|
requestId: this._requestId,
|
||||||
|
url: overrides.url,
|
||||||
|
method: overrides.method,
|
||||||
|
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 responsePayload = await this._responseInterceptedPromise;
|
||||||
|
return new InterceptedResponse(request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function wkMillisToRoundishMillis(value: number): number {
|
function wkMillisToRoundishMillis(value: number): number {
|
||||||
// WebKit uses -1000 for unavailable.
|
// WebKit uses -1000 for unavailable.
|
||||||
if (value === -1000)
|
if (value === -1000)
|
||||||
|
@ -37,7 +37,7 @@ import { WKBrowserContext } from './wkBrowser';
|
|||||||
import { WKSession } from './wkConnection';
|
import { WKSession } from './wkConnection';
|
||||||
import { WKExecutionContext } from './wkExecutionContext';
|
import { WKExecutionContext } from './wkExecutionContext';
|
||||||
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './wkInput';
|
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './wkInput';
|
||||||
import { WKInterceptableRequest } from './wkInterceptableRequest';
|
import { WKInterceptableRequest, WKRouteImpl } from './wkInterceptableRequest';
|
||||||
import { WKProvisionalPage } from './wkProvisionalPage';
|
import { WKProvisionalPage } from './wkProvisionalPage';
|
||||||
import { WKWorkers } from './wkWorkers';
|
import { WKWorkers } from './wkWorkers';
|
||||||
import { debugLogger } from '../../utils/debugLogger';
|
import { debugLogger } from '../../utils/debugLogger';
|
||||||
@ -949,16 +949,16 @@ export class WKPage implements PageDelegate {
|
|||||||
_onRequestWillBeSent(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) {
|
_onRequestWillBeSent(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) {
|
||||||
if (event.request.url.startsWith('data:'))
|
if (event.request.url.startsWith('data:'))
|
||||||
return;
|
return;
|
||||||
let redirectedFrom: network.Request | null = null;
|
let redirectedFrom: WKInterceptableRequest | null = null;
|
||||||
if (event.redirectResponse) {
|
if (event.redirectResponse) {
|
||||||
const request = this._requestIdToRequest.get(event.requestId);
|
const request = this._requestIdToRequest.get(event.requestId);
|
||||||
// If we connect late to the target, we could have missed the requestWillBeSent event.
|
// If we connect late to the target, we could have missed the requestWillBeSent event.
|
||||||
if (request) {
|
if (request) {
|
||||||
this._handleRequestRedirect(request, event.redirectResponse, event.timestamp);
|
this._handleRequestRedirect(request, event.redirectResponse, event.timestamp);
|
||||||
redirectedFrom = request.request;
|
redirectedFrom = request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const frame = redirectedFrom ? redirectedFrom.frame() : this._page._frameManager.frame(event.frameId);
|
const frame = redirectedFrom ? redirectedFrom.request.frame() : this._page._frameManager.frame(event.frameId);
|
||||||
// sometimes we get stray network events for detached frames
|
// sometimes we get stray network events for detached frames
|
||||||
// TODO(einbinder) why?
|
// TODO(einbinder) why?
|
||||||
if (!frame)
|
if (!frame)
|
||||||
@ -967,11 +967,13 @@ export class WKPage implements PageDelegate {
|
|||||||
// TODO(einbinder) this will fail if we are an XHR document request
|
// TODO(einbinder) this will fail if we are an XHR document request
|
||||||
const isNavigationRequest = event.type === 'Document';
|
const isNavigationRequest = event.type === 'Document';
|
||||||
const documentId = isNavigationRequest ? event.loaderId : undefined;
|
const documentId = isNavigationRequest ? event.loaderId : undefined;
|
||||||
|
let route = null;
|
||||||
// We do not support intercepting redirects.
|
// We do not support intercepting redirects.
|
||||||
const allowInterception = this._page._needsRequestInterception() && !redirectedFrom;
|
if (this._page._needsRequestInterception() && !redirectedFrom)
|
||||||
const request = new WKInterceptableRequest(session, allowInterception, frame, event, redirectedFrom, documentId);
|
route = new WKRouteImpl(session, this, event.requestId);
|
||||||
|
const request = new WKInterceptableRequest(session, route, frame, event, redirectedFrom, documentId);
|
||||||
this._requestIdToRequest.set(event.requestId, request);
|
this._requestIdToRequest.set(event.requestId, request);
|
||||||
this._page._frameManager.requestStarted(request.request);
|
this._page._frameManager.requestStarted(request.request, route || undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleRequestRedirect(request: WKInterceptableRequest, responsePayload: Protocol.Network.Response, timestamp: number) {
|
private _handleRequestRedirect(request: WKInterceptableRequest, responsePayload: Protocol.Network.Response, timestamp: number) {
|
||||||
@ -990,22 +992,23 @@ export class WKPage implements PageDelegate {
|
|||||||
session.sendMayFail('Network.interceptRequestWithError', {errorType: 'Cancellation', requestId: event.requestId});
|
session.sendMayFail('Network.interceptRequestWithError', {errorType: 'Cancellation', requestId: event.requestId});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!request._allowInterception) {
|
if (!request._route) {
|
||||||
// Intercepted, although we do not intend to allow interception.
|
// Intercepted, although we do not intend to allow interception.
|
||||||
// Just continue.
|
// Just continue.
|
||||||
session.sendMayFail('Network.interceptWithRequest', { requestId: request._requestId });
|
session.sendMayFail('Network.interceptWithRequest', { requestId: request._requestId });
|
||||||
} else {
|
} else {
|
||||||
request._interceptedCallback();
|
request._route._requestInterceptedCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onResponseIntercepted(session: WKSession, event: Protocol.Network.responseInterceptedPayload) {
|
_onResponseIntercepted(session: WKSession, event: Protocol.Network.responseInterceptedPayload) {
|
||||||
const request = this._requestIdToRequest.get(event.requestId);
|
const request = this._requestIdToRequest.get(event.requestId);
|
||||||
if (!request || !request._responseInterceptedCallback) {
|
const route = request?._routeForRedirectChain();
|
||||||
|
if (!route?._responseInterceptedCallback) {
|
||||||
session.sendMayFail('Network.interceptContinue', { requestId: event.requestId, stage: 'response' });
|
session.sendMayFail('Network.interceptContinue', { requestId: event.requestId, stage: 'response' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
request._responseInterceptedCallback(event.response);
|
route._responseInterceptedCallback(event.response);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
|
_onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
|
||||||
|
@ -150,8 +150,8 @@ it('should be abortable after interception', async ({page, server, browserName})
|
|||||||
expect(failed).toBe(true);
|
expect(failed).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fulfill after redirects', async ({page, server}) => {
|
it('should fulfill after redirects', async ({page, server, browserName}) => {
|
||||||
it.fixme();
|
it.fixme(browserName !== 'chromium');
|
||||||
server.setRedirect('/redirect/1.html', '/redirect/2.html');
|
server.setRedirect('/redirect/1.html', '/redirect/2.html');
|
||||||
server.setRedirect('/redirect/2.html', '/empty.html');
|
server.setRedirect('/redirect/2.html', '/empty.html');
|
||||||
const expectedUrls = ['/redirect/1.html', '/redirect/2.html', '/empty.html'].map(s => server.PREFIX + s);
|
const expectedUrls = ['/redirect/1.html', '/redirect/2.html', '/empty.html'].map(s => server.PREFIX + s);
|
||||||
@ -161,7 +161,9 @@ it('should fulfill after redirects', async ({page, server}) => {
|
|||||||
page.on('request', request => requestUrls.push(request.url()));
|
page.on('request', request => requestUrls.push(request.url()));
|
||||||
page.on('response', response => responseUrls.push(response.url()));
|
page.on('response', response => responseUrls.push(response.url()));
|
||||||
page.on('requestfinished', request => requestFinishedUrls.push(request.url()));
|
page.on('requestfinished', request => requestFinishedUrls.push(request.url()));
|
||||||
|
let routeCalls = 0;
|
||||||
await page.route('**/*', async route => {
|
await page.route('**/*', async route => {
|
||||||
|
++routeCalls;
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
await route._intercept({});
|
await route._intercept({});
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
@ -178,6 +180,7 @@ it('should fulfill after redirects', async ({page, server}) => {
|
|||||||
expect(responseUrls).toEqual(expectedUrls);
|
expect(responseUrls).toEqual(expectedUrls);
|
||||||
await response.finished();
|
await response.finished();
|
||||||
expect(requestFinishedUrls).toEqual(expectedUrls);
|
expect(requestFinishedUrls).toEqual(expectedUrls);
|
||||||
|
expect(routeCalls).toBe(1);
|
||||||
|
|
||||||
const redirectChain = [];
|
const redirectChain = [];
|
||||||
for (let req = response.request(); req; req = req.redirectedFrom())
|
for (let req = response.request(); req; req = req.redirectedFrom())
|
||||||
@ -189,3 +192,70 @@ it('should fulfill after redirects', async ({page, server}) => {
|
|||||||
expect(response.headers()['content-type']).toBe('text/plain');
|
expect(response.headers()['content-type']).toBe('text/plain');
|
||||||
expect(await response.text()).toBe('Yo, page!');
|
expect(await response.text()).toBe('Yo, page!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fulfill original response after redirects', async ({page, browserName, server}) => {
|
||||||
|
it.fixme(browserName !== 'chromium');
|
||||||
|
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
|
||||||
|
await route._intercept({});
|
||||||
|
await route.fulfill();
|
||||||
|
});
|
||||||
|
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('<title>Woof-Woof</title>\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should abort after redirects', async ({page, browserName, server}) => {
|
||||||
|
it.fixme(browserName !== 'chromium');
|
||||||
|
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._intercept({});
|
||||||
|
await route.abort('connectionreset');
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await page.goto(server.PREFIX + '/redirect/1.html');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toContain('ERR_CONNECTION_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);
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user