diff --git a/docs/api.md b/docs/api.md
index 41bbc9e399f..6e4c0240e4f 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -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:
diff --git a/lib/ElementHandle.js b/lib/ElementHandle.js
index 517d57012b7..40cc8ab4e78 100644
--- a/lib/ElementHandle.js
+++ b/lib/ElementHandle.js
@@ -195,21 +195,30 @@ class ElementHandle extends JSHandle {
/**
* @param {string} expression
- * @return {!Promise}
+ * @return {!Promise>}
*/
- 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;
}
}
diff --git a/lib/FrameManager.js b/lib/FrameManager.js
index 1508e4af22b..1a3822b840f 100644
--- a/lib/FrameManager.js
+++ b/lib/FrameManager.js
@@ -307,11 +307,11 @@ class Frame {
/**
* @param {string} expression
- * @return {!Promise}
+ * @return {!Promise>}
*/
- 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;
}
diff --git a/lib/Page.js b/lib/Page.js
index 82df3cf7300..46019550e25 100644
--- a/lib/Page.js
+++ b/lib/Page.js
@@ -239,10 +239,10 @@ class Page extends EventEmitter {
/**
* @param {string} expression
- * @return {!Promise}
+ * @return {!Promise>}
*/
- async xpath(expression) {
- return this.mainFrame().xpath(expression);
+ async $x(expression) {
+ return this.mainFrame().$x(expression);
}
/**
diff --git a/test/test.js b/test/test.js
index 40a068c6ac6..4c58fdb61a4 100644
--- a/test/test.js
+++ b/test/test.js
@@ -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('