feat: rename page.xpath into page.$x, return an array of elements (#1713)

Fixes #1705.
This commit is contained in:
JoelEinbinder 2018-01-03 15:37:08 -08:00 committed by Andrey Lushnikov
parent d062381978
commit f183664d0f
5 changed files with 65 additions and 49 deletions

View File

@ -44,6 +44,7 @@
* [page.$$(selector)](#pageselector)
* [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args)
* [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args)
* [page.$x(expression)](#pagexexpression)
* [page.addScriptTag(options)](#pageaddscripttagoptions)
* [page.addStyleTag(options)](#pageaddstyletagoptions)
* [page.authenticate(credentials)](#pageauthenticatecredentials)
@ -94,7 +95,6 @@
* [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
* [page.waitForNavigation(options)](#pagewaitfornavigationoptions)
* [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options)
* [page.xpath(expression)](#pagexpathexpression)
- [class: Keyboard](#class-keyboard)
* [keyboard.down(key[, options])](#keyboarddownkey-options)
* [keyboard.press(key[, options])](#keyboardpresskey-options)
@ -126,6 +126,7 @@
* [frame.$$(selector)](#frameselector)
* [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args)
* [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args)
* [frame.$x(expression)](#framexexpression)
* [frame.addScriptTag(options)](#frameaddscripttagoptions)
* [frame.addStyleTag(options)](#frameaddstyletagoptions)
* [frame.childFrames()](#framechildframes)
@ -142,7 +143,6 @@
* [frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args)
* [frame.waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args)
* [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
* [frame.xpath(expression)](#framexpathexpression)
- [class: ExecutionContext](#class-executioncontext)
* [executionContext.evaluate(pageFunction, ...args)](#executioncontextevaluatepagefunction-args)
* [executionContext.evaluateHandle(pageFunction, ...args)](#executioncontextevaluatehandlepagefunction-args)
@ -157,6 +157,7 @@
- [class: ElementHandle](#class-elementhandle)
* [elementHandle.$(selector)](#elementhandleselector)
* [elementHandle.$$(selector)](#elementhandleselector)
* [elementHandle.$x(expression)](#elementhandlexexpression)
* [elementHandle.asElement()](#elementhandleaselement)
* [elementHandle.boundingBox()](#elementhandleboundingbox)
* [elementHandle.click([options])](#elementhandleclickoptions)
@ -173,7 +174,6 @@
* [elementHandle.toString()](#elementhandletostring)
* [elementHandle.type(text[, options])](#elementhandletypetext-options)
* [elementHandle.uploadFile(...filePaths)](#elementhandleuploadfilefilepaths)
* [elementHandle.xpath(expression)](#elementhandlexpathexpression)
- [class: Request](#class-request)
* [request.abort([errorCode])](#requestaborterrorcode)
* [request.continue([overrides])](#requestcontinueoverrides)
@ -531,6 +531,14 @@ const html = await page.$eval('.main-container', e => e.outerHTML);
Shortcut for [page.mainFrame().$eval(selector, pageFunction)](#frameevalselector-pagefunction-args).
#### page.$x(expression)
- `expression` <[string]> Expression to [evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate).
- returns: <[Promise]<[Array]<[ElementHandle]>>>
The method evluates the XPath expression.
Shortcut for [page.mainFrame().$x(expression)](#frameexpression)
#### page.addScriptTag(options)
- `options` <[Object]>
- `url` <[string]> Url of a script to be added.
@ -1226,13 +1234,6 @@ puppeteer.launch().then(async browser => {
```
Shortcut for [page.mainFrame().waitForSelector(selector[, options])](#framewaitforselectorselector-options).
#### page.xpath(expression)
- `expression` <[string]> Expression to [evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate).
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves to ElementHandle pointing to the page element.
The method evluates the XPath expression. If there's no such element within the page, the method will resolve to `null`.
Shortcut for [page.mainFrame().xpath(expression)](#framexpathexpression)
### class: Keyboard
@ -1518,6 +1519,12 @@ const preloadHref = await frame.$eval('link[rel=preload]', el => el.href);
const html = await frame.$eval('.main-container', e => e.outerHTML);
```
#### frame.$x(expression)
- `expression` <[string]> Expression to [evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate).
- returns: <[Promise]<[Array]<[ElementHandle]>>>
The method evluates the XPath expression.
#### frame.addScriptTag(options)
- `options` <[Object]>
- `url` <[string]> Url of a script to be added.
@ -1682,12 +1689,6 @@ puppeteer.launch().then(async browser => {
});
```
#### frame.xpath(expression)
- `expression` <[string]> Expression to [evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate).
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves to ElementHandle pointing to the frame element.
The method evluates the XPath expression. If there's no such element within the frame, the method will resolve to `null`.
### class: ExecutionContext
The class represents a context for JavaScript execution. Examples of JavaScript contexts are:
@ -1860,6 +1861,12 @@ The method runs `element.querySelector` within the page. If no element matches t
The method runs `element.querySelectorAll` within the page. If no elements match the selector, the return value resolve to `[]`.
#### elementHandle.$x(expression)
- `expression` <[string]> Expression to [evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate).
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves to ElementHandle pointing to the frame element.
The method evluates the XPath expression relative to the elementHandle. If there's no such element, the method will resolve to `null`.
#### elementHandle.asElement()
- returns: <[elementhandle]>
@ -1988,12 +1995,6 @@ await elementHandle.press('Enter');
This method expects `elementHandle` to point to an [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input).
#### elementHandle.xpath(expression)
- `expression` <[string]> Expression to [evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate).
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves to ElementHandle pointing to the frame element.
The method evluates the XPath expression relative to the elementHandle. If there's no such element, the method will resolve to `null`.
### class: Request
Whenever the page sends a request, the following events are emitted by puppeteer's page:

View File

@ -195,21 +195,30 @@ class ElementHandle extends JSHandle {
/**
* @param {string} expression
* @return {!Promise<?ElementHandle>}
* @return {!Promise<!Array<!ElementHandle>>}
*/
async xpath(expression) {
const handle = await this.executionContext().evaluateHandle(
async $x(expression) {
const arrayHandle = await this.executionContext().evaluateHandle(
(element, expression) => {
const document = element.ownerDocument || element;
return document.evaluate(expression, element, null, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue;
const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
const array = [];
let item;
while ((item = iterator.iterateNext()))
array.push(item);
return array;
},
this, expression
);
const element = handle.asElement();
if (element)
return element;
await handle.dispose();
return null;
const properties = await arrayHandle.getProperties();
await arrayHandle.dispose();
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle)
result.push(elementHandle);
}
return result;
}
}

View File

@ -307,11 +307,11 @@ class Frame {
/**
* @param {string} expression
* @return {!Promise<?ElementHandle>}
* @return {!Promise<!Array<!ElementHandle>>}
*/
async xpath(expression) {
async $x(expression) {
const document = await this._document();
const value = await document.xpath(expression);
const value = await document.$x(expression);
return value;
}

View File

@ -239,10 +239,10 @@ class Page extends EventEmitter {
/**
* @param {string} expression
* @return {!Promise<?Puppeteer.ElementHandle>}
* @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
*/
async xpath(expression) {
return this.mainFrame().xpath(expression);
async $x(expression) {
return this.mainFrame().$x(expression);
}
/**

View File

@ -1727,15 +1727,21 @@ describe('Page', function() {
});
});
describe('Path.xpath', function() {
describe('Path.$x', function() {
it('should query existing element', async({page, server}) => {
await page.setContent('<section>test</section>');
const element = await page.xpath('/html/body/section');
expect(element).toBeTruthy();
const elements = await page.$x('/html/body/section');
expect(elements[0]).toBeTruthy();
expect(elements.length).toBe(1);
});
it('should return null for non-existing element', async({page, server}) => {
const element = await page.xpath('/html/body/non-existing-element');
expect(element).toBe(null);
it('should return empty array for non-existing element', async({page, server}) => {
const element = await page.$x('/html/body/non-existing-element');
expect(element).toEqual([]);
});
it('should return multiple elements', async({page, sever}) => {
await page.setContent('<div></div><div></div>');
const elements = await page.$x('/html/body/div');
expect(elements.length).toBe(2);
});
});
@ -1930,22 +1936,22 @@ describe('Page', function() {
});
describe('ElementHandle.xpath', function() {
describe('ElementHandle.$x', function() {
it('should query existing element', 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.$('html');
const second = await html.xpath(`./body/div[contains(@class, 'second')]`);
const inner = await second.xpath(`./div[contains(@class, 'inner')]`);
const content = await page.evaluate(e => e.textContent, inner);
const second = await html.$x(`./body/div[contains(@class, 'second')]`);
const inner = await second[0].$x(`./div[contains(@class, 'inner')]`);
const content = await page.evaluate(e => e.textContent, inner[0]);
expect(content).toBe('A');
});
it('should return null for non-existing element', async({page, server}) => {
await page.setContent('<html><body><div class="second"><div class="inner">B</div></div></body></html>');
const html = await page.$('html');
const second = await html.xpath(`/div[contains(@class, 'third')]`);
expect(second).toBe(null);
const second = await html.$x(`/div[contains(@class, 'third')]`);
expect(second).toEqual([]);
});
});