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('//'))
|
||||
return 'xpath=' + selector;
|
||||
if (selector.startsWith('"'))
|
||||
return 'zs=' + selector;
|
||||
return 'text=' + selector;
|
||||
return 'css=' + selector;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import { SelectorEngine, SelectorRoot } from './selectorEngine';
|
||||
import { Utils } from './utils';
|
||||
import { CSSEngine } from './cssSelectorEngine';
|
||||
import { XPathEngine } from './xpathSelectorEngine';
|
||||
import { TextEngine } from './textSelectorEngine';
|
||||
|
||||
function createAttributeEngine(attribute: string): SelectorEngine {
|
||||
const engine: SelectorEngine = {
|
||||
@ -53,6 +54,7 @@ class Injected {
|
||||
const defaultEngines = [
|
||||
CSSEngine,
|
||||
XPathEngine,
|
||||
TextEngine,
|
||||
createAttributeEngine('id'),
|
||||
createAttributeEngine('data-testid'),
|
||||
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);
|
||||
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}) => {
|
||||
await page.setContent('<section id="testAttribute">43543</section>');
|
||||
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');
|
||||
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>');
|
||||
const element = await page.$('"test"');
|
||||
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>');
|
||||
});
|
||||
});
|
||||
|
||||
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