Introduce new interception API (#242)

This patch introduces new interception API, via killing InterceptedRequest and giving the `abort` and `continue` methods to the Request object.
This commit is contained in:
Andrey Lushnikov 2017-08-11 17:24:31 -07:00 committed by GitHub
parent d793e51a9a
commit 4551afc6dc
7 changed files with 402 additions and 168 deletions

View File

@ -49,7 +49,7 @@
+ [page.screenshot([options])](#pagescreenshotoptions)
+ [page.setContent(html)](#pagesetcontenthtml)
+ [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders)
+ [page.setRequestInterceptor(interceptor)](#pagesetrequestinterceptorinterceptor)
+ [page.setRequestInterceptionEnabled(value)](#pagesetrequestinterceptionenabledvalue)
+ [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
+ [page.setViewport(viewport)](#pagesetviewportviewport)
+ [page.title()](#pagetitle)
@ -98,6 +98,8 @@
+ [frame.waitForFunction(pageFunction[, options, ...args])](#framewaitforfunctionpagefunction-options-args)
+ [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
* [class: Request](#class-request)
+ [request.abort()](#requestabort)
+ [request.continue([overrides])](#requestcontinueoverrides)
+ [request.headers](#requestheaders)
+ [request.method](#requestmethod)
+ [request.postData](#requestpostdata)
@ -110,16 +112,8 @@
+ [response.ok](#responseok)
+ [response.request()](#responserequest)
+ [response.status](#responsestatus)
+ [response.statusText](#responsestatustext)
+ [response.text()](#responsetext)
+ [response.url](#responseurl)
* [class: InterceptedRequest](#class-interceptedrequest)
+ [interceptedRequest.abort()](#interceptedrequestabort)
+ [interceptedRequest.continue([overrides])](#interceptedrequestcontinueoverrides)
+ [interceptedRequest.headers](#interceptedrequestheaders)
+ [interceptedRequest.method](#interceptedrequestmethod)
+ [interceptedRequest.postData](#interceptedrequestpostdata)
+ [interceptedRequest.url](#interceptedrequesturl)
<!-- tocstop -->
@ -297,7 +291,8 @@ Emitted when an unhandled exception happens on the page. The only argument of th
#### event: 'request'
- <[Request]>
Emitted when a page issues a request. The [request] object is a read-only object. In order to intercept and mutate requests, see [page.setRequestInterceptor](#pagesetrequestinterceptorinterceptor)
Emitted when a page issues a request. The [request] object is a read-only object.
In order to intercept and mutate requests, see `page.setRequestInterceptionEnabled`.
#### event: 'requestfailed'
- <[Request]>
@ -618,19 +613,20 @@ The extra HTTP headers will be sent with every request the page initiates.
> **NOTE** page.setExtraHTTPHeaders does not guarantee the order of headers in the outgoing requests.
#### page.setRequestInterceptor(interceptor)
- `interceptor` <[function]> Callback function which accepts a single argument of type <[InterceptedRequest]>.
- returns: <[Promise]> Promise which resolves when request interceptor is successfully installed on the page.
#### page.setRequestInterceptionEnabled(value)
- `value` <[boolean]> Whether to enable request interception.
- returns: <[Promise]> Promise which resolves when request interception change is applied.
After the request interceptor is installed on the page, every request will be reported to the interceptor. The [InterceptedRequest] could be modified and then either continued via the `continue()` method, or aborted via the `abort()` method.
Activating request interception enables `request.abort` and `request.continue`.
En example of a naive request interceptor which aborts all image requests:
An example of a naïve request interceptor which aborts all image requests:
```js
const {Browser} = require('puppeteer');
const browser = new Browser();
browser.newPage().then(async page =>
await page.setRequestInterceptor(interceptedRequest => {
await page.setRequestInterceptionEnabled(true);
page.on('request', request => {
if (interceptedRequest.url.endsWith('.png') || interceptedRequest.url.endsWith('.jpg'))
interceptedRequest.abort();
else
@ -1103,6 +1099,21 @@ If request gets a 'redirect' response, the request is successfully finished with
[Request] class represents requests which are sent by page. [Request] implements [Body] mixin, which in case of HTTP POST requests allows clients to call `request.json()` or `request.text()` to get different representations of request's body.
#### request.abort()
Aborts request. To use this, request interception should be enabled with `page.setRequestInterceptionEnabled`.
Exception is immediately thrown if the request interception is not enabled.
#### request.continue([overrides])
- `overrides` <[Object]> Optional request overwrites, which could be one of the following:
- `url` <[string]> If set, the request url will be changed
- `method` <[string]> If set changes the request method (e.g. `GET` or `POST`)
- `postData` <[string]> If set changes the post data of request
- `headers` <[Map]> If set changes the request HTTP headers
Continues request with optional request overrides. To use this, request interception should be enabled with `page.setRequestInterceptionEnabled`.
Exception is immediately thrown if the request interception is not enabled.
#### request.headers
- <[Map]> A map of HTTP headers associated with the request.
@ -1152,11 +1163,6 @@ Contains a boolean stating whether the response was successful (status in the ra
Contains the status code of the response (e.g., 200 for a success).
#### response.statusText
- <[string]>
Contains the status message corresponding to the status code (e.g., OK for 200).
#### response.text()
- returns: <Promise<[text]>> Promise which resolves to a text representation of response body.
@ -1166,41 +1172,6 @@ Contains the status message corresponding to the status code (e.g., OK for 200).
Contains the URL of the response.
### class: InterceptedRequest
[InterceptedRequest] represents an intercepted request, which can be either continued or aborted. [InterceptedRequest] which is not continued or aborted will be in a 'hanging' state.
#### interceptedRequest.abort()
Aborts request.
#### interceptedRequest.continue([overrides])
- `overrides` <[Object]> Optional request overwrites, which could be one of the following:
- `url` <[string]> If set, the request url will be changed
- `method` <[string]> If set changes the request method (e.g. `GET` or `POST`)
- `postData` <[string]> If set changes the post data of request
- `headers` <[Map]> If set changes the request HTTP headers
Continues request with optional request overrides.
#### interceptedRequest.headers
- <[Map]> A map of HTTP headers associated with the request.
#### interceptedRequest.method
- <[string]>
Contains the request's method (GET, POST, etc.)
#### interceptedRequest.postData
- <[string]>
Contains `POST` data for `POST` requests.
#### interceptedRequest.url
- <[string]>
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array"
[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean"
[Buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer "Buffer"
@ -1208,7 +1179,6 @@ Contains `POST` data for `POST` requests.
[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number"
[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object "Object"
[Page]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page "Page"
[InterceptedRequest]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-interceptedrequest "Page"
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
[stream.Readable]: https://nodejs.org/api/stream.html#stream_class_stream_readable "stream.Readable"

138
lib/Multimap.js Normal file
View File

@ -0,0 +1,138 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @template K, V
*/
class Multimap {
constructor() {
/** @type {!Map<K, !Set<!V>>} */
this._map = new Map();
}
/**
* @param {K} key
* @param {V} value
*/
set(key, value) {
let set = this._map.get(key);
if (!set) {
set = new Set();
this._map.set(key, set);
}
set.add(value);
}
/**
* @param {K} key
* @return {!Set<!V>}
*/
get(key) {
let result = this._map.get(key);
if (!result)
result = new Set();
return result;
}
/**
* @param {K} key
* @return {boolean}
*/
has(key) {
return this._map.has(key);
}
/**
* @param {K} key
* @param {V} value
* @return {boolean}
*/
hasValue(key, value) {
let set = this._map.get(key);
if (!set)
return false;
return set.has(value);
}
/**
* @return {number}
*/
get size() {
return this._map.size;
}
/**
* @param {K} key
* @param {V} value
* @return {boolean}
*/
delete(key, value) {
let values = this.get(key);
let result = values.delete(value);
if (!values.size)
this._map.delete(key);
return result;
}
/**
* @param {K} key
*/
deleteAll(key) {
this._map.delete(key);
}
/**
* @return {!Array<K>}
*/
keysArray() {
return this._map.keysArray();
}
/**
* @param {K} key
* @return {?V} value
*/
firstValue(key) {
let set = this._map.get(key);
if (!set)
return null;
return set.values().next().value;
}
/**
* @return {K}
*/
firstKey() {
return this._map.keys().next().value;
}
/**
* @return {!Array<!V>}
*/
valuesArray() {
let result = [];
let keys = this.keysArray();
for (let i = 0; i < keys.length; ++i)
result.pushAll(this.get(keys[i]).valuesArray());
return result;
}
clear() {
this._map.clear();
}
}
module.exports = Multimap;

View File

@ -15,6 +15,7 @@
*/
const EventEmitter = require('events');
const helper = require('./helper');
const Multimap = require('./Multimap');
class NetworkManager extends EventEmitter {
/**
@ -23,12 +24,19 @@ class NetworkManager extends EventEmitter {
constructor(client) {
super();
this._client = client;
this._requestInterceptor = null;
/** @type {!Map<string, !Request>} */
this._idToRequest = new Map();
this._requestIdToRequest = new Map();
/** @type {!Map<string, !Request>} */
this._interceptionIdToRequest = new Map();
/** @type {!Map<string, string>} */
this._extraHTTPHeaders = new Map();
this._requestInterceptionEnabled = false;
/** @type {!Multimap<string, string>} */
this._requestHashToRequestIds = new Multimap();
/** @type {!Multimap<string, !Object>} */
this._requestHashToInterceptions = new Multimap();
this._client.on('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this));
this._client.on('Network.requestIntercepted', this._onRequestIntercepted.bind(this));
this._client.on('Network.responseReceived', this._onResponseReceived.bind(this));
@ -64,47 +72,100 @@ class NetworkManager extends EventEmitter {
}
/**
* @param {?function(!InterceptedRequest)} interceptor
* @param {boolean} value
* @return {!Promise}
*/
async setRequestInterceptor(interceptor) {
this._requestInterceptor = interceptor;
await this._client.send('Network.setRequestInterceptionEnabled', {enabled: !!interceptor});
async setRequestInterceptionEnabled(value) {
await this._client.send('Network.setRequestInterceptionEnabled', {enabled: !!value});
this._requestInterceptionEnabled = value;
}
/**
* @param {!Object} event
*/
_onRequestIntercepted(event) {
let request = new InterceptedRequest(this._client, event.interceptionId, event.request);
this._requestInterceptor(request);
if (event.redirectStatusCode) {
let request = this._interceptionIdToRequest.get(event.interceptionId);
console.assert(request, 'INTERNAL ERROR: failed to find request for interception redirect.');
this._handleRequestRedirect(request, event.redirectStatusCode, event.redirectHeaders);
this._handleRequestStart(request._requestId, event.interceptionId, event.redirectUrl, event.request);
return;
}
let requestHash = generateRequestHash(event.request);
this._requestHashToInterceptions.set(requestHash, event);
this._maybeResolveInterception(requestHash);
}
/**
* @param {!Object} event
* @param {!Request} request
* @param {number} redirectStatus
* @param {!Object} redirectHeaders
*/
_onRequestWillBeSent(event) {
if (event.redirectResponse) {
let request = this._idToRequest.get(event.requestId);
let response = new Response(this._client, request, event.redirectResponse);
request._response = response;
this.emit(NetworkManager.Events.Response, response);
this.emit(NetworkManager.Events.RequestFinished, request);
}
let request = new Request(event.requestId, event.request);
this._idToRequest.set(event.requestId, request);
_handleRequestRedirect(request, redirectStatus, redirectHeaders) {
let response = new Response(this._client, request, redirectStatus, redirectHeaders);
request._response = response;
this._requestIdToRequest.delete(request._requestId);
this._interceptionIdToRequest.delete(request._interceptionId);
this.emit(NetworkManager.Events.Response, response);
this.emit(NetworkManager.Events.RequestFinished, request);
}
/**
* @param {string} requestId
* @param {string} interceptionId
* @param {string} url
* @param {!Object} requestPayload
*/
_handleRequestStart(requestId, interceptionId, url, requestPayload) {
let request = new Request(this._client, requestId, interceptionId, url, requestPayload);
this._requestIdToRequest.set(requestId, request);
this._interceptionIdToRequest.set(interceptionId, request);
this.emit(NetworkManager.Events.Request, request);
}
/**
* @param {!Object} event
*/
_onRequestWillBeSent(event) {
if (this._requestInterceptionEnabled) {
// All redirects are handled in requestIntercepted.
if (event.redirectResponse)
return;
let requestHash = generateRequestHash(event.request);
this._requestHashToRequestIds.set(requestHash, event.requestId);
this._maybeResolveInterception(requestHash);
return;
}
if (event.redirectResponse) {
let request = this._requestIdToRequest.get(event.requestId);
this._handleRequestRedirect(request, event.redirectResponse.status, event.redirectResponse.headers);
}
this._handleRequestStart(event.requestId, null, event.request.url, event.request);
}
/**
* @param {string} requestHash
* @param {!{requestEvent: ?Object, interceptionEvent: ?Object}} interception
*/
_maybeResolveInterception(requestHash) {
const requestId = this._requestHashToRequestIds.firstValue(requestHash);
const interception = this._requestHashToInterceptions.firstValue(requestHash);
if (!requestId || !interception)
return;
this._requestHashToRequestIds.delete(requestHash, requestId);
this._requestHashToInterceptions.delete(requestHash, interception);
this._handleRequestStart(requestId, interception.interceptionId, interception.request.url, interception.request);
}
/**
* @param {!Object} event
*/
_onResponseReceived(event) {
let request = this._idToRequest.get(event.requestId);
let request = this._requestIdToRequest.get(event.requestId);
// FileUpload sends a response without a matching request.
if (!request)
return;
let response = new Response(this._client, request, event.response);
let response = new Response(this._client, request, event.response.status, event.response.headers);
request._response = response;
this.emit(NetworkManager.Events.Response, response);
}
@ -113,13 +174,14 @@ class NetworkManager extends EventEmitter {
* @param {!Object} event
*/
_onLoadingFinished(event) {
let request = this._idToRequest.get(event.requestId);
let request = this._requestIdToRequest.get(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
if (!request)
return;
request._completePromiseFulfill.call(null);
this._idToRequest.delete(event.requestId);
this._requestIdToRequest.delete(event.requestId);
this._interceptionIdToRequest.delete(event.interceptionId);
this.emit(NetworkManager.Events.RequestFinished, request);
}
@ -127,28 +189,37 @@ class NetworkManager extends EventEmitter {
* @param {!Object} event
*/
_onLoadingFailed(event) {
let request = this._idToRequest.get(event.requestId);
let request = this._requestIdToRequest.get(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
if (!request)
return;
request._completePromiseFulfill.call(null);
this._idToRequest.delete(event.requestId);
this._requestIdToRequest.delete(event.requestId);
this._interceptionIdToRequest.delete(event.interceptionId);
this.emit(NetworkManager.Events.RequestFailed, request);
}
}
class Request {
/**
* @param {!Connection} client
* @param {string} requestId
* @param {string} interceptionId
* @param {string} url
* @param {!Object} payload
*/
constructor(requestId, payload) {
constructor(client, requestId, interceptionId, url, payload) {
this._client = client;
this._requestId = requestId;
this._interceptionId = interceptionId;
this._interceptionHandled = false;
this._response = null;
this._completePromise = new Promise(fulfill => {
this._completePromiseFulfill = fulfill;
});
this.url = payload.url;
this.url = url;
this.method = payload.method;
this.postData = payload.postData;
this.headers = new Map(Object.entries(payload.headers));
@ -160,25 +231,57 @@ class Request {
response() {
return this._response;
}
/**
* @param {!Object=} overrides
*/
continue(overrides = {}) {
console.assert(this._interceptionId, 'Request Interception is not enabled!');
console.assert(!this._interceptionHandled, 'Request is already handled!');
this._interceptionHandled = true;
let headers = undefined;
if (overrides.headers) {
headers = {};
for (let entry of overrides.headers)
headers[entry[0]] = entry[1];
}
this._client.send('Network.continueInterceptedRequest', {
interceptionId: this._interceptionId,
url: overrides.url,
method: overrides.method,
postData: overrides.postData,
headers: headers
});
}
abort() {
console.assert(this._interceptionId, 'Request Interception is not enabled!');
console.assert(!this._interceptionHandled, 'Request is already handled!');
this._interceptionHandled = true;
this._client.send('Network.continueInterceptedRequest', {
interceptionId: this._interceptionId,
errorReason: 'Failed'
});
}
}
helper.tracePublicAPI(Request);
class Response {
/**
* @param {!Session} client
* @param {?Request} request
* @param {!Object} payload
* @param {!Request} request
* @param {integer} status
* @param {!Object} headers
*/
constructor(client, request, payload) {
constructor(client, request, status, headers) {
this._client = client;
this._request = request;
this._contentPromise = null;
this.headers = new Map(Object.entries(payload.headers));
this.ok = payload.status >= 200 && payload.status <= 299;
this.status = payload.status;
this.statusText = payload.statusText;
this.url = payload.url;
this.headers = new Map(Object.entries(headers));
this.status = status;
this.ok = status >= 200 && status <= 299;
this.url = request.url;
}
/**
@ -213,7 +316,7 @@ class Response {
}
/**
* @return {?Response}
* @return {!Response}
*/
request() {
return this._request;
@ -221,52 +324,25 @@ class Response {
}
helper.tracePublicAPI(Response);
class InterceptedRequest {
/**
* @param {!Session} client
* @param {string} interceptionId
* @param {!Object} payload
*/
constructor(client, interceptionId, payload) {
this._client = client;
this._interceptionId = interceptionId;
this._handled = false;
this.url = payload.url;
this.method = payload.method;
this.headers = new Map(Object.entries(payload.headers));
this.postData = payload.postData;
}
abort() {
console.assert(!this._handled, 'This request is already handled!');
this._handled = true;
this._client.send('Network.continueInterceptedRequest', {
interceptionId: this._interceptionId,
errorReason: 'Failed'
});
}
/**
* @param {!Object} overrides
*/
continue(overrides = {}) {
console.assert(!this._handled, 'This request is already handled!');
this._handled = true;
let headers = undefined;
if (overrides.headers) {
headers = {};
for (let entry of overrides.headers.entries())
headers[entry[0]] = entry[1];
}
this._client.send('Network.continueInterceptedRequest', {
interceptionId: this._interceptionId,
url: overrides.url,
method: overrides.method,
postData: overrides.postData,
headers: headers
});
/**
* @param {!Object} request
* @return {string}
*/
function generateRequestHash(request) {
let hash = {
url: request.url,
method: request.method,
postData: request.postData,
headers: {},
};
let headers = Object.keys(request.headers);
headers.sort();
for (let header of headers) {
if (header === 'Accept' || header === 'Referer')
continue;
hash.headers[header] = request.headers[header];
}
return JSON.stringify(hash);
}
NetworkManager.Events = {

View File

@ -116,10 +116,10 @@ class Page extends EventEmitter {
}
/**
* @param {?function(!InterceptedRequest)} interceptor
* @param {boolean} value
*/
async setRequestInterceptor(interceptor) {
return this._networkManager.setRequestInterceptor(interceptor);
async setRequestInterceptionEnabled(value) {
return this._networkManager.setRequestInterceptionEnabled(value);
}
/**

View File

@ -63,6 +63,7 @@ class WebPage {
this._onError = noop;
this._pageEvents = new AsyncEmitter(this._page);
this._pageEvents.on(PageEvents.Request, request => this._onRequest(request));
this._pageEvents.on(PageEvents.Response, response => this._onResponseReceived(response));
this._pageEvents.on(PageEvents.RequestFinished, request => this._onRequestFinished(request));
this._pageEvents.on(PageEvents.RequestFailed, event => (this.onResourceError || noop).call(null, event));
@ -229,24 +230,23 @@ class WebPage {
* @return {?function(!Object, !Request)} callback
*/
set onResourceRequested(callback) {
await(this._page.setRequestInterceptionEnabled(!!callback));
this._onResourceRequestedCallback = callback;
this._page.setRequestInterceptor(callback ? resourceInterceptor : null);
}
/**
* @param {!InterceptedRequest} request
*/
function resourceInterceptor(request) {
let requestData = new RequestData(request);
let phantomRequest = new PhantomRequest();
callback(requestData, phantomRequest);
if (phantomRequest._aborted) {
request.abort();
} else {
request.continue({
url: phantomRequest._url,
headers: phantomRequest._headers,
});
}
_onRequest(request) {
if (!this._onResourceRequestedCallback)
return;
let requestData = new RequestData(request);
let phantomRequest = new PhantomRequest();
this._onResourceRequestedCallback.call(null, requestData, phantomRequest);
if (phantomRequest._aborted) {
request.abort();
} else {
request.continue({
url: phantomRequest._url,
headers: phantomRequest._headers,
});
}
}

View File

@ -746,9 +746,10 @@ describe('Page', function() {
}));
});
describe('Page.setRequestInterceptor', function() {
describe('Page.setRequestInterceptionEnabled', function() {
it('should intercept', SX(async function() {
await page.setRequestInterceptor(request => {
await page.setRequestInterceptionEnabled(true);
page.on('request', request => {
expect(request.url).toContain('empty.html');
expect(request.headers.has('User-Agent')).toBeTruthy();
expect(request.method).toBe('GET');
@ -762,7 +763,8 @@ describe('Page', function() {
await page.setExtraHTTPHeaders(new Map(Object.entries({
foo: 'bar'
})));
await page.setRequestInterceptor(request => {
await page.setRequestInterceptionEnabled(true);
page.on('request', request => {
expect(request.headers.get('foo')).toBe('bar');
request.continue();
});
@ -770,7 +772,8 @@ describe('Page', function() {
expect(response.ok).toBe(true);
}));
it('should be abortable', SX(async function() {
await page.setRequestInterceptor(request => {
await page.setRequestInterceptionEnabled(true);
page.on('request', request => {
if (request.url.endsWith('.css'))
request.abort();
else
@ -783,10 +786,11 @@ describe('Page', function() {
expect(failedRequests).toBe(1);
}));
it('should amend HTTP headers', SX(async function() {
await page.setRequestInterceptor(request => {
await page.setRequestInterceptionEnabled(true);
page.on('request', request => {
let headers = new Map(request.headers);
headers.set('foo', 'bar');
request.continue({headers});
request.continue({ headers });
});
await page.goto(EMPTY_PAGE);
const [request] = await Promise.all([
@ -796,7 +800,8 @@ describe('Page', function() {
expect(request.headers['foo']).toBe('bar');
}));
it('should fail navigation when aborting main resource', SX(async function() {
await page.setRequestInterceptor(request => request.abort());
await page.setRequestInterceptionEnabled(true);
page.on('request', request => request.abort());
let error = null;
try {
await page.goto(EMPTY_PAGE);
@ -807,10 +812,54 @@ describe('Page', function() {
expect(error.message).toContain('Failed to navigate');
}));
it('should work with redirects', SX(async function() {
server.setRedirect('/non-existing-page.html', '/empty.html');
await page.setRequestInterceptor(request => request.continue());
await page.setRequestInterceptionEnabled(true);
page.on('request', request => request.continue());
server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html');
server.setRedirect('/non-existing-page-2.html', '/non-existing-page-3.html');
server.setRedirect('/non-existing-page-3.html', '/non-existing-page-4.html');
server.setRedirect('/non-existing-page-4.html', '/empty.html');
let response = await page.goto(PREFIX + '/non-existing-page.html');
expect(response.status).toBe(200);
expect(response.url).toContain('empty.html');
}));
it('should be able to abort redirects', SX(async function() {
await page.setRequestInterceptionEnabled(true);
server.setRedirect('/non-existing.json', '/non-existing-2.json');
server.setRedirect('/non-existing-2.json', '/simple.html');
page.on('request', request => {
if (request.url.includes('non-existing-2'))
request.abort();
else
request.continue();
});
await page.goto(EMPTY_PAGE);
let result = await page.evaluate(async() => {
try {
await fetch('/non-existing.json');
} catch (e) {
return e.message;
}
});
expect(result).toContain('Failed to fetch');
}));
it('should work with equal requests', SX(async function() {
await page.goto(EMPTY_PAGE);
let responseCount = 1;
server.setRoute('/zzz', (req, res) => res.end((responseCount++) * 11 + ''));
await page.setRequestInterceptionEnabled(true);
let spinner = false;
// Cancel 2nd request.
page.on('request', request => {
spinner ? request.abort() : request.continue();
spinner = !spinner;
});
let results = await page.evaluate(() => Promise.all([
fetch('/zzz').then(response => response.text()).catch(e => 'FAILED'),
fetch('/zzz').then(response => response.text()).catch(e => 'FAILED'),
fetch('/zzz').then(response => response.text()).catch(e => 'FAILED'),
]));
expect(results).toEqual(['11', 'FAILED', '22']);
}));
});
@ -1328,7 +1377,8 @@ describe('Page', function() {
expect(await responseText).toBe('hello world!');
}));
it('Page.Events.RequestFailed', SX(async function() {
page.setRequestInterceptor(request => {
await page.setRequestInterceptionEnabled(true);
page.on('request', request => {
if (request.url.endsWith('css'))
request.abort();
else

View File

@ -24,6 +24,7 @@ const EXCLUDE_CLASSES = new Set([
'EmulationManager',
'FrameManager',
'Helper',
'Multimap',
'NavigatorWatcher',
'NetworkManager',
'ProxyStream',
@ -38,7 +39,6 @@ const EXCLUDE_METHODS = new Set([
'Frame.constructor',
'Headers.constructor',
'Headers.fromPayload',
'InterceptedRequest.constructor',
'Keyboard.constructor',
'Mouse.constructor',
'Tracing.constructor',