diff --git a/packages/playwright-core/src/server/isomorphic/selectorParser.ts b/packages/playwright-core/src/server/isomorphic/selectorParser.ts index d71af4c117..3606204624 100644 --- a/packages/playwright-core/src/server/isomorphic/selectorParser.ts +++ b/packages/playwright-core/src/server/isomorphic/selectorParser.ts @@ -59,25 +59,25 @@ export function parseSelector(selector: string): ParsedSelector { try { const unescaped = JSON.parse('[' + part.body + ']'); 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]; if (unescaped.length === 2) { 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]; } } 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 } }; 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 { ...part, source: part.body }; }); 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 { capture: result.capture, parts @@ -241,8 +241,8 @@ export function parseAttributeSelector(selector: string, allowUnquotedStrings: b const syntaxError = (stage: string|undefined) => { if (EOL) - throw new Error(`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(`Unexpected end of selector while parsing selector \`${selector}\``); + throw new InvalidSelectorError(`Error while parsing selector \`${selector}\` - unexpected symbol "${next()}" at position ${wp}` + (stage ? ' during ' + stage : '')); }; function skipSpaces() { @@ -313,7 +313,7 @@ export function parseAttributeSelector(selector: string, allowUnquotedStrings: b try { return new RegExp(source, flags); } 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(); if (next() === '/') { 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(); } else if (next() === `'` || next() === `"`) { value = readQuotedString(next()).slice(1, -1); @@ -403,7 +403,7 @@ export function parseAttributeSelector(selector: string, allowUnquotedStrings: b eat1(); 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 }; } @@ -420,6 +420,6 @@ export function parseAttributeSelector(selector: string, allowUnquotedStrings: b if (!EOL) syntaxError(undefined); 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; } diff --git a/tests/page/expect-boolean.spec.ts b/tests/page/expect-boolean.spec.ts index f8d0974cef..5566469881 100644 --- a/tests/page/expect-boolean.spec.ts +++ b/tests/page/expect-boolean.spec.ts @@ -311,6 +311,41 @@ test.describe('toBeVisible', () => { await page.setContent('
Text content
'); await expect(page.locator('no-such-thing')).not.toBeVisible({ timeout: 1 }); }); + + test('with frameLocator', async ({ page }) => { + await page.setContent('
'); + 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(''); + await promise; + expect(done).toBe(true); + }); + + test('with frameLocator 2', async ({ page }) => { + await page.setContent(''); + 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(''); + 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', () => { diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index d83a425e94..5fcc8a86e6 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -516,7 +516,7 @@ test('should print expected/received on Ctrl+C', async ({ runInlineTest }) => { test('times out waiting for text', async ({ page }) => { await page.setContent('
Text content
'); 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%%'); await promise; });