diff --git a/src/dom.ts b/src/dom.ts index 9f27997dd5..acf3598dda 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -99,7 +99,7 @@ export class FrameExecutionContext extends js.ExecutionContext { async _$(selector: string, scope?: ElementHandle): Promise | 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> { 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 extends js.JSHandle { } } -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; 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 => { diff --git a/src/injected/injected.ts b/src/injected/injected.ts index 30278e8fa3..e01fffea3d 100644 --- a/src/injected/injected.ts +++ b/src/injected/injected.ts @@ -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}`); diff --git a/test/queryselector.spec.js b/test/queryselector.spec.js index b4938fb4c1..31bb506407 100644 --- a/test/queryselector.spec.js +++ b/test/queryselector.spec.js @@ -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('
43543Hello
'); + 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('
hello
'); const text = await page.$eval('section', (e, suffix) => e.textContent + suffix, ' world!');