feat(urlMatch): simplify the url match

This commit is contained in:
Pavel Feldman 2019-12-19 22:55:29 -08:00
parent a4cb386b70
commit 55ad47c047
4 changed files with 32 additions and 139 deletions

View file

@ -17,6 +17,7 @@
import * as debug from 'debug';
import * as types from './types';
import * as URL from 'url';
import { TimeoutError } from './errors';
export const debugError = debug(`playwright:error`);
@ -167,81 +168,37 @@ class Helper {
throw new Error(`url match field "${name}" must be a string or a RegExp, got ${typeof match}`);
}
static searchParamsMatch(params: URLSearchParams, match: types.SearchParamsMatch, strict: boolean, name: string): boolean {
static queryParamsMatch(params: { [key: string]: string | string[] | undefined; }, match: types.URLQueryParamsMatch, name: string): boolean {
if (typeof match !== 'object' || match === null)
throw new Error(`url match field "${name}" must be an object, got ${typeof match}`);
const keys = new Set((params as any).keys()) as Set<string>;
if (strict && keys.size !== Object.keys(match).length)
return false;
for (const key of keys) {
let expected = [];
if (key in match) {
let keyMatch = match[key];
if (!Array.isArray(keyMatch))
keyMatch = [keyMatch];
expected = keyMatch;
} else if (!strict) {
continue;
}
const values = params.getAll(key);
if (strict && values.length !== expected.length)
for (const key of Object.keys(match)) {
const value = params[key];
if (!value)
return false;
for (const v of values) {
if (typeof value !== 'string') {
let found = false;
for (const e of expected) {
if (helper.stringMatches(v, e, name + '.' + key)) {
found = true;
break;
}
}
for (const v of value)
found = found || helper.stringMatches(v, match[key], name + '.' + key);
if (!found)
return false;
} else {
if (!helper.stringMatches(value, match[key], name + '.' + key))
return false;
}
}
return true;
}
static urlMatches(urlString: string, match: types.URLMatch): boolean {
let url;
let url: URL.UrlWithParsedQuery;
try {
url = new URL(urlString);
url = URL.parse(urlString, true);
} catch (e) {
return urlString === match.url &&
match.hash === undefined &&
match.host === undefined &&
match.hostname === undefined &&
match.origin === undefined &&
match.password === undefined &&
match.pathname === undefined &&
match.port === undefined &&
match.protocol === undefined &&
match.search === undefined &&
match.searchParams === undefined &&
match.username === undefined;
return urlString === match.url && !match.queryParams;
}
if (match.url !== undefined && !helper.stringMatches(urlString, match.url, 'url'))
return false;
if (match.hash !== undefined && !helper.stringMatches(url.hash, match.hash, 'hash'))
return false;
if (match.host !== undefined && !helper.stringMatches(url.host, match.host, 'host'))
return false;
if (match.hostname !== undefined && !helper.stringMatches(url.hostname, match.hostname, 'hostname'))
return false;
if (match.origin !== undefined && !helper.stringMatches(url.origin, match.origin, 'origin'))
return false;
if (match.password !== undefined && !helper.stringMatches(url.password, match.password, 'password'))
return false;
if (match.pathname !== undefined && !helper.stringMatches(url.pathname, match.pathname, 'pathname'))
return false;
if (match.port !== undefined && !helper.stringMatches(url.port, match.port, 'port'))
return false;
if (match.protocol !== undefined && !helper.stringMatches(url.protocol, match.protocol, 'protocol'))
return false;
if (match.search !== undefined && !helper.stringMatches(url.search, match.search, 'search'))
return false;
if (match.username !== undefined && !helper.stringMatches(url.username, match.username, 'username'))
return false;
if (match.searchParams !== undefined && !helper.searchParamsMatch(url.searchParams, match.searchParams, !!match.strictSearchParams, 'searchParams'))
if (match.queryParams && !helper.queryParamsMatch(url.query, match.queryParams, 'queryParams'))
return false;
return true;
}

View file

@ -50,19 +50,8 @@ export type Viewport = {
hasTouch?: boolean;
};
export type SearchParamsMatch = { [key: string]: string | RegExp | (string | RegExp)[] };
export type URLQueryParamsMatch = { [key: string]: string | RegExp };
export type URLMatch = {
url?: string | RegExp,
hash?: string | RegExp,
host?: string | RegExp,
hostname?: string | RegExp,
origin?: string | RegExp,
password?: string | RegExp,
pathname?: string | RegExp,
port?: string | RegExp,
protocol?: string | RegExp,
search?: string | RegExp,
strictSearchParams?: boolean,
searchParams?: SearchParamsMatch,
username?: string | RegExp,
queryParams?: URLQueryParamsMatch,
};

View file

@ -668,35 +668,31 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROME
let response1 = null;
const response1Promise = page.waitForNavigation({ url: /one-style\.html/ }).then(response => response1 = response);
let response2 = null;
const response2Promise = page.waitForNavigation({ pathname: '/frame.html' }).then(response => response2 = response);
let response3 = null;
const response3Promise = page.waitForNavigation({ searchParams: { 'foo': 'bar' }, strictSearchParams: true }).then(response => response3 = response);
const response2Promise = page.waitForNavigation({ queryParams: { 'foo': 'bar' }}).then(response => response2 = response);
expect(response1).toBe(null);
expect(response2).toBe(null);
expect(response3).toBe(null);
await page.goto(server.EMPTY_PAGE);
expect(response1).toBe(null);
expect(response2).toBe(null);
expect(response3).toBe(null);
await page.goto(server.PREFIX + '/frame.html');
expect(response1).toBe(null);
await response2Promise;
expect(response2).not.toBe(null);
expect(response3).toBe(null);
expect(response2).toBe(null);
await page.goto(server.PREFIX + '/one-style.html');
await response1Promise;
expect(response1).not.toBe(null);
expect(response2).not.toBe(null);
expect(response3).toBe(null);
expect(response2).toBe(null);
await page.goto(server.PREFIX + '/frame.html?foo=bar');
await response3Promise;
await response2Promise;
expect(response1).not.toBe(null);
expect(response2).not.toBe(null);
expect(response3).not.toBe(null);
await page.goto(server.PREFIX + '/empty.html');
expect(response1.url()).toBe(server.PREFIX + '/one-style.html');
expect(response2.url()).toBe(server.PREFIX + '/frame.html');
expect(response3.url()).toBe(server.PREFIX + '/frame.html?foo=bar');
expect(response2.url()).toBe(server.PREFIX + '/frame.html?foo=bar');
});
});

View file

@ -344,47 +344,10 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
]);
expect(request.url()).toBe(server.PREFIX + '/digits/1.png');
});
it('should work with pathname match', async({page, server}) => {
it('should work with query params match', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([
page.waitForRequest({ pathname: '/digits/2.png' }),
page.evaluate(() => {
fetch('/digits/1.png');
fetch('/digits/2.png');
fetch('/digits/3.png');
})
]);
expect(request.url()).toBe(server.PREFIX + '/digits/2.png');
});
it('should work with multiple matches', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([
page.waitForRequest({ pathname: '/digits/2.png', url: /\d\.png/, port: String(server.PORT) }),
page.evaluate(() => {
fetch('/digits/1.png');
fetch('/digits/2.png');
fetch('/digits/3.png');
})
]);
expect(request.url()).toBe(server.PREFIX + '/digits/2.png');
});
it('should work with strict search params match', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([
page.waitForRequest({ searchParams: { 'foo': [/^baz$/, 'bar'], 'bar': 'foo' }, strictSearchParams: true }),
page.evaluate(() => {
fetch('/digits/2.png?foo=bar&foo=baz&bar=foo&key=value');
fetch('/digits/1.png?foo=bar&bar=foo');
fetch('/digits/4.png?foo=bar&bar=foo&foo=baz');
fetch('/digits/3.png?bar=foo');
})
]);
expect(request.url()).toBe(server.PREFIX + '/digits/4.png?foo=bar&bar=foo&foo=baz');
});
it('should work with relaxed search params match', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([
page.waitForRequest({ searchParams: { 'foo': ['bar', /^baz$/], 'bar': 'foo' }, url: /\.png/ }),
page.waitForRequest({ queryParams: { 'foo': /baz|bar/ }, url: /\.png/ }),
page.evaluate(() => {
fetch('/digits/1.png?key=value&foo=something');
fetch('/digits/2.png?foo=baz');
@ -402,15 +365,15 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
]);
expect(error.message).toBe('url match field "url" must be a string or a RegExp, got object');
});
it('should throw for incorrect searchParams match', async({page, server}) => {
it('should throw for incorrect queryParams match', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [error] = await Promise.all([
page.waitForRequest({ searchParams: { 'foo': 123 }, url: /\.png/ }).catch(e => e),
page.waitForRequest({ queryParams: { 'foo': 123 }, url: /\.png/ }).catch(e => e),
page.evaluate(() => {
fetch('/digits/1.png?foo=bar');
})
]);
expect(error.message).toBe('url match field "searchParams.foo" must be a string or a RegExp, got number');
expect(error.message).toBe('url match field "queryParams.foo" must be a string or a RegExp, got number');
});
});
@ -462,18 +425,6 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
]);
expect(response.url()).toBe(server.PREFIX + '/digits/2.png');
});
it('should work with multiple matches', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [response] = await Promise.all([
page.waitForResponse({ pathname: '/digits/2.png', url: /\d\.png/, port: String(server.PORT) }),
page.evaluate(() => {
fetch('/digits/1.png');
fetch('/digits/2.png');
fetch('/digits/3.png');
})
]);
expect(response.url()).toBe(server.PREFIX + '/digits/2.png');
});
});
describe('Page.exposeFunction', function() {