mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-27 05:46:28 +03:00
feat(strict): list ambiguous matches when throwing strict exception (#8449)
This commit is contained in:
parent
02bef1eded
commit
95be45967a
@ -22,6 +22,7 @@ export type ParsedSelectorPart = {
|
||||
};
|
||||
|
||||
export type ParsedSelector = {
|
||||
selector: string,
|
||||
parts: ParsedSelectorPart[],
|
||||
capture?: number,
|
||||
};
|
||||
@ -48,6 +49,7 @@ export function parseSelector(selector: string): ParsedSelector {
|
||||
return part;
|
||||
});
|
||||
return {
|
||||
selector,
|
||||
capture: result.capture,
|
||||
parts
|
||||
};
|
||||
|
@ -949,7 +949,7 @@ export function waitForSelectorTask(selector: SelectorInfo, state: 'attached' |
|
||||
} else {
|
||||
if (elements.length > 1) {
|
||||
if (strict)
|
||||
throw new Error(`strict mode violation: selector resolved to ${elements.length} elements.`);
|
||||
throw new Error(injected.strictModeViolationErrorMessage(parsed, elements));
|
||||
progress.log(` selector resolved to ${elements.length} elements. Proceeding with the first one.`);
|
||||
}
|
||||
progress.log(` selector resolved to ${visible ? 'visible' : 'hidden'} ${injected.previewNode(element)}`);
|
||||
|
@ -22,6 +22,7 @@ import { ParsedSelector, ParsedSelectorPart, parseSelector } from '../common/sel
|
||||
import { FatalDOMError } from '../common/domErrors';
|
||||
import { SelectorEvaluatorImpl, isVisible, parentElementOrShadowHost, elementMatchesText, TextMatcher, createRegexTextMatcher, createStrictTextMatcher, createLaxTextMatcher } from './selectorEvaluator';
|
||||
import { CSSComplexSelectorList } from '../common/cssParser';
|
||||
import { generateSelector } from './selectorGenerator';
|
||||
|
||||
type Predicate<T> = (progress: InjectedScriptProgress, continuePolling: symbol) => T | symbol;
|
||||
|
||||
@ -103,7 +104,7 @@ export class InjectedScript {
|
||||
try {
|
||||
const result = this._querySelectorRecursively([{ element: root as Element, capture: undefined }], selector, 0, new Map());
|
||||
if (strict && result.length > 1)
|
||||
throw new Error(`strict mode violation: selector resolved to ${result.length} elements.`);
|
||||
throw new Error(this.strictModeViolationErrorMessage(selector, result.map(r => r.element)));
|
||||
return result[0]?.capture || result[0]?.element;
|
||||
} finally {
|
||||
this._evaluator.end();
|
||||
@ -737,6 +738,17 @@ export class InjectedScript {
|
||||
text = text.substring(0, 49) + '\u2026';
|
||||
return oneLine(`<${element.nodeName.toLowerCase()}${attrText}>${text}</${element.nodeName.toLowerCase()}>`);
|
||||
}
|
||||
|
||||
strictModeViolationErrorMessage(selector: ParsedSelector, matches: Element[]): string {
|
||||
const infos = matches.slice(0, 10).map(m => ({
|
||||
preview: this.previewNode(m),
|
||||
selector: generateSelector(this, m).selector
|
||||
}));
|
||||
const lines = infos.map((info, i) => `\n ${i + 1}) ${info.preview} aka playwright.$("${info.selector}")`);
|
||||
if (infos.length < matches.length)
|
||||
lines.push('\n ...');
|
||||
return `strict mode violation: "${selector.selector}" resolved to ${matches.length} elements:${lines.join('')}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const autoClosingTags = new Set(['AREA', 'BASE', 'BR', 'COL', 'COMMAND', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'MENUITEM', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR']);
|
||||
|
@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type InjectedScript from '../../injected/injectedScript';
|
||||
import { elementText } from '../../injected/selectorEvaluator';
|
||||
import type InjectedScript from './injectedScript';
|
||||
import { elementText } from './selectorEvaluator';
|
||||
|
||||
type SelectorToken = {
|
||||
engine: string;
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import type InjectedScript from '../../injected/injectedScript';
|
||||
import { generateSelector } from './selectorGenerator';
|
||||
import { generateSelector } from '../../injected/selectorGenerator';
|
||||
|
||||
type ConsoleAPIInterface = {
|
||||
$: (selector: string) => void;
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import type * as actions from '../recorder/recorderActions';
|
||||
import type InjectedScript from '../../injected/injectedScript';
|
||||
import { generateSelector, querySelector } from './selectorGenerator';
|
||||
import { generateSelector, querySelector } from '../../injected/selectorGenerator';
|
||||
import type { Point } from '../../../common/types';
|
||||
import type { UIState } from '../recorder/recorderTypes';
|
||||
|
||||
|
@ -20,6 +20,8 @@ it('should fail page.textContent in strict mode', async ({ page }) => {
|
||||
await page.setContent(`<span>span1</span><div><span>target</span></div>`);
|
||||
const error = await page.textContent('span', { strict: true }).catch(e => e);
|
||||
expect(error.message).toContain('strict mode violation');
|
||||
expect(error.message).toContain('1) <span>span1</span> aka playwright.$("text=span1")');
|
||||
expect(error.message).toContain('2) <span>target</span> aka playwright.$("text=target")');
|
||||
});
|
||||
|
||||
it('should fail page.getAttribute in strict mode', async ({ page }) => {
|
||||
@ -32,6 +34,8 @@ it('should fail page.fill in strict mode', async ({ page }) => {
|
||||
await page.setContent(`<input></input><div><input></input></div>`);
|
||||
const error = await page.fill('input', 'text', { strict: true }).catch(e => e);
|
||||
expect(error.message).toContain('strict mode violation');
|
||||
expect(error.message).toContain('1) <input/> aka playwright.$("input")');
|
||||
expect(error.message).toContain('2) <input/> aka playwright.$("div input")');
|
||||
});
|
||||
|
||||
it('should fail page.$ in strict mode', async ({ page }) => {
|
||||
@ -50,4 +54,6 @@ it('should fail page.dispatchEvent in strict mode', async ({ page }) => {
|
||||
await page.setContent(`<span></span><div><span></span></div>`);
|
||||
const error = await page.dispatchEvent('span', 'click', {}, { strict: true }).catch(e => e);
|
||||
expect(error.message).toContain('strict mode violation');
|
||||
expect(error.message).toContain('1) <span></span> aka playwright.$("span")');
|
||||
expect(error.message).toContain('2) <span></span> aka playwright.$("div span")');
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user