feat(selectors): auto-detect each selector part (#1160)
This commit is contained in:
parent
c4f55bf22c
commit
7843c29d32
20
src/dom.ts
20
src/dom.ts
|
|
@ -99,7 +99,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
||||||
async _$(selector: string, scope?: ElementHandle): Promise<ElementHandle<Element> | null> {
|
async _$(selector: string, scope?: ElementHandle): Promise<ElementHandle<Element> | null> {
|
||||||
const handle = await this.evaluateHandle(
|
const handle = await this.evaluateHandle(
|
||||||
(injected: Injected, selector: string, scope?: Node) => injected.querySelector(selector, scope || document),
|
(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())
|
if (!handle.asElement())
|
||||||
await handle.dispose();
|
await handle.dispose();
|
||||||
|
|
@ -109,7 +109,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
||||||
async _$array(selector: string, scope?: ElementHandle): Promise<js.JSHandle<Element[]>> {
|
async _$array(selector: string, scope?: ElementHandle): Promise<js.JSHandle<Element[]>> {
|
||||||
const arrayHandle = await this.evaluateHandle(
|
const arrayHandle = await this.evaluateHandle(
|
||||||
(injected: Injected, selector: string, scope?: Node) => injected.querySelectorAll(selector, scope || document),
|
(injected: Injected, selector: string, scope?: Node) => injected.querySelectorAll(selector, scope || document),
|
||||||
await this._injected(), normalizeSelector(selector), scope
|
await this._injected(), selector, scope
|
||||||
);
|
);
|
||||||
return arrayHandle;
|
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>;
|
export type Task = (context: FrameExecutionContext) => Promise<js.JSHandle>;
|
||||||
|
|
||||||
function assertPolling(polling: types.Polling) {
|
function assertPolling(polling: types.Polling) {
|
||||||
|
|
@ -438,8 +425,6 @@ export function waitForFunctionTask(selector: string | undefined, pageFunction:
|
||||||
const { polling = 'raf' } = options;
|
const { polling = 'raf' } = options;
|
||||||
assertPolling(polling);
|
assertPolling(polling);
|
||||||
const predicateBody = helper.isString(pageFunction) ? 'return (' + pageFunction + ')' : 'return (' + pageFunction + ')(...args)';
|
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) => {
|
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);
|
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 {
|
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) => {
|
return async (context: FrameExecutionContext) => context.evaluateHandle((injected: Injected, selector: string, visibility: types.Visibility, timeout: number) => {
|
||||||
const polling = visibility === 'any' ? 'mutation' : 'raf';
|
const polling = visibility === 'any' ? 'mutation' : 'raf';
|
||||||
return injected.poll(polling, selector, timeout, (element: Element | undefined): Element | boolean => {
|
return injected.poll(polling, selector, timeout, (element: Element | undefined): Element | boolean => {
|
||||||
|
|
|
||||||
|
|
@ -106,12 +106,25 @@ class Injected {
|
||||||
let start = 0;
|
let start = 0;
|
||||||
const result: ParsedSelector = [];
|
const result: ParsedSelector = [];
|
||||||
const append = () => {
|
const append = () => {
|
||||||
const part = selector.substring(start, index);
|
const part = selector.substring(start, index).trim();
|
||||||
const eqIndex = part.indexOf('=');
|
const eqIndex = part.indexOf('=');
|
||||||
if (eqIndex === -1)
|
let name: string;
|
||||||
throw new Error(`Cannot parse selector ${selector}`);
|
let body: string;
|
||||||
const name = part.substring(0, eqIndex).trim();
|
if (eqIndex !== -1 && part.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-]+$/)) {
|
||||||
const body = part.substring(eqIndex + 1);
|
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());
|
const engine = this.engines.get(name.toLowerCase());
|
||||||
if (!engine)
|
if (!engine)
|
||||||
throw new Error(`Unknown engine ${name} while parsing selector ${selector}`);
|
throw new Error(`Unknown engine ${name} while parsing selector ${selector}`);
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,11 @@ module.exports.describe = function({testRunner, expect, selectors, FFOX, CHROMIU
|
||||||
const idAttribute = await page.$eval('section[id="testAttribute"]', e => e.id);
|
const idAttribute = await page.$eval('section[id="testAttribute"]', e => e.id);
|
||||||
expect(idAttribute).toBe('testAttribute');
|
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}) => {
|
it('should accept arguments', async({page, server}) => {
|
||||||
await page.setContent('<section>hello</section>');
|
await page.setContent('<section>hello</section>');
|
||||||
const text = await page.$eval('section', (e, suffix) => e.textContent + suffix, ' world!');
|
const text = await page.$eval('section', (e, suffix) => e.textContent + suffix, ' world!');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue