feat: page.unrouteAll and context.unrouteAll (#28635)
Reference https://github.com/microsoft/playwright/issues/23781
This commit is contained in:
Родитель
d9ab83c5f7
Коммит
f28ceffa37
|
@ -1415,6 +1415,14 @@ Returns storage state for this browser context, contains current cookies and loc
|
|||
* since: v1.12
|
||||
- type: <[Tracing]>
|
||||
|
||||
## async method: BrowserContext.unrouteAll
|
||||
* since: v1.41
|
||||
|
||||
Removes all routes created with [`method: BrowserContext.route`] and [`method: BrowserContext.routeFromHAR`].
|
||||
|
||||
### option: BrowserContext.unrouteAll.behavior = %%-unroute-all-options-behavior-%%
|
||||
* since: v1.41
|
||||
|
||||
## async method: BrowserContext.unroute
|
||||
* since: v1.8
|
||||
|
||||
|
|
|
@ -3870,6 +3870,14 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Page.uncheck.trial = %%-input-trial-%%
|
||||
* since: v1.11
|
||||
|
||||
## async method: Page.unrouteAll
|
||||
* since: v1.41
|
||||
|
||||
Removes all routes created with [`method: Page.route`] and [`method: Page.routeFromHAR`].
|
||||
|
||||
### option: Page.unrouteAll.behavior = %%-unroute-all-options-behavior-%%
|
||||
* since: v1.41
|
||||
|
||||
## async method: Page.unroute
|
||||
* since: v1.8
|
||||
|
||||
|
|
|
@ -734,6 +734,14 @@ Whether to allow sites to register Service workers. Defaults to `'allow'`.
|
|||
* `'allow'`: [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) can be registered.
|
||||
* `'block'`: Playwright will block all registration of Service Workers.
|
||||
|
||||
## unroute-all-options-behavior
|
||||
* since: v1.41
|
||||
- `behavior` <[UnrouteAllBehavior]<"wait"|"ignoreErrors"|"default">>
|
||||
|
||||
Specifies wether to wait for already running handlers and what to do if they throw errors:
|
||||
* `'default'` - do not wait for current handler calls (if any) to finish, if unrouted handler throws, it may result in unhandled error
|
||||
* `'wait'` - wait for current handler calls (if any) to finish
|
||||
* `'ignoreErrors'` - do not wait for current handler calls (if any) to finish, all errors thrown by the handlers after unrouting are silently caught
|
||||
|
||||
## select-options-values
|
||||
* langs: java, js, csharp
|
||||
|
|
|
@ -64,6 +64,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
private _harRecorders = new Map<string, { path: string, content: 'embed' | 'attach' | 'omit' | undefined }>();
|
||||
_closeWasCalled = false;
|
||||
private _closeReason: string | undefined;
|
||||
private _harRouters: HarRouter[] = [];
|
||||
|
||||
static from(context: channels.BrowserContextChannel): BrowserContext {
|
||||
return (context as any)._object;
|
||||
|
@ -212,7 +213,9 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
if (handled)
|
||||
return;
|
||||
}
|
||||
await route._innerContinue(true);
|
||||
// If the page is closed or unrouteAll() was called without waiting and interception disabled,
|
||||
// the method will throw an error - silence it.
|
||||
await route._innerContinue(true).catch(() => {});
|
||||
}
|
||||
|
||||
async _onBinding(bindingCall: BindingCall) {
|
||||
|
@ -331,7 +334,18 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
return;
|
||||
}
|
||||
const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url });
|
||||
harRouter.addContextRoute(this);
|
||||
this._harRouters.push(harRouter);
|
||||
await harRouter.addContextRoute(this);
|
||||
}
|
||||
|
||||
private _disposeHarRouters() {
|
||||
this._harRouters.forEach(router => router.dispose());
|
||||
this._harRouters = [];
|
||||
}
|
||||
|
||||
async unrouteAll(options?: { behavior?: 'wait'|'ignoreErrors'|'default' }): Promise<void> {
|
||||
await this._unrouteInternal(this._routes, [], options);
|
||||
this._disposeHarRouters();
|
||||
}
|
||||
|
||||
async unroute(url: URLMatch, handler?: network.RouteHandlerCallback, options?: { noWaitForActive?: boolean }): Promise<void> {
|
||||
|
@ -343,11 +357,17 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
else
|
||||
remaining.push(route);
|
||||
}
|
||||
const behavior = options?.noWaitForActive ? 'ignoreErrors' : 'wait';
|
||||
await this._unrouteInternal(removed, remaining, { behavior });
|
||||
}
|
||||
|
||||
private async _unrouteInternal(removed: network.RouteHandler[], remaining: network.RouteHandler[], options?: { behavior?: 'wait'|'ignoreErrors'|'default' }): Promise<void> {
|
||||
this._routes = remaining;
|
||||
await this._updateInterceptionPatterns();
|
||||
const promises = removed.map(routeHandler => routeHandler.stopAndWaitForRunningHandlers(null, options?.noWaitForActive));
|
||||
if (!options?.behavior || options?.behavior === 'default')
|
||||
return;
|
||||
const promises = removed.map(routeHandler => routeHandler.stopAndWaitForRunningHandlers(null, options?.behavior === 'ignoreErrors'));
|
||||
await Promise.all(promises);
|
||||
|
||||
}
|
||||
|
||||
private async _updateInterceptionPatterns() {
|
||||
|
@ -402,6 +422,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
if (this._browser)
|
||||
this._browser._contexts.delete(this);
|
||||
this._browserType?._contexts?.delete(this);
|
||||
this._disposeHarRouters();
|
||||
this.emit(Events.BrowserContext.Close, this);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
import { debugLogger } from '../common/debugLogger';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import { Events } from './events';
|
||||
import type { LocalUtils } from './localUtils';
|
||||
import type { Route } from './network';
|
||||
import type { URLMatch } from './types';
|
||||
|
@ -85,12 +84,10 @@ export class HarRouter {
|
|||
|
||||
async addContextRoute(context: BrowserContext) {
|
||||
await context.route(this._options.urlMatch || '**/*', route => this._handle(route));
|
||||
context.once(Events.BrowserContext.Close, () => this.dispose());
|
||||
}
|
||||
|
||||
async addPageRoute(page: Page) {
|
||||
await page.route(this._options.urlMatch || '**/*', route => this._handle(route));
|
||||
page.once(Events.Page.Close, () => this.dispose());
|
||||
}
|
||||
|
||||
async [Symbol.asyncDispose]() {
|
||||
|
|
|
@ -95,6 +95,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
|||
readonly _opener: Page | null;
|
||||
private _closeReason: string | undefined;
|
||||
_closeWasCalled: boolean = false;
|
||||
private _harRouters: HarRouter[] = [];
|
||||
|
||||
static from(page: channels.PageChannel): Page {
|
||||
return (page as any)._object;
|
||||
|
@ -215,6 +216,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
|||
this._closed = true;
|
||||
this._browserContext._pages.delete(this);
|
||||
this._browserContext._backgroundPages.delete(this);
|
||||
this._disposeHarRouters();
|
||||
this.emit(Events.Page.Close, this);
|
||||
}
|
||||
|
||||
|
@ -467,7 +469,18 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
|||
return;
|
||||
}
|
||||
const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url });
|
||||
harRouter.addPageRoute(this);
|
||||
this._harRouters.push(harRouter);
|
||||
await harRouter.addPageRoute(this);
|
||||
}
|
||||
|
||||
private _disposeHarRouters() {
|
||||
this._harRouters.forEach(router => router.dispose());
|
||||
this._harRouters = [];
|
||||
}
|
||||
|
||||
async unrouteAll(options?: { behavior?: 'wait'|'ignoreErrors'|'default' }): Promise<void> {
|
||||
await this._unrouteInternal(this._routes, [], options);
|
||||
this._disposeHarRouters();
|
||||
}
|
||||
|
||||
async unroute(url: URLMatch, handler?: RouteHandlerCallback, options?: { noWaitForActive?: boolean }): Promise<void> {
|
||||
|
@ -479,9 +492,16 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
|||
else
|
||||
remaining.push(route);
|
||||
}
|
||||
const behavior = options?.noWaitForActive ? 'ignoreErrors' : 'wait';
|
||||
await this._unrouteInternal(removed, remaining, { behavior });
|
||||
}
|
||||
|
||||
private async _unrouteInternal(removed: RouteHandler[], remaining: RouteHandler[], options?: { behavior?: 'wait'|'ignoreErrors'|'default' }): Promise<void> {
|
||||
this._routes = remaining;
|
||||
await this._updateInterceptionPatterns();
|
||||
const promises = removed.map(routeHandler => routeHandler.stopAndWaitForRunningHandlers(this, options?.noWaitForActive));
|
||||
if (!options?.behavior || options?.behavior === 'default')
|
||||
return;
|
||||
const promises = removed.map(routeHandler => routeHandler.stopAndWaitForRunningHandlers(this, options?.behavior === 'ignoreErrors'));
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
|
|
|
@ -4253,6 +4253,24 @@ export interface Page {
|
|||
noWaitForActive?: boolean;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes all routes created with
|
||||
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route) and
|
||||
* [page.routeFromHAR(har[, options])](https://playwright.dev/docs/api/class-page#page-route-from-har).
|
||||
* @param options
|
||||
*/
|
||||
unrouteAll(options?: {
|
||||
/**
|
||||
* Specifies wether to wait for already running handlers and what to do if they throw errors:
|
||||
* - `'default'` - do not wait for current handler calls (if any) to finish, if unrouted handler throws, it may
|
||||
* result in unhandled error
|
||||
* - `'wait'` - wait for current handler calls (if any) to finish
|
||||
* - `'ignoreErrors'` - do not wait for current handler calls (if any) to finish, all errors thrown by the handlers
|
||||
* after unrouting are silently caught
|
||||
*/
|
||||
behavior?: "wait"|"ignoreErrors"|"default";
|
||||
}): Promise<void>;
|
||||
|
||||
url(): string;
|
||||
|
||||
/**
|
||||
|
@ -8636,6 +8654,25 @@ export interface BrowserContext {
|
|||
noWaitForActive?: boolean;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes all routes created with
|
||||
* [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route)
|
||||
* and
|
||||
* [browserContext.routeFromHAR(har[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route-from-har).
|
||||
* @param options
|
||||
*/
|
||||
unrouteAll(options?: {
|
||||
/**
|
||||
* Specifies wether to wait for already running handlers and what to do if they throw errors:
|
||||
* - `'default'` - do not wait for current handler calls (if any) to finish, if unrouted handler throws, it may
|
||||
* result in unhandled error
|
||||
* - `'wait'` - wait for current handler calls (if any) to finish
|
||||
* - `'ignoreErrors'` - do not wait for current handler calls (if any) to finish, all errors thrown by the handlers
|
||||
* after unrouting are silently caught
|
||||
*/
|
||||
behavior?: "wait"|"ignoreErrors"|"default";
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* **NOTE** Only works with Chromium browser's persistent context.
|
||||
*
|
||||
|
|
|
@ -403,3 +403,29 @@ it('should update extracted har.zip for page', async ({ contextFactory, server }
|
|||
expect(await page2.content()).toContain('hello, world!');
|
||||
await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
|
||||
});
|
||||
|
||||
it('page.unrouteAll should stop page.routeFromHAR', async ({ contextFactory, server, asset }, testInfo) => {
|
||||
const harPath = asset('har-fulfill.har');
|
||||
const context1 = await contextFactory();
|
||||
const page1 = await context1.newPage();
|
||||
// The har file contains requests for another domain, so the router
|
||||
// is expected to abort all requests.
|
||||
await page1.routeFromHAR(harPath, { notFound: 'abort' });
|
||||
await expect(page1.goto(server.EMPTY_PAGE)).rejects.toThrow();
|
||||
await page1.unrouteAll({ behavior: 'wait' });
|
||||
const response = await page1.goto(server.EMPTY_PAGE);
|
||||
expect(response.ok()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('context.unrouteAll should stop context.routeFromHAR', async ({ contextFactory, server, asset }, testInfo) => {
|
||||
const harPath = asset('har-fulfill.har');
|
||||
const context1 = await contextFactory();
|
||||
const page1 = await context1.newPage();
|
||||
// The har file contains requests for another domain, so the router
|
||||
// is expected to abort all requests.
|
||||
await context1.routeFromHAR(harPath, { notFound: 'abort' });
|
||||
await expect(page1.goto(server.EMPTY_PAGE)).rejects.toThrow();
|
||||
await context1.unrouteAll({ behavior: 'wait' });
|
||||
const response = await page1.goto(server.EMPTY_PAGE);
|
||||
expect(response.ok()).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -139,6 +139,77 @@ it('unroute should not wait for pending handlers to complete if noWaitForActive
|
|||
expect(secondHandlerCalled).toBe(true);
|
||||
});
|
||||
|
||||
it('unrouteAll removes all handlers', async ({ page, context, server }) => {
|
||||
await context.route('**/*', route => {
|
||||
void route.abort();
|
||||
});
|
||||
await context.route('**/empty.html', route => {
|
||||
void route.abort();
|
||||
});
|
||||
await context.unrouteAll();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
});
|
||||
|
||||
it('unrouteAll should wait for pending handlers to complete', async ({ page, context, server }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23781' });
|
||||
let secondHandlerCalled = false;
|
||||
await context.route(/.*/, async route => {
|
||||
secondHandlerCalled = true;
|
||||
await route.abort();
|
||||
});
|
||||
let routeCallback;
|
||||
const routePromise = new Promise(f => routeCallback = f);
|
||||
let continueRouteCallback;
|
||||
const routeBarrier = new Promise(f => continueRouteCallback = f);
|
||||
const handler = async route => {
|
||||
routeCallback();
|
||||
await routeBarrier;
|
||||
await route.fallback();
|
||||
};
|
||||
await context.route(/.*/, handler);
|
||||
const navigationPromise = page.goto(server.EMPTY_PAGE);
|
||||
await routePromise;
|
||||
let didUnroute = false;
|
||||
const unroutePromise = context.unrouteAll({ behavior: 'wait' }).then(() => didUnroute = true);
|
||||
await new Promise(f => setTimeout(f, 500));
|
||||
expect(didUnroute).toBe(false);
|
||||
continueRouteCallback();
|
||||
await unroutePromise;
|
||||
expect(didUnroute).toBe(true);
|
||||
await navigationPromise;
|
||||
expect(secondHandlerCalled).toBe(false);
|
||||
});
|
||||
|
||||
it('unrouteAll should not wait for pending handlers to complete if behavior is ignoreErrors', async ({ page, context, server }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23781' });
|
||||
let secondHandlerCalled = false;
|
||||
await context.route(/.*/, async route => {
|
||||
secondHandlerCalled = true;
|
||||
await route.abort();
|
||||
});
|
||||
let routeCallback;
|
||||
const routePromise = new Promise(f => routeCallback = f);
|
||||
let continueRouteCallback;
|
||||
const routeBarrier = new Promise(f => continueRouteCallback = f);
|
||||
const handler = async route => {
|
||||
routeCallback();
|
||||
await routeBarrier;
|
||||
throw new Error('Handler error');
|
||||
};
|
||||
await context.route(/.*/, handler);
|
||||
const navigationPromise = page.goto(server.EMPTY_PAGE);
|
||||
await routePromise;
|
||||
let didUnroute = false;
|
||||
const unroutePromise = context.unrouteAll({ behavior: 'ignoreErrors' }).then(() => didUnroute = true);
|
||||
await new Promise(f => setTimeout(f, 500));
|
||||
await unroutePromise;
|
||||
expect(didUnroute).toBe(true);
|
||||
continueRouteCallback();
|
||||
await navigationPromise.catch(e => void e);
|
||||
// The error in the unrouted handler should be silently caught and remaining handler called.
|
||||
expect(secondHandlerCalled).toBe(false);
|
||||
});
|
||||
|
||||
it('should yield to page.route', async ({ browser, server }) => {
|
||||
const context = await browser.newContext();
|
||||
await context.route('**/empty.html', route => {
|
||||
|
|
|
@ -132,6 +132,78 @@ it('unroute should not wait for pending handlers to complete if noWaitForActive
|
|||
expect(secondHandlerCalled).toBe(true);
|
||||
});
|
||||
|
||||
it('unrouteAll removes all routes', async ({ page, server }) => {
|
||||
await page.route('**/*', route => {
|
||||
void route.abort();
|
||||
});
|
||||
await page.route('**/empty.html', route => {
|
||||
void route.abort();
|
||||
});
|
||||
await page.unrouteAll();
|
||||
const response = await page.goto(server.EMPTY_PAGE);
|
||||
expect(response.ok()).toBe(true);
|
||||
});
|
||||
|
||||
it('unrouteAll should wait for pending handlers to complete', async ({ page, server }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23781' });
|
||||
let secondHandlerCalled = false;
|
||||
await page.route(/.*/, async route => {
|
||||
secondHandlerCalled = true;
|
||||
await route.abort();
|
||||
});
|
||||
let routeCallback;
|
||||
const routePromise = new Promise(f => routeCallback = f);
|
||||
let continueRouteCallback;
|
||||
const routeBarrier = new Promise(f => continueRouteCallback = f);
|
||||
const handler = async route => {
|
||||
routeCallback();
|
||||
await routeBarrier;
|
||||
await route.fallback();
|
||||
};
|
||||
await page.route(/.*/, handler);
|
||||
const navigationPromise = page.goto(server.EMPTY_PAGE);
|
||||
await routePromise;
|
||||
let didUnroute = false;
|
||||
const unroutePromise = page.unrouteAll({ behavior: 'wait' }).then(() => didUnroute = true);
|
||||
await new Promise(f => setTimeout(f, 500));
|
||||
expect(didUnroute).toBe(false);
|
||||
continueRouteCallback();
|
||||
await unroutePromise;
|
||||
expect(didUnroute).toBe(true);
|
||||
await navigationPromise;
|
||||
expect(secondHandlerCalled).toBe(false);
|
||||
});
|
||||
|
||||
it('unrouteAll should not wait for pending handlers to complete if behavior is ignoreErrors', async ({ page, server }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23781' });
|
||||
let secondHandlerCalled = false;
|
||||
await page.route(/.*/, async route => {
|
||||
secondHandlerCalled = true;
|
||||
await route.abort();
|
||||
});
|
||||
let routeCallback;
|
||||
const routePromise = new Promise(f => routeCallback = f);
|
||||
let continueRouteCallback;
|
||||
const routeBarrier = new Promise(f => continueRouteCallback = f);
|
||||
const handler = async route => {
|
||||
routeCallback();
|
||||
await routeBarrier;
|
||||
throw new Error('Handler error');
|
||||
};
|
||||
await page.route(/.*/, handler);
|
||||
const navigationPromise = page.goto(server.EMPTY_PAGE);
|
||||
await routePromise;
|
||||
let didUnroute = false;
|
||||
const unroutePromise = page.unrouteAll({ behavior: 'ignoreErrors' }).then(() => didUnroute = true);
|
||||
await new Promise(f => setTimeout(f, 500));
|
||||
await unroutePromise;
|
||||
expect(didUnroute).toBe(true);
|
||||
continueRouteCallback();
|
||||
await navigationPromise.catch(e => void e);
|
||||
// The error in the unrouted handler should be silently caught.
|
||||
expect(secondHandlerCalled).toBe(false);
|
||||
});
|
||||
|
||||
it('should support ? in glob pattern', async ({ page, server }) => {
|
||||
server.setRoute('/index', (req, res) => res.end('index-no-hello'));
|
||||
server.setRoute('/index123hello', (req, res) => res.end('index123hello'));
|
||||
|
|
Загрузка…
Ссылка в новой задаче