From 41d583829740c204dbb9c1df43cb91db9dc6f566 Mon Sep 17 00:00:00 2001 From: Yaniv Efraim Date: Thu, 29 Mar 2018 23:52:28 +0300 Subject: [PATCH] feat(ElementHandle): add ElementHandle.boxModel method (#2256) This patch introduces ElementHandle.boxModel to get element's box model. Fixes #1357 --- docs/api.md | 12 ++++++++++ lib/ElementHandle.js | 46 +++++++++++++++++++++++++++++++++++--- test/elementhandle.spec.js | 27 ++++++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/docs/api.md b/docs/api.md index 9b767d28047f2..60be5563929e7 100644 --- a/docs/api.md +++ b/docs/api.md @@ -186,6 +186,7 @@ * [elementHandle.$x(expression)](#elementhandlexexpression) * [elementHandle.asElement()](#elementhandleaselement) * [elementHandle.boundingBox()](#elementhandleboundingbox) + * [elementHandle.boxModel()](#elementhandleboxmodel) * [elementHandle.click([options])](#elementhandleclickoptions) * [elementHandle.contentFrame()](#elementhandlecontentframe) * [elementHandle.dispose()](#elementhandledispose) @@ -2197,6 +2198,17 @@ The method evaluates the XPath expression relative to the elementHandle. If ther This method returns the bounding box of the element (relative to the main frame), or `null` if the element is not visible. +#### elementHandle.boxModel() +- returns: <[Promise]> + - content <[Array]<[Object]>> Content box, represented as an array of {x, y} points. + - padding <[Array]<[Object]>> Padding box, represented as an array of {x, y} points. + - border <[Array]<[Object]>> Border box, represented as an array of {x, y} points. + - margin <[Array]<[Object]>> Margin box, represented as an array of {x, y} points. + - width <[number]> Element's width. + - height <[number]> Element's height. + +This method returns boxes of the element, or `null` if the element is not visible. Boxes are represented as an array of objects, {x, y} for each point, points clock-wise. See [getBoxModel](https://chromedevtools.github.io/devtools-protocol/tot/DOM#method-getBoxModel) for more details. + #### elementHandle.click([options]) - `options` <[Object]> - `button` <[string]> `left`, `right`, or `middle`, defaults to `left`. diff --git a/lib/ElementHandle.js b/lib/ElementHandle.js index 41aa336e1fa74..2d8c9999a6923 100644 --- a/lib/ElementHandle.js +++ b/lib/ElementHandle.js @@ -79,6 +79,28 @@ class ElementHandle extends JSHandle { }; } + /** + * @return {!Promise} + */ + _getBoxModel() { + return this._client.send('DOM.getBoxModel', { + objectId: this._remoteObject.objectId + }).catch(error => debugError(error)); + } + + /** + * @param {!Array} quad + * @return {!Array} + */ + _fromProtocolQuad(quad) { + return [ + {x: quad[0], y: quad[1]}, + {x: quad[2], y: quad[3]}, + {x: quad[4], y: quad[5]}, + {x: quad[6], y: quad[7]} + ]; + } + async hover() { const {x, y} = await this._visibleCenter(); await this._page.mouse.move(x, y); @@ -133,9 +155,7 @@ class ElementHandle extends JSHandle { * @return {!Promise} */ async boundingBox() { - const result = await this._client.send('DOM.getBoxModel', { - objectId: this._remoteObject.objectId - }).catch(error => void debugError(error)); + const result = await this._getBoxModel(); if (!result) return null; @@ -149,6 +169,26 @@ class ElementHandle extends JSHandle { return {x, y, width, height}; } + /** + * @return {!Promise} + */ + async boxModel() { + const result = await this._getBoxModel(); + + if (!result) + return null; + + const {content, padding, border, margin, width, height} = result.model; + return { + content: this._fromProtocolQuad(content), + padding: this._fromProtocolQuad(padding), + border: this._fromProtocolQuad(border), + margin: this._fromProtocolQuad(margin), + width, + height + }; + } + /** * @return {!Promise} */ diff --git a/test/elementhandle.spec.js b/test/elementhandle.spec.js index aded656c7cd5c..44932e95628af 100644 --- a/test/elementhandle.spec.js +++ b/test/elementhandle.spec.js @@ -52,6 +52,33 @@ module.exports.addTests = function({testRunner, expect}) { }); }); + describe('ElementHandle.boxModel', function() { + it('should work', async({page, server}) => { + const leftTop = {x: 28, y: 260}; + const rightTop = {x: 292, y: 260}; + const rightBottom = {x: 292, y: 278}; + const leftBottom = {x: 28, y: 278}; + + await page.setViewport({width: 500, height: 500}); + await page.goto(server.PREFIX + '/frames/nested-frames.html'); + const nestedFrame = page.frames()[1].childFrames()[1]; + const elementHandle = await nestedFrame.$('div'); + const box = await elementHandle.boxModel(); + expect(box.content).toEqual([leftTop, rightTop, rightBottom, leftBottom]); + expect(box.padding).toEqual([leftTop, rightTop, rightBottom, leftBottom]); + expect(box.border).toEqual([leftTop, rightTop, rightBottom, leftBottom]); + expect(box.margin).toEqual([leftTop, rightTop, rightBottom, leftBottom]); + expect(box.height).toBe(18); + expect(box.width).toBe(264); + }); + + it('should return null for invisible elements', async({page, server}) => { + await page.setContent('
hi
'); + const element = await page.$('div'); + expect(await element.boxModel()).toBe(null); + }); + }); + describe('ElementHandle.contentFrame', function() { it('should work', async({page,server}) => { await page.goto(server.EMPTY_PAGE);