From 48be99a56e3fb6271f00db28d66a5b95ce687b04 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 16 Dec 2019 20:49:18 -0800 Subject: [PATCH] feat(selectors): add id selectors (#270) --- src/dom.ts | 8 ++-- src/injected/cssSelectorEngine.ts | 4 +- .../cssSelectorEngine.webpack.config.js | 32 ---------------- src/injected/injected.ts | 37 ++++++++++++++++++- src/injected/xpathSelectorEngine.ts | 4 +- .../xpathSelectorEngine.webpack.config.js | 32 ---------------- test/queryselector.spec.js | 20 ++++++++++ utils/runWebpack.js | 2 - 8 files changed, 60 insertions(+), 79 deletions(-) delete mode 100644 src/injected/cssSelectorEngine.webpack.config.js delete mode 100644 src/injected/xpathSelectorEngine.webpack.config.js diff --git a/src/dom.ts b/src/dom.ts index e6cc789ad0..8d10de6945 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -6,8 +6,6 @@ import * as input from './input'; import * as js from './javascript'; import * as types from './types'; import * as injectedSource from './generated/injectedSource'; -import * as cssSelectorEngineSource from './generated/cssSelectorEngineSource'; -import * as xpathSelectorEngineSource from './generated/xpathSelectorEngineSource'; import * as zsSelectorEngineSource from './generated/zsSelectorEngineSource'; import { assert, helper, debugError } from './helper'; import Injected from './injected/injected'; @@ -38,10 +36,10 @@ export class FrameExecutionContext extends js.ExecutionContext { _injected(): Promise { if (!this._injectedPromise) { - const engineSources = [cssSelectorEngineSource.source, xpathSelectorEngineSource.source, zsSelectorEngineSource.source]; + const additionalEngineSources = [zsSelectorEngineSource.source]; const source = ` new (${injectedSource.source})([ - ${engineSources.join(',\n')} + ${additionalEngineSources.join(',\n')}, ]) `; this._injectedPromise = this.evaluateHandle(source); @@ -417,7 +415,7 @@ 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]+$/)) + if (eqIndex !== -1 && selector.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-]+$/)) return selector; if (selector.startsWith('//')) return 'xpath=' + selector; diff --git a/src/injected/cssSelectorEngine.ts b/src/injected/cssSelectorEngine.ts index 5e554c6b96..843b618c68 100644 --- a/src/injected/cssSelectorEngine.ts +++ b/src/injected/cssSelectorEngine.ts @@ -3,7 +3,7 @@ import { SelectorEngine, SelectorRoot } from './selectorEngine'; -const CSSEngine: SelectorEngine = { +export const CSSEngine: SelectorEngine = { name: 'css', create(root: SelectorRoot, targetElement: Element): string | undefined { @@ -74,5 +74,3 @@ const CSSEngine: SelectorEngine = { return Array.from(root.querySelectorAll(selector)); } }; - -export default CSSEngine; diff --git a/src/injected/cssSelectorEngine.webpack.config.js b/src/injected/cssSelectorEngine.webpack.config.js deleted file mode 100644 index b1c60573d4..0000000000 --- a/src/injected/cssSelectorEngine.webpack.config.js +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -const path = require('path'); -const InlineSource = require('./webpack-inline-source-plugin.js'); - -module.exports = { - entry: path.join(__dirname, 'cssSelectorEngine.ts'), - devtool: 'source-map', - module: { - rules: [ - { - test: /\.tsx?$/, - loader: 'ts-loader', - options: { - transpileOnly: true - }, - exclude: /node_modules/ - } - ] - }, - resolve: { - extensions: [ '.tsx', '.ts', '.js' ] - }, - output: { - filename: 'cssSelectorEngineSource.js', - path: path.resolve(__dirname, '../../lib/injected/generated') - }, - plugins: [ - new InlineSource(path.join(__dirname, '..', 'generated', 'cssSelectorEngineSource.ts')), - ] -}; diff --git a/src/injected/injected.ts b/src/injected/injected.ts index c5d94362af..885a64ea4c 100644 --- a/src/injected/injected.ts +++ b/src/injected/injected.ts @@ -3,6 +3,31 @@ import { SelectorEngine, SelectorRoot } from './selectorEngine'; import { Utils } from './utils'; +import { CSSEngine } from './cssSelectorEngine'; +import { XPathEngine } from './xpathSelectorEngine'; + +function createAttributeEngine(attribute: string): SelectorEngine { + const engine: SelectorEngine = { + name: attribute, + + create(root: SelectorRoot, target: Element): string | undefined { + const value = target.getAttribute(attribute); + if (!value) + return; + if (root.querySelector(`[${attribute}=${value}]`) === target) + return value; + }, + + query(root: SelectorRoot, selector: string): Element | undefined { + return root.querySelector(`[${attribute}=${selector}]`) || undefined; + }, + + queryAll(root: SelectorRoot, selector: string): Element[] { + return Array.from(root.querySelectorAll(`[${attribute}=${selector}]`)); + } + }; + return engine; +} type ParsedSelector = { engine: SelectorEngine, selector: string }[]; @@ -10,10 +35,18 @@ class Injected { readonly utils: Utils; readonly engines: Map; - constructor(engines: SelectorEngine[]) { + constructor(customEngines: SelectorEngine[]) { + const defaultEngines = [ + CSSEngine, + XPathEngine, + createAttributeEngine('id'), + createAttributeEngine('data-testid'), + createAttributeEngine('data-test-id'), + createAttributeEngine('data-test'), + ]; this.utils = new Utils(); this.engines = new Map(); - for (const engine of engines) + for (const engine of [...defaultEngines, ...customEngines]) this.engines.set(engine.name, engine); } diff --git a/src/injected/xpathSelectorEngine.ts b/src/injected/xpathSelectorEngine.ts index a4535604f0..9a1e2d2ecd 100644 --- a/src/injected/xpathSelectorEngine.ts +++ b/src/injected/xpathSelectorEngine.ts @@ -6,7 +6,7 @@ import { SelectorEngine, SelectorType, SelectorRoot } from './selectorEngine'; const maxTextLength = 80; const minMeaningfulSelectorLegth = 100; -const XPathEngine: SelectorEngine = { +export const XPathEngine: SelectorEngine = { name: 'xpath', create(root: SelectorRoot, targetElement: Element, type: SelectorType): string | undefined { @@ -175,5 +175,3 @@ function createNoText(root: SelectorRoot, targetElement: Element): string { return '/' + steps.join('/'); } - -export default XPathEngine; diff --git a/src/injected/xpathSelectorEngine.webpack.config.js b/src/injected/xpathSelectorEngine.webpack.config.js deleted file mode 100644 index 335f5c92bd..0000000000 --- a/src/injected/xpathSelectorEngine.webpack.config.js +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -const path = require('path'); -const InlineSource = require('./webpack-inline-source-plugin.js'); - -module.exports = { - entry: path.join(__dirname, 'xpathSelectorEngine.ts'), - devtool: 'source-map', - module: { - rules: [ - { - test: /\.tsx?$/, - loader: 'ts-loader', - options: { - transpileOnly: true - }, - exclude: /node_modules/ - } - ] - }, - resolve: { - extensions: [ '.tsx', '.ts', '.js' ] - }, - output: { - filename: 'xpathSelectorEngineSource.js', - path: path.resolve(__dirname, '../../lib/injected/generated') - }, - plugins: [ - new InlineSource(path.join(__dirname, '..', 'generated', 'xpathSelectorEngineSource.ts')), - ] -}; diff --git a/test/queryselector.spec.js b/test/queryselector.spec.js index 0fb9c88594..4f29d94ede 100644 --- a/test/queryselector.spec.js +++ b/test/queryselector.spec.js @@ -26,6 +26,26 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W const idAttribute = await page.$eval('css=section', e => e.id); expect(idAttribute).toBe('testAttribute'); }); + it('should work with id selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('id=testAttribute', e => e.id); + expect(idAttribute).toBe('testAttribute'); + }); + it('should work with data-test selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('data-test=foo', e => e.id); + expect(idAttribute).toBe('testAttribute'); + }); + it('should work with data-testid selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('data-testid=foo', e => e.id); + expect(idAttribute).toBe('testAttribute'); + }); + it('should work with data-test-id selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('data-test-id=foo', e => e.id); + expect(idAttribute).toBe('testAttribute'); + }); it('should work with zs selector', async({page, server}) => { await page.setContent('
43543
'); const idAttribute = await page.$eval('zs="43543"', e => e.id); diff --git a/utils/runWebpack.js b/utils/runWebpack.js index 2b8b2c962d..343da03b96 100644 --- a/utils/runWebpack.js +++ b/utils/runWebpack.js @@ -5,8 +5,6 @@ const child_process = require('child_process'); const path = require('path'); const files = [ - path.join('src', 'injected', 'cssSelectorEngine.webpack.config.js'), - path.join('src', 'injected', 'xpathSelectorEngine.webpack.config.js'), path.join('src', 'injected', 'zsSelectorEngine.webpack.config.js'), path.join('src', 'injected', 'injected.webpack.config.js'), ];