Skip to content

Commit

Permalink
Implement page.$$ method (puppeteer#463)
Browse files Browse the repository at this point in the history
This patch implements page.$$ which runs document.querySelectorAll
in page and returns results as an array of ElementHandle instances.

Fixes puppeteer#384.
  • Loading branch information
aslushnikov authored Aug 23, 2017
1 parent 7e1f2f0 commit 151d512
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 3 deletions.
19 changes: 17 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
+ [event: 'requestfinished'](#event-requestfinished)
+ [event: 'response'](#event-response)
+ [page.$(selector)](#pageselector)
+ [page.$$(selector)](#pageselector)
+ [page.addScriptTag(url)](#pageaddscripttagurl)
+ [page.click(selector[, options])](#pageclickselector-options)
+ [page.close()](#pageclose)
Expand Down Expand Up @@ -85,6 +86,7 @@
+ [dialog.type](#dialogtype)
* [class: Frame](#class-frame)
+ [frame.$(selector)](#frameselector)
+ [frame.$$(selector)](#frameselector)
+ [frame.addScriptTag(url)](#frameaddscripttagurl)
+ [frame.childFrames()](#framechildframes)
+ [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
Expand Down Expand Up @@ -297,6 +299,14 @@ The method runs `document.querySelector` within the page. If no element matches

Shortcut for [page.mainFrame().$(selector)](#frameselector).

#### page.$$(selector)
- `selector` <[string]> Selector to query page for
- returns: <[Promise]<[Array]<[ElementHandle]>>>

The method runs `document.querySelectorAll` within the page. If no elements match the selector, the return value resolve to `[]`.

Shortcut for [page.mainFrame().$$(selector)](#frameselector-1).

#### page.addScriptTag(url)
- `url` <[string]> Url of the `<script>` tag
- returns: <[Promise]> which resolves when the script's onload fires.
Expand Down Expand Up @@ -976,10 +986,15 @@ puppeteer.launch().then(async browser => {

#### frame.$(selector)
- `selector` <[string]> Selector to query page for
- returns: <[Promise]<[ElementHandle]>> Promise which resolves to ElementHandle pointing to the page element.
- returns: <[Promise]<[ElementHandle]>> Promise which resolves to ElementHandle pointing to the frame element.

The method queries frame for the selector. If there's no such element within the frame, the method will resolve to `null`.

The method queries page for the selector. If there's no such element within the page, the method will resolve to `null`.
#### frame.$$(selector)
- `selector` <[string]> Selector to query page for
- returns: <[Promise]<[Array]<[ElementHandle]>>> Promise which resolves to ElementHandles pointing to the frame elements.

The method runs `document.querySelectorAll` within the frame. If no elements match the selector, the return value resolve to `[]`.

#### frame.addScriptTag(url)
- `url` <[string]> Url of a script to be added
Expand Down
25 changes: 24 additions & 1 deletion lib/FrameManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,33 @@ class Frame {
const remoteObject = await this._rawEvaluate(selector => document.querySelector(selector), selector);
if (remoteObject.subtype === 'node')
return new ElementHandle(this._client, remoteObject, this._mouse);
helper.releaseObject(this._client, remoteObject);
await helper.releaseObject(this._client, remoteObject);
return null;
}

/**
* @param {string} selector
* @return {!Promise<!Array<!ElementHandle>>}
*/
async $$(selector) {
const remoteObject = await this._rawEvaluate(selector => Array.from(document.querySelectorAll(selector)), selector);
const response = await this._client.send('Runtime.getProperties', {
objectId: remoteObject.objectId,
ownProperties: true
});
const properties = response.result;
const result = [];
const releasePromises = [helper.releaseObject(this._client, remoteObject)];
for (const property of properties) {
if (property.enumerable && property.value.subtype === 'node')
result.push(new ElementHandle(this._client, property.value, this._mouse));
else
releasePromises.push(helper.releaseObject(this._client, property.value));
}
await Promise.all(releasePromises);
return result;
}

/**
* @param {function()|string} pageFunction
* @param {!Array<*>} args
Expand Down
8 changes: 8 additions & 0 deletions lib/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ class Page extends EventEmitter {
return this.mainFrame().$(selector);
}

/**
* @param {string} selector
* @return {!Promise<!Array<!ElementHandle>>}
*/
async $$(selector) {
return this.mainFrame().$$(selector);
}

/**
* @param {string} url
* @return {!Promise}
Expand Down
14 changes: 14 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,20 @@ describe('Page', function() {
expect(element).toBe(null);
}));
});
describe('Page.$$', function() {
it('should query existing elements', SX(async function() {
await page.setContent('<div>A</div><br/><div>B</div>');
const elements = await page.$$('div');
expect(elements.length).toBe(2);
const promises = elements.map(element => element.evaluate(e => e.textContent));
expect(await Promise.all(promises)).toEqual(['A', 'B']);
}));
it('should return ampty array if nothing is found', SX(async function() {
await page.goto(EMPTY_PAGE);
const elements = await page.$$('div');
expect(elements.length).toBe(0);
}));
});

describe('ElementHandle.evaluate', function() {
it('should work', SX(async function() {
Expand Down

0 comments on commit 151d512

Please sign in to comment.