api(route): pass Route object instead of Request to route handlers (#1385)

References #1348.
This commit is contained in:
Dmitry Gozman 2020-03-13 14:30:40 -07:00 committed by GitHub
parent 26479119b6
commit 69be12ae12
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 251 additions and 255 deletions

View file

@ -101,7 +101,7 @@ const { firefox } = require('playwright');
#### Intercept network requests #### Intercept network requests
This code snippet sets up network interception for a WebKit page to log all network requests. This code snippet sets up request routing for a WebKit page to log all network requests.
```js ```js
const { webkit } = require('playwright'); const { webkit } = require('playwright');
@ -112,9 +112,9 @@ const { webkit } = require('playwright');
const page = await context.newPage(); const page = await context.newPage();
// Log and continue all network requests // Log and continue all network requests
page.route('**', request => { page.route('**', route => {
console.log(request.url()); console.log(route.request().url());
request.continue(); route.continue();
}); });
await page.goto('http://todomvc.com'); await page.goto('http://todomvc.com');

View file

@ -20,6 +20,7 @@
- [class: Request](#class-request) - [class: Request](#class-request)
- [class: Response](#class-response) - [class: Response](#class-response)
- [class: Selectors](#class-selectors) - [class: Selectors](#class-selectors)
- [class: Route](#class-route)
- [class: TimeoutError](#class-timeouterror) - [class: TimeoutError](#class-timeouterror)
- [class: Accessibility](#class-accessibility) - [class: Accessibility](#class-accessibility)
- [class: Worker](#class-worker) - [class: Worker](#class-worker)
@ -456,17 +457,17 @@ Creates a new page in the browser context.
#### browserContext.route(url, handler) #### browserContext.route(url, handler)
- `url` <[string]|[RegExp]|[function]\([string]\):[boolean]> A glob pattern, regex pattern or predicate receiving [URL] to match while routing. - `url` <[string]|[RegExp]|[function]\([string]\):[boolean]> A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
- `handler` <[function]\([Request]\)> handler function to route the request. - `handler` <[function]\([Route], [Request]\)> handler function to route the request.
- returns: <[Promise]>. - returns: <[Promise]>
Routing activates the request interception and enables `request.abort`, `request.continue` and `request.fulfill` methods on the request. This provides the capability to modify network requests that are made by any page in the browser context. Routing provides the capability to modify network requests that are made by any page in the browser context.
Once route is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
Once request interception is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted. An example of a naïve handler that aborts all image requests:
An example of a naïve request interceptor that aborts all image requests:
```js ```js
const context = await browser.newContext(); const context = await browser.newContext();
await context.route('**/*.{png,jpg,jpeg}', request => request.abort()); await context.route('**/*.{png,jpg,jpeg}', route => route.abort());
const page = await context.newPage(); const page = await context.newPage();
await page.goto('https://example.com'); await page.goto('https://example.com');
await browser.close(); await browser.close();
@ -476,7 +477,7 @@ or the same snippet using a regex pattern instead:
```js ```js
const context = await browser.newContext(); const context = await browser.newContext();
await context.route(/(\.png$)|(\.jpg$)/, request => request.abort()); await context.route(/(\.png$)|(\.jpg$)/, route => route.abort());
const page = await context.newPage(); const page = await context.newPage();
await page.goto('https://example.com'); await page.goto('https://example.com');
await browser.close(); await browser.close();
@ -484,7 +485,7 @@ await browser.close();
Page routes (set up with [page.route(url, handler)](#pagerouteurl-handler)) take precedence over browser context routes when request matches both handlers. Page routes (set up with [page.route(url, handler)](#pagerouteurl-handler)) take precedence over browser context routes when request matches both handlers.
> **NOTE** Enabling request interception disables http cache. > **NOTE** Enabling routing disables http cache.
#### browserContext.setDefaultNavigationTimeout(timeout) #### browserContext.setDefaultNavigationTimeout(timeout)
- `timeout` <[number]> Maximum navigation time in milliseconds - `timeout` <[number]> Maximum navigation time in milliseconds
@ -783,7 +784,7 @@ const popup = await event.page();
- <[Request]> - <[Request]>
Emitted when a page issues a request. The [request] object is read-only. Emitted when a page issues a request. The [request] object is read-only.
In order to intercept and mutate requests, see `page.route()`. In order to intercept and mutate requests, see !!!`page.route()` or `brows.
#### event: 'requestfailed' #### event: 'requestfailed'
- <[Request]> - <[Request]>
@ -1423,18 +1424,18 @@ If `key` is a single character and no modifier keys besides `Shift` are being he
#### page.route(url, handler) #### page.route(url, handler)
- `url` <[string]|[RegExp]|[function]\([string]\):[boolean]> A glob pattern, regex pattern or predicate receiving [URL] to match while routing. - `url` <[string]|[RegExp]|[function]\([string]\):[boolean]> A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
- `handler` <[function]\([Request]\)> handler function to route the request. - `handler` <[function]\([Route], [Request]\)> handler function to route the request.
- returns: <[Promise]>. - returns: <[Promise]>.
Routing activates the request interception and enables `request.abort`, `request.continue` and Routing provides the capability to modify network requests that are made by a page.
`request.fulfill` methods on the request. This provides the capability to modify network requests that are made by a page.
Once request interception is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted. Once routing is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
An example of a naïve request interceptor that aborts all image requests:
An example of a naïve handler that aborts all image requests:
```js ```js
const page = await browser.newPage(); const page = await browser.newPage();
await page.route('**/*.{png,jpg,jpeg}', request => request.abort()); await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
await page.goto('https://example.com'); await page.goto('https://example.com');
await browser.close(); await browser.close();
``` ```
@ -1443,14 +1444,14 @@ or the same snippet using a regex pattern instead:
```js ```js
const page = await browser.newPage(); const page = await browser.newPage();
await page.route(/(\.png$)|(\.jpg$)/, request => request.abort()); await page.route(/(\.png$)|(\.jpg$)/, route => route.abort());
await page.goto('https://example.com'); await page.goto('https://example.com');
await browser.close(); await browser.close();
``` ```
Page routes take precedence over browser context routes (set up with [browserContext.route(url, handler)](#browsercontextrouteurl-handler)) when request matches both handlers. Page routes take precedence over browser context routes (set up with [browserContext.route(url, handler)](#browsercontextrouteurl-handler)) when request matches both handlers.
> **NOTE** Enabling request interception disables http cache. > **NOTE** Enabling rouing disables http cache.
#### page.screenshot([options]) #### page.screenshot([options])
- `options` <[Object]> Options object which might have the following properties: - `options` <[Object]> Options object which might have the following properties:
@ -3131,11 +3132,8 @@ If request fails at some point, then instead of `'requestfinished'` event (and p
If request gets a 'redirect' response, the request is successfully finished with the 'requestfinished' event, and a new request is issued to a redirected url. If request gets a 'redirect' response, the request is successfully finished with the 'requestfinished' event, and a new request is issued to a redirected url.
<!-- GEN:toc --> <!-- GEN:toc -->
- [request.abort([errorCode])](#requestaborterrorcode)
- [request.continue([overrides])](#requestcontinueoverrides)
- [request.failure()](#requestfailure) - [request.failure()](#requestfailure)
- [request.frame()](#requestframe) - [request.frame()](#requestframe)
- [request.fulfill(response)](#requestfulfillresponse)
- [request.headers()](#requestheaders) - [request.headers()](#requestheaders)
- [request.isNavigationRequest()](#requestisnavigationrequest) - [request.isNavigationRequest()](#requestisnavigationrequest)
- [request.method()](#requestmethod) - [request.method()](#requestmethod)
@ -3146,50 +3144,6 @@ If request gets a 'redirect' response, the request is successfully finished with
- [request.url()](#requesturl) - [request.url()](#requesturl)
<!-- GEN:stop --> <!-- GEN:stop -->
#### request.abort([errorCode])
- `errorCode` <[string]> Optional error code. Defaults to `failed`, could be
one of the following:
- `aborted` - An operation was aborted (due to user action)
- `accessdenied` - Permission to access a resource, other than the network, was denied
- `addressunreachable` - The IP address is unreachable. This usually means
that there is no route to the specified host or network.
- `blockedbyclient` - The client chose to block the request.
- `blockedbyresponse` - The request failed because the response was delivered along with requirements which are not met ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).
- `connectionaborted` - A connection timed out as a result of not receiving an ACK for data sent.
- `connectionclosed` - A connection was closed (corresponding to a TCP FIN).
- `connectionfailed` - A connection attempt failed.
- `connectionrefused` - A connection attempt was refused.
- `connectionreset` - A connection was reset (corresponding to a TCP RST).
- `internetdisconnected` - The Internet connection has been lost.
- `namenotresolved` - The host name could not be resolved.
- `timedout` - An operation timed out.
- `failed` - A generic failure occurred.
- returns: <[Promise]>
Aborts request. To use this, request interception should be enabled with `page.route`.
Exception is immediately thrown if the request interception is not enabled.
#### request.continue([overrides])
- `overrides` <[Object]> Optional request overrides, which can be one of the following:
- `method` <[string]> If set changes the request method (e.g. GET or POST)
- `postData` <[string]> If set changes the post data of request
- `headers` <[Object]> If set changes the request HTTP headers. Header values will be converted to a string.
- returns: <[Promise]>
Continues request with optional request overrides. To use this, request interception should be enabled with `page.route`.
Exception is immediately thrown if the request interception is not enabled.
```js
await page.route('**/*', request => {
// Override headers
const headers = Object.assign({}, request.headers(), {
foo: 'bar', // set "foo" header
origin: undefined, // remove "origin" header
});
request.continue({headers});
});
```
#### request.failure() #### request.failure()
- returns: <?[Object]> Object describing request failure, if any - returns: <?[Object]> Object describing request failure, if any
- `errorText` <[string]> Human-readable error message, e.g. `'net::ERR_FAILED'`. - `errorText` <[string]> Human-readable error message, e.g. `'net::ERR_FAILED'`.
@ -3208,37 +3162,6 @@ page.on('requestfailed', request => {
#### request.frame() #### request.frame()
- returns: <[Frame]> A [Frame] that initiated this request. - returns: <[Frame]> A [Frame] that initiated this request.
#### request.fulfill(response)
- `response` <[Object]> Response that will fulfill this request
- `status` <[number]> Response status code, defaults to `200`.
- `headers` <[Object]> Optional response headers. Header values will be converted to a string.
- `contentType` <[string]> If set, equals to setting `Content-Type` response header.
- `body` <[string]|[Buffer]> Optional response body.
- `path` <[string]> Optional file path to respond with. The content type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
- returns: <[Promise]>
Fulfills request with given response. To use this, request interception should
be enabled with `page.route`. Exception is thrown if
request interception is not enabled.
An example of fulfilling all requests with 404 responses:
```js
await page.route('**/*', request => {
request.fulfill({
status: 404,
contentType: 'text/plain',
body: 'Not Found!'
});
});
```
An example of serving static file:
```js
await page.route('**/xhr_endpoint', request => request.fulfill({ path: 'mock_data.json' }));
```
#### request.headers() #### request.headers()
- returns: <[Object]> An object with HTTP headers associated with the request. All header names are lower-case. - returns: <[Object]> An object with HTTP headers associated with the request. All header names are lower-case.
@ -3410,6 +3333,93 @@ const { selectors, firefox } = require('playwright'); // Or 'chromium' or 'webk
})(); })();
``` ```
### class: Route
Whenever a network route is set up with [page.route(url, handler)](#pagerouteurl-handler) or [browserContext.route(url, handler)](#browsercontextrouteurl-handler), the `Route` object allows to handle the route.
<!-- GEN:toc -->
- [route.abort([errorCode])](#routeaborterrorcode)
- [route.continue([overrides])](#routecontinueoverrides)
- [route.fulfill(response)](#routefulfillresponse)
- [route.request()](#routerequest)
<!-- GEN:stop -->
#### route.abort([errorCode])
- `errorCode` <[string]> Optional error code. Defaults to `failed`, could be
one of the following:
- `aborted` - An operation was aborted (due to user action)
- `accessdenied` - Permission to access a resource, other than the network, was denied
- `addressunreachable` - The IP address is unreachable. This usually means
that there is no route to the specified host or network.
- `blockedbyclient` - The client chose to block the request.
- `blockedbyresponse` - The request failed because the response was delivered along with requirements which are not met ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).
- `connectionaborted` - A connection timed out as a result of not receiving an ACK for data sent.
- `connectionclosed` - A connection was closed (corresponding to a TCP FIN).
- `connectionfailed` - A connection attempt failed.
- `connectionrefused` - A connection attempt was refused.
- `connectionreset` - A connection was reset (corresponding to a TCP RST).
- `internetdisconnected` - The Internet connection has been lost.
- `namenotresolved` - The host name could not be resolved.
- `timedout` - An operation timed out.
- `failed` - A generic failure occurred.
- returns: <[Promise]>
Aborts the route's request.
#### route.continue([overrides])
- `overrides` <[Object]> Optional request overrides, which can be one of the following:
- `method` <[string]> If set changes the request method (e.g. GET or POST)
- `postData` <[string]> If set changes the post data of request
- `headers` <[Object]> If set changes the request HTTP headers. Header values will be converted to a string.
- returns: <[Promise]>
Continues route's request with optional overrides.
```js
await page.route('**/*', (route, request) => {
// Override headers
const headers = Object.assign({}, request.headers(), {
foo: 'bar', // set "foo" header
origin: undefined, // remove "origin" header
});
route.continue({headers});
});
```
#### route.fulfill(response)
- `response` <[Object]> Response that will fulfill this route's request.
- `status` <[number]> Response status code, defaults to `200`.
- `headers` <[Object]> Optional response headers. Header values will be converted to a string.
- `contentType` <[string]> If set, equals to setting `Content-Type` response header.
- `body` <[string]|[Buffer]> Optional response body.
- `path` <[string]> Optional file path to respond with. The content type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
- returns: <[Promise]>
Fulfills route's request with given response.
An example of fulfilling all requests with 404 responses:
```js
await page.route('**/*', route => {
route.fulfill({
status: 404,
contentType: 'text/plain',
body: 'Not Found!'
});
});
```
An example of serving static file:
```js
await page.route('**/xhr_endpoint', route => route.fulfill({ path: 'mock_data.json' }));
```
#### route.request()
- returns: <[Request]> A request to be routed.
### class: TimeoutError ### class: TimeoutError
* extends: [Error] * extends: [Error]
@ -4052,6 +4062,7 @@ const { chromium } = require('playwright');
[RegExp]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp [RegExp]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
[Request]: #class-request "Request" [Request]: #class-request "Request"
[Response]: #class-response "Response" [Response]: #class-response "Response"
[Route]: #class-route "Route"
[Selectors]: #class-selectors "Selectors" [Selectors]: #class-selectors "Selectors"
[Serializable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description "Serializable" [Serializable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description "Serializable"
[TimeoutError]: #class-timeouterror "TimeoutError" [TimeoutError]: #class-timeouterror "TimeoutError"

View file

@ -24,7 +24,7 @@ export { TimeoutError } from './errors';
export { Frame } from './frames'; export { Frame } from './frames';
export { Keyboard, Mouse } from './input'; export { Keyboard, Mouse } from './input';
export { JSHandle } from './javascript'; export { JSHandle } from './javascript';
export { Request, Response } from './network'; export { Request, Response, Route } from './network';
export { FileChooser, Page, PageEvent, Worker } from './page'; export { FileChooser, Page, PageEvent, Worker } from './page';
export { Selectors } from './selectors'; export { Selectors } from './selectors';

View file

@ -63,7 +63,7 @@ export abstract class BrowserContextBase extends platform.EventEmitter implement
readonly _timeoutSettings = new TimeoutSettings(); readonly _timeoutSettings = new TimeoutSettings();
readonly _pageBindings = new Map<string, PageBinding>(); readonly _pageBindings = new Map<string, PageBinding>();
readonly _options: BrowserContextOptions; readonly _options: BrowserContextOptions;
readonly _routes: { url: types.URLMatch, handler: (request: network.Request) => any }[] = []; readonly _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = [];
_closed = false; _closed = false;
private readonly _closePromise: Promise<Error>; private readonly _closePromise: Promise<Error>;
private _closePromiseFulfill: ((error: Error) => void) | undefined; private _closePromiseFulfill: ((error: Error) => void) | undefined;

View file

@ -241,7 +241,7 @@ export class CRNetworkManager {
} }
} }
class InterceptableRequest implements network.RequestDelegate { class InterceptableRequest implements network.RouteDelegate {
readonly request: network.Request; readonly request: network.Request;
_requestId: string; _requestId: string;
_interceptionId: string | null; _interceptionId: string | null;

View file

@ -141,7 +141,7 @@ const causeToResourceType: {[key: string]: string} = {
TYPE_WEB_MANIFEST: 'manifest', TYPE_WEB_MANIFEST: 'manifest',
}; };
class InterceptableRequest implements network.RequestDelegate { class InterceptableRequest implements network.RouteDelegate {
readonly request: network.Request; readonly request: network.Request;
_id: string; _id: string;
private _session: FFSession; private _session: FFSession;

View file

@ -41,8 +41,6 @@ export type SetNetworkCookieParam = {
sameSite?: 'Strict' | 'Lax' | 'None' sameSite?: 'Strict' | 'Lax' | 'None'
}; };
export type RouteHandler = (request: Request) => void;
export function filterCookies(cookies: NetworkCookie[], urls: string | string[] = []): NetworkCookie[] { export function filterCookies(cookies: NetworkCookie[], urls: string | string[] = []): NetworkCookie[] {
if (!Array.isArray(urls)) if (!Array.isArray(urls))
urls = [ urls ]; urls = [ urls ];
@ -95,7 +93,7 @@ function stripFragmentFromUrl(url: string): string {
export type Headers = { [key: string]: string }; export type Headers = { [key: string]: string };
export class Request { export class Request {
private _delegate: RequestDelegate | null; readonly _routeDelegate: RouteDelegate | null;
private _response: Response | null = null; private _response: Response | null = null;
_redirectChain: Request[]; _redirectChain: Request[];
_finalRequest: Request; _finalRequest: Request;
@ -110,12 +108,11 @@ export class Request {
private _frame: frames.Frame; private _frame: frames.Frame;
private _waitForResponsePromise: Promise<Response | null>; private _waitForResponsePromise: Promise<Response | null>;
private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {}; private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {};
private _interceptionHandled = false;
constructor(delegate: RequestDelegate | null, frame: frames.Frame, redirectChain: Request[], documentId: string | undefined, constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectChain: Request[], documentId: string | undefined,
url: string, resourceType: string, method: string, postData: string | null, headers: Headers) { url: string, resourceType: string, method: string, postData: string | null, headers: Headers) {
assert(!url.startsWith('data:'), 'Data urls should not fire requests'); assert(!url.startsWith('data:'), 'Data urls should not fire requests');
this._delegate = delegate; this._routeDelegate = routeDelegate;
this._frame = frame; this._frame = frame;
this._redirectChain = redirectChain; this._redirectChain = redirectChain;
this._finalRequest = this; this._finalRequest = this;
@ -189,17 +186,36 @@ export class Request {
}; };
} }
_route(): Route | null {
if (!this._routeDelegate)
return null;
return new Route(this, this._routeDelegate);
}
}
export class Route {
private readonly _request: Request;
private readonly _delegate: RouteDelegate;
private _handled = false;
constructor(request: Request, delegate: RouteDelegate) {
this._request = request;
this._delegate = delegate;
}
request(): Request {
return this._request;
}
async abort(errorCode: string = 'failed') { async abort(errorCode: string = 'failed') {
assert(this._delegate, 'Request Interception is not enabled!'); assert(!this._handled, 'Route is already handled!');
assert(!this._interceptionHandled, 'Request is already handled!'); this._handled = true;
this._interceptionHandled = true;
await this._delegate.abort(errorCode); await this._delegate.abort(errorCode);
} }
async fulfill(response: FulfillResponse & { path?: string }) { async fulfill(response: FulfillResponse & { path?: string }) {
assert(this._delegate, 'Request Interception is not enabled!'); assert(!this._handled, 'Route is already handled!');
assert(!this._interceptionHandled, 'Request is already handled!'); this._handled = true;
this._interceptionHandled = true;
if (response.path) { if (response.path) {
response = { response = {
status: response.status, status: response.status,
@ -212,16 +228,13 @@ export class Request {
} }
async continue(overrides: { method?: string; headers?: Headers; postData?: string } = {}) { async continue(overrides: { method?: string; headers?: Headers; postData?: string } = {}) {
assert(this._delegate, 'Request Interception is not enabled!'); assert(!this._handled, 'Route is already handled!');
assert(!this._interceptionHandled, 'Request is already handled!');
await this._delegate.continue(overrides); await this._delegate.continue(overrides);
} }
_isIntercepted(): boolean {
return !!this._delegate;
}
} }
export type RouteHandler = (route: Route, request: Request) => void;
type GetResponseBodyCallback = () => Promise<platform.BufferType>; type GetResponseBodyCallback = () => Promise<platform.BufferType>;
export class Response { export class Response {
@ -313,7 +326,7 @@ export type FulfillResponse = {
body?: string | platform.BufferType, body?: string | platform.BufferType,
}; };
export interface RequestDelegate { export interface RouteDelegate {
abort(errorCode: string): Promise<void>; abort(errorCode: string): Promise<void>;
fulfill(response: FulfillResponse): Promise<void>; fulfill(response: FulfillResponse): Promise<void>;
continue(overrides: { method?: string; headers?: Headers; postData?: string; }): Promise<void>; continue(overrides: { method?: string; headers?: Headers; postData?: string; }): Promise<void>;

View file

@ -150,7 +150,7 @@ export class Page extends platform.EventEmitter {
private _workers = new Map<string, Worker>(); private _workers = new Map<string, Worker>();
readonly pdf: ((options?: types.PDFOptions) => Promise<platform.BufferType>) | undefined; readonly pdf: ((options?: types.PDFOptions) => Promise<platform.BufferType>) | undefined;
readonly coverage: any; readonly coverage: any;
readonly _routes: { url: types.URLMatch, handler: (request: network.Request) => any }[] = []; readonly _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = [];
_ownedContext: BrowserContext | undefined; _ownedContext: BrowserContext | undefined;
constructor(delegate: PageDelegate, browserContext: BrowserContextBase) { constructor(delegate: PageDelegate, browserContext: BrowserContextBase) {
@ -419,21 +419,22 @@ export class Page extends platform.EventEmitter {
_requestStarted(request: network.Request) { _requestStarted(request: network.Request) {
this.emit(Events.Page.Request, request); this.emit(Events.Page.Request, request);
if (!request._isIntercepted()) const route = request._route();
if (!route)
return; return;
for (const { url, handler } of this._routes) { for (const { url, handler } of this._routes) {
if (platform.urlMatches(request.url(), url)) { if (platform.urlMatches(request.url(), url)) {
handler(request); handler(route, request);
return; return;
} }
} }
for (const { url, handler } of this._browserContext._routes) { for (const { url, handler } of this._browserContext._routes) {
if (platform.urlMatches(request.url(), url)) { if (platform.urlMatches(request.url(), url)) {
handler(request); handler(route, request);
return; return;
} }
} }
request.continue(); route.continue();
} }
async screenshot(options?: types.ScreenshotOptions): Promise<platform.BufferType> { async screenshot(options?: types.ScreenshotOptions): Promise<platform.BufferType> {

View file

@ -39,7 +39,7 @@ const errorReasons: { [reason: string]: string } = {
'failed': 'General', 'failed': 'General',
}; };
export class WKInterceptableRequest implements network.RequestDelegate { export class WKInterceptableRequest implements network.RouteDelegate {
private readonly _session: WKSession; private readonly _session: WKSession;
readonly request: network.Request; readonly request: network.Request;
readonly _requestId: string; readonly _requestId: string;

View file

@ -371,8 +371,9 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF
it('should intercept', async({browser, server}) => { it('should intercept', async({browser, server}) => {
const context = await browser.newContext(); const context = await browser.newContext();
let intercepted = false; let intercepted = false;
await context.route('**/empty.html', request => { await context.route('**/empty.html', route => {
intercepted = true; intercepted = true;
const request = route.request();
expect(request.url()).toContain('empty.html'); expect(request.url()).toContain('empty.html');
expect(request.headers()['user-agent']).toBeTruthy(); expect(request.headers()['user-agent']).toBeTruthy();
expect(request.method()).toBe('GET'); expect(request.method()).toBe('GET');
@ -381,7 +382,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF
expect(request.resourceType()).toBe('document'); expect(request.resourceType()).toBe('document');
expect(request.frame() === page.mainFrame()).toBe(true); expect(request.frame() === page.mainFrame()).toBe(true);
expect(request.frame().url()).toBe('about:blank'); expect(request.frame().url()).toBe('about:blank');
request.continue(); route.continue();
}); });
const page = await context.newPage(); const page = await context.newPage();
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
@ -391,12 +392,12 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF
}); });
it('should yield to page.route', async({browser, server}) => { it('should yield to page.route', async({browser, server}) => {
const context = await browser.newContext(); const context = await browser.newContext();
await context.route('**/empty.html', request => { await context.route('**/empty.html', route => {
request.fulfill({ status: 200, body: 'context' }); route.fulfill({ status: 200, body: 'context' });
}); });
const page = await context.newPage(); const page = await context.newPage();
await page.route('**/empty.html', request => { await page.route('**/empty.html', route => {
request.fulfill({ status: 200, body: 'page' }); route.fulfill({ status: 200, body: 'page' });
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);

View file

@ -57,7 +57,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
res.end('console.log(1);'); res.end('console.log(1);');
}); });
await page.route('*', request => request.continue()); await page.route('*', route => route.continue());
await page.goto(server.PREFIX + '/intervention'); await page.goto(server.PREFIX + '/intervention');
// Check for feature URL substring rather than https://www.chromestatus.com to // Check for feature URL substring rather than https://www.chromestatus.com to
// make it work with Edgium. // make it work with Edgium.

View file

@ -51,7 +51,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, b
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
}); });
it('should load oopif iframes with subresources and request interception', async function({browser, page, server, context}) { it('should load oopif iframes with subresources and request interception', async function({browser, page, server, context}) {
await page.route('**/*', request => request.continue()); await page.route('**/*', route => route.continue());
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1); expect(await countOOPIFs(browser)).toBe(1);
}); });
@ -68,8 +68,8 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, b
const browser = await browserType.launch(headfulOptions); const browser = await browserType.launch(headfulOptions);
const page = await browser.newPage(); const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.route('**/*', request => { await page.route('**/*', route => {
request.fulfill({body: 'YO, GOOGLE.COM'}); route.fulfill({body: 'YO, GOOGLE.COM'});
}); });
await page.evaluate(() => { await page.evaluate(() => {
const frame = document.createElement('iframe'); const frame = document.createElement('iframe');

View file

@ -32,7 +32,8 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Page.route', function() { describe('Page.route', function() {
it('should intercept', async({page, server}) => { it('should intercept', async({page, server}) => {
let intercepted = false; let intercepted = false;
await page.route('**/empty.html', request => { await page.route('**/empty.html', (route, request) => {
expect(route.request()).toBe(request);
expect(request.url()).toContain('empty.html'); expect(request.url()).toContain('empty.html');
expect(request.headers()['user-agent']).toBeTruthy(); expect(request.headers()['user-agent']).toBeTruthy();
expect(request.method()).toBe('GET'); expect(request.method()).toBe('GET');
@ -41,7 +42,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(request.resourceType()).toBe('document'); expect(request.resourceType()).toBe('document');
expect(request.frame() === page.mainFrame()).toBe(true); expect(request.frame() === page.mainFrame()).toBe(true);
expect(request.frame().url()).toBe('about:blank'); expect(request.frame().url()).toBe('about:blank');
request.continue(); route.continue();
intercepted = true; intercepted = true;
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
@ -51,7 +52,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
it('should work when POST is redirected with 302', async({page, server}) => { it('should work when POST is redirected with 302', async({page, server}) => {
server.setRedirect('/rredirect', '/empty.html'); server.setRedirect('/rredirect', '/empty.html');
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.route('**/*', request => request.continue()); await page.route('**/*', route => route.continue());
await page.setContent(` await page.setContent(`
<form action='/rredirect' method='post'> <form action='/rredirect' method='post'>
<input type="hidden" id="foo" name="foo" value="FOOBAR"> <input type="hidden" id="foo" name="foo" value="FOOBAR">
@ -65,22 +66,22 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
// @see https://github.com/GoogleChrome/puppeteer/issues/3973 // @see https://github.com/GoogleChrome/puppeteer/issues/3973
it('should work when header manipulation headers with redirect', async({page, server}) => { it('should work when header manipulation headers with redirect', async({page, server}) => {
server.setRedirect('/rrredirect', '/empty.html'); server.setRedirect('/rrredirect', '/empty.html');
await page.route('**/*', request => { await page.route('**/*', route => {
const headers = Object.assign({}, request.headers(), { const headers = Object.assign({}, route.request().headers(), {
foo: 'bar' foo: 'bar'
}); });
request.continue({ headers }); route.continue({ headers });
}); });
await page.goto(server.PREFIX + '/rrredirect'); await page.goto(server.PREFIX + '/rrredirect');
}); });
// @see https://github.com/GoogleChrome/puppeteer/issues/4743 // @see https://github.com/GoogleChrome/puppeteer/issues/4743
it('should be able to remove headers', async({page, server}) => { it('should be able to remove headers', async({page, server}) => {
await page.route('**/*', request => { await page.route('**/*', route => {
const headers = Object.assign({}, request.headers(), { const headers = Object.assign({}, route.request().headers(), {
foo: 'bar', foo: 'bar',
origin: undefined, // remove "origin" header origin: undefined, // remove "origin" header
}); });
request.continue({ headers }); route.continue({ headers });
}); });
const [serverRequest] = await Promise.all([ const [serverRequest] = await Promise.all([
@ -92,9 +93,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
}); });
it('should contain referer header', async({page, server}) => { it('should contain referer header', async({page, server}) => {
const requests = []; const requests = [];
await page.route('**/*', request => { await page.route('**/*', route => {
requests.push(request); requests.push(route.request());
request.continue(); route.continue();
}); });
await page.goto(server.PREFIX + '/one-style.html'); await page.goto(server.PREFIX + '/one-style.html');
expect(requests[1].url()).toContain('/one-style.css'); expect(requests[1].url()).toContain('/one-style.css');
@ -106,7 +107,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await context.addCookies([{ url: server.EMPTY_PAGE, name: 'foo', value: 'bar'}]); await context.addCookies([{ url: server.EMPTY_PAGE, name: 'foo', value: 'bar'}]);
// Setup request interception. // Setup request interception.
await page.route('**/*', request => request.continue()); await page.route('**/*', route => route.continue());
const response = await page.reload(); const response = await page.reload();
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
@ -114,9 +115,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.setExtraHTTPHeaders({ await page.setExtraHTTPHeaders({
foo: 'bar' foo: 'bar'
}); });
await page.route('**/*', request => { await page.route('**/*', route => {
expect(request.headers()['foo']).toBe('bar'); expect(route.request().headers()['foo']).toBe('bar');
request.continue(); route.continue();
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
@ -125,7 +126,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
it('should work with redirect inside sync XHR', async({page, server}) => { it('should work with redirect inside sync XHR', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
server.setRedirect('/logo.png', '/pptr.png'); server.setRedirect('/logo.png', '/pptr.png');
await page.route('**/*', request => request.continue()); await page.route('**/*', route => route.continue());
const status = await page.evaluate(async() => { const status = await page.evaluate(async() => {
const request = new XMLHttpRequest(); const request = new XMLHttpRequest();
request.open('GET', '/logo.png', false); // `false` makes the request synchronous request.open('GET', '/logo.png', false); // `false` makes the request synchronous
@ -136,15 +137,15 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
}); });
it('should work with custom referer headers', async({page, server}) => { it('should work with custom referer headers', async({page, server}) => {
await page.setExtraHTTPHeaders({ 'referer': server.EMPTY_PAGE }); await page.setExtraHTTPHeaders({ 'referer': server.EMPTY_PAGE });
await page.route('**/*', request => { await page.route('**/*', route => {
expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); expect(route.request().headers()['referer']).toBe(server.EMPTY_PAGE);
request.continue(); route.continue();
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
}); });
it('should be abortable', async({page, server}) => { it('should be abortable', async({page, server}) => {
await page.route(/\.css$/, request => request.abort()); await page.route(/\.css$/, route => route.abort());
let failedRequests = 0; let failedRequests = 0;
page.on('requestfailed', event => ++failedRequests); page.on('requestfailed', event => ++failedRequests);
const response = await page.goto(server.PREFIX + '/one-style.html'); const response = await page.goto(server.PREFIX + '/one-style.html');
@ -153,7 +154,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(failedRequests).toBe(1); expect(failedRequests).toBe(1);
}); });
it('should be abortable with custom error codes', async({page, server}) => { it('should be abortable with custom error codes', async({page, server}) => {
await page.route('**/*', request => request.abort('internetdisconnected')); await page.route('**/*', route => route.abort('internetdisconnected'));
let failedRequest = null; let failedRequest = null;
page.on('requestfailed', request => failedRequest = request); page.on('requestfailed', request => failedRequest = request);
await page.goto(server.EMPTY_PAGE).catch(e => {}); await page.goto(server.EMPTY_PAGE).catch(e => {});
@ -169,7 +170,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.setExtraHTTPHeaders({ await page.setExtraHTTPHeaders({
referer: 'http://google.com/' referer: 'http://google.com/'
}); });
await page.route('**/*', request => request.continue()); await page.route('**/*', route => route.continue());
const [request] = await Promise.all([ const [request] = await Promise.all([
server.waitForRequest('/grid.html'), server.waitForRequest('/grid.html'),
page.goto(server.PREFIX + '/grid.html'), page.goto(server.PREFIX + '/grid.html'),
@ -177,7 +178,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(request.headers['referer']).toBe('http://google.com/'); expect(request.headers['referer']).toBe('http://google.com/');
}); });
it('should fail navigation when aborting main resource', async({page, server}) => { it('should fail navigation when aborting main resource', async({page, server}) => {
await page.route('**/*', request => request.abort()); await page.route('**/*', route => route.abort());
let error = null; let error = null;
await page.goto(server.EMPTY_PAGE).catch(e => error = e); await page.goto(server.EMPTY_PAGE).catch(e => error = e);
expect(error).toBeTruthy(); expect(error).toBeTruthy();
@ -190,9 +191,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
}); });
it('should work with redirects', async({page, server}) => { it('should work with redirects', async({page, server}) => {
const requests = []; const requests = [];
await page.route('**/*', request => { await page.route('**/*', route => {
request.continue(); route.continue();
requests.push(request); requests.push(route.request());
}); });
server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html'); 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-2.html', '/non-existing-page-3.html');
@ -216,9 +217,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
}); });
it('should work with redirects for subresources', async({page, server}) => { it('should work with redirects for subresources', async({page, server}) => {
const requests = []; const requests = [];
await page.route('**/*', request => { await page.route('**/*', route => {
request.continue(); route.continue();
requests.push(request); requests.push(route.request());
}); });
server.setRedirect('/one-style.css', '/two-style.css'); server.setRedirect('/one-style.css', '/two-style.css');
server.setRedirect('/two-style.css', '/three-style.css'); server.setRedirect('/two-style.css', '/three-style.css');
@ -244,8 +245,8 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
let spinner = false; let spinner = false;
// Cancel 2nd request. // Cancel 2nd request.
await page.route('**/*', request => { await page.route('**/*', route => {
spinner ? request.abort() : request.continue(); spinner ? route.abort() : route.continue();
spinner = !spinner; spinner = !spinner;
}); });
const results = await page.evaluate(() => Promise.all([ const results = await page.evaluate(() => Promise.all([
@ -257,9 +258,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
}); });
it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => { it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => {
const requests = []; const requests = [];
await page.route('**/*', request => { await page.route('**/*', route => {
requests.push(request); requests.push(route.request());
request.continue(); route.continue();
}); });
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
const response = await page.goto(dataURL); const response = await page.goto(dataURL);
@ -269,9 +270,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
it('should be able to fetch dataURL and not fire dataURL requests', async({page, server}) => { it('should be able to fetch dataURL and not fire dataURL requests', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const requests = []; const requests = [];
await page.route('**/*', request => { await page.route('**/*', route => {
requests.push(request); requests.push(route.request());
request.continue(); route.continue();
}); });
const dataURL = 'data:text/html,<div>yo</div>'; const dataURL = 'data:text/html,<div>yo</div>';
const text = await page.evaluate(url => fetch(url).then(r => r.text()), dataURL); const text = await page.evaluate(url => fetch(url).then(r => r.text()), dataURL);
@ -280,9 +281,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
}); });
it('should navigate to URL with hash and and fire requests without hash', async({page, server}) => { it('should navigate to URL with hash and and fire requests without hash', async({page, server}) => {
const requests = []; const requests = [];
await page.route('**/*', request => { await page.route('**/*', route => {
requests.push(request); requests.push(route.request());
request.continue(); route.continue();
}); });
const response = await page.goto(server.EMPTY_PAGE + '#hash'); const response = await page.goto(server.EMPTY_PAGE + '#hash');
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
@ -293,13 +294,13 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
it('should work with encoded server', async({page, server}) => { it('should work with encoded server', async({page, server}) => {
// The requestWillBeSent will report encoded URL, whereas interception will // The requestWillBeSent will report encoded URL, whereas interception will
// report URL as-is. @see crbug.com/759388 // report URL as-is. @see crbug.com/759388
await page.route('**/*', request => request.continue()); await page.route('**/*', route => route.continue());
const response = await page.goto(server.PREFIX + '/some nonexisting page'); const response = await page.goto(server.PREFIX + '/some nonexisting page');
expect(response.status()).toBe(404); expect(response.status()).toBe(404);
}); });
it('should work with badly encoded server', async({page, server}) => { it('should work with badly encoded server', async({page, server}) => {
server.setRoute('/malformed?rnd=%911', (req, res) => res.end()); server.setRoute('/malformed?rnd=%911', (req, res) => res.end());
await page.route('**/*', request => request.continue()); await page.route('**/*', route => route.continue());
const response = await page.goto(server.PREFIX + '/malformed?rnd=%911'); const response = await page.goto(server.PREFIX + '/malformed?rnd=%911');
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
@ -307,9 +308,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
// The requestWillBeSent will report URL as-is, whereas interception will // The requestWillBeSent will report URL as-is, whereas interception will
// report encoded URL for stylesheet. @see crbug.com/759388 // report encoded URL for stylesheet. @see crbug.com/759388
const requests = []; const requests = [];
await page.route('**/*', request => { await page.route('**/*', route => {
request.continue(); route.continue();
requests.push(request); requests.push(route.request());
}); });
const response = await page.goto(`data:text/html,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>`); const response = await page.goto(`data:text/html,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>`);
expect(response).toBe(null); expect(response).toBe(null);
@ -318,75 +319,40 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
}); });
it('should not throw "Invalid Interception Id" if the request was cancelled', async({page, server}) => { it('should not throw "Invalid Interception Id" if the request was cancelled', async({page, server}) => {
await page.setContent('<iframe></iframe>'); await page.setContent('<iframe></iframe>');
let request = null; let route = null;
await page.route('**/*', async r => request = r); await page.route('**/*', async r => route = r);
page.$eval('iframe', (frame, url) => frame.src = url, server.EMPTY_PAGE), page.$eval('iframe', (frame, url) => frame.src = url, server.EMPTY_PAGE),
// Wait for request interception. // Wait for request interception.
await utils.waitEvent(page, 'request'); await utils.waitEvent(page, 'request');
// Delete frame to cause request to be canceled. // Delete frame to cause request to be canceled.
await page.$eval('iframe', frame => frame.remove()); await page.$eval('iframe', frame => frame.remove());
let error = null; let error = null;
await request.continue().catch(e => error = e); await route.continue().catch(e => error = e);
expect(error).toBe(null); expect(error).toBe(null);
}); });
it('should throw if interception is not enabled', async({browser, server}) => {
let error = null;
const context = await browser.newContext();
const page = await context.newPage();
page.on('request', async request => {
try {
await request.continue();
} catch (e) {
error = e;
}
});
await page.goto(server.EMPTY_PAGE);
expect(error.message).toContain('Request Interception is not enabled');
await context.close();
});
it('should intercept main resource during cross-process navigation', async({page, server}) => { it('should intercept main resource during cross-process navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
let intercepted = false; let intercepted = false;
await page.route(server.CROSS_PROCESS_PREFIX + '/empty.html', request => { await page.route(server.CROSS_PROCESS_PREFIX + '/empty.html', route => {
intercepted = true; intercepted = true;
request.continue(); route.continue();
}); });
const response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); const response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(intercepted).toBe(true); expect(intercepted).toBe(true);
}); });
it('should not throw when continued after navigation', async({page, server}) => {
await page.route(server.PREFIX + '/one-style.css', () => {});
// For some reason, Firefox issues load event with one outstanding request.
const firstNavigation = page.goto(server.PREFIX + '/one-style.html', { waitUntil: FFOX ? 'networkidle0' : 'load' }).catch(e => e);
const request = await page.waitForRequest(server.PREFIX + '/one-style.css');
await page.goto(server.PREFIX + '/empty.html');
await firstNavigation;
const notAnError = await request.continue().then(() => null).catch(e => e);
expect(notAnError).toBe(null);
});
it('should not throw when continued after cross-process navigation', async({page, server}) => {
await page.route(server.PREFIX + '/one-style.css', () => {});
// For some reason, Firefox issues load event with one outstanding request.
const firstNavigation = page.goto(server.PREFIX + '/one-style.html', { waitUntil: FFOX ? 'networkidle0' : 'load' }).catch(e => e);
const request = await page.waitForRequest(server.PREFIX + '/one-style.css');
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
await firstNavigation;
const notAnError = await request.continue().then(() => null).catch(e => e);
expect(notAnError).toBe(null);
});
}); });
describe('Request.continue', function() { describe('Request.continue', function() {
it('should work', async({page, server}) => { it('should work', async({page, server}) => {
await page.route('**/*', request => request.continue()); await page.route('**/*', route => route.continue());
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
}); });
it('should amend HTTP headers', async({page, server}) => { it('should amend HTTP headers', async({page, server}) => {
await page.route('**/*', request => { await page.route('**/*', route => {
const headers = Object.assign({}, request.headers()); const headers = Object.assign({}, route.request().headers());
headers['FOO'] = 'bar'; headers['FOO'] = 'bar';
request.continue({ headers }); route.continue({ headers });
}); });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([ const [request] = await Promise.all([
@ -398,7 +364,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
it('should amend method', async({page, server}) => { it('should amend method', async({page, server}) => {
const sRequest = server.waitForRequest('/sleep.zzz'); const sRequest = server.waitForRequest('/sleep.zzz');
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.route('**/*', request => request.continue({ method: 'POST' })); await page.route('**/*', route => route.continue({ method: 'POST' }));
const [request] = await Promise.all([ const [request] = await Promise.all([
server.waitForRequest('/sleep.zzz'), server.waitForRequest('/sleep.zzz'),
page.evaluate(() => fetch('/sleep.zzz')) page.evaluate(() => fetch('/sleep.zzz'))
@ -408,14 +374,14 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
}); });
it('should amend method on main request', async({page, server}) => { it('should amend method on main request', async({page, server}) => {
const request = server.waitForRequest('/empty.html'); const request = server.waitForRequest('/empty.html');
await page.route('**/*', request => request.continue({ method: 'POST' })); await page.route('**/*', route => route.continue({ method: 'POST' }));
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect((await request).method).toBe('POST'); expect((await request).method).toBe('POST');
}); });
it('should amend post data', async({page, server}) => { it('should amend post data', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.route('**/*', request => { await page.route('**/*', route => {
request.continue({ postData: 'doggo' }); route.continue({ postData: 'doggo' });
}); });
const [serverRequest] = await Promise.all([ const [serverRequest] = await Promise.all([
server.waitForRequest('/sleep.zzz'), server.waitForRequest('/sleep.zzz'),
@ -427,8 +393,8 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Request.fulfill', function() { describe('Request.fulfill', function() {
it('should work', async({page, server}) => { it('should work', async({page, server}) => {
await page.route('**/*', request => { await page.route('**/*', route => {
request.fulfill({ route.fulfill({
status: 201, status: 201,
headers: { headers: {
foo: 'bar' foo: 'bar'
@ -443,8 +409,8 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!');
}); });
it('should work with status code 422', async({page, server}) => { it('should work with status code 422', async({page, server}) => {
await page.route('**/*', request => { await page.route('**/*', route => {
request.fulfill({ route.fulfill({
status: 422, status: 422,
body: 'Yo, page!' body: 'Yo, page!'
}); });
@ -455,9 +421,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!');
}); });
it('should allow mocking binary responses', async({page, server}) => { it('should allow mocking binary responses', async({page, server}) => {
await page.route('**/*', request => { await page.route('**/*', route => {
const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png')); const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png'));
request.fulfill({ route.fulfill({
contentType: 'image/png', contentType: 'image/png',
body: imageBuffer body: imageBuffer
}); });
@ -472,7 +438,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); expect(await img.screenshot()).toBeGolden('mock-binary-response.png');
}); });
it('should work with file path', async({page, server}) => { it('should work with file path', async({page, server}) => {
await page.route('**/*', request => request.fulfill({ contentType: 'shouldBeIgnored', path: path.join(__dirname, 'assets', 'pptr.png') })); await page.route('**/*', route => route.fulfill({ contentType: 'shouldBeIgnored', path: path.join(__dirname, 'assets', 'pptr.png') }));
await page.evaluate(PREFIX => { await page.evaluate(PREFIX => {
const img = document.createElement('img'); const img = document.createElement('img');
img.src = PREFIX + '/does-not-exist.png'; img.src = PREFIX + '/does-not-exist.png';
@ -483,8 +449,8 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); expect(await img.screenshot()).toBeGolden('mock-binary-response.png');
}); });
it('should stringify intercepted request response headers', async({page, server}) => { it('should stringify intercepted request response headers', async({page, server}) => {
await page.route('**/*', request => { await page.route('**/*', route => {
request.fulfill({ route.fulfill({
status: 200, status: 200,
headers: { headers: {
'foo': true 'foo': true
@ -503,9 +469,9 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Interception vs isNavigationRequest', () => { describe('Interception vs isNavigationRequest', () => {
it('should work with request interception', async({page, server}) => { it('should work with request interception', async({page, server}) => {
const requests = new Map(); const requests = new Map();
await page.route('**/*', request => { await page.route('**/*', route => {
requests.set(request.url().split('/').pop(), request); requests.set(route.request().url().split('/').pop(), route.request());
request.continue(); route.continue();
}); });
server.setRedirect('/rrredirect', '/frames/one-frame.html'); server.setRedirect('/rrredirect', '/frames/one-frame.html');
await page.goto(server.PREFIX + '/rrredirect'); await page.goto(server.PREFIX + '/rrredirect');
@ -522,7 +488,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const context = await browser.newContext({ ignoreHTTPSErrors: true }); const context = await browser.newContext({ ignoreHTTPSErrors: true });
const page = await context.newPage(); const page = await context.newPage();
await page.route('**/*', request => request.continue()); await page.route('**/*', route => route.continue());
const response = await page.goto(httpsServer.EMPTY_PAGE); const response = await page.goto(httpsServer.EMPTY_PAGE);
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
await context.close(); await context.close();
@ -538,10 +504,10 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const swResponse = await page.evaluate(() => fetchDummy('foo')); const swResponse = await page.evaluate(() => fetchDummy('foo'));
expect(swResponse).toBe('responseFromServiceWorker:foo'); expect(swResponse).toBe('responseFromServiceWorker:foo');
await page.route('**/foo', request => { await page.route('**/foo', route => {
const slash = request.url().lastIndexOf('/'); const slash = route.request().url().lastIndexOf('/');
const name = request.url().substring(slash + 1); const name = route.request().url().substring(slash + 1);
request.fulfill({ route.fulfill({
status: 200, status: 200,
contentType: 'text/css', contentType: 'text/css',
body: 'responseFromInterception:' + name body: 'responseFromInterception:' + name
@ -581,8 +547,10 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
it('should work with regular expression passed from a different context', async({page, server}) => { it('should work with regular expression passed from a different context', async({page, server}) => {
const ctx = vm.createContext(); const ctx = vm.createContext();
const regexp = vm.runInContext('new RegExp("empty\\.html")', ctx); const regexp = vm.runInContext('new RegExp("empty\\.html")', ctx);
let intercepted = false;
await page.route(regexp, request => { await page.route(regexp, (route, request) => {
expect(route.request()).toBe(request);
expect(request.url()).toContain('empty.html'); expect(request.url()).toContain('empty.html');
expect(request.headers()['user-agent']).toBeTruthy(); expect(request.headers()['user-agent']).toBeTruthy();
expect(request.method()).toBe('GET'); expect(request.method()).toBe('GET');
@ -591,11 +559,13 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(request.resourceType()).toBe('document'); expect(request.resourceType()).toBe('document');
expect(request.frame() === page.mainFrame()).toBe(true); expect(request.frame() === page.mainFrame()).toBe(true);
expect(request.frame().url()).toBe('about:blank'); expect(request.frame().url()).toBe('about:blank');
request.continue(); route.continue();
intercepted = true;
}); });
const response = await page.goto(server.EMPTY_PAGE); const response = await page.goto(server.EMPTY_PAGE);
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
expect(intercepted).toBe(true);
}); });
}); });
}; };

View file

@ -44,8 +44,8 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="empty.html">link</a>'); await page.setContent('<a target=_blank rel=noopener href="empty.html">link</a>');
let intercepted = false; let intercepted = false;
await context.route('**/empty.html', request => { await context.route('**/empty.html', route => {
request.continue(); route.continue();
intercepted = true; intercepted = true;
}); });
const [popup] = await Promise.all([ const [popup] = await Promise.all([
@ -143,8 +143,8 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
let intercepted = false; let intercepted = false;
await context.route('**/empty.html', request => { await context.route('**/empty.html', route => {
request.continue(); route.continue();
intercepted = true; intercepted = true;
}); });
await page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE); await page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE);