feat(selectors): auto-detect xpath starting with ".." (#3239)

This commit is contained in:
Dmitry Gozman 2020-07-30 16:21:48 -07:00 committed by GitHub
parent 235c5df8de
commit 2f95b6e34e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 15 additions and 4 deletions

View file

@ -4452,7 +4452,7 @@ Selector describes an element in the page. It can be used to obtain `ElementHand
Selector has the following format: `engine=body [>> engine=body]*`. Here `engine` is one of the supported [selector engines](selectors.md) (e.g. `css` or `xpath`), and `body` is a selector body in the format of the particular engine. When multiple `engine=body` clauses are present (separated by `>>`), next one is queried relative to the previous one's result.
For convenience, selectors in the wrong format are heuristically converted to the right format:
- selector starting with `//` is assumed to be `xpath=selector`;
- selector starting with `//` or `..` is assumed to be `xpath=selector`;
- selector starting and ending with a quote (either `"` or `'`) is assumed to be `text=selector`;
- otherwise selector is assumed to be `css=selector`.
@ -4478,6 +4478,9 @@ const handle = await page.$('//html/body/div');
// converted to 'text="foo"'
const handle = await page.$('"foo"');
// queries '../span' xpath selector starting with the result of 'div' css selector
const handle = await page.$('div >> ../span');
// queries 'span' css selector inside the div handle
const handle = await divHandle.$('css=span');
```

View file

@ -31,7 +31,7 @@ document
Selector engine name can be prefixed with `*` to capture element that matches the particular clause instead of the last one. For example, `css=article >> text=Hello` captures the element with the text `Hello`, and `*css=article >> text=Hello` (note the `*`) captures the `article` element that contains some element with the text `Hello`.
For convenience, selectors in the wrong format are heuristically converted to the right format:
- Selector starting with `//` is assumed to be `xpath=selector`. Example: `page.click('//html')` is converted to `page.click('xpath=//html')`.
- Selector starting with `//` or `..` is assumed to be `xpath=selector`. Example: `page.click('//html')` is converted to `page.click('xpath=//html')`.
- Selector starting and ending with a quote (either `"` or `'`) is assumed to be `text=selector`. Example: `page.click('"foo"')` is converted to `page.click('text="foo"')`.
- Otherwise, selector is assumed to be `css=selector`. Example: `page.click('div')` is converted to `page.click('css=div')`.
@ -108,7 +108,7 @@ Note that `<open mode shadow root>` is not an html element, but rather a shadow
XPath engine is equivalent to [`Document.evaluate`](https://developer.mozilla.org/en/docs/Web/API/Document/evaluate). Example: `xpath=//html/body`.
Malformed selector starting with `//` is assumed to be an xpath selector. For example, Playwright converts `page.$('//html/body')` to `page.$('xpath=//html/body')`.
Malformed selector starting with `//` or `..` is assumed to be an xpath selector. For example, Playwright converts `page.$('//html/body')` to `page.$('xpath=//html/body')`.
Note that `xpath` does not pierce shadow roots.

View file

@ -43,9 +43,10 @@ export function parseSelector(selector: string): ParsedSelector {
} else if (part.length > 1 && part[0] === "'" && part[part.length - 1] === "'") {
name = 'text';
body = part;
} else if (/^\(*\/\//.test(part)) {
} else if (/^\(*\/\//.test(part) || part.startsWith('..')) {
// If selector starts with '//' or '//' prefixed with multiple opening
// parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817
// If selector starts with '..', consider xpath as well.
name = 'xpath';
body = part;
} else {

View file

@ -263,6 +263,13 @@ describe('Page.$', function() {
const element = await page.$('(//section)[1]');
expect(element).toBeTruthy();
});
it('should auto-detect xpath selector starting with ..', async({page, server}) => {
await page.setContent('<div><section>test</section><span></span></div>');
const span = await page.$('"test" >> ../span');
expect(await span.evaluate(e => e.nodeName)).toBe('SPAN');
const div = await page.$('"test" >> ..');
expect(await div.evaluate(e => e.nodeName)).toBe('DIV');
});
it('should auto-detect text selector', async({page, server}) => {
await page.setContent('<section>test</section>');
const element = await page.$('"test"');