Skip to content

Commit

Permalink
feat: page.unrouteAll and context.unrouteAll
Browse files Browse the repository at this point in the history
Reference microsoft#23781
  • Loading branch information
yury-s committed Dec 14, 2023
1 parent 9b2585c commit 8389195
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 3 deletions.
8 changes: 8 additions & 0 deletions docs/src/api/class-browsercontext.md
Expand Up @@ -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`].

### option: BrowserContext.unrouteAll.behavior = %%-unroute-all-options-behavior-%%
* since: v1.41

## async method: BrowserContext.unroute
* since: v1.8

Expand Down
8 changes: 8 additions & 0 deletions docs/src/api/class-page.md
Expand Up @@ -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`].
### option: Page.unrouteAll.behavior = %%-unroute-all-options-behavior-%%
* since: v1.41
## async method: Page.unroute
* since: v1.8
Expand Down
8 changes: 8 additions & 0 deletions docs/src/api/params.md
Expand Up @@ -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
Expand Down
14 changes: 12 additions & 2 deletions packages/playwright-core/src/client/browserContext.ts
Expand Up @@ -334,6 +334,10 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
harRouter.addContextRoute(this);
}

async unrouteAll(options?: { behavior?: 'wait'|'ignoreErrors'|'default' }): Promise<void> {
await this._unrouteInternal(this._routes, [], options);
}

async unroute(url: URLMatch, handler?: network.RouteHandlerCallback, options?: { noWaitForActive?: boolean }): Promise<void> {
const removed = [];
const remaining = [];
Expand All @@ -343,11 +347,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 || options?.behavior === 'default')
return;
const promises = removed.map(routeHandler => routeHandler.stopAndWaitForRunningHandlers(null, options?.behavior === 'ignoreErrors'));
await Promise.all(promises);

}

private async _updateInterceptionPatterns() {
Expand Down
13 changes: 12 additions & 1 deletion packages/playwright-core/src/client/page.ts
Expand Up @@ -470,6 +470,10 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
harRouter.addPageRoute(this);
}

async unrouteAll(options?: { behavior?: 'wait'|'ignoreErrors'|'default' }): Promise<void> {
await this._unrouteInternal(this._routes, [], options);
}

async unroute(url: URLMatch, handler?: RouteHandlerCallback, options?: { noWaitForActive?: boolean }): Promise<void> {
const removed = [];
const remaining = [];
Expand All @@ -479,9 +483,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 || options?.behavior === 'default')
return;
const promises = removed.map(routeHandler => routeHandler.stopAndWaitForRunningHandlers(this, options?.behavior === 'ignoreErrors'));
await Promise.all(promises);
}

Expand Down
34 changes: 34 additions & 0 deletions packages/playwright-core/types/types.d.ts
Expand Up @@ -4253,6 +4253,23 @@ 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).
* @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;

/**
Expand Down Expand Up @@ -8636,6 +8653,23 @@ 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).
* @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.
*
Expand Down
71 changes: 71 additions & 0 deletions tests/library/browsercontext-route.spec.ts
Expand Up @@ -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 => {
Expand Down
72 changes: 72 additions & 0 deletions tests/page/page-route.spec.ts
Expand Up @@ -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'));
Expand Down

0 comments on commit 8389195

Please sign in to comment.