diff --git a/packages/playwright-core/src/utils/glob.ts b/packages/playwright-core/src/utils/glob.ts index 37fd223204..ae9a46b645 100644 --- a/packages/playwright-core/src/utils/glob.ts +++ b/packages/playwright-core/src/utils/glob.ts @@ -14,15 +14,17 @@ * limitations under the License. */ -const escapeGlobChars = new Set(['/', '$', '^', '+', '.', '(', ')', '=', '!', '|']); +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping +const escapedChars = new Set(['$', '^', '+', '.', '*', '(', ')', '|', '\\', '?', '{', '}', '[', ']']); export function globToRegex(glob: string): RegExp { const tokens = ['^']; - let inGroup; + let inGroup = false; for (let i = 0; i < glob.length; ++i) { const c = glob[i]; - if (escapeGlobChars.has(c)) { - tokens.push('\\' + c); + if (c === '\\' && i + 1 < glob.length) { + const char = glob[++i]; + tokens.push(escapedChars.has(char) ? '\\' + char : char); continue; } if (c === '*') { @@ -65,7 +67,7 @@ export function globToRegex(glob: string): RegExp { tokens.push('\\' + c); break; default: - tokens.push(c); + tokens.push(escapedChars.has(c) ? '\\' + c : c); } } tokens.push('$'); diff --git a/tests/page/interception.spec.ts b/tests/page/interception.spec.ts index 17098269c2..9c73c0f31b 100644 --- a/tests/page/interception.spec.ts +++ b/tests/page/interception.spec.ts @@ -90,6 +90,13 @@ it('should work with glob', async () => { expect(globToRegex('foo*').test('foo/bar.js')).toBeFalsy(); expect(globToRegex('http://localhost:3000/signin-oidc*').test('http://localhost:3000/signin-oidc/foo')).toBeFalsy(); expect(globToRegex('http://localhost:3000/signin-oidc*').test('http://localhost:3000/signin-oidcnice')).toBeTruthy(); + + expect(globToRegex('\\?')).toEqual(/^\?$/); + expect(globToRegex('\\')).toEqual(/^\\$/); + expect(globToRegex('\\\\')).toEqual(/^\\$/); + expect(globToRegex('\\[')).toEqual(/^\[$/); + expect(globToRegex('[')).toEqual(/^\[$/); + expect(globToRegex('$^+.\\*()|\\?\\{\\}[]')).toEqual(/^\$\^\+\.\*\(\)\|\?\{\}\[\]$/); }); it('should intercept network activity from worker', async function({ page, server, isAndroid, browserName, browserMajorVersion }) { diff --git a/tests/page/page-route.spec.ts b/tests/page/page-route.spec.ts index 95718d2d1b..b1ce8ba443 100644 --- a/tests/page/page-route.spec.ts +++ b/tests/page/page-route.spec.ts @@ -72,6 +72,32 @@ it('should unroute', async ({ page, server }) => { expect(intercepted).toEqual([1]); }); +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')); + server.setRoute('/index?hello', (req, res) => res.end('index?hello')); + + await page.route('**/index?hello', async (route, request) => { + await route.fulfill({ body: 'intercepted any character' }); + }); + + await page.route('**/index\\?hello', async (route, request) => { + await route.fulfill({ body: 'intercepted question mark' }); + }); + + await page.goto(server.PREFIX + '/index?hello'); + expect(await page.content()).toContain('intercepted question mark'); + + await page.goto(server.PREFIX + '/index'); + expect(await page.content()).toContain('index-no-hello'); + + await page.goto(server.PREFIX + '/index1hello'); + expect(await page.content()).toContain('intercepted any character'); + + await page.goto(server.PREFIX + '/index123hello'); + expect(await page.content()).toContain('index123hello'); +}); + it('should work when POST is redirected with 302', async ({ page, server }) => { server.setRedirect('/rredirect', '/empty.html'); await page.goto(server.EMPTY_PAGE);