Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/playwright-core/src/client/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ export class WebSocketRouteHandler {
}

public matches(wsURL: string): boolean {
return urlMatches(this._baseURL, wsURL, this.url);
return urlMatches(this._baseURL, wsURL, this.url, true);
}

public async handle(webSocketRoute: WebSocketRoute) {
Expand Down
8 changes: 8 additions & 0 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,14 @@ scheme.LocalUtilsTraceDiscardedParams = tObject({
stacksId: tString,
});
scheme.LocalUtilsTraceDiscardedResult = tOptional(tObject({}));
scheme.LocalUtilsGlobToRegexParams = tObject({
glob: tString,
baseURL: tOptional(tString),
webSocketUrl: tOptional(tBoolean),
});
scheme.LocalUtilsGlobToRegexResult = tObject({
regex: tString,
});
scheme.RootInitializer = tOptional(tObject({}));
scheme.RootInitializeParams = tObject({
sdkLanguage: tEnum(['javascript', 'python', 'java', 'csharp']),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Progress, ProgressController } from '../progress';
import { SocksInterceptor } from '../socksInterceptor';
import { WebSocketTransport } from '../transport';
import { fetchData } from '../utils/network';
import { resolveGlobToRegexPattern } from '../../utils/isomorphic/urlMatch';

import type { HarBackend } from '../harBackend';
import type { CallMetadata } from '../instrumentation';
Expand Down Expand Up @@ -120,6 +121,11 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
return { pipe, headers: transport.headers };
}, params.timeout || 0);
}

async globToRegex(params: channels.LocalUtilsGlobToRegexParams, metadata?: CallMetadata): Promise<channels.LocalUtilsGlobToRegexResult> {
const regex = resolveGlobToRegexPattern(params.baseURL, params.glob, params.webSocketUrl);
return { regex };
}
}

async function urlToWSEndpoint(progress: Progress | undefined, endpointURL: string): Promise<string> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export class WebSocketRouteDispatcher extends Dispatcher<{ guid: string }, chann
function matchesPattern(dispatcher: PageDispatcher | BrowserContextDispatcher, baseURL: string | undefined, url: string) {
for (const pattern of dispatcher._webSocketInterceptionPatterns || []) {
const urlMatch = pattern.regexSource ? new RegExp(pattern.regexSource, pattern.regexFlags) : pattern.glob;
if (urlMatches(baseURL, url, urlMatch))
if (urlMatches(baseURL, url, urlMatch, true))
return true;
}
return false;
Expand Down
50 changes: 33 additions & 17 deletions packages/playwright-core/src/utils/isomorphic/urlMatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { isString } from './stringUtils';
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping
const escapedChars = new Set(['$', '^', '+', '.', '*', '(', ')', '|', '\\', '?', '{', '}', '[', ']']);

export function globToRegex(glob: string): RegExp {
export function globToRegexPattern(glob: string): string {
const tokens = ['^'];
let inGroup = false;
for (let i = 0; i < glob.length; ++i) {
Expand Down Expand Up @@ -70,7 +70,7 @@ export function globToRegex(glob: string): RegExp {
}
}
tokens.push('$');
return new RegExp(tokens.join(''));
return tokens.join('');
}

function isRegExp(obj: any): obj is RegExp {
Expand All @@ -85,14 +85,39 @@ export function urlMatchesEqual(match1: URLMatch, match2: URLMatch) {
return match1 === match2;
}

export function urlMatches(baseURL: string | undefined, urlString: string, match: URLMatch | undefined): boolean {
export function urlMatches(baseURL: string | undefined, urlString: string, match: URLMatch | undefined, webSocketUrl?: boolean): boolean {
if (match === undefined || match === '')
return true;
if (isString(match) && !match.startsWith('*')) {
// Allow http(s) baseURL to match ws(s) urls.
if (baseURL && /^https?:\/\//.test(baseURL) && /^wss?:\/\//.test(urlString))
baseURL = baseURL.replace(/^http/, 'ws');
if (isString(match))
match = new RegExp(resolveGlobToRegexPattern(baseURL, match, webSocketUrl));
if (isRegExp(match)) {
const r = match.test(urlString);
return r;
}
const url = parseURL(urlString);
if (!url)
return false;
if (typeof match !== 'function')
throw new Error('url parameter should be string, RegExp or function');
return match(url);
}

export function resolveGlobToRegexPattern(baseURL: string | undefined, glob: string, webSocketUrl?: boolean): string {
if (webSocketUrl)
baseURL = toWebSocketBaseUrl(baseURL);
glob = resolveGlobBase(baseURL, glob);
return globToRegexPattern(glob);
}

function toWebSocketBaseUrl(baseURL: string | undefined) {
// Allow http(s) baseURL to match ws(s) urls.
if (baseURL && /^https?:\/\//.test(baseURL))
baseURL = baseURL.replace(/^http/, 'ws');
return baseURL;
}

function resolveGlobBase(baseURL: string | undefined, match: string): string {
if (!match.startsWith('*')) {
const tokenMap = new Map<string, string>();
function mapToken(original: string, replacement: string) {
if (original.length === 0)
Expand Down Expand Up @@ -123,16 +148,7 @@ export function urlMatches(baseURL: string | undefined, urlString: string, match
resolved = resolved.replace(token, original);
match = resolved;
}
if (isString(match))
match = globToRegex(match);
if (isRegExp(match))
return match.test(urlString);
const url = parseURL(urlString);
if (!url)
return false;
if (typeof match !== 'function')
throw new Error('url parameter should be string, RegExp or function');
return match(url);
return match;
}

function parseURL(url: string): URL | null {
Expand Down
13 changes: 13 additions & 0 deletions packages/protocol/src/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ export interface LocalUtilsChannel extends LocalUtilsEventTarget, Channel {
tracingStarted(params: LocalUtilsTracingStartedParams, metadata?: CallMetadata): Promise<LocalUtilsTracingStartedResult>;
addStackToTracingNoReply(params: LocalUtilsAddStackToTracingNoReplyParams, metadata?: CallMetadata): Promise<LocalUtilsAddStackToTracingNoReplyResult>;
traceDiscarded(params: LocalUtilsTraceDiscardedParams, metadata?: CallMetadata): Promise<LocalUtilsTraceDiscardedResult>;
globToRegex(params: LocalUtilsGlobToRegexParams, metadata?: CallMetadata): Promise<LocalUtilsGlobToRegexResult>;
}
export type LocalUtilsZipParams = {
zipFile: string,
Expand Down Expand Up @@ -572,6 +573,18 @@ export type LocalUtilsTraceDiscardedOptions = {

};
export type LocalUtilsTraceDiscardedResult = void;
export type LocalUtilsGlobToRegexParams = {
glob: string,
baseURL?: string,
webSocketUrl?: boolean,
};
export type LocalUtilsGlobToRegexOptions = {
baseURL?: string,
webSocketUrl?: boolean,
};
export type LocalUtilsGlobToRegexResult = {
regex: string,
};

export interface LocalUtilsEvents {
}
Expand Down
8 changes: 8 additions & 0 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,14 @@ LocalUtils:
parameters:
stacksId: string

globToRegex:
parameters:
glob: string
baseURL: string?
webSocketUrl: boolean?
returns:
regex: string

Root:
type: interface

Expand Down
5 changes: 4 additions & 1 deletion tests/page/interception.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import { test as it, expect } from './pageTest';
import { globToRegex, urlMatches } from '../../packages/playwright-core/lib/utils/isomorphic/urlMatch';
import { globToRegexPattern, urlMatches } from '../../packages/playwright-core/lib/utils/isomorphic/urlMatch';
import vm from 'vm';

it('should work with navigation @smoke', async ({ page, server }) => {
Expand Down Expand Up @@ -71,6 +71,9 @@ it('should intercept after a service worker', async ({ page, server, browserName
});

it('should work with glob', async () => {
function globToRegex(glob: string): RegExp {
return new RegExp(globToRegexPattern(glob));
}
expect(globToRegex('**/*.js').test('https://localhost:8080/foo.js')).toBeTruthy();
expect(globToRegex('**/*.css').test('https://localhost:8080/foo.js')).toBeFalsy();
expect(globToRegex('*.js').test('https://localhost:8080/foo.js')).toBeFalsy();
Expand Down