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:
parent
515f2cd03d
commit
7e28dbafb5
18
docs/api.md
18
docs/api.md
@ -137,6 +137,7 @@
|
||||
+ [jsHandle.toString()](#jshandletostring)
|
||||
* [class: ElementHandle](#class-elementhandle)
|
||||
+ [elementHandle.asElement()](#elementhandleaselement)
|
||||
+ [elementHandle.boundingBox()](#elementhandleboundingbox)
|
||||
+ [elementHandle.click([options])](#elementhandleclickoptions)
|
||||
+ [elementHandle.dispose()](#elementhandledispose)
|
||||
+ [elementHandle.executionContext()](#elementhandleexecutioncontext)
|
||||
@ -146,6 +147,7 @@
|
||||
+ [elementHandle.hover()](#elementhandlehover)
|
||||
+ [elementHandle.jsonValue()](#elementhandlejsonvalue)
|
||||
+ [elementHandle.press(key[, options])](#elementhandlepresskey-options)
|
||||
+ [elementHandle.screenshot([options])](#elementhandlescreenshotoptions)
|
||||
+ [elementHandle.tap()](#elementhandletap)
|
||||
+ [elementHandle.toString()](#elementhandletostring)
|
||||
+ [elementHandle.type(text[, options])](#elementhandletypetext-options)
|
||||
@ -1535,6 +1537,15 @@ ElementHandle instances can be used as arguments in [`page.$eval()`](#pageevalse
|
||||
#### elementHandle.asElement()
|
||||
- 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])
|
||||
- `options` <[Object]>
|
||||
- `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).
|
||||
|
||||
#### 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()
|
||||
- returns: <[Promise]> Promise which resolves when the element is successfully tapped. Promise gets rejected if the element is detached from DOM.
|
||||
|
||||
|
@ -40,25 +40,28 @@ class ElementHandle extends JSHandle {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise<{x: number, y: number}>}
|
||||
*/
|
||||
async _visibleCenter() {
|
||||
async _scrollIntoViewIfNeeded() {
|
||||
const error = await this.executionContext().evaluate(element => {
|
||||
if (!element.ownerDocument.contains(element))
|
||||
return 'Node is detached from document';
|
||||
if (element.nodeType !== Node.ELEMENT_NODE)
|
||||
return 'Node is not of type HTMLElement';
|
||||
element.scrollIntoViewIfNeeded();
|
||||
return false;
|
||||
}, this);
|
||||
if (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 {
|
||||
x: (model.content[0] + model.content[4]) / 2,
|
||||
y: (model.content[1] + model.content[5]) / 2
|
||||
x: box.x + box.width / 2,
|
||||
y: box.y + box.height / 2
|
||||
};
|
||||
}
|
||||
|
||||
@ -111,6 +114,37 @@ class ElementHandle extends JSHandle {
|
||||
await this.focus();
|
||||
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;
|
||||
|
@ -29,8 +29,8 @@ class ExecutionContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function()|string} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @param {function(*)|string} pageFunction
|
||||
* @param {...*} args
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async evaluate(pageFunction, ...args) {
|
||||
@ -41,8 +41,8 @@ class ExecutionContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function()|string} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @param {function(*)|string} pageFunction
|
||||
* @param {...*} args
|
||||
* @return {!Promise<!JSHandle>}
|
||||
*/
|
||||
async evaluateHandle(pageFunction, ...args) {
|
||||
|
BIN
test/golden/screenshot-element-bounding-box.png
Normal file
BIN
test/golden/screenshot-element-bounding-box.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 461 B |
29
test/test.js
29
test/test.js
@ -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() {
|
||||
it('should work', SX(async function() {
|
||||
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() {
|
||||
it('should click the button', SX(async function() {
|
||||
await page.goto(PREFIX + '/input/button.html');
|
||||
|
Loading…
Reference in New Issue
Block a user