diff --git a/docs/api.md b/docs/api.md index 96a650f1db..c4cd39f24d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -305,6 +305,7 @@ await context.close(); - [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation) - [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials) - [browserContext.setOffline(offline)](#browsercontextsetofflineoffline) +- [browserContext.unroute(url[, handler])](#browsercontextunrouteurl-handler) - [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate) @@ -576,6 +577,13 @@ To disable authentication, pass `null`. - `offline` <[boolean]> Whether to emulate network being offline for the browser context. - returns: <[Promise]> +#### browserContext.unroute(url[, handler]) +- `url` <[string]|[RegExp]|[function]\([string]\):[boolean]> A glob pattern, regex pattern or predicate receiving [URL] to match while routing. +- `handler` <[function]\([Route], [Request]\)> Handler function to route the request. +- returns: <[Promise]> + +Removes a route created with [browserContext.route(url, handler)](#browsercontextrouteurl-handler). When `handler` is not specified, removes all routes for the `url`. + #### browserContext.waitForEvent(event[, optionsOrPredicate]) - `event` <[string]> Event name, same one would pass into `browserContext.on(event)`. - `optionsOrPredicate` <[Function]|[Object]> Either a predicate that receives an event or an options object. @@ -694,6 +702,7 @@ page.removeListener('request', logRequest); - [page.title()](#pagetitle) - [page.type(selector, text[, options])](#pagetypeselector-text-options) - [page.uncheck(selector, [options])](#pageuncheckselector-options) +- [page.unroute(url[, handler])](#pageunrouteurl-handler) - [page.url()](#pageurl) - [page.viewportSize()](#pageviewportsize) - [page.waitFor(selectorOrFunctionOrTimeout[, options[, arg]])](#pagewaitforselectororfunctionortimeout-options-arg) @@ -1654,6 +1663,13 @@ If there's no element matching `selector`, the method throws an error. Shortcut for [page.mainFrame().uncheck(selector[, options])](#frameuncheckselector-options). +#### page.unroute(url[, handler]) +- `url` <[string]|[RegExp]|[function]\([string]\):[boolean]> A glob pattern, regex pattern or predicate receiving [URL] to match while routing. +- `handler` <[function]\([Route], [Request]\)> Handler function to route the request. +- returns: <[Promise]> + +Removes a route created with [page.route(url, handler)](#pagerouteurl-handler). When `handler` is not specified, removes all routes for the `url`. + #### page.url() - returns: <[string]> @@ -3976,6 +3992,7 @@ const backgroundPage = await context.waitForEvent('backgroundpage'); - [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation) - [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials) - [browserContext.setOffline(offline)](#browsercontextsetofflineoffline) +- [browserContext.unroute(url[, handler])](#browsercontextunrouteurl-handler) - [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate) diff --git a/src/browserContext.ts b/src/browserContext.ts index 1d00f40263..8bbadb21a4 100644 --- a/src/browserContext.ts +++ b/src/browserContext.ts @@ -61,6 +61,7 @@ export interface BrowserContext { addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise; exposeFunction(name: string, playwrightFunction: Function): Promise; route(url: types.URLMatch, handler: network.RouteHandler): Promise; + unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise; waitForEvent(event: string, optionsOrPredicate?: Function | (types.TimeoutOptions & { predicate?: Function })): Promise; close(): Promise; } @@ -69,7 +70,7 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements readonly _timeoutSettings = new TimeoutSettings(); readonly _pageBindings = new Map(); readonly _options: BrowserContextOptions; - readonly _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = []; + _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = []; _closed = false; private readonly _closePromise: Promise; private _closePromiseFulfill: ((error: Error) => void) | undefined; @@ -120,6 +121,7 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements abstract addInitScript(script: string | Function | { path?: string | undefined; content?: string | undefined; }, arg?: any): Promise; abstract exposeFunction(name: string, playwrightFunction: Function): Promise; abstract route(url: types.URLMatch, handler: network.RouteHandler): Promise; + abstract unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise; abstract close(): Promise; async grantPermissions(permissions: string[], options?: { origin?: string }) { diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index 9a104b9925..e59e4f224a 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -422,6 +422,12 @@ export class CRBrowserContext extends BrowserContextBase { await (page._delegate as CRPage).updateRequestInterception(); } + async unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise { + this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler)); + for (const page of this.pages()) + await (page._delegate as CRPage).updateRequestInterception(); + } + async close() { if (this._closed) return; diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index 33eeebd56d..c77f59e86d 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -313,6 +313,12 @@ export class FFBrowserContext extends BrowserContextBase { await this._browser._connection.send('Browser.setRequestInterception', { browserContextId: this._browserContextId || undefined, enabled: true }); } + async unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise { + this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler)); + if (this._routes.length === 0) + await this._browser._connection.send('Browser.setRequestInterception', { browserContextId: this._browserContextId || undefined, enabled: false }); + } + async close() { if (this._closed) return; diff --git a/src/page.ts b/src/page.ts index 8dc74b867f..d2870da2e0 100644 --- a/src/page.ts +++ b/src/page.ts @@ -108,7 +108,7 @@ export class Page extends ExtendedEventEmitter { private _workers = new Map(); readonly pdf: ((options?: types.PDFOptions) => Promise) | undefined; readonly coverage: any; - readonly _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = []; + _routes: { url: types.URLMatch, handler: network.RouteHandler }[] = []; _ownedContext: BrowserContext | undefined; constructor(delegate: PageDelegate, browserContext: BrowserContextBase) { @@ -385,6 +385,11 @@ export class Page extends ExtendedEventEmitter { await this._delegate.updateRequestInterception(); } + async unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise { + this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler)); + await this._delegate.updateRequestInterception(); + } + _requestStarted(request: network.Request) { this.emit(Events.Page.Request, request); const route = request._route(); diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index cf4fa01caf..a3a6e7c807 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -315,6 +315,12 @@ export class WKBrowserContext extends BrowserContextBase { await (page._delegate as WKPage).updateRequestInterception(); } + async unroute(url: types.URLMatch, handler?: network.RouteHandler): Promise { + this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler)); + for (const page of this.pages()) + await (page._delegate as WKPage).updateRequestInterception(); + } + async close() { if (this._closed) return; diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js index 31bd882836..1364bca2eb 100644 --- a/test/browsercontext.spec.js +++ b/test/browsercontext.spec.js @@ -383,6 +383,43 @@ describe('BrowserContext.route', () => { expect(intercepted).toBe(true); await context.close(); }); + it('should unroute', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + + let intercepted = []; + const handler1 = route => { + intercepted.push(1); + route.continue(); + }; + await context.route('**/empty.html', handler1); + await context.route('**/empty.html', route => { + intercepted.push(2); + route.continue(); + }); + await context.route('**/empty.html', route => { + intercepted.push(3); + route.continue(); + }); + await context.route('**/*', route => { + intercepted.push(4); + route.continue(); + }); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([1]); + + intercepted = []; + await context.unroute('**/empty.html', handler1); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([2]); + + intercepted = []; + await context.unroute('**/empty.html'); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([4]); + + await context.close(); + }); it('should yield to page.route', async({browser, server}) => { const context = await browser.newContext(); await context.route('**/empty.html', route => { diff --git a/test/interception.spec.js b/test/interception.spec.js index 4e9303fb46..614d565a3d 100644 --- a/test/interception.spec.js +++ b/test/interception.spec.js @@ -41,6 +41,38 @@ describe('Page.route', function() { expect(response.ok()).toBe(true); expect(intercepted).toBe(true); }); + it('should unroute', async({page, server}) => { + let intercepted = []; + const handler1 = route => { + intercepted.push(1); + route.continue(); + }; + await page.route('**/empty.html', handler1); + await page.route('**/empty.html', route => { + intercepted.push(2); + route.continue(); + }); + await page.route('**/empty.html', route => { + intercepted.push(3); + route.continue(); + }); + await page.route('**/*', route => { + intercepted.push(4); + route.continue(); + }); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([1]); + + intercepted = []; + await page.unroute('**/empty.html', handler1); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([2]); + + intercepted = []; + await page.unroute('**/empty.html'); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual([4]); + }); it('should work when POST is redirected with 302', async({page, server}) => { server.setRedirect('/rredirect', '/empty.html'); await page.goto(server.EMPTY_PAGE); @@ -346,7 +378,7 @@ describe('Page.route', function() { status: 301, headers: { 'location': '/empty.html', - } + } }); });