Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions packages/injected/src/injectedScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ export class InjectedScript {
const result = this.querySelectorAll(selector, root);
if (strict && result.length > 1)
throw this.strictModeViolationError(selector, result);
this.checkDeprecatedSelectorUsage(selector, result);
return result[0];
}

Expand Down Expand Up @@ -1228,28 +1229,44 @@ export class InjectedScript {
return oneLine(`<${element.nodeName.toLowerCase()}${attrText}>${trimStringWithEllipsis(text, 50)}</${element.nodeName.toLowerCase()}>`);
}

strictModeViolationError(selector: ParsedSelector, matches: Element[]): Error {
private _generateSelectors(elements: Element[]) {
this._evaluator.begin();
beginAriaCaches();
beginDOMCaches();
try {
// Firefox is slow to access DOM bindings in the utility world, making it very expensive to generate a lot of selectors.
const maxElements = this._isUtilityWorld && this._browserName === 'firefox' ? 2 : 10;
const infos = matches.slice(0, maxElements).map(m => ({
const infos = elements.slice(0, maxElements).map(m => ({
preview: this.previewNode(m),
selector: this.generateSelectorSimple(m),
}));
const lines = infos.map((info, i) => `\n ${i + 1}) ${info.preview} aka ${asLocator(this._sdkLanguage, info.selector)}`);
if (infos.length < matches.length)
lines.push('\n ...');
return this.createStacklessError(`strict mode violation: ${asLocator(this._sdkLanguage, stringifySelector(selector))} resolved to ${matches.length} elements:${lines.join('')}\n`);
return infos.map((info, i) => `${i + 1}) ${info.preview} aka ${asLocator(this._sdkLanguage, info.selector)}`);
} finally {
endDOMCaches();
endAriaCaches();
this._evaluator.end();
}
}

strictModeViolationError(selector: ParsedSelector, matches: Element[]): Error {
const lines = this._generateSelectors(matches).map(line => `\n ` + line);
if (lines.length < matches.length)
lines.push('\n ...');
return this.createStacklessError(`strict mode violation: ${asLocator(this._sdkLanguage, stringifySelector(selector))} resolved to ${matches.length} elements:${lines.join('')}\n`);
}

checkDeprecatedSelectorUsage(selector: ParsedSelector, matches: Element[]) {
if (!matches.length)
return;
const deperecated = selector.parts.find(part => part.name === '_react' || part.name === '_vue');
if (!deperecated)
return;
const lines = this._generateSelectors(matches).map(line => `\n ` + line);
if (lines.length < matches.length)
lines.push('\n ...');
throw this.createStacklessError(`"${deperecated.name}" selector is not supported: ${asLocator(this._sdkLanguage, stringifySelector(selector))} resolved to ${matches.length} element${matches.length === 1 ? '' : 's'}:${lines.join('')}\n`);
}

createStacklessError(message: string): Error {
if (this._browserName === 'firefox') {
const error = new Error('Error: ' + message);
Expand Down
12 changes: 9 additions & 3 deletions packages/playwright-core/src/server/frameSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ export class FrameSelectors {
if (!resolved)
throw new Error(`Failed to find frame for selector "${selector}"`);
return await resolved.injected.evaluateHandle((injected, { info, scope }) => {
return injected.querySelectorAll(info.parsed, scope || document);
const elements = injected.querySelectorAll(info.parsed, scope || document);
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return elements;
}, { info: resolved.info, scope: resolved.scope });
}

Expand All @@ -82,7 +84,9 @@ export class FrameSelectors {
throw new Error(`Failed to find frame for selector "${selector}"`);
await options.__testHookBeforeQuery?.();
return await resolved.injected.evaluate((injected, { info }) => {
return injected.querySelectorAll(info.parsed, document).length;
const elements = injected.querySelectorAll(info.parsed, document);
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return elements.length;
}, { info: resolved.info });
}

Expand All @@ -92,7 +96,9 @@ export class FrameSelectors {
if (!resolved)
return [];
const arrayHandle = await resolved.injected.evaluateHandle((injected, { info, scope }) => {
return injected.querySelectorAll(info.parsed, scope || document);
const elements = injected.querySelectorAll(info.parsed, scope || document);
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return elements;
}, { info: resolved.info, scope: resolved.scope });

const properties = await arrayHandle.getProperties();
Expand Down
4 changes: 4 additions & 0 deletions packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,7 @@ export class Frame extends SdkObject {
} else if (element) {
log = ` locator resolved to ${visible ? 'visible' : 'hidden'} ${injected.previewNode(element)}`;
}
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return { log, element, visible, attached: !!element };
}, { info: resolved.info, root: resolved.frame === this ? scope : undefined }));
const { log, visible, attached } = await progress.race(result.evaluate(r => ({ log: r.log, visible: r.visible, attached: r.attached })));
Expand Down Expand Up @@ -1115,6 +1116,7 @@ export class Frame extends SdkObject {
} else if (element) {
log = ` locator resolved to ${injected.previewNode(element)}`;
}
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return { log, success: !!element, element };
}, { info: resolved.info, callId: progress.metadata.id }));
const { log, success } = await progress.race(result.evaluate(r => ({ log: r.log, success: r.success })));
Expand Down Expand Up @@ -1444,6 +1446,8 @@ export class Frame extends SdkObject {
throw injected.strictModeViolationError(info!.parsed, elements);
else if (elements.length)
log = ` locator resolved to ${injected.previewNode(elements[0])}`;
if (info)
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return { log, ...await injected.expect(elements[0], options, elements) };
}, { info, options, callId: progress.metadata.id }));

Expand Down
Loading
Loading