feat(selectors): auto-detect each selector part (#1160)

This commit is contained in:
Dmitry Gozman 2020-02-28 14:41:32 -08:00 committed by GitHub
parent c4f55bf22c
commit 7843c29d32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 25 additions and 23 deletions

View file

@ -99,7 +99,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
async _$(selector: string, scope?: ElementHandle): Promise<ElementHandle<Element> | null> {
const handle = await this.evaluateHandle(
(injected: Injected, selector: string, scope?: Node) => injected.querySelector(selector, scope || document),
await this._injected(), normalizeSelector(selector), scope
await this._injected(), selector, scope
);
if (!handle.asElement())
await handle.dispose();
@ -109,7 +109,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
async _$array(selector: string, scope?: ElementHandle): Promise<js.JSHandle<Element[]>> {
const arrayHandle = await this.evaluateHandle(
(injected: Injected, selector: string, scope?: Node) => injected.querySelectorAll(selector, scope || document),
await this._injected(), normalizeSelector(selector), scope
await this._injected(), selector, scope
);
return arrayHandle;
}
@ -410,19 +410,6 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
}
function normalizeSelector(selector: string): string {
const eqIndex = selector.indexOf('=');
if (eqIndex !== -1 && selector.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-]+$/))
return selector;
// If selector starts with '//' or '//' prefixed with multiple opening
// parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817
if (/^\(*\/\//.test(selector))
return 'xpath=' + selector;
if (selector.startsWith('"'))
return 'text=' + selector;
return 'css=' + selector;
}
export type Task = (context: FrameExecutionContext) => Promise<js.JSHandle>;
function assertPolling(polling: types.Polling) {
@ -438,8 +425,6 @@ export function waitForFunctionTask(selector: string | undefined, pageFunction:
const { polling = 'raf' } = options;
assertPolling(polling);
const predicateBody = helper.isString(pageFunction) ? 'return (' + pageFunction + ')' : 'return (' + pageFunction + ')(...args)';
if (selector !== undefined)
selector = normalizeSelector(selector);
return async (context: FrameExecutionContext) => context.evaluateHandle((injected: Injected, selector: string | undefined, predicateBody: string, polling: types.Polling, timeout: number, ...args) => {
const innerPredicate = new Function('...args', predicateBody);
@ -452,7 +437,6 @@ export function waitForFunctionTask(selector: string | undefined, pageFunction:
}
export function waitForSelectorTask(selector: string, visibility: types.Visibility, timeout: number): Task {
selector = normalizeSelector(selector);
return async (context: FrameExecutionContext) => context.evaluateHandle((injected: Injected, selector: string, visibility: types.Visibility, timeout: number) => {
const polling = visibility === 'any' ? 'mutation' : 'raf';
return injected.poll(polling, selector, timeout, (element: Element | undefined): Element | boolean => {

View file

@ -106,12 +106,25 @@ class Injected {
let start = 0;
const result: ParsedSelector = [];
const append = () => {
const part = selector.substring(start, index);
const part = selector.substring(start, index).trim();
const eqIndex = part.indexOf('=');
if (eqIndex === -1)
throw new Error(`Cannot parse selector ${selector}`);
const name = part.substring(0, eqIndex).trim();
const body = part.substring(eqIndex + 1);
let name: string;
let body: string;
if (eqIndex !== -1 && part.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-]+$/)) {
name = part.substring(0, eqIndex).trim();
body = part.substring(eqIndex + 1);
} else if (part.startsWith('"')) {
name = 'text';
body = part;
} else if (/^\(*\/\//.test(part)) {
// If selector starts with '//' or '//' prefixed with multiple opening
// parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817
name = 'xpath';
body = part;
} else {
name = 'css';
body = part;
}
const engine = this.engines.get(name.toLowerCase());
if (!engine)
throw new Error(`Unknown engine ${name} while parsing selector ${selector}`);

View file

@ -76,6 +76,11 @@ module.exports.describe = function({testRunner, expect, selectors, FFOX, CHROMIU
const idAttribute = await page.$eval('section[id="testAttribute"]', e => e.id);
expect(idAttribute).toBe('testAttribute');
});
it('should auto-detect nested selectors', async({page, server}) => {
await page.setContent('<div foo=bar><section>43543<span>Hello<div id=target></div></span></section></div>');
const idAttribute = await page.$eval('div[foo=bar] > section >> "Hello" >> div', e => e.id);
expect(idAttribute).toBe('target');
});
it('should accept arguments', async({page, server}) => {
await page.setContent('<section>hello</section>');
const text = await page.$eval('section', (e, suffix) => e.textContent + suffix, ' world!');