feat(ElementHandle): add EH.boundingBox and EH.screenshot

This patch:
- adds `ElementHandle.boundingBox()` method to get bounding box of element relative
  to the page
- adds `ElementHandle.screenshot()` method to capture a screenshot of an element
This commit is contained in:
Eli Sherer 2017-10-10 09:14:09 +03:00 committed by Andrey Lushnikov
parent 515f2cd03d
commit 7e28dbafb5
5 changed files with 94 additions and 13 deletions

View File

@ -137,6 +137,7 @@
+ [jsHandle.toString()](#jshandletostring) + [jsHandle.toString()](#jshandletostring)
* [class: ElementHandle](#class-elementhandle) * [class: ElementHandle](#class-elementhandle)
+ [elementHandle.asElement()](#elementhandleaselement) + [elementHandle.asElement()](#elementhandleaselement)
+ [elementHandle.boundingBox()](#elementhandleboundingbox)
+ [elementHandle.click([options])](#elementhandleclickoptions) + [elementHandle.click([options])](#elementhandleclickoptions)
+ [elementHandle.dispose()](#elementhandledispose) + [elementHandle.dispose()](#elementhandledispose)
+ [elementHandle.executionContext()](#elementhandleexecutioncontext) + [elementHandle.executionContext()](#elementhandleexecutioncontext)
@ -146,6 +147,7 @@
+ [elementHandle.hover()](#elementhandlehover) + [elementHandle.hover()](#elementhandlehover)
+ [elementHandle.jsonValue()](#elementhandlejsonvalue) + [elementHandle.jsonValue()](#elementhandlejsonvalue)
+ [elementHandle.press(key[, options])](#elementhandlepresskey-options) + [elementHandle.press(key[, options])](#elementhandlepresskey-options)
+ [elementHandle.screenshot([options])](#elementhandlescreenshotoptions)
+ [elementHandle.tap()](#elementhandletap) + [elementHandle.tap()](#elementhandletap)
+ [elementHandle.toString()](#elementhandletostring) + [elementHandle.toString()](#elementhandletostring)
+ [elementHandle.type(text[, options])](#elementhandletypetext-options) + [elementHandle.type(text[, options])](#elementhandletypetext-options)
@ -1535,6 +1537,15 @@ ElementHandle instances can be used as arguments in [`page.$eval()`](#pageevalse
#### elementHandle.asElement() #### elementHandle.asElement()
- returns: <[ElementHandle]> - returns: <[ElementHandle]>
#### elementHandle.boundingBox()
- returns: <[Object]>
- x <[number]> the x coordinate of the element in pixels.
- y <[number]> the y coordinate of the element in pixels.
- width <[number]> the width of the element in pixels.
- height <[number]> the height of the element in pixels.
This method returns the bounding box of the element (relative to the main frame), or `null` if element is detached from dom.
#### elementHandle.click([options]) #### elementHandle.click([options])
- `options` <[Object]> - `options` <[Object]>
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`. - `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
@ -1603,6 +1614,13 @@ Returns a JSON representation of the object. The JSON is generated by running [`
Focuses the element, and then uses [`keyboard.down`](#keyboarddownkey-options) and [`keyboard.up`](#keyboardupkey). Focuses the element, and then uses [`keyboard.down`](#keyboarddownkey-options) and [`keyboard.up`](#keyboardupkey).
#### elementHandle.screenshot([options])
- `options` <[Object]> Same options as in [page.screenshot](#pagescreenshotoptions).
- returns: <[Promise]<[Buffer]>> Promise which resolves to buffer with captured screenshot.
This method scrolls element into view if needed, and then uses [page.screenshot](#pagescreenshotoptions) to take a screenshot of the element.
If the element is detached from DOM, the method throws an error.
#### elementHandle.tap() #### elementHandle.tap()
- returns: <[Promise]> Promise which resolves when the element is successfully tapped. Promise gets rejected if the element is detached from DOM. - returns: <[Promise]> Promise which resolves when the element is successfully tapped. Promise gets rejected if the element is detached from DOM.

View File

@ -40,25 +40,28 @@ class ElementHandle extends JSHandle {
return this; return this;
} }
/** async _scrollIntoViewIfNeeded() {
* @return {!Promise<{x: number, y: number}>}
*/
async _visibleCenter() {
const error = await this.executionContext().evaluate(element => { const error = await this.executionContext().evaluate(element => {
if (!element.ownerDocument.contains(element)) if (!element.ownerDocument.contains(element))
return 'Node is detached from document'; return 'Node is detached from document';
if (element.nodeType !== Node.ELEMENT_NODE) if (element.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement'; return 'Node is not of type HTMLElement';
element.scrollIntoViewIfNeeded(); element.scrollIntoViewIfNeeded();
return false;
}, this); }, this);
if (error) if (error)
throw new Error(error); throw new Error(error);
const {model} = await this._client.send('DOM.getBoxModel', { }
objectId: this._remoteObject.objectId
}); /**
* @return {!Promise<{x: number, y: number}>}
*/
async _visibleCenter() {
await this._scrollIntoViewIfNeeded();
const box = await this.boundingBox();
return { return {
x: (model.content[0] + model.content[4]) / 2, x: box.x + box.width / 2,
y: (model.content[1] + model.content[5]) / 2 y: box.y + box.height / 2
}; };
} }
@ -111,6 +114,37 @@ class ElementHandle extends JSHandle {
await this.focus(); await this.focus();
await this._page.keyboard.press(key, options); await this._page.keyboard.press(key, options);
} }
/**
* @return {!Promise<Object>}
*/
async boundingBox() {
const boxModel = await this._client.send('DOM.getBoxModel', {
objectId: this._remoteObject.objectId
});
if (!boxModel || !boxModel.model)
return null;
return {
x: boxModel.model.margin[0],
y: boxModel.model.margin[1],
width: boxModel.model.width,
height: boxModel.model.height
};
}
/**
*
* @param {!Object=} options
* @returns {!Promise<Object>}
*/
async screenshot(options = {}) {
await this._scrollIntoViewIfNeeded();
const boundingBox = await this.boundingBox();
return await this._page.screenshot(Object.assign({}, {
clip: boundingBox
}, options));
}
} }
module.exports = ElementHandle; module.exports = ElementHandle;

View File

@ -29,8 +29,8 @@ class ExecutionContext {
} }
/** /**
* @param {function()|string} pageFunction * @param {function(*)|string} pageFunction
* @param {!Array<*>} args * @param {...*} args
* @return {!Promise<(!Object|undefined)>} * @return {!Promise<(!Object|undefined)>}
*/ */
async evaluate(pageFunction, ...args) { async evaluate(pageFunction, ...args) {
@ -41,8 +41,8 @@ class ExecutionContext {
} }
/** /**
* @param {function()|string} pageFunction * @param {function(*)|string} pageFunction
* @param {!Array<*>} args * @param {...*} args
* @return {!Promise<!JSHandle>} * @return {!Promise<!JSHandle>}
*/ */
async evaluateHandle(pageFunction, ...args) { async evaluateHandle(pageFunction, ...args) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

View File

@ -1373,6 +1373,24 @@ describe('Page', function() {
})); }));
}); });
describe('ElementHandle.boundingBox', function() {
it('should work', SX(async function() {
await page.setViewport({width: 500, height: 500});
await page.goto(PREFIX + '/grid.html');
const elementHandle = await page.$('.box:nth-of-type(13)');
const box = await elementHandle.boundingBox();
expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 });
}));
it('should handle nested frames', SX(async function() {
await page.setViewport({width: 500, height: 500});
await page.goto(PREFIX + '/frames/nested-frames.html');
const nestedFrame = page.frames()[1].childFrames()[1];
const elementHandle = await nestedFrame.$('div');
const box = await elementHandle.boundingBox();
expect(box).toEqual({ x: 28, y: 260, width: 264, height: 18 });
}));
});
describe('ElementHandle.click', function() { describe('ElementHandle.click', function() {
it('should work', SX(async function() { it('should work', SX(async function() {
await page.goto(PREFIX + '/input/button.html'); await page.goto(PREFIX + '/input/button.html');
@ -1406,6 +1424,17 @@ describe('Page', function() {
})); }));
}); });
describe('ElementHandle.screenshot', function() {
it('should work', SX(async function() {
await page.setViewport({width: 500, height: 500});
await page.goto(PREFIX + '/grid.html');
await page.evaluate(() => window.scrollBy(50, 100));
const elementHandle = await page.$('.box:nth-of-type(3)');
const screenshot = await elementHandle.screenshot();
expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
}));
});
describe('input', function() { describe('input', function() {
it('should click the button', SX(async function() { it('should click the button', SX(async function() {
await page.goto(PREFIX + '/input/button.html'); await page.goto(PREFIX + '/input/button.html');