Skip to content

Commit

Permalink
feat(ElementHandle): add ElementHandle.$ and ElementHandle.$$ (#1151)
Browse files Browse the repository at this point in the history
This patch adds `ElementHandle.$` and `ElementHandle.$$` methods to query nested
elements.

Fixes #508
  • Loading branch information
sheerun authored and aslushnikov committed Oct 27, 2017
1 parent 9e39f5f commit 5ffbd0d
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 1 deletion.
16 changes: 15 additions & 1 deletion docs/api.md
Expand Up @@ -145,6 +145,8 @@
* [jsHandle.getProperty(propertyName)](#jshandlegetpropertypropertyname)
* [jsHandle.jsonValue()](#jshandlejsonvalue)
- [class: ElementHandle](#class-elementhandle)
* [elementHandle.$(selector)](#elementhandleselector)
* [elementHandle.$$(selector)](#elementhandleselector)
* [elementHandle.asElement()](#elementhandleaselement)
* [elementHandle.boundingBox()](#elementhandleboundingbox)
* [elementHandle.click([options])](#elementhandleclickoptions)
Expand Down Expand Up @@ -1720,8 +1722,20 @@ ElementHandle prevents DOM element from garbage collection unless the handle is

ElementHandle instances can be used as arguments in [`page.$eval()`](#pageevalselector-pagefunction-args) and [`page.evaluate()`](#pageevaluatepagefunction-args) methods.

#### elementHandle.$(selector)
- `selector` <[string]> A [selector] to query element for
- returns: <[Promise]<[ElementHandle]>>

The method runs `element.querySelector` within the page. If no element matches the selector, the return value resolve to `null`.

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

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

#### elementHandle.asElement()
- returns: <[ElementHandle]>
- returns: <[elementhandle]>

#### elementHandle.boundingBox()
- returns: <[Object]>
Expand Down
36 changes: 36 additions & 0 deletions lib/ElementHandle.js
Expand Up @@ -147,6 +147,42 @@ class ElementHandle extends JSHandle {
clip: boundingBox
}, options));
}

/**
* @param {string} selector
* @return {!Promise<?ElementHandle>}
*/
async $(selector) {
const handle = await this.executionContext().evaluateHandle(
(element, selector) => element.querySelector(selector),
this, selector
);
const element = handle.asElement();
if (element)
return element;
await handle.dispose();
return null;
}

/**
* @param {string} selector
* @return {!Promise<!Array<!ElementHandle>>}
*/
async $$(selector) {
const arrayHandle = await this.executionContext().evaluateHandle(
(element, selector) => element.querySelectorAll(selector),
this, selector
);
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;
}
}

module.exports = ElementHandle;
Expand Down
37 changes: 37 additions & 0 deletions test/test.js
Expand Up @@ -1718,6 +1718,43 @@ describe('Page', function() {
}));
});

describe('ElementHandle.$', function() {
it('should query existing element', SX(async function() {
await page.goto(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.$('.second');
const inner = await second.$('.inner');
const content = await page.evaluate(e => e.textContent, inner);
expect(content).toBe('A');
}));

it('should return null for non-existing element', SX(async function() {
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.$('.third');
expect(second).toBe(null);
}));
});

describe('ElementHandle.$$', function() {
it('should query existing elements', SX(async function() {
await page.setContent('<html><body><div>A</div><br/><div>B</div></body></html>');
const html = await page.$('html');
const elements = await html.$$('div');
expect(elements.length).toBe(2);
const promises = elements.map(element => page.evaluate(e => e.textContent, element));
expect(await Promise.all(promises)).toEqual(['A', 'B']);
}));

it('should return empty array for non-existing elements', SX(async function() {
await page.setContent('<html><body><span>A</span><br/><span>B</span></body></html>');
const html = await page.$('html');
const elements = await html.$$('div');
expect(elements.length).toBe(0);
}));
});

describe('input', function() {
it('should click the button', SX(async function() {
await page.goto(PREFIX + '/input/button.html');
Expand Down

0 comments on commit 5ffbd0d

Please sign in to comment.