From 47dd626368dc7e0a31cab038f834d502db12a7c1 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 23 Jan 2026 14:04:06 +0100 Subject: [PATCH 1/5] feat: toHaveURLPattern --- docs/src/api/class-browsercontext.md | 12 +++- docs/src/api/class-page.md | 14 +++-- docs/src/api/class-pageassertions.md | 5 +- docs/src/api/params.md | 4 +- packages/playwright-client/types/types.d.ts | 61 +++++++++++-------- .../src/utils/isomorphic/urlMatch.ts | 12 +++- packages/playwright-core/types/types.d.ts | 61 +++++++++++-------- packages/playwright/src/matchers/matchers.ts | 10 ++- packages/playwright/types/test.d.ts | 10 ++- tests/page/expect-misc.spec.ts | 9 +++ utils/generate_types/overrides-test.d.ts | 5 ++ utils/generate_types/overrides.d.ts | 5 ++ 12 files changed, 138 insertions(+), 70 deletions(-) diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index 3e7529c4828ef..45cbdaaa1bf1e 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -1173,6 +1173,14 @@ Enabling routing disables http cache. ### param: BrowserContext.route.url * since: v1.8 +* langs: js +- `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> + +A glob pattern, regex pattern, URL pattern, or predicate that receives a [URL] to match during routing. If [`option: Browser.newContext.baseURL`] is set in the context options and the provided URL is a string that does not start with `*`, it is resolved using the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. + +### param: BrowserContext.route.url +* since: v1.8 +* langs: python, csharp, java - `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If [`option: Browser.newContext.baseURL`] is set in the context options and the provided URL is a string that does not start with `*`, it is resolved using the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. @@ -1520,9 +1528,9 @@ routes for the [`param: url`]. ### param: BrowserContext.unroute.url * since: v1.8 -- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> +- `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> -A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with +A glob pattern, regex pattern, URL pattern, or predicate receiving [URL] used to register a routing with [`method: BrowserContext.route`]. ### param: BrowserContext.unroute.handler diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 13299d4646cf7..5e1bef95da2ed 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -2177,7 +2177,7 @@ var frame = page.FrameByUrl(".*domain.*"); * langs: js - `frameSelector` <[string]|[Object]> - `name` ?<[string]> Frame name specified in the `iframe`'s `name` attribute. Optional. - - `url` ?<[string]|[RegExp]|[function]\([URL]\):[boolean]> A glob pattern, regex pattern or predicate receiving + - `url` ?<[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> A glob pattern, regex pattern, URL pattern or predicate receiving frame's `url` as a [URL] object. Optional. Frame name or other frame lookup options. @@ -3691,9 +3691,11 @@ Enabling routing disables http cache. ### param: Page.route.url * since: v1.8 -- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> +- `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> + +TODO: dont support ports -A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If [`option: Browser.newContext.baseURL`] is set in the context options and the provided URL is a string that does not start with `*`, it is resolved using the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. +A glob pattern, regex pattern, URL Pattern, or predicate that receives a [URL] to match during routing. If [`option: Browser.newContext.baseURL`] is set in the context options and the provided URL is a string that does not start with `*`, it is resolved using the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. ### param: Page.route.handler * since: v1.8 @@ -3823,7 +3825,7 @@ await page.RouteWebSocketAsync("/ws", ws => { ### param: Page.routeWebSocket.url * since: v1.48 -- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> +- `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the [`option: Browser.newContext.baseURL`] context option. @@ -4348,9 +4350,9 @@ the [`param: url`]. ### param: Page.unroute.url * since: v1.8 -- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> +- `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> -A glob pattern, regex pattern or predicate receiving [URL] to match while routing. +A glob pattern, regex pattern, URL pattern, or predicate receiving [URL] to match while routing. ### param: Page.unroute.handler * since: v1.8 diff --git a/docs/src/api/class-pageassertions.md b/docs/src/api/class-pageassertions.md index 6420735f2be59..49e00880ee33e 100644 --- a/docs/src/api/class-pageassertions.md +++ b/docs/src/api/class-pageassertions.md @@ -302,6 +302,9 @@ await expect(page).toHaveURL('https://playwright.dev/docs/intro'); // Check for the page URL to contain 'doc', followed by an optional 's', followed by '/' await expect(page).toHaveURL(/docs?\//); +// Check for the page URL to match the URL pattern +await expect(page).toHaveURL(new URLPattern({ pathname: '/docs/*' })); + // Check for the predicate to be satisfied // For example: verify query strings await expect(page).toHaveURL(url => { @@ -337,7 +340,7 @@ await Expect(Page).ToHaveURLAsync(new Regex(".*checkout")); ### param: PageAssertions.toHaveURL.url * since: v1.18 * langs: js -- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> +- `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> Expected URL string, RegExp, or predicate receiving [URL] to match. When [`option: Browser.newContext.baseURL`] is provided via the context options and the `url` argument is a string, the two values are merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor and used for the comparison against the current browser URL. diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 90c3b9e3621f4..a46488ba44bae 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -895,9 +895,9 @@ first option matching one of the passed options is selected. String values are m is considered matching if all specified properties match. ## wait-for-navigation-url -- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> +- `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> -A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if +A glob pattern, regex pattern, URL pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to the string. diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index f6e6165885f0a..531d54e882621 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -20,6 +20,11 @@ import { ReadStream } from 'fs'; import { Protocol } from './protocol'; import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from './structs'; +// we depend on @types/node@18 which does not have URLPattern yet, so we polyfill it +type URLPattern = { + test(input: string | URL): boolean; +} + type PageWaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & { state?: 'visible'|'attached'; }; @@ -2750,9 +2755,9 @@ export interface Page { name?: string; /** - * A glob pattern, regex pattern or predicate receiving frame's `url` as a [URL] object. Optional. + * A glob pattern, regex pattern, URL pattern or predicate receiving frame's `url` as a [URL] object. Optional. */ - url?: string|RegExp|((url: URL) => boolean); + url?: string|RegExp|URLPattern|((url: URL) => boolean); }): null|Frame; /** @@ -4007,14 +4012,16 @@ export interface Page { * * **NOTE** Enabling routing disables http cache. * - * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If + * @param url TODO: dont support ports + * + * A glob pattern, regex pattern, URL Pattern, or predicate that receives a [URL] to match during routing. If * [`baseURL`](https://playwright.dev/docs/api/class-browser#browser-new-context-option-base-url) is set in the * context options and the provided URL is a string that does not start with `*`, it is resolved using 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) => Promise|any), options?: { + route(url: string|RegExp|URLPattern|((url: URL) => boolean), handler: ((route: Route, request: Request) => Promise|any), options?: { /** * How often a route should be used. By default it will be used every time. */ @@ -4095,7 +4102,7 @@ export interface Page { * [`baseURL`](https://playwright.dev/docs/api/class-browser#browser-new-context-option-base-url) context option. * @param handler Handler function to route the WebSocket. */ - routeWebSocket(url: string|RegExp|((url: URL) => boolean), handler: ((websocketroute: WebSocketRoute) => Promise|any)): Promise; + routeWebSocket(url: string|RegExp|URLPattern|((url: URL) => boolean), handler: ((websocketroute: WebSocketRoute) => Promise|any)): Promise; /** * Returns the buffer with the captured screenshot. @@ -4686,10 +4693,10 @@ export interface Page { * [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route). When * [`handler`](https://playwright.dev/docs/api/class-page#page-unroute-option-handler) is not specified, removes all * routes for the [`url`](https://playwright.dev/docs/api/class-page#page-unroute-option-url). - * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. + * @param url A glob pattern, regex pattern, URL pattern, or predicate receiving [URL] to match while routing. * @param handler Optional handler function to route the request. */ - unroute(url: string|RegExp|((url: URL) => boolean), handler?: ((route: Route, request: Request) => Promise|any)): Promise; + unroute(url: string|RegExp|URLPattern|((url: URL) => boolean), handler?: ((route: Route, request: Request) => Promise|any)): Promise; /** * Removes all routes created with @@ -5018,11 +5025,11 @@ export interface Page { timeout?: number; /** - * A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if - * the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly - * equal to the string. + * A glob pattern, regex pattern, URL pattern or predicate receiving [URL] to match while waiting for the navigation. + * Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that + * is exactly equal to the string. */ - url?: string|RegExp|((url: URL) => boolean); + url?: string|RegExp|URLPattern|((url: URL) => boolean); /** * When to consider operation succeeded, defaults to `load`. Events can be either: @@ -5136,12 +5143,12 @@ export interface Page { * await page.waitForURL('**\/target.html'); * ``` * - * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if - * the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly - * equal to the string. + * @param url A glob pattern, regex pattern, URL pattern or predicate receiving [URL] to match while waiting for the navigation. + * Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that + * is exactly equal to the string. * @param options */ - waitForURL(url: string|RegExp|((url: URL) => boolean), options?: { + waitForURL(url: string|RegExp|URLPattern|((url: URL) => boolean), options?: { /** * Maximum operation time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via * `navigationTimeout` option in the config, or by using the @@ -7964,11 +7971,11 @@ export interface Frame { timeout?: number; /** - * A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if - * the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly - * equal to the string. + * A glob pattern, regex pattern, URL pattern or predicate receiving [URL] to match while waiting for the navigation. + * Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that + * is exactly equal to the string. */ - url?: string|RegExp|((url: URL) => boolean); + url?: string|RegExp|URLPattern|((url: URL) => boolean); /** * When to consider operation succeeded, defaults to `load`. Events can be either: @@ -8005,12 +8012,12 @@ export interface Frame { * await frame.waitForURL('**\/target.html'); * ``` * - * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if - * the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly - * equal to the string. + * @param url A glob pattern, regex pattern, URL pattern or predicate receiving [URL] to match while waiting for the navigation. + * Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that + * is exactly equal to the string. * @param options */ - waitForURL(url: string|RegExp|((url: URL) => boolean), options?: { + waitForURL(url: string|RegExp|URLPattern|((url: URL) => boolean), options?: { /** * Maximum operation time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via * `navigationTimeout` option in the config, or by using the @@ -9088,14 +9095,14 @@ export interface BrowserContext { * * **NOTE** Enabling routing disables http cache. * - * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If + * @param url A glob pattern, regex pattern, URL pattern, or predicate that receives a [URL] to match during routing. If * [`baseURL`](https://playwright.dev/docs/api/class-browser#browser-new-context-option-base-url) is set in the * context options and the provided URL is a string that does not start with `*`, it is resolved using 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) => Promise|any), options?: { + route(url: string|RegExp|URLPattern|((url: URL) => boolean), handler: ((route: Route, request: Request) => Promise|any), options?: { /** * How often a route should be used. By default it will be used every time. */ @@ -9343,12 +9350,12 @@ export interface BrowserContext { * When [`handler`](https://playwright.dev/docs/api/class-browsercontext#browser-context-unroute-option-handler) is * not specified, removes all routes for the * [`url`](https://playwright.dev/docs/api/class-browsercontext#browser-context-unroute-option-url). - * @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with + * @param url A glob pattern, regex pattern, URL 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) => Promise|any)): Promise; + unroute(url: string|RegExp|URLPattern|((url: URL) => boolean), handler?: ((route: Route, request: Request) => Promise|any)): Promise; /** * Removes all routes created with diff --git a/packages/playwright-core/src/utils/isomorphic/urlMatch.ts b/packages/playwright-core/src/utils/isomorphic/urlMatch.ts index 00cdfc019b4bc..d83aa94f0bab5 100644 --- a/packages/playwright-core/src/utils/isomorphic/urlMatch.ts +++ b/packages/playwright-core/src/utils/isomorphic/urlMatch.ts @@ -82,7 +82,11 @@ function isRegExp(obj: any): obj is RegExp { return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]'; } -export type URLMatch = string | RegExp | ((url: URL) => boolean); +export type URLMatch = string | RegExp | ((url: URL) => boolean) | URLPattern; +// URLPattern is not in @types/node@18, so we polyfill it ourselves +export type URLPattern = { + test(input: string | URL): boolean; +}; export function urlMatchesEqual(match1: URLMatch, match2: URLMatch) { if (isRegExp(match1) && isRegExp(match2)) @@ -102,8 +106,12 @@ export function urlMatches(baseURL: string | undefined, urlString: string, match const url = parseURL(urlString); if (!url) return false; + // @ts-expect-error urlpattern is not in @types/node yet + // eslint-disable-next-line no-restricted-globals + if (typeof globalThis.URLPattern === 'function' && match instanceof globalThis.URLPattern) + return (match as URLPattern).test(url.href); if (typeof match !== 'function') - throw new Error('url parameter should be string, RegExp or function'); + throw new Error('url parameter should be string, RegExp, URLPattern or function'); return match(url); } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index f6e6165885f0a..531d54e882621 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -20,6 +20,11 @@ import { ReadStream } from 'fs'; import { Protocol } from './protocol'; import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from './structs'; +// we depend on @types/node@18 which does not have URLPattern yet, so we polyfill it +type URLPattern = { + test(input: string | URL): boolean; +} + type PageWaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & { state?: 'visible'|'attached'; }; @@ -2750,9 +2755,9 @@ export interface Page { name?: string; /** - * A glob pattern, regex pattern or predicate receiving frame's `url` as a [URL] object. Optional. + * A glob pattern, regex pattern, URL pattern or predicate receiving frame's `url` as a [URL] object. Optional. */ - url?: string|RegExp|((url: URL) => boolean); + url?: string|RegExp|URLPattern|((url: URL) => boolean); }): null|Frame; /** @@ -4007,14 +4012,16 @@ export interface Page { * * **NOTE** Enabling routing disables http cache. * - * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If + * @param url TODO: dont support ports + * + * A glob pattern, regex pattern, URL Pattern, or predicate that receives a [URL] to match during routing. If * [`baseURL`](https://playwright.dev/docs/api/class-browser#browser-new-context-option-base-url) is set in the * context options and the provided URL is a string that does not start with `*`, it is resolved using 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) => Promise|any), options?: { + route(url: string|RegExp|URLPattern|((url: URL) => boolean), handler: ((route: Route, request: Request) => Promise|any), options?: { /** * How often a route should be used. By default it will be used every time. */ @@ -4095,7 +4102,7 @@ export interface Page { * [`baseURL`](https://playwright.dev/docs/api/class-browser#browser-new-context-option-base-url) context option. * @param handler Handler function to route the WebSocket. */ - routeWebSocket(url: string|RegExp|((url: URL) => boolean), handler: ((websocketroute: WebSocketRoute) => Promise|any)): Promise; + routeWebSocket(url: string|RegExp|URLPattern|((url: URL) => boolean), handler: ((websocketroute: WebSocketRoute) => Promise|any)): Promise; /** * Returns the buffer with the captured screenshot. @@ -4686,10 +4693,10 @@ export interface Page { * [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route). When * [`handler`](https://playwright.dev/docs/api/class-page#page-unroute-option-handler) is not specified, removes all * routes for the [`url`](https://playwright.dev/docs/api/class-page#page-unroute-option-url). - * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. + * @param url A glob pattern, regex pattern, URL pattern, or predicate receiving [URL] to match while routing. * @param handler Optional handler function to route the request. */ - unroute(url: string|RegExp|((url: URL) => boolean), handler?: ((route: Route, request: Request) => Promise|any)): Promise; + unroute(url: string|RegExp|URLPattern|((url: URL) => boolean), handler?: ((route: Route, request: Request) => Promise|any)): Promise; /** * Removes all routes created with @@ -5018,11 +5025,11 @@ export interface Page { timeout?: number; /** - * A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if - * the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly - * equal to the string. + * A glob pattern, regex pattern, URL pattern or predicate receiving [URL] to match while waiting for the navigation. + * Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that + * is exactly equal to the string. */ - url?: string|RegExp|((url: URL) => boolean); + url?: string|RegExp|URLPattern|((url: URL) => boolean); /** * When to consider operation succeeded, defaults to `load`. Events can be either: @@ -5136,12 +5143,12 @@ export interface Page { * await page.waitForURL('**\/target.html'); * ``` * - * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if - * the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly - * equal to the string. + * @param url A glob pattern, regex pattern, URL pattern or predicate receiving [URL] to match while waiting for the navigation. + * Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that + * is exactly equal to the string. * @param options */ - waitForURL(url: string|RegExp|((url: URL) => boolean), options?: { + waitForURL(url: string|RegExp|URLPattern|((url: URL) => boolean), options?: { /** * Maximum operation time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via * `navigationTimeout` option in the config, or by using the @@ -7964,11 +7971,11 @@ export interface Frame { timeout?: number; /** - * A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if - * the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly - * equal to the string. + * A glob pattern, regex pattern, URL pattern or predicate receiving [URL] to match while waiting for the navigation. + * Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that + * is exactly equal to the string. */ - url?: string|RegExp|((url: URL) => boolean); + url?: string|RegExp|URLPattern|((url: URL) => boolean); /** * When to consider operation succeeded, defaults to `load`. Events can be either: @@ -8005,12 +8012,12 @@ export interface Frame { * await frame.waitForURL('**\/target.html'); * ``` * - * @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if - * the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly - * equal to the string. + * @param url A glob pattern, regex pattern, URL pattern or predicate receiving [URL] to match while waiting for the navigation. + * Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that + * is exactly equal to the string. * @param options */ - waitForURL(url: string|RegExp|((url: URL) => boolean), options?: { + waitForURL(url: string|RegExp|URLPattern|((url: URL) => boolean), options?: { /** * Maximum operation time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via * `navigationTimeout` option in the config, or by using the @@ -9088,14 +9095,14 @@ export interface BrowserContext { * * **NOTE** Enabling routing disables http cache. * - * @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If + * @param url A glob pattern, regex pattern, URL pattern, or predicate that receives a [URL] to match during routing. If * [`baseURL`](https://playwright.dev/docs/api/class-browser#browser-new-context-option-base-url) is set in the * context options and the provided URL is a string that does not start with `*`, it is resolved using 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) => Promise|any), options?: { + route(url: string|RegExp|URLPattern|((url: URL) => boolean), handler: ((route: Route, request: Request) => Promise|any), options?: { /** * How often a route should be used. By default it will be used every time. */ @@ -9343,12 +9350,12 @@ export interface BrowserContext { * When [`handler`](https://playwright.dev/docs/api/class-browsercontext#browser-context-unroute-option-handler) is * not specified, removes all routes for the * [`url`](https://playwright.dev/docs/api/class-browsercontext#browser-context-unroute-option-url). - * @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with + * @param url A glob pattern, regex pattern, URL 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) => Promise|any)): Promise; + unroute(url: string|RegExp|URLPattern|((url: URL) => boolean), handler?: ((route: Route, request: Request) => Promise|any)): Promise; /** * Removes all routes created with diff --git a/packages/playwright/src/matchers/matchers.ts b/packages/playwright/src/matchers/matchers.ts index 08321af0c4cfe..fca545c2e57ad 100644 --- a/packages/playwright/src/matchers/matchers.ts +++ b/packages/playwright/src/matchers/matchers.ts @@ -33,7 +33,7 @@ import type { TestStepInfoImpl } from '../worker/testInfo'; import type { APIResponse, Locator, Frame, Page } from 'playwright-core'; import type { FrameExpectParams } from 'playwright-core/lib/client/types'; import type { ExpectMatcherUtils } from '../../types/test'; -import type { InternalMatcherUtils } from 'playwright-core/lib/utils'; +import type { InternalMatcherUtils, URLPattern } from 'playwright-core/lib/utils'; export type ExpectMatcherStateInternal = Omit & { _stepInfo?: TestStepInfoImpl; @@ -423,9 +423,12 @@ export function toHaveTitle( export function toHaveURL( this: ExpectMatcherStateInternal, page: Page, - expected: string | RegExp | ((url: URL) => boolean), + expected: string | RegExp | URLPattern | ((url: URL) => boolean), options?: { ignoreCase?: boolean; timeout?: number }, ) { + if (isURLPattern(expected)) + return toHaveURLWithPredicate.call(this, page, url => (expected as URLPattern).test(url.href), options); + // Ports don't support predicates. Keep separate server and client codepaths if (typeof expected === 'function') return toHaveURLWithPredicate.call(this, page, expected, options); @@ -438,6 +441,9 @@ export function toHaveURL( }, expected, options); } +// @ts-expect-error globalThis.URLPattern is not in @types/node yet +const isURLPattern = (v: any): v is URLPattern => 'URLPattern' in globalThis && v instanceof globalThis.URLPattern; + export async function toBeOK( this: ExpectMatcherStateInternal, response: APIResponseEx diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 31caf199d7e3c..1172c3ffb0e04 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -7693,6 +7693,11 @@ type CustomProperties = ExcludeProps = Project, PlaywrightWorkerOptions & CustomProperties>; export type PlaywrightTestConfig = Config, PlaywrightWorkerOptions & CustomProperties>; +// we depend on @types/node@18 which does not have URLPattern yet, so we polyfill it +type URLPattern = { + test(input: string | URL): boolean; +} + type AsymmetricMatcher = Record; interface AsymmetricMatchers { @@ -9663,6 +9668,9 @@ interface PageAssertions { * // Check for the page URL to contain 'doc', followed by an optional 's', followed by '/' * await expect(page).toHaveURL(/docs?\//); * + * // Check for the page URL to match the URL pattern + * await expect(page).toHaveURL(new URLPattern({ pathname: '/docs/*' })); + * * // Check for the predicate to be satisfied * // For example: verify query strings * await expect(page).toHaveURL(url => { @@ -9678,7 +9686,7 @@ interface PageAssertions { * against the current browser URL. * @param options */ - toHaveURL(url: string|RegExp|((url: URL) => boolean), options?: { + toHaveURL(url: string|RegExp|URLPattern|((url: URL) => boolean), options?: { /** * Whether to perform case-insensitive match. * [`ignoreCase`](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-url-option-ignore-case) diff --git a/tests/page/expect-misc.spec.ts b/tests/page/expect-misc.spec.ts index e550d3ca72ee9..d6d55d84a736f 100644 --- a/tests/page/expect-misc.spec.ts +++ b/tests/page/expect-misc.spec.ts @@ -410,6 +410,15 @@ Timeout: 10000ms await page.goto('data:text/html,
A
'); await expect(page).toHaveURL('DATA:teXT/HTml,
a
', { ignoreCase: true }); }); + + test('support URLPattern', async ({ page }) => { + test.skip(globalThis.URLPattern === undefined, 'URLPattern is not supported in this environment'); + await page.goto('data:text/html,
A
'); + // @ts-expect-error URLPattern is not in @types/node yet + await expect(page).toHaveURL(new URLPattern({ protocol: 'data' })); + // @ts-expect-error + await expect(page).not.toHaveURL(new URLPattern({ protocol: 'http' })); + }); }); test.describe('toHaveAttribute', () => { diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 7b46b3402b491..6fe3639dc2150 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -315,6 +315,11 @@ type CustomProperties = ExcludeProps = Project, PlaywrightWorkerOptions & CustomProperties>; export type PlaywrightTestConfig = Config, PlaywrightWorkerOptions & CustomProperties>; +// we depend on @types/node@18 which does not have URLPattern yet, so we polyfill it +type URLPattern = { + test(input: string | URL): boolean; +} + type AsymmetricMatcher = Record; interface AsymmetricMatchers { diff --git a/utils/generate_types/overrides.d.ts b/utils/generate_types/overrides.d.ts index a88c80ba6ecdf..604d996e1bf97 100644 --- a/utils/generate_types/overrides.d.ts +++ b/utils/generate_types/overrides.d.ts @@ -19,6 +19,11 @@ import { ReadStream } from 'fs'; import { Protocol } from './protocol'; import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from './structs'; +// we depend on @types/node@18 which does not have URLPattern yet, so we polyfill it +type URLPattern = { + test(input: string | URL): boolean; +} + type PageWaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & { state?: 'visible'|'attached'; }; From 8f8b071d9a89f4c0aa0a4618d59d9f22282dc0c4 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 23 Jan 2026 15:26:29 +0100 Subject: [PATCH 2/5] address feedback --- docs/src/api/class-browsercontext.md | 9 +++++ docs/src/api/class-frame.md | 10 ++++- docs/src/api/class-page.md | 38 ++++++++++++++++--- docs/src/api/params.md | 13 ++++++- packages/playwright-client/types/types.d.ts | 21 +++++----- .../src/utils/isomorphic/urlMatch.ts | 10 +++-- packages/playwright-core/types/types.d.ts | 21 +++++----- packages/playwright/src/matchers/matchers.ts | 5 +-- packages/playwright/types/test.d.ts | 8 ++-- tests/page/expect-misc.spec.ts | 4 +- utils/generate_types/overrides-test.d.ts | 8 ++-- utils/generate_types/overrides.d.ts | 7 ++-- 12 files changed, 97 insertions(+), 57 deletions(-) diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index 45cbdaaa1bf1e..0d97e060da539 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -1528,11 +1528,20 @@ routes for the [`param: url`]. ### param: BrowserContext.unroute.url * since: v1.8 +* langs: js - `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> A glob pattern, regex pattern, URL pattern, or predicate receiving [URL] used to register a routing with [`method: BrowserContext.route`]. +### param: BrowserContext.unroute.url +* since: v1.8 +* langs: python, csharp, java +- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> + +A glob pattern, regex pattern, or predicate receiving [URL] used to register a routing with +[`method: BrowserContext.route`]. + ### param: BrowserContext.unroute.handler * since: v1.8 * langs: js, python diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index f9c366e427413..531901bcbb8b7 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -2077,7 +2077,10 @@ a navigation. ### param: Frame.waitForNavigation.action = %%-csharp-wait-for-event-action-%% * since: v1.12 -### option: Frame.waitForNavigation.url = %%-wait-for-navigation-url-%% +### option: Frame.waitForNavigation.url = %%-js-wait-for-navigation-url-%% +* since: v1.8 + +### option: Frame.waitForNavigation.url = %%-python-csharp-java-wait-for-navigation-url-%% * since: v1.8 ### option: Frame.waitForNavigation.waitUntil = %%-navigation-wait-until-%% @@ -2273,7 +2276,10 @@ await frame.ClickAsync("a.delayed-navigation"); // clicking the link will indire await frame.WaitForURLAsync("**/target.html"); ``` -### param: Frame.waitForURL.url = %%-wait-for-navigation-url-%% +### param: Frame.waitForURL.url = %%-js-wait-for-navigation-url-%% +* since: v1.11 + +### param: Frame.waitForURL.url = %%-python-csharp-java-wait-for-navigation-url-%% * since: v1.11 ### option: Frame.waitForURL.timeout = %%-navigation-timeout-%% diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 5e1bef95da2ed..5a08e5204bebc 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -2177,7 +2177,7 @@ var frame = page.FrameByUrl(".*domain.*"); * langs: js - `frameSelector` <[string]|[Object]> - `name` ?<[string]> Frame name specified in the `iframe`'s `name` attribute. Optional. - - `url` ?<[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> A glob pattern, regex pattern, URL pattern or predicate receiving + - `url` ?<[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> A glob pattern, regex pattern, URL pattern, or predicate receiving frame's `url` as a [URL] object. Optional. Frame name or other frame lookup options. @@ -3691,11 +3691,17 @@ Enabling routing disables http cache. ### param: Page.route.url * since: v1.8 +* langs: js - `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> -TODO: dont support ports +A glob pattern, regex pattern, URL pattern, or predicate that receives a [URL] to match during routing. If [`option: Browser.newContext.baseURL`] is set in the context options and the provided URL is a string that does not start with `*`, it is resolved using the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. + +### param: Page.route.url +* since: v1.8 +* langs: python, csharp, java +- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> -A glob pattern, regex pattern, URL Pattern, or predicate that receives a [URL] to match during routing. If [`option: Browser.newContext.baseURL`] is set in the context options and the provided URL is a string that does not start with `*`, it is resolved using the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. +A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If [`option: Browser.newContext.baseURL`] is set in the context options and the provided URL is a string that does not start with `*`, it is resolved using the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. ### param: Page.route.handler * since: v1.8 @@ -3825,10 +3831,18 @@ await page.RouteWebSocketAsync("/ws", ws => { ### param: Page.routeWebSocket.url * since: v1.48 +* langs: js - `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the [`option: Browser.newContext.baseURL`] context option. +### param: Page.routeWebSocket.url +* since: v1.48 +* langs: python, csharp, java +- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> + +Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the [`option: Browser.newContext.baseURL`] context option. + ### param: Page.routeWebSocket.handler * since: v1.48 * langs: js, python @@ -4350,10 +4364,18 @@ the [`param: url`]. ### param: Page.unroute.url * since: v1.8 +* langs: js - `url` <[string]|[RegExp]|[URLPattern]|[function]\([URL]\):[boolean]> A glob pattern, regex pattern, URL pattern, or predicate receiving [URL] to match while routing. +### param: Page.unroute.url +* since: v1.8 +* langs: python, csharp, java +- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> + +A glob pattern, regex pattern, or predicate receiving [URL] to match while routing. + ### param: Page.unroute.handler * since: v1.8 * langs: js, python @@ -4848,7 +4870,10 @@ a navigation. ### param: Page.waitForNavigation.action = %%-csharp-wait-for-event-action-%% * since: v1.12 -### option: Page.waitForNavigation.url = %%-wait-for-navigation-url-%% +### option: Page.waitForNavigation.url = %%-js-wait-for-navigation-url-%% +* since: v1.8 + +### option: Page.waitForNavigation.url = %%-python-csharp-java-wait-for-navigation-url-%% * since: v1.8 ### option: Page.waitForNavigation.waitUntil = %%-navigation-wait-until-%% @@ -5385,7 +5410,10 @@ await page.ClickAsync("a.delayed-navigation"); // clicking the link will indirec await page.WaitForURLAsync("**/target.html"); ``` -### param: Page.waitForURL.url = %%-wait-for-navigation-url-%% +### param: Page.waitForURL.url = %%-js-wait-for-navigation-url-%% +* since: v1.11 + +### param: Page.waitForURL.url = %%-python-csharp-java-wait-for-navigation-url-%% * since: v1.11 ### option: Page.waitForURL.timeout = %%-navigation-timeout-%% diff --git a/docs/src/api/params.md b/docs/src/api/params.md index a46488ba44bae..3441a7914d8ca 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -894,10 +894,19 @@ Options to select. If the `