mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(selectors): text=foo selector engine (#475)
This commit is contained in:
parent
b388722777
commit
74b208cae5
@ -504,7 +504,7 @@ function normalizeSelector(selector: string): string {
|
|||||||
if (selector.startsWith('//'))
|
if (selector.startsWith('//'))
|
||||||
return 'xpath=' + selector;
|
return 'xpath=' + selector;
|
||||||
if (selector.startsWith('"'))
|
if (selector.startsWith('"'))
|
||||||
return 'zs=' + selector;
|
return 'text=' + selector;
|
||||||
return 'css=' + selector;
|
return 'css=' + selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import { SelectorEngine, SelectorRoot } from './selectorEngine';
|
|||||||
import { Utils } from './utils';
|
import { Utils } from './utils';
|
||||||
import { CSSEngine } from './cssSelectorEngine';
|
import { CSSEngine } from './cssSelectorEngine';
|
||||||
import { XPathEngine } from './xpathSelectorEngine';
|
import { XPathEngine } from './xpathSelectorEngine';
|
||||||
|
import { TextEngine } from './textSelectorEngine';
|
||||||
|
|
||||||
function createAttributeEngine(attribute: string): SelectorEngine {
|
function createAttributeEngine(attribute: string): SelectorEngine {
|
||||||
const engine: SelectorEngine = {
|
const engine: SelectorEngine = {
|
||||||
@ -53,6 +54,7 @@ class Injected {
|
|||||||
const defaultEngines = [
|
const defaultEngines = [
|
||||||
CSSEngine,
|
CSSEngine,
|
||||||
XPathEngine,
|
XPathEngine,
|
||||||
|
TextEngine,
|
||||||
createAttributeEngine('id'),
|
createAttributeEngine('id'),
|
||||||
createAttributeEngine('data-testid'),
|
createAttributeEngine('data-testid'),
|
||||||
createAttributeEngine('data-test-id'),
|
createAttributeEngine('data-test-id'),
|
||||||
|
85
src/injected/textSelectorEngine.ts
Normal file
85
src/injected/textSelectorEngine.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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)
|
||||||
|
return;
|
||||||
|
for (let child = targetElement.firstChild; child; child = child.nextSibling) {
|
||||||
|
if (child.nodeType === 3 /* Node.TEXT_NODE */) {
|
||||||
|
const text = child.nodeValue;
|
||||||
|
if (!text)
|
||||||
|
continue;
|
||||||
|
if (text.match(/^\s*[a-zA-Z0-9]+\s*$/) && TextEngine.query(root, text.trim()) === targetElement)
|
||||||
|
return text.trim();
|
||||||
|
if (TextEngine.query(root, JSON.stringify(text)) === targetElement)
|
||||||
|
return JSON.stringify(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
query(root: SelectorRoot, selector: string): Element | undefined {
|
||||||
|
const document = root instanceof Document ? root : root.ownerDocument;
|
||||||
|
if (!document)
|
||||||
|
return;
|
||||||
|
const matcher = createMatcher(selector);
|
||||||
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
||||||
|
while (walker.nextNode()) {
|
||||||
|
const node = walker.currentNode;
|
||||||
|
const element = node.parentElement;
|
||||||
|
const text = node.nodeValue;
|
||||||
|
if (element && text && matcher(text))
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
queryAll(root: SelectorRoot, selector: string): Element[] {
|
||||||
|
const result: Element[] = [];
|
||||||
|
const document = root instanceof Document ? root : root.ownerDocument;
|
||||||
|
if (!document)
|
||||||
|
return result;
|
||||||
|
const matcher = createMatcher(selector);
|
||||||
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
||||||
|
while (walker.nextNode()) {
|
||||||
|
const node = walker.currentNode;
|
||||||
|
const element = node.parentElement;
|
||||||
|
const text = node.nodeValue;
|
||||||
|
if (element && text && matcher(text))
|
||||||
|
result.push(element);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type Matcher = (text: string) => boolean;
|
||||||
|
function createMatcher(selector: string): Matcher {
|
||||||
|
if (selector[0] === '"' && selector[selector.length - 1] === '"') {
|
||||||
|
const parsed = JSON.parse(selector);
|
||||||
|
return text => text === parsed;
|
||||||
|
}
|
||||||
|
if (selector[0] === '/' && selector.lastIndexOf('/') > 0) {
|
||||||
|
const lastSlash = selector.lastIndexOf('/');
|
||||||
|
const re = new RegExp(selector.substring(1, lastSlash), selector.substring(lastSlash + 1));
|
||||||
|
return text => re.test(text);
|
||||||
|
}
|
||||||
|
selector = selector.trim();
|
||||||
|
return text => text.trim() === selector;
|
||||||
|
}
|
@ -56,6 +56,11 @@ module.exports.describe = function({testRunner, expect, product, FFOX, CHROMIUM,
|
|||||||
const idAttribute = await page.$eval('xpath=/html/body/section', e => e.id);
|
const idAttribute = await page.$eval('xpath=/html/body/section', e => e.id);
|
||||||
expect(idAttribute).toBe('testAttribute');
|
expect(idAttribute).toBe('testAttribute');
|
||||||
});
|
});
|
||||||
|
it('should work with text selector', async({page, server}) => {
|
||||||
|
await page.setContent('<section id="testAttribute">43543</section>');
|
||||||
|
const idAttribute = await page.$eval('text=43543', e => e.id);
|
||||||
|
expect(idAttribute).toBe('testAttribute');
|
||||||
|
});
|
||||||
it('should auto-detect css selector', async({page, server}) => {
|
it('should auto-detect css selector', async({page, server}) => {
|
||||||
await page.setContent('<section id="testAttribute">43543</section>');
|
await page.setContent('<section id="testAttribute">43543</section>');
|
||||||
const idAttribute = await page.$eval('section', e => e.id);
|
const idAttribute = await page.$eval('section', e => e.id);
|
||||||
@ -172,7 +177,7 @@ module.exports.describe = function({testRunner, expect, product, FFOX, CHROMIUM,
|
|||||||
const element = await page.$('//html/body/section');
|
const element = await page.$('//html/body/section');
|
||||||
expect(element).toBeTruthy();
|
expect(element).toBeTruthy();
|
||||||
});
|
});
|
||||||
it('should auto-detect zs selector', async({page, server}) => {
|
it('should auto-detect text selector', async({page, server}) => {
|
||||||
await page.setContent('<section>test</section>');
|
await page.setContent('<section>test</section>');
|
||||||
const element = await page.$('"test"');
|
const element = await page.$('"test"');
|
||||||
expect(element).toBeTruthy();
|
expect(element).toBeTruthy();
|
||||||
@ -467,4 +472,45 @@ module.exports.describe = function({testRunner, expect, product, FFOX, CHROMIUM,
|
|||||||
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 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>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('text selector', () => {
|
||||||
|
it('query', async ({page}) => {
|
||||||
|
await page.setContent(`<div>yo</div><div>ya</div><div>\nye </div>`);
|
||||||
|
expect(await page.$eval(`text=ya`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||||
|
expect(await page.$eval(`text="ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||||
|
expect(await page.$eval(`text=/^[ay]+$/`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||||
|
expect(await page.$eval(`text=/Ya/i`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||||
|
expect(await page.$eval(`text=ye`, e => e.outerHTML)).toBe('<div>\nye </div>');
|
||||||
|
|
||||||
|
await page.setContent(`<div> ye </div><div>ye</div>`);
|
||||||
|
expect(await page.$eval(`text="ye"`, e => e.outerHTML)).toBe('<div>ye</div>');
|
||||||
|
|
||||||
|
await page.setContent(`<div>yo</div><div>"ya</div><div> hello world! </div>`);
|
||||||
|
expect(await page.$eval(`text="\\"ya"`, e => e.outerHTML)).toBe('<div>"ya</div>');
|
||||||
|
expect(await page.$eval(`text=/hello/`, e => e.outerHTML)).toBe('<div> hello world! </div>');
|
||||||
|
expect(await page.$eval(`text=/^\\s*heLLo/i`, e => e.outerHTML)).toBe('<div> hello world! </div>');
|
||||||
|
|
||||||
|
await page.setContent(`<div>yo<div>ya</div>hey<div>hey</div></div>`);
|
||||||
|
expect(await page.$eval(`text=hey`, e => e.outerHTML)).toBe('<div>yo<div>ya</div>hey<div>hey</div></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(`text=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>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create', async ({page}) => {
|
||||||
|
await page.setContent(`<div>yo</div><div>"ya</div><div>ye ye</div>`);
|
||||||
|
expect(await page._createSelector('text', await page.$('div'))).toBe('yo');
|
||||||
|
expect(await page._createSelector('text', await page.$('div:nth-child(2)'))).toBe('"\\"ya"');
|
||||||
|
expect(await page._createSelector('text', await page.$('div:nth-child(3)'))).toBe('"ye ye"');
|
||||||
|
|
||||||
|
await page.setContent(`<div>yo</div><div>yo<div>ya</div>hey</div>`);
|
||||||
|
expect(await page._createSelector('text', await page.$('div:nth-child(2)'))).toBe('hey');
|
||||||
|
|
||||||
|
await page.setContent(`<div> yo <div></div>ya</div>`);
|
||||||
|
expect(await page._createSelector('text', await page.$('div'))).toBe('yo');
|
||||||
|
|
||||||
|
await page.setContent(`<div> "yo <div></div>ya</div>`);
|
||||||
|
expect(await page._createSelector('text', await page.$('div'))).toBe('" \\"yo "');
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user