chore: resolve glob to regex in local utils for language ports (#35136)

This commit is contained in:
Yury Semikhatsky 2025-03-11 15:54:58 -07:00 committed by GitHub
parent 85a66912c6
commit 74acbcea56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 74 additions and 20 deletions

View File

@ -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) {

View File

@ -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']),

View File

@ -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';
@ -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> {

View File

@ -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;

View File

@ -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) {
@ -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 {
@ -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)
@ -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 {

View File

@ -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,
@ -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 {
}

View File

@ -705,6 +705,14 @@ LocalUtils:
parameters:
stacksId: string
globToRegex:
parameters:
glob: string
baseURL: string?
webSocketUrl: boolean?
returns:
regex: string
Root:
type: interface

View File

@ -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 }) => {
@ -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();