feat(routes): add support for the times option (#8399)

This commit is contained in:
Max Schmitt 2021-08-24 20:45:50 +02:00 коммит произвёл GitHub
Родитель 59422a00f5
Коммит 8e20f13079
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 130 добавлений и 55 удалений

Просмотреть файл

@ -1021,6 +1021,11 @@ handler function to route the request.
handler function to route the request.
### option: BrowserContext.route.times
- `times` <[int]>
How often a route should be used. By default it will be used every time.
## method: BrowserContext.serviceWorkers
* langs: js, python
- returns: <[Array]<[Worker]>>

Просмотреть файл

@ -2561,6 +2561,11 @@ handler function to route the request.
handler function to route the request.
### option: Page.route.times
- `times` <[int]>
How often a route should be used. By default it will be used every time.
## async method: Page.screenshot
- returns: <[Buffer]>

Просмотреть файл

@ -21,7 +21,7 @@ import * as network from './network';
import * as channels from '../protocol/channels';
import fs from 'fs';
import { ChannelOwner } from './channelOwner';
import { deprecate, evaluationScript, urlMatches } from './clientHelper';
import { deprecate, evaluationScript } from './clientHelper';
import { Browser } from './browser';
import { Worker } from './worker';
import { Events } from './events';
@ -38,7 +38,7 @@ import type { BrowserType } from './browserType';
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel, channels.BrowserContextInitializer> implements api.BrowserContext {
_pages = new Set<Page>();
private _routes: { url: URLMatch, handler: network.RouteHandler }[] = [];
private _routes: network.RouteHandler[] = [];
readonly _browser: Browser | null = null;
private _browserType: BrowserType | undefined;
readonly _bindings = new Map<string, (source: structs.BindingSource, ...args: any[]) => any>();
@ -132,9 +132,9 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
}
_onRoute(route: network.Route, request: network.Request) {
for (const {url, handler} of this._routes) {
if (urlMatches(this._options.baseURL, request.url(), url)) {
handler(route, request);
for (const routeHandler of this._routes) {
if (routeHandler.matches(request.url())) {
routeHandler.handle(route, request);
return;
}
}
@ -258,15 +258,15 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
});
}
async route(url: URLMatch, handler: network.RouteHandler): Promise<void> {
async route(url: URLMatch, handler: network.RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
this._routes.unshift({ url, handler });
this._routes.unshift(new network.RouteHandler(this._options.baseURL, url, handler, options.times));
if (this._routes.length === 1)
await channel.setNetworkInterceptionEnabled({ enabled: true });
});
}
async unroute(url: URLMatch, handler?: network.RouteHandler): Promise<void> {
async unroute(url: URLMatch, handler?: network.RouteHandlerCallback): Promise<void> {
return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
if (this._routes.length === 0)

Просмотреть файл

@ -26,6 +26,8 @@ import { Events } from './events';
import { Page } from './page';
import { Waiter } from './waiter';
import * as api from '../../types/types';
import { URLMatch } from '../common/types';
import { urlMatches } from './clientHelper';
export type NetworkCookie = {
name: string,
@ -352,7 +354,7 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
}
}
export type RouteHandler = (route: Route, request: Request) => void;
export type RouteHandlerCallback = (route: Route, request: Request) => void;
export type ResourceTiming = {
startTime: number;
@ -516,3 +518,29 @@ export function validateHeaders(headers: Headers) {
throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
}
}
export class RouteHandler {
private handledCount = 0;
private readonly _baseURL: string | undefined;
private readonly _times: number | undefined;
readonly url: URLMatch;
readonly handler: RouteHandlerCallback;
constructor(baseURL: string | undefined, url: URLMatch, handler: RouteHandlerCallback, times?: number) {
this._baseURL = baseURL;
this._times = times;
this.url = url;
this.handler = handler;
}
public matches(requestURL: string): boolean {
if (this._times && this.handledCount >= this._times)
return false;
return urlMatches(this._baseURL, requestURL, this.url);
}
public handle(route: Route, request: Request) {
this.handler(route, request);
this.handledCount++;
}
}

Просмотреть файл

@ -32,7 +32,7 @@ import { Worker } from './worker';
import { Frame, verifyLoadState, WaitForNavigationOptions } from './frame';
import { Keyboard, Mouse, Touchscreen } from './input';
import { assertMaxArguments, serializeArgument, parseResult, JSHandle } from './jsHandle';
import { Request, Response, Route, RouteHandler, WebSocket, validateHeaders } from './network';
import { Request, Response, Route, RouteHandlerCallback, WebSocket, validateHeaders, RouteHandler } from './network';
import { FileChooser } from './fileChooser';
import { Buffer } from 'buffer';
import { Coverage } from './coverage';
@ -71,7 +71,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
private _closed = false;
_closedOrCrashedPromise: Promise<void>;
private _viewportSize: Size | null;
private _routes: { url: URLMatch, handler: RouteHandler }[] = [];
private _routes: RouteHandler[] = [];
readonly accessibility: Accessibility;
readonly coverage: Coverage;
@ -161,9 +161,9 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
}
private _onRoute(route: Route, request: Request) {
for (const {url, handler} of this._routes) {
if (urlMatches(this._browserContext._options.baseURL, request.url(), url)) {
handler(route, request);
for (const routeHandler of this._routes) {
if (routeHandler.matches(request.url())) {
routeHandler.handle(route, request);
return;
}
}
@ -443,15 +443,15 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
});
}
async route(url: URLMatch, handler: RouteHandler): Promise<void> {
async route(url: URLMatch, handler: RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
return this._wrapApiCall(async (channel: channels.PageChannel) => {
this._routes.unshift({ url, handler });
this._routes.unshift(new RouteHandler(this._browserContext._options.baseURL, url, handler, options.times));
if (this._routes.length === 1)
await channel.setNetworkInterceptionEnabled({ enabled: true });
});
}
async unroute(url: URLMatch, handler?: RouteHandler): Promise<void> {
async unroute(url: URLMatch, handler?: RouteHandlerCallback): Promise<void> {
return this._wrapApiCall(async (channel: channels.PageChannel) => {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
if (this._routes.length === 0)

Просмотреть файл

@ -198,3 +198,15 @@ it('should work with ignoreHTTPSErrors', async ({browser, httpsServer}) => {
expect(response.status()).toBe(200);
await context.close();
});
it('should support the times parameter with route matching', async ({context, page, server}) => {
const intercepted = [];
await context.route('**/empty.html', route => {
intercepted.push(1);
route.continue();
}, { times: 1});
await page.goto(server.EMPTY_PAGE);
await page.goto(server.EMPTY_PAGE);
await page.goto(server.EMPTY_PAGE);
expect(intercepted).toHaveLength(1);
});

Просмотреть файл

@ -652,3 +652,15 @@ it('should support cors for different methods', async ({page, server}) => {
expect(resp).toEqual(['DELETE', 'electric', 'gas']);
}
});
it('should support the times parameter with route matching', async ({page, server}) => {
const intercepted = [];
await page.route('**/empty.html', route => {
intercepted.push(1);
route.continue();
}, { times: 1});
await page.goto(server.EMPTY_PAGE);
await page.goto(server.EMPTY_PAGE);
await page.goto(server.EMPTY_PAGE);
expect(intercepted).toHaveLength(1);
});

2
types/test.d.ts поставляемый
Просмотреть файл

@ -2508,7 +2508,7 @@ export interface PlaywrightTestOptions {
viewport: ViewportSize | null | undefined;
/**
* When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto),
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route),
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route),
* [page.waitForURL(url[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-url),
* [page.waitForRequest(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-request), or
* [page.waitForResponse(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-response) it

87
types/types.d.ts поставляемый
Просмотреть файл

@ -507,8 +507,8 @@ export interface Page {
/**
* Emitted when a page issues a request. The [request] object is read-only. In order to intercept and mutate requests, see
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route) or
* [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route) or
* [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
*/
on(event: 'request', listener: (request: Request) => void): this;
@ -778,8 +778,8 @@ export interface Page {
/**
* Emitted when a page issues a request. The [request] object is read-only. In order to intercept and mutate requests, see
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route) or
* [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route) or
* [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
*/
addListener(event: 'request', listener: (request: Request) => void): this;
@ -2339,9 +2339,9 @@ export interface Page {
* Once routing is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
*
* > NOTE: The handler will only be called for the first url if the response is a redirect.
* > NOTE: [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route) will not intercept requests
* intercepted by Service Worker. See [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend
* disabling Service Workers when using request interception. Via `await context.addInitScript(() => delete
* > NOTE: [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route) will not intercept
* requests intercepted by Service Worker. See [this](https://github.com/microsoft/playwright/issues/1090) issue. We
* recommend disabling Service Workers when using request interception. Via `await context.addInitScript(() => delete
* window.navigator.serviceWorker);`
*
* An example of a naive handler that aborts all image requests:
@ -2375,8 +2375,8 @@ export interface Page {
* ```
*
* Page routes take precedence over browser context routes (set up with
* [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route)) when
* request matches both handlers.
* [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route))
* when request matches both handlers.
*
* To remove a route with its handler you can use
* [page.unroute(url[, handler])](https://playwright.dev/docs/api/class-page#page-unroute).
@ -2385,8 +2385,14 @@ export interface Page {
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a `baseURL` via the context options was provided and the passed URL is a path, it gets merged via the
* [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
* @param handler handler function to route the request.
* @param options
*/
route(url: string|RegExp|((url: URL) => boolean), handler: ((route: Route, request: Request) => void)): Promise<void>;
route(url: string|RegExp|((url: URL) => boolean), handler: ((route: Route, request: Request) => void), options?: {
/**
* How often a route should be used. By default it will be used every time.
*/
times?: number;
}): Promise<void>;
/**
* Returns the buffer with the captured screenshot.
@ -2853,8 +2859,9 @@ export interface Page {
}): Promise<void>;
/**
* Removes a route created with [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route). When
* `handler` is not specified, removes all routes for the `url`.
* Removes a route created with
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route). When `handler` is not
* specified, removes all routes for the `url`.
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
* @param handler Optional handler function to route the request.
*/
@ -3019,8 +3026,8 @@ export interface Page {
/**
* Emitted when a page issues a request. The [request] object is read-only. In order to intercept and mutate requests, see
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route) or
* [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route) or
* [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
*/
waitForEvent(event: 'request', optionsOrPredicate?: { predicate?: (request: Request) => boolean | Promise<boolean>, timeout?: number } | ((request: Request) => boolean | Promise<boolean>)): Promise<Request>;
@ -5029,8 +5036,8 @@ export interface BrowserContext {
* [page.on('request')](https://playwright.dev/docs/api/class-page#page-event-request).
*
* In order to intercept and mutate requests, see
* [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) or
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route).
* [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route)
* or [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route).
*/
on(event: 'request', listener: (request: Request) => void): this;
@ -5156,8 +5163,8 @@ export interface BrowserContext {
* [page.on('request')](https://playwright.dev/docs/api/class-page#page-event-request).
*
* In order to intercept and mutate requests, see
* [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) or
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route).
* [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route)
* or [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route).
*/
addListener(event: 'request', listener: (request: Request) => void): this;
@ -5500,9 +5507,9 @@ export interface BrowserContext {
* 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.
*
* > NOTE: [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route) will not intercept requests
* intercepted by Service Worker. See [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend
* disabling Service Workers when using request interception. Via `await context.addInitScript(() => delete
* > NOTE: [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route) will not intercept
* requests intercepted by Service Worker. See [this](https://github.com/microsoft/playwright/issues/1090) issue. We
* recommend disabling Service Workers when using request interception. Via `await context.addInitScript(() => delete
* window.navigator.serviceWorker);`
*
* An example of a naive handler that aborts all image requests:
@ -5537,8 +5544,8 @@ export interface BrowserContext {
* });
* ```
*
* Page routes (set up with [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route)) take
* precedence over browser context routes when request matches both handlers.
* Page routes (set up with [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route))
* take precedence over browser context routes when request matches both handlers.
*
* To remove a route with its handler you can use
* [browserContext.unroute(url[, handler])](https://playwright.dev/docs/api/class-browsercontext#browser-context-unroute).
@ -5547,8 +5554,14 @@ export interface BrowserContext {
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a `baseURL` via the context options was provided and the passed URL is a path, it gets merged via the
* [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
* @param handler handler function to route the request.
* @param options
*/
route(url: string|RegExp|((url: URL) => boolean), handler: ((route: Route, request: Request) => void)): Promise<void>;
route(url: string|RegExp|((url: URL) => boolean), handler: ((route: Route, request: Request) => void), options?: {
/**
* How often a route should be used. By default it will be used every time.
*/
times?: number;
}): Promise<void>;
/**
* > NOTE: Service workers are only supported on Chromium-based browsers.
@ -5693,10 +5706,10 @@ export interface BrowserContext {
/**
* Removes a route created with
* [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route). When
* `handler` is not specified, removes all routes for the `url`.
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
* @param handler Optional handler function used to register a routing with [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
* [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
* When `handler` is not specified, removes all routes for the `url`.
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
* @param handler Optional handler function used to register a routing with [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route).
*/
unroute(url: string|RegExp|((url: URL) => boolean), handler?: ((route: Route, request: Request) => void)): Promise<void>;
@ -5749,8 +5762,8 @@ export interface BrowserContext {
* [page.on('request')](https://playwright.dev/docs/api/class-page#page-event-request).
*
* In order to intercept and mutate requests, see
* [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) or
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route).
* [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route)
* or [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route).
*/
waitForEvent(event: 'request', optionsOrPredicate?: { predicate?: (request: Request) => boolean | Promise<boolean>, timeout?: number } | ((request: Request) => boolean | Promise<boolean>)): Promise<Request>;
@ -8250,7 +8263,7 @@ export interface BrowserType<Unused = {}> {
/**
* When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto),
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route),
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route),
* [page.waitForURL(url[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-url),
* [page.waitForRequest(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-request), or
* [page.waitForResponse(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-response) it
@ -9430,7 +9443,7 @@ export interface AndroidDevice {
/**
* When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto),
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route),
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route),
* [page.waitForURL(url[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-url),
* [page.waitForRequest(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-request), or
* [page.waitForResponse(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-response) it
@ -10202,7 +10215,7 @@ export interface Browser extends EventEmitter {
/**
* When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto),
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route),
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route),
* [page.waitForURL(url[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-url),
* [page.waitForRequest(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-request), or
* [page.waitForResponse(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-response) it
@ -11838,9 +11851,9 @@ export interface Response {
/**
* Whenever a network route is set up with
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route) or
* [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browser-context-route), the
* `Route` object allows to handle the route.
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route) or
* [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route),
* the `Route` object allows to handle the route.
*/
export interface Route {
/**
@ -12357,7 +12370,7 @@ export interface BrowserContextOptions {
/**
* When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto),
* [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route),
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route),
* [page.waitForURL(url[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-url),
* [page.waitForRequest(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-request), or
* [page.waitForResponse(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-response) it