chore: throw InvalidSelectorError from selector parser, add some tests (#19636)

This commit is contained in:
Dmitry Gozman 2022-12-21 14:27:35 -08:00 committed by GitHub
parent 3acf3f5cae
commit eaae8ebbf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 47 additions and 12 deletions

View File

@ -59,25 +59,25 @@ export function parseSelector(selector: string): ParsedSelector {
try { try {
const unescaped = JSON.parse('[' + part.body + ']'); const unescaped = JSON.parse('[' + part.body + ']');
if (!Array.isArray(unescaped) || unescaped.length < 1 || unescaped.length > 2 || typeof unescaped[0] !== 'string') if (!Array.isArray(unescaped) || unescaped.length < 1 || unescaped.length > 2 || typeof unescaped[0] !== 'string')
throw new Error(`Malformed selector: ${part.name}=` + part.body); throw new InvalidSelectorError(`Malformed selector: ${part.name}=` + part.body);
innerSelector = unescaped[0]; innerSelector = unescaped[0];
if (unescaped.length === 2) { if (unescaped.length === 2) {
if (typeof unescaped[1] !== 'number' || !kNestedSelectorNamesWithDistance.has(part.name)) if (typeof unescaped[1] !== 'number' || !kNestedSelectorNamesWithDistance.has(part.name))
throw new Error(`Malformed selector: ${part.name}=` + part.body); throw new InvalidSelectorError(`Malformed selector: ${part.name}=` + part.body);
distance = unescaped[1]; distance = unescaped[1];
} }
} catch (e) { } catch (e) {
throw new Error(`Malformed selector: ${part.name}=` + part.body); throw new InvalidSelectorError(`Malformed selector: ${part.name}=` + part.body);
} }
const result = { name: part.name, source: part.body, body: { parsed: parseSelector(innerSelector), distance } }; const result = { name: part.name, source: part.body, body: { parsed: parseSelector(innerSelector), distance } };
if (result.body.parsed.parts.some(part => part.name === 'internal:control' && part.body === 'enter-frame')) if (result.body.parsed.parts.some(part => part.name === 'internal:control' && part.body === 'enter-frame'))
throw new Error(`Frames are not allowed inside "${part.name}" selectors`); throw new InvalidSelectorError(`Frames are not allowed inside "${part.name}" selectors`);
return result; return result;
} }
return { ...part, source: part.body }; return { ...part, source: part.body };
}); });
if (kNestedSelectorNames.has(parts[0].name)) if (kNestedSelectorNames.has(parts[0].name))
throw new Error(`"${parts[0].name}" selector cannot be first`); throw new InvalidSelectorError(`"${parts[0].name}" selector cannot be first`);
return { return {
capture: result.capture, capture: result.capture,
parts parts
@ -241,8 +241,8 @@ export function parseAttributeSelector(selector: string, allowUnquotedStrings: b
const syntaxError = (stage: string|undefined) => { const syntaxError = (stage: string|undefined) => {
if (EOL) if (EOL)
throw new Error(`Unexpected end of selector while parsing selector \`${selector}\``); throw new InvalidSelectorError(`Unexpected end of selector while parsing selector \`${selector}\``);
throw new Error(`Error while parsing selector \`${selector}\` - unexpected symbol "${next()}" at position ${wp}` + (stage ? ' during ' + stage : '')); throw new InvalidSelectorError(`Error while parsing selector \`${selector}\` - unexpected symbol "${next()}" at position ${wp}` + (stage ? ' during ' + stage : ''));
}; };
function skipSpaces() { function skipSpaces() {
@ -313,7 +313,7 @@ export function parseAttributeSelector(selector: string, allowUnquotedStrings: b
try { try {
return new RegExp(source, flags); return new RegExp(source, flags);
} catch (e) { } catch (e) {
throw new Error(`Error while parsing selector \`${selector}\`: ${e.message}`); throw new InvalidSelectorError(`Error while parsing selector \`${selector}\`: ${e.message}`);
} }
} }
@ -369,7 +369,7 @@ export function parseAttributeSelector(selector: string, allowUnquotedStrings: b
skipSpaces(); skipSpaces();
if (next() === '/') { if (next() === '/') {
if (operator !== '=') if (operator !== '=')
throw new Error(`Error while parsing selector \`${selector}\` - cannot use ${operator} in attribute with regular expression`); throw new InvalidSelectorError(`Error while parsing selector \`${selector}\` - cannot use ${operator} in attribute with regular expression`);
value = readRegularExpression(); value = readRegularExpression();
} else if (next() === `'` || next() === `"`) { } else if (next() === `'` || next() === `"`) {
value = readQuotedString(next()).slice(1, -1); value = readQuotedString(next()).slice(1, -1);
@ -403,7 +403,7 @@ export function parseAttributeSelector(selector: string, allowUnquotedStrings: b
eat1(); eat1();
if (operator !== '=' && typeof value !== 'string') if (operator !== '=' && typeof value !== 'string')
throw new Error(`Error while parsing selector \`${selector}\` - cannot use ${operator} in attribute with non-string matching value - ${value}`); throw new InvalidSelectorError(`Error while parsing selector \`${selector}\` - cannot use ${operator} in attribute with non-string matching value - ${value}`);
return { name: jsonPath.join('.'), jsonPath, op: operator, value, caseSensitive }; return { name: jsonPath.join('.'), jsonPath, op: operator, value, caseSensitive };
} }
@ -420,6 +420,6 @@ export function parseAttributeSelector(selector: string, allowUnquotedStrings: b
if (!EOL) if (!EOL)
syntaxError(undefined); syntaxError(undefined);
if (!result.name && !result.attributes.length) if (!result.name && !result.attributes.length)
throw new Error(`Error while parsing selector \`${selector}\` - selector cannot be empty`); throw new InvalidSelectorError(`Error while parsing selector \`${selector}\` - selector cannot be empty`);
return result; return result;
} }

View File

@ -311,6 +311,41 @@ test.describe('toBeVisible', () => {
await page.setContent('<div id=node>Text content</div>'); await page.setContent('<div id=node>Text content</div>');
await expect(page.locator('no-such-thing')).not.toBeVisible({ timeout: 1 }); await expect(page.locator('no-such-thing')).not.toBeVisible({ timeout: 1 });
}); });
test('with frameLocator', async ({ page }) => {
await page.setContent('<div></div>');
const locator = page.frameLocator('iframe').locator('input');
let done = false;
const promise = expect(locator).toBeVisible().then(() => done = true);
await page.waitForTimeout(1000);
expect(done).toBe(false);
await page.setContent('<iframe srcdoc="<input>"></iframe>');
await promise;
expect(done).toBe(true);
});
test('with frameLocator 2', async ({ page }) => {
await page.setContent('<iframe></iframe>');
const locator = page.frameLocator('iframe').locator('input');
let done = false;
const promise = expect(locator).toBeVisible().then(() => done = true);
await page.waitForTimeout(1000);
expect(done).toBe(false);
await page.setContent('<iframe srcdoc="<input>"></iframe>');
await promise;
expect(done).toBe(true);
});
test('over navigation', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
let done = false;
const promise = expect(page.locator('input')).toBeVisible().then(() => done = true);
await page.waitForTimeout(1000);
expect(done).toBe(false);
await page.goto(server.PREFIX + '/input/checkbox.html');
await promise;
expect(done).toBe(true);
});
}); });
test.describe('toBeHidden', () => { test.describe('toBeHidden', () => {

View File

@ -516,7 +516,7 @@ test('should print expected/received on Ctrl+C', async ({ runInlineTest }) => {
test('times out waiting for text', async ({ page }) => { test('times out waiting for text', async ({ page }) => {
await page.setContent('<div id=node>Text content</div>'); await page.setContent('<div id=node>Text content</div>');
const promise = expect(page.locator('#node')).toHaveText('Text 2'); const promise = expect(page.locator('#node')).toHaveText('Text 2');
await new Promise(f => setTimeout(f, 500)); await new Promise(f => setTimeout(f, 1000));
console.log('\\n%%SEND-SIGINT%%'); console.log('\\n%%SEND-SIGINT%%');
await promise; await promise;
}); });