api(selectors): pass selector name when registering, allow file path (#1162)
This commit is contained in:
parent
d511d7dd99
commit
ac2f04f10f
17
docs/api.md
17
docs/api.md
|
|
@ -3149,12 +3149,14 @@ Contains the URL of the response.
|
|||
Selectors can be used to install custom selector engines. See [Working with selectors](#working-with-selectors) for more information.
|
||||
|
||||
<!-- GEN:toc -->
|
||||
- [selectors.register(engineFunction[, ...args])](#selectorsregisterenginefunction-args)
|
||||
- [selectors.register(name, script)](#selectorsregistername-script)
|
||||
<!-- GEN:stop -->
|
||||
|
||||
#### selectors.register(engineFunction[, ...args])
|
||||
- `engineFunction` <[function]|[string]> Function that evaluates to a selector engine instance.
|
||||
- `...args` <...[Serializable]> Arguments to pass to `engineFunction`.
|
||||
#### selectors.register(name, script)
|
||||
- `name` <[string]> Name that is used in selectors as a prefix, e.g. `{name: 'foo'}` enables `foo=myselectorbody` selectors. May only contain `[a-zA-Z0-9_]` characters.
|
||||
- `script` <[function]|[string]|[Object]> Script that evaluates to a selector engine instance.
|
||||
- `path` <[string]> Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
|
||||
- `content` <[string]> Raw script content.
|
||||
- returns: <[Promise]>
|
||||
|
||||
An example of registering selector engine that queries elements based on a tag name:
|
||||
|
|
@ -3164,9 +3166,6 @@ const { selectors, firefox } = require('playwright'); // Or 'chromium' or 'webk
|
|||
(async () => {
|
||||
// Must be a function that evaluates to a selector engine instance.
|
||||
const createTagNameEngine = () => ({
|
||||
// Selectors will be prefixed with "tag=".
|
||||
name: 'tag',
|
||||
|
||||
// Creates a selector that matches given target when queried at the root.
|
||||
// Can return undefined if unable to create one.
|
||||
create(root, target) {
|
||||
|
|
@ -3184,8 +3183,8 @@ const { selectors, firefox } = require('playwright'); // Or 'chromium' or 'webk
|
|||
}
|
||||
});
|
||||
|
||||
// Register the engine.
|
||||
await selectors.register(createTagNameEngine);
|
||||
// Register the engine. Selectors will be prefixed with "tag=".
|
||||
await selectors.register('tag', createTagNameEngine);
|
||||
|
||||
const browser = await firefox.launch();
|
||||
const page = await browser.newPage();
|
||||
|
|
|
|||
|
|
@ -84,11 +84,10 @@ Id engines are selecting based on the corresponding atrribute value. For example
|
|||
|
||||
## Custom selector engines
|
||||
|
||||
Playwright supports custom selector engines, registered with [selectors.register(engineFunction[, ...args])](api.md#selectorsregisterenginefunction-args).
|
||||
Playwright supports custom selector engines, registered with [selectors.register(name, script)](api.md#selectorsregistername-script).
|
||||
|
||||
Selector engine should have the following properties:
|
||||
|
||||
- `name` Selector name used in selector strings.
|
||||
- `create` Function to create a relative selector from `root` (root is either a `Document`, `ShadowRoot` or `Element`) to a `target` element.
|
||||
- `query` Function to query first element matching `selector` relative to the `root`.
|
||||
- `queryAll` Function to query all elements matching `selector` relative to the `root`.
|
||||
|
|
@ -97,9 +96,6 @@ An example of registering selector engine that queries elements based on a tag n
|
|||
```js
|
||||
// Must be a function that evaluates to a selector engine instance.
|
||||
const createTagNameEngine = () => ({
|
||||
// Selectors will be prefixed with "tag=".
|
||||
name: 'tag',
|
||||
|
||||
// Creates a selector that matches given target when queried at the root.
|
||||
// Can return undefined if unable to create one.
|
||||
create(root, target) {
|
||||
|
|
@ -117,8 +113,8 @@ const createTagNameEngine = () => ({
|
|||
}
|
||||
});
|
||||
|
||||
// Register the engine.
|
||||
await selectors.register(createTagNameEngine);
|
||||
// Register the engine. Selectors will be prefixed with "tag=".
|
||||
await selectors.register('tag', createTagNameEngine);
|
||||
|
||||
// Now we can use 'tag=' selectors.
|
||||
const button = await page.$('tag=button');
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
|
|||
}
|
||||
|
||||
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
|
||||
const source = await helper.evaluationScript(script, ...args);
|
||||
const source = await helper.evaluationScript(script, args);
|
||||
this._evaluateOnNewDocumentSources.push(source);
|
||||
for (const page of this._existingPages())
|
||||
await (page._delegate as CRPage).evaluateOnNewDocument(source);
|
||||
|
|
|
|||
|
|
@ -85,9 +85,12 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
|||
this._injectedPromise = undefined;
|
||||
}
|
||||
if (!this._injectedPromise) {
|
||||
const custom: string[] = [];
|
||||
for (const [name, source] of selectors._engines)
|
||||
custom.push(`{ name: '${name}', engine: (${source}) }`);
|
||||
const source = `
|
||||
new (${injectedSource.source})([
|
||||
${selectors._sources.join(',\n')}
|
||||
${custom.join(',\n')}
|
||||
])
|
||||
`;
|
||||
this._injectedPromise = this.evaluateHandle(source);
|
||||
|
|
|
|||
|
|
@ -360,7 +360,7 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
|
|||
}
|
||||
|
||||
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
|
||||
const source = await helper.evaluationScript(script, ...args);
|
||||
const source = await helper.evaluationScript(script, args);
|
||||
this._evaluateOnNewDocumentSources.push(source);
|
||||
await this._browser._connection.send('Browser.addScriptToEvaluateOnNewDocument', { browserContextId: this._browserContextId || undefined, script: source });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,13 +41,14 @@ class Helper {
|
|||
}
|
||||
}
|
||||
|
||||
static async evaluationScript(fun: Function | string | { path?: string, content?: string }, ...args: any[]): Promise<string> {
|
||||
static async evaluationScript(fun: Function | string | { path?: string, content?: string }, args: any[] = [], addSourceUrl: boolean = true): Promise<string> {
|
||||
if (!helper.isString(fun) && typeof fun !== 'function') {
|
||||
if (fun.content !== undefined) {
|
||||
fun = fun.content;
|
||||
} else if (fun.path !== undefined) {
|
||||
let contents = await platform.readFileAsync(fun.path, 'utf8');
|
||||
contents += '//# sourceURL=' + fun.path.replace(/\n/g, '');
|
||||
if (addSourceUrl)
|
||||
contents += '//# sourceURL=' + fun.path.replace(/\n/g, '');
|
||||
fun = contents;
|
||||
} else {
|
||||
throw new Error('Either path or content property must be present');
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
import { SelectorEngine, SelectorRoot } from './selectorEngine';
|
||||
|
||||
export const CSSEngine: SelectorEngine = {
|
||||
name: 'css',
|
||||
|
||||
create(root: SelectorRoot, targetElement: Element): string | undefined {
|
||||
const tokens: string[] = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ import * as types from '../types';
|
|||
|
||||
function createAttributeEngine(attribute: string): SelectorEngine {
|
||||
const engine: SelectorEngine = {
|
||||
name: attribute,
|
||||
|
||||
create(root: SelectorRoot, target: Element): string | undefined {
|
||||
const value = target.getAttribute(attribute);
|
||||
if (!value)
|
||||
|
|
@ -51,20 +49,19 @@ class Injected {
|
|||
readonly utils: Utils;
|
||||
readonly engines: Map<string, SelectorEngine>;
|
||||
|
||||
constructor(customEngines: SelectorEngine[]) {
|
||||
const defaultEngines = [
|
||||
CSSEngine,
|
||||
XPathEngine,
|
||||
TextEngine,
|
||||
createAttributeEngine('id'),
|
||||
createAttributeEngine('data-testid'),
|
||||
createAttributeEngine('data-test-id'),
|
||||
createAttributeEngine('data-test'),
|
||||
];
|
||||
constructor(customEngines: { name: string, engine: SelectorEngine}[]) {
|
||||
this.utils = new Utils();
|
||||
this.engines = new Map();
|
||||
for (const engine of [...defaultEngines, ...customEngines])
|
||||
this.engines.set(engine.name, engine);
|
||||
// Note: keep predefined names in sync with Selectors class.
|
||||
this.engines.set('css', CSSEngine);
|
||||
this.engines.set('xpath', XPathEngine);
|
||||
this.engines.set('text', TextEngine);
|
||||
this.engines.set('id', createAttributeEngine('id'));
|
||||
this.engines.set('data-testid', createAttributeEngine('data-testid'));
|
||||
this.engines.set('data-test-id', createAttributeEngine('data-test-id'));
|
||||
this.engines.set('data-test', createAttributeEngine('data-test'));
|
||||
for (const {name, engine} of customEngines)
|
||||
this.engines.set(name, engine);
|
||||
}
|
||||
|
||||
querySelector(selector: string, root: Node): Element | undefined {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ export type SelectorType = 'default' | 'notext';
|
|||
export type SelectorRoot = Element | ShadowRoot | Document;
|
||||
|
||||
export interface SelectorEngine {
|
||||
name: string;
|
||||
create(root: SelectorRoot, target: Element, type?: SelectorType): string | undefined;
|
||||
query(root: SelectorRoot, selector: string): Element | undefined;
|
||||
queryAll(root: SelectorRoot, selector: string): Element[];
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
import { SelectorEngine, SelectorType, SelectorRoot } from './selectorEngine';
|
||||
|
||||
export const TextEngine: SelectorEngine = {
|
||||
name: 'text',
|
||||
|
||||
create(root: SelectorRoot, targetElement: Element, type: SelectorType): string | undefined {
|
||||
const document = root instanceof Document ? root : root.ownerDocument;
|
||||
if (!document)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ const maxTextLength = 80;
|
|||
const minMeaningfulSelectorLegth = 100;
|
||||
|
||||
export const XPathEngine: SelectorEngine = {
|
||||
name: 'xpath',
|
||||
|
||||
create(root: SelectorRoot, targetElement: Element, type: SelectorType): string | undefined {
|
||||
const maybeDocument = root instanceof Document ? root : root.ownerDocument;
|
||||
if (!maybeDocument)
|
||||
|
|
|
|||
|
|
@ -751,8 +751,6 @@ class Engine {
|
|||
}
|
||||
|
||||
const ZSSelectorEngine: SelectorEngine = {
|
||||
name: 'zs',
|
||||
|
||||
create(root: SelectorRoot, element: Element, type?: SelectorType): string {
|
||||
return new Engine().create(root, element, type || 'default');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ export class Page extends platform.EventEmitter {
|
|||
}
|
||||
|
||||
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
|
||||
await this._delegate.evaluateOnNewDocument(await helper.evaluationScript(script, ...args));
|
||||
await this._delegate.evaluateOnNewDocument(await helper.evaluationScript(script, args));
|
||||
}
|
||||
|
||||
async setCacheEnabled(enabled: boolean = true) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { helper } from './helper';
|
|||
let selectors: Selectors;
|
||||
|
||||
export class Selectors {
|
||||
readonly _sources: string[];
|
||||
readonly _engines: Map<string, string>;
|
||||
_generation = 0;
|
||||
|
||||
static _instance() {
|
||||
|
|
@ -31,12 +31,19 @@ export class Selectors {
|
|||
}
|
||||
|
||||
constructor() {
|
||||
this._sources = [];
|
||||
this._engines = new Map();
|
||||
}
|
||||
|
||||
async register(engineFunction: string | Function, ...args: any[]) {
|
||||
const source = helper.evaluationString(engineFunction, ...args);
|
||||
this._sources.push(source);
|
||||
async register(name: string, script: string | Function | { path?: string, content?: string }): Promise<void> {
|
||||
if (!name.match(/^[a-zA-Z_0-9-]+$/))
|
||||
throw new Error('Selector engine name may only contain [a-zA-Z0-9_] characters');
|
||||
// Note: keep in sync with Injected class, and also keep 'zs' for future.
|
||||
if (['css', 'xpath', 'text', 'id', 'zs', 'data-testid', 'data-test-id', 'data-test'].includes(name))
|
||||
throw new Error(`"${name}" is a predefined selector engine`);
|
||||
const source = await helper.evaluationScript(script, [], false);
|
||||
if (this._engines.has(name))
|
||||
throw new Error(`"${name}" selector engine has been already registered`);
|
||||
this._engines.set(name, source);
|
||||
++this._generation;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo
|
|||
}
|
||||
|
||||
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
|
||||
const source = await helper.evaluationScript(script, ...args);
|
||||
const source = await helper.evaluationScript(script, args);
|
||||
this._evaluateOnNewDocumentSources.push(source);
|
||||
for (const page of this._existingPages())
|
||||
await (page._delegate as WKPage)._updateBootstrapScript();
|
||||
|
|
|
|||
10
test/assets/sectionselectorengine.js
Normal file
10
test/assets/sectionselectorengine.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
({
|
||||
create(root, target) {
|
||||
},
|
||||
query(root, selector) {
|
||||
return root.querySelector('section');
|
||||
},
|
||||
queryAll(root, selector) {
|
||||
return Array.from(root.querySelectorAll('section'));
|
||||
}
|
||||
})
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const zsSelectorEngineSource = require('../lib/generated/zsSelectorEngineSource');
|
||||
|
||||
/**
|
||||
|
|
@ -359,69 +360,74 @@ module.exports.describe = function({testRunner, expect, selectors, FFOX, CHROMIU
|
|||
|
||||
describe('zselector', () => {
|
||||
beforeAll(async () => {
|
||||
await selectors.register(zsSelectorEngineSource.source);
|
||||
try {
|
||||
await selectors.register('z', zsSelectorEngineSource.source);
|
||||
} catch (e) {
|
||||
if (!e.message.includes('has been already registered'))
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('query', async ({page}) => {
|
||||
await page.setContent(`<div>yo</div><div>ya</div><div>ye</div>`);
|
||||
expect(await page.$eval(`zs="ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`z="ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
|
||||
await page.setContent(`<div foo="baz"></div><div foo="bar space"></div>`);
|
||||
expect(await page.$eval(`zs=[foo="bar space"]`, e => e.outerHTML)).toBe('<div foo="bar space"></div>');
|
||||
expect(await page.$eval(`z=[foo="bar space"]`, e => e.outerHTML)).toBe('<div foo="bar space"></div>');
|
||||
|
||||
await page.setContent(`<div>yo<span></span></div>`);
|
||||
expect(await page.$eval(`zs=span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
expect(await page.$eval(`zs=div > span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
expect(await page.$eval(`zs=div span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
expect(await page.$eval(`zs="yo" > span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
expect(await page.$eval(`zs="yo" span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
expect(await page.$eval(`zs=span ^`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`zs=span ~ div`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`zs=span ~ "yo"`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`z=span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
expect(await page.$eval(`z=div > span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
expect(await page.$eval(`z=div span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
expect(await page.$eval(`z="yo" > span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
expect(await page.$eval(`z="yo" span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
expect(await page.$eval(`z=span ^`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`z=span ~ div`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`z=span ~ "yo"`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
|
||||
await page.setContent(`<div>yo</div><div>yo<span></span></div>`);
|
||||
expect(await page.$eval(`zs="yo"#0`, e => e.outerHTML)).toBe('<div>yo</div>');
|
||||
expect(await page.$eval(`zs="yo"#1`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`zs="yo" ~ DIV#1`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`zs=span ~ div#1`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`zs=span ~ div#0`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`zs=span ~ "yo"#1 ^ > div`, e => e.outerHTML)).toBe('<div>yo</div>');
|
||||
expect(await page.$eval(`zs=span ~ "yo"#1 ^ > div#1`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`z="yo"#0`, e => e.outerHTML)).toBe('<div>yo</div>');
|
||||
expect(await page.$eval(`z="yo"#1`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`z="yo" ~ DIV#1`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`z=span ~ div#1`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`z=span ~ div#0`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
expect(await page.$eval(`z=span ~ "yo"#1 ^ > div`, e => e.outerHTML)).toBe('<div>yo</div>');
|
||||
expect(await page.$eval(`z=span ~ "yo"#1 ^ > div#1`, e => e.outerHTML)).toBe('<div>yo<span></span></div>');
|
||||
|
||||
await page.setContent(`<div>yo<span id="s1"></span></div><div>yo<span id="s2"></span><span id="s3"></span></div>`);
|
||||
expect(await page.$eval(`zs="yo"`, e => e.outerHTML)).toBe('<div>yo<span id="s1"></span></div>');
|
||||
expect(await page.$$eval(`zs="yo"`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<div>yo<span id="s1"></span></div>\n<div>yo<span id="s2"></span><span id="s3"></span></div>');
|
||||
expect(await page.$$eval(`zs="yo"#1`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<div>yo<span id="s2"></span><span id="s3"></span></div>');
|
||||
expect(await page.$$eval(`zs="yo" ~ span`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<span id="s1"></span>\n<span id="s2"></span>\n<span id="s3"></span>');
|
||||
expect(await page.$$eval(`zs="yo"#1 ~ span`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<span id="s2"></span>\n<span id="s3"></span>');
|
||||
expect(await page.$$eval(`zs="yo" ~ span#0`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<span id="s1"></span>\n<span id="s2"></span>');
|
||||
expect(await page.$$eval(`zs="yo" ~ span#1`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<span id="s2"></span>\n<span id="s3"></span>');
|
||||
expect(await page.$eval(`z="yo"`, e => e.outerHTML)).toBe('<div>yo<span id="s1"></span></div>');
|
||||
expect(await page.$$eval(`z="yo"`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<div>yo<span id="s1"></span></div>\n<div>yo<span id="s2"></span><span id="s3"></span></div>');
|
||||
expect(await page.$$eval(`z="yo"#1`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<div>yo<span id="s2"></span><span id="s3"></span></div>');
|
||||
expect(await page.$$eval(`z="yo" ~ span`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<span id="s1"></span>\n<span id="s2"></span>\n<span id="s3"></span>');
|
||||
expect(await page.$$eval(`z="yo"#1 ~ span`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<span id="s2"></span>\n<span id="s3"></span>');
|
||||
expect(await page.$$eval(`z="yo" ~ span#0`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<span id="s1"></span>\n<span id="s2"></span>');
|
||||
expect(await page.$$eval(`z="yo" ~ span#1`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<span id="s2"></span>\n<span id="s3"></span>');
|
||||
});
|
||||
|
||||
it('create', async ({page}) => {
|
||||
await page.setContent(`<div>yo</div><div>ya</div><div>ya</div>`);
|
||||
expect(await selectors._createSelector('zs', await page.$('div'))).toBe('"yo"');
|
||||
expect(await selectors._createSelector('zs', await page.$('div:nth-child(2)'))).toBe('"ya"');
|
||||
expect(await selectors._createSelector('zs', await page.$('div:nth-child(3)'))).toBe('"ya"#1');
|
||||
expect(await selectors._createSelector('z', await page.$('div'))).toBe('"yo"');
|
||||
expect(await selectors._createSelector('z', await page.$('div:nth-child(2)'))).toBe('"ya"');
|
||||
expect(await selectors._createSelector('z', await page.$('div:nth-child(3)'))).toBe('"ya"#1');
|
||||
|
||||
await page.setContent(`<img alt="foo bar">`);
|
||||
expect(await selectors._createSelector('zs', await page.$('img'))).toBe('img[alt="foo bar"]');
|
||||
expect(await selectors._createSelector('z', await page.$('img'))).toBe('img[alt="foo bar"]');
|
||||
|
||||
await page.setContent(`<div>yo<span></span></div><span></span>`);
|
||||
expect(await selectors._createSelector('zs', await page.$('span'))).toBe('"yo"~SPAN');
|
||||
expect(await selectors._createSelector('zs', await page.$('span:nth-child(2)'))).toBe('SPAN#1');
|
||||
expect(await selectors._createSelector('z', await page.$('span'))).toBe('"yo"~SPAN');
|
||||
expect(await selectors._createSelector('z', await page.$('span:nth-child(2)'))).toBe('SPAN#1');
|
||||
});
|
||||
|
||||
it('children of various display parents', async ({page}) => {
|
||||
await page.setContent(`<body><div style='position: fixed;'><span>yo</span></div></body>`);
|
||||
expect(await selectors._createSelector('zs', await page.$('span'))).toBe('"yo"');
|
||||
expect(await selectors._createSelector('z', await page.$('span'))).toBe('"yo"');
|
||||
|
||||
await page.setContent(`<div style='position: relative;'><span>yo</span></div>`);
|
||||
expect(await selectors._createSelector('zs', await page.$('span'))).toBe('"yo"');
|
||||
expect(await selectors._createSelector('z', await page.$('span'))).toBe('"yo"');
|
||||
|
||||
// "display: none" makes all children text invisible - fallback to tag name.
|
||||
await page.setContent(`<div style='display: none;'><span>yo</span></div>`);
|
||||
expect(await selectors._createSelector('zs', await page.$('span'))).toBe('SPAN');
|
||||
expect(await selectors._createSelector('z', await page.$('span'))).toBe('SPAN');
|
||||
});
|
||||
|
||||
it('boundary', async ({page}) => {
|
||||
|
|
@ -472,18 +478,18 @@ module.exports.describe = function({testRunner, expect, selectors, FFOX, CHROMIU
|
|||
<div id=target2>hello</div>
|
||||
</div>
|
||||
</div>`);
|
||||
expect(await selectors._createSelector('zs', await page.$('#target'))).toBe('"ya"~"hey"~"hello"');
|
||||
expect(await page.$eval(`zs="ya"~"hey"~"hello"`, e => e.outerHTML)).toBe('<div id="target">hello</div>');
|
||||
expect(await page.$eval(`zs="ya"~"hey"~"unique"`, e => e.outerHTML).catch(e => e.message)).toBe('Error: failed to find element matching selector "zs="ya"~"hey"~"unique""');
|
||||
expect(await page.$$eval(`zs="ya" ~ "hey" ~ "hello"`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<div id="target">hello</div>\n<div id="target2">hello</div>');
|
||||
expect(await selectors._createSelector('z', await page.$('#target'))).toBe('"ya"~"hey"~"hello"');
|
||||
expect(await page.$eval(`z="ya"~"hey"~"hello"`, e => e.outerHTML)).toBe('<div id="target">hello</div>');
|
||||
expect(await page.$eval(`z="ya"~"hey"~"unique"`, e => e.outerHTML).catch(e => e.message)).toBe('Error: failed to find element matching selector "z="ya"~"hey"~"unique""');
|
||||
expect(await page.$$eval(`z="ya" ~ "hey" ~ "hello"`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<div id="target">hello</div>\n<div id="target2">hello</div>');
|
||||
});
|
||||
|
||||
it('should query existing element with zs selector', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/playground.html');
|
||||
await page.setContent('<html><body><div class="second"><div class="inner">A</div></div></body></html>');
|
||||
const html = await page.$('zs=html');
|
||||
const second = await html.$('zs=.second');
|
||||
const inner = await second.$('zs=.inner');
|
||||
const html = await page.$('z=html');
|
||||
const second = await html.$('z=.second');
|
||||
const inner = await second.$('z=.inner');
|
||||
const content = await page.evaluate(e => e.textContent, inner);
|
||||
expect(content).toBe('A');
|
||||
});
|
||||
|
|
@ -538,7 +544,6 @@ module.exports.describe = function({testRunner, expect, selectors, FFOX, CHROMIU
|
|||
describe('selectors.register', () => {
|
||||
it('should work', async ({page}) => {
|
||||
const createTagSelector = () => ({
|
||||
name: 'tag',
|
||||
create(root, target) {
|
||||
return target.nodeName;
|
||||
},
|
||||
|
|
@ -549,33 +554,49 @@ module.exports.describe = function({testRunner, expect, selectors, FFOX, CHROMIU
|
|||
return Array.from(root.querySelectorAll(selector));
|
||||
}
|
||||
});
|
||||
await selectors.register(`(${createTagSelector.toString()})()`);
|
||||
await selectors.register('tag', `(${createTagSelector.toString()})()`);
|
||||
await page.setContent('<div><span></span></div><div></div>');
|
||||
expect(await selectors._createSelector('tag', await page.$('div'))).toBe('DIV');
|
||||
expect(await page.$eval('tag=DIV', e => e.nodeName)).toBe('DIV');
|
||||
expect(await page.$eval('tag=SPAN', e => e.nodeName)).toBe('SPAN');
|
||||
expect(await page.$$eval('tag=DIV', es => es.length)).toBe(2);
|
||||
});
|
||||
it('should work with path', async ({page}) => {
|
||||
await selectors.register('foo', { path: path.join(__dirname, 'assets/sectionselectorengine.js') });
|
||||
await page.setContent('<section></section>');
|
||||
expect(await page.$eval('foo=whatever', e => e.nodeName)).toBe('SECTION');
|
||||
});
|
||||
it('should update', async ({page}) => {
|
||||
await page.setContent('<div><dummy id=d1></dummy></div><span><dummy id=d2></dummy></span>');
|
||||
expect(await page.$eval('div', e => e.nodeName)).toBe('DIV');
|
||||
const error = await page.$('dummy=foo').catch(e => e);
|
||||
expect(error.message).toContain('Unknown engine dummy while parsing selector dummy=foo');
|
||||
const createDummySelector = (name) => ({
|
||||
name,
|
||||
|
||||
let error = await page.$('dummy=ignored').catch(e => e);
|
||||
expect(error.message).toContain('Unknown engine dummy while parsing selector dummy=ignored');
|
||||
|
||||
const createDummySelector = () => ({
|
||||
create(root, target) {
|
||||
return target.nodeName;
|
||||
},
|
||||
query(root, selector) {
|
||||
return root.querySelector(name);
|
||||
return root.querySelector('dummy');
|
||||
},
|
||||
queryAll(root, selector) {
|
||||
return Array.from(root.querySelectorAll(name));
|
||||
return Array.from(root.querySelectorAll('dummy'));
|
||||
}
|
||||
});
|
||||
await selectors.register(createDummySelector, 'dummy');
|
||||
expect(await page.$eval('dummy=foo', e => e.id)).toBe('d1');
|
||||
expect(await page.$eval('css=span >> dummy=foo', e => e.id)).toBe('d2');
|
||||
|
||||
error = await selectors.register('$', createDummySelector).catch(e => e);
|
||||
expect(error.message).toBe('Selector engine name may only contain [a-zA-Z0-9_] characters');
|
||||
|
||||
await selectors.register('dummy', createDummySelector);
|
||||
expect(await page.$eval('dummy=ignored', e => e.id)).toBe('d1');
|
||||
expect(await page.$eval('css=span >> dummy=ignored', e => e.id)).toBe('d2');
|
||||
|
||||
error = await selectors.register('dummy', createDummySelector).catch(e => e);
|
||||
expect(error.message).toBe('"dummy" selector engine has been already registered');
|
||||
|
||||
error = await selectors.register('css', createDummySelector).catch(e => e);
|
||||
expect(error.message).toBe('"css" is a predefined selector engine');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -250,16 +250,16 @@ class TestPass {
|
|||
if (error === TimeoutError) {
|
||||
const location = `${hook.location.fileName}:${hook.location.lineNumber}:${hook.location.columnNumber}`;
|
||||
const message = `${location} - Timeout Exceeded ${hook.timeout}ms while running "${hookName}" in suite "${suite.fullName}"`;
|
||||
this._runner._didFailHook(suite, hook, hookName);
|
||||
this._runner._didFailHook(suite, hook, hookName, workerId);
|
||||
return await this._terminate(TestResult.Crashed, message, null);
|
||||
}
|
||||
if (error) {
|
||||
const location = `${hook.location.fileName}:${hook.location.lineNumber}:${hook.location.columnNumber}`;
|
||||
const message = `${location} - FAILED while running "${hookName}" in suite "${suite.fullName}"`;
|
||||
this._runner._didFailHook(suite, hook, hookName);
|
||||
this._runner._didFailHook(suite, hook, hookName, workerId);
|
||||
return await this._terminate(TestResult.Crashed, message, error);
|
||||
}
|
||||
this._runner._didCompleteHook(suite, hook, hookName);
|
||||
this._runner._didCompleteHook(suite, hook, hookName, workerId);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -495,23 +495,23 @@ class TestRunner extends EventEmitter {
|
|||
}
|
||||
|
||||
_willStartTestBody(test, workerId) {
|
||||
debug('testrunner:test')(`starting "${test.fullName}" (${test.location.fileName + ':' + test.location.lineNumber})`);
|
||||
debug('testrunner:test')(`[${workerId}] starting "${test.fullName}" (${test.location.fileName + ':' + test.location.lineNumber})`);
|
||||
}
|
||||
|
||||
_didFinishTestBody(test, workerId) {
|
||||
debug('testrunner:test')(`${test.result.toUpperCase()} "${test.fullName}" (${test.location.fileName + ':' + test.location.lineNumber})`);
|
||||
debug('testrunner:test')(`[${workerId}] ${test.result.toUpperCase()} "${test.fullName}" (${test.location.fileName + ':' + test.location.lineNumber})`);
|
||||
}
|
||||
|
||||
_willStartHook(suite, hook, hookName, workerId) {
|
||||
debug('testrunner:hook')(`"${hookName}" started for "${suite.fullName}" (${hook.location.fileName + ':' + hook.location.lineNumber})`);
|
||||
debug('testrunner:hook')(`[${workerId}] "${hookName}" started for "${suite.fullName}" (${hook.location.fileName + ':' + hook.location.lineNumber})`);
|
||||
}
|
||||
|
||||
_didFailHook(suite, hook, hookName, workerId) {
|
||||
debug('testrunner:hook')(`"${hookName}" FAILED for "${suite.fullName}" (${hook.location.fileName + ':' + hook.location.lineNumber})`);
|
||||
debug('testrunner:hook')(`[${workerId}] "${hookName}" FAILED for "${suite.fullName}" (${hook.location.fileName + ':' + hook.location.lineNumber})`);
|
||||
}
|
||||
|
||||
_didCompleteHook(suite, hook, hookName, workerId) {
|
||||
debug('testrunner:hook')(`"${hookName}" OK for "${suite.fullName}" (${hook.location.fileName + ':' + hook.location.lineNumber})`);
|
||||
debug('testrunner:hook')(`[${workerId}] "${hookName}" OK for "${suite.fullName}" (${hook.location.fileName + ':' + hook.location.lineNumber})`);
|
||||
}
|
||||
|
||||
_willTerminate(termination) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue