fix(selectors): do not hide selector errors (#10595)
This commit is contained in:
parent
e6ef3e3680
commit
3997671ab7
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { InvalidSelectorError } from './selectorErrors';
|
||||
import * as css from './cssTokenizer';
|
||||
|
||||
// Note: '>=' is used internally for text engine to preserve backwards compatibility.
|
||||
|
|
@ -61,13 +62,13 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
|
|||
(token instanceof css.PercentageToken);
|
||||
});
|
||||
if (unsupportedToken)
|
||||
throw new Error(`Unsupported token "${unsupportedToken.toSource()}" while parsing selector "${selector}"`);
|
||||
throw new InvalidSelectorError(`Unsupported token "${unsupportedToken.toSource()}" while parsing selector "${selector}"`);
|
||||
|
||||
let pos = 0;
|
||||
const names = new Set<string>();
|
||||
|
||||
function unexpected() {
|
||||
return new Error(`Unexpected token "${tokens[pos].toSource()}" while parsing selector "${selector}"`);
|
||||
return new InvalidSelectorError(`Unexpected token "${tokens[pos].toSource()}" while parsing selector "${selector}"`);
|
||||
}
|
||||
|
||||
function skipWhitespace() {
|
||||
|
|
@ -221,9 +222,9 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
|
|||
|
||||
const result = consumeFunctionArguments();
|
||||
if (!isEOF())
|
||||
throw new Error(`Error while parsing selector "${selector}"`);
|
||||
throw new InvalidSelectorError(`Error while parsing selector "${selector}"`);
|
||||
if (result.some(arg => typeof arg !== 'object' || !('simples' in arg)))
|
||||
throw new Error(`Error while parsing selector "${selector}"`);
|
||||
throw new InvalidSelectorError(`Error while parsing selector "${selector}"`);
|
||||
return { selector: result as CSSComplexSelector[], names: Array.from(names) };
|
||||
}
|
||||
|
||||
|
|
|
|||
22
packages/playwright-core/src/server/common/selectorErrors.ts
Normal file
22
packages/playwright-core/src/server/common/selectorErrors.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export class InvalidSelectorError extends Error {
|
||||
}
|
||||
|
||||
export function isInvalidSelectorError(error: Error) {
|
||||
return error instanceof InvalidSelectorError;
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { InvalidSelectorError } from './selectorErrors';
|
||||
import { CSSComplexSelectorList, parseCSS } from './cssParser';
|
||||
|
||||
export type ParsedSelectorPart = {
|
||||
|
|
@ -66,7 +67,7 @@ export function splitSelectorByFrame(selectorText: string): ParsedSelector[] {
|
|||
const part = selector.parts[i];
|
||||
if (part.name === 'control' && part.body === 'enter-frame') {
|
||||
if (!chunk.parts.length)
|
||||
throw new Error('Selector cannot start with entering frame, select the iframe first');
|
||||
throw new InvalidSelectorError('Selector cannot start with entering frame, select the iframe first');
|
||||
result.push(chunk);
|
||||
chunk = { parts: [] };
|
||||
chunkStartIndex = i + 1;
|
||||
|
|
@ -77,10 +78,10 @@ export function splitSelectorByFrame(selectorText: string): ParsedSelector[] {
|
|||
chunk.parts.push(part);
|
||||
}
|
||||
if (!chunk.parts.length)
|
||||
throw new Error(`Selector cannot end with entering frame, while parsing selector ${selectorText}`);
|
||||
throw new InvalidSelectorError(`Selector cannot end with entering frame, while parsing selector ${selectorText}`);
|
||||
result.push(chunk);
|
||||
if (typeof selector.capture === 'number' && typeof result[result.length - 1].capture !== 'number')
|
||||
throw new Error(`Can not capture the selector before diving into the frame. Only use * after the last frame has been selected`);
|
||||
throw new InvalidSelectorError(`Can not capture the selector before diving into the frame. Only use * after the last frame has been selected`);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +131,7 @@ function parseSelectorString(selector: string): ParsedSelectorStrings {
|
|||
result.parts.push({ name, body });
|
||||
if (capture) {
|
||||
if (result.capture !== undefined)
|
||||
throw new Error(`Only one of the selectors can capture using * modifier`);
|
||||
throw new InvalidSelectorError(`Only one of the selectors can capture using * modifier`);
|
||||
result.capture = result.parts.length - 1;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import type { ElementStateWithoutStable, FrameExpectParams, InjectedScriptPoll,
|
|||
import { isSessionClosedError } from './common/protocolError';
|
||||
import { splitSelectorByFrame, stringifySelector } from './common/selectorParser';
|
||||
import { SelectorInfo } from './selectors';
|
||||
import { isInvalidSelectorError } from './common/selectorErrors';
|
||||
|
||||
type ContextData = {
|
||||
contextPromise: ManualPromise<dom.FrameExecutionContext | Error>;
|
||||
|
|
@ -1277,7 +1278,7 @@ export class Frame extends SdkObject {
|
|||
}, timeout).catch(e => {
|
||||
// Q: Why not throw upon isSessionClosedError(e) as in other places?
|
||||
// A: We want user to receive a friendly message containing the last intermediate result.
|
||||
if (js.isJavaScriptErrorInEvaluate(e))
|
||||
if (js.isJavaScriptErrorInEvaluate(e) || isInvalidSelectorError(e))
|
||||
throw e;
|
||||
return { received: controller.lastIntermediateResult(), matches: options.isNot, log: metadata.log };
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import * as frames from './frames';
|
|||
import * as js from './javascript';
|
||||
import * as types from './types';
|
||||
import { ParsedSelector, parseSelector, stringifySelector } from './common/selectorParser';
|
||||
import { InvalidSelectorError } from './common/selectorErrors';
|
||||
import { createGuid } from '../utils/utils';
|
||||
|
||||
export type SelectorInfo = {
|
||||
|
|
@ -130,7 +131,7 @@ export class Selectors {
|
|||
for (const part of parsed.parts) {
|
||||
const custom = this._engines.get(part.name);
|
||||
if (!custom && !this._builtinEngines.has(part.name))
|
||||
throw new Error(`Unknown engine "${part.name}" while parsing selector ${stringifySelector(parsed)}`);
|
||||
throw new InvalidSelectorError(`Unknown engine "${part.name}" while parsing selector ${stringifySelector(parsed)}`);
|
||||
if (custom && !custom.contentScript)
|
||||
needsMainWorld = true;
|
||||
if (this._builtinEnginesInMainWorld.has(part.name))
|
||||
|
|
|
|||
|
|
@ -294,3 +294,31 @@ test('should support toBeFocused', async ({ runInlineTest }) => {
|
|||
expect(result.passed).toBe(1);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should print unknown engine error', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('focused', async ({ page }) => {
|
||||
await expect(page.locator('row="row"]')).toBeVisible();
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.output).toContain(`Unknown engine "row" while parsing selector row="row"]`);
|
||||
});
|
||||
|
||||
test('should print syntax error', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('focused', async ({ page }) => {
|
||||
await expect(page.locator('row]')).toBeVisible();
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 });
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.output).toContain(`Unexpected token "]" while parsing selector "row]"`);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue