feat(selectors): add id selectors (#270)

This commit is contained in:
Dmitry Gozman 2019-12-16 20:49:18 -08:00 committed by Pavel Feldman
parent 8828228702
commit 48be99a56e
8 changed files with 60 additions and 79 deletions

View File

@ -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<js.JSHandle> {
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<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]+$/))
if (eqIndex !== -1 && selector.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-]+$/))
return selector;
if (selector.startsWith('//'))
return 'xpath=' + selector;

View File

@ -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;

View File

@ -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')),
]
};

View File

@ -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<string, SelectorEngine>;
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);
}

View File

@ -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;

View File

@ -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')),
]
};

View File

@ -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('<section id="testAttribute">43543</section>');
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('<section data-test=foo id="testAttribute">43543</section>');
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('<section data-testid=foo id="testAttribute">43543</section>');
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('<section data-test-id=foo id="testAttribute">43543</section>');
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('<section id="testAttribute">43543</section>');
const idAttribute = await page.$eval('zs="43543"', e => e.id);

View File

@ -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'),
];