diff --git a/docs/api.md b/docs/api.md index cfa74d0ba4a..ee1671cdcd7 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1786,7 +1786,7 @@ The method runs `element.querySelectorAll` within the page. If no elements match - 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. +This method returns the bounding box of the element (relative to the main frame), or `null` if the element is not visible. #### elementHandle.click([options]) - `options` <[Object]> diff --git a/lib/ElementHandle.js b/lib/ElementHandle.js index b83e7dbfacf..3a70676fe09 100644 --- a/lib/ElementHandle.js +++ b/lib/ElementHandle.js @@ -15,7 +15,7 @@ */ const path = require('path'); const {JSHandle} = require('./ExecutionContext'); -const {helper} = require('./helper'); +const {helper, debugError} = require('./helper'); class ElementHandle extends JSHandle { /** @@ -59,6 +59,8 @@ class ElementHandle extends JSHandle { async _visibleCenter() { await this._scrollIntoViewIfNeeded(); const box = await this.boundingBox(); + if (!box) + throw new Error('Node is not visible'); return { x: box.x + box.width / 2, y: box.y + box.height / 2 @@ -116,16 +118,17 @@ class ElementHandle extends JSHandle { } /** - * @return {!Promise<{x: number, y: number, width: number, height: number}>} + * @return {!Promise{x: number, y: number, width: number, height: number}>} */ async boundingBox() { - const {model} = await this._client.send('DOM.getBoxModel', { + const result = await this._client.send('DOM.getBoxModel', { objectId: this._remoteObject.objectId - }); - if (!model) - throw new Error('Node is detached from document'); + }).catch(error => void debugError(error)); - const quad = model.border; + if (!result) + return null; + + const quad = result.model.border; const x = Math.min(quad[0], quad[2], quad[4], quad[6]); const y = Math.min(quad[1], quad[3], quad[5], quad[7]); const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x; @@ -142,6 +145,8 @@ class ElementHandle extends JSHandle { async screenshot(options = {}) { await this._scrollIntoViewIfNeeded(); const boundingBox = await this.boundingBox(); + if (!boundingBox) + throw new Error('Node is not visible'); return await this._page.screenshot(Object.assign({}, { clip: boundingBox diff --git a/test/test.js b/test/test.js index 53f86d2a977..a47eeb83998 100644 --- a/test/test.js +++ b/test/test.js @@ -1688,6 +1688,11 @@ describe('Page', function() { const box = await elementHandle.boundingBox(); expect(box).toEqual({ x: 28, y: 260, width: 264, height: 18 }); })); + it('should return null for invisible elements', SX(async function() { + await page.setContent('
'); + const element = await page.$('div'); + expect(await element.boundingBox()).toBe(null); + })); }); describe('ElementHandle.click', function() { @@ -1718,6 +1723,26 @@ describe('Page', function() { await button.click().catch(err => error = err); expect(error.message).toBe('Node is detached from document'); })); + it('should throw for hidden nodes', SX(async function() { + await page.goto(PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(button => button.style.display = 'none', button); + const error = await button.click().catch(err => err); + expect(error.message).toBe('Node is not visible'); + })); + it('should throw for recursively hidden nodes', SX(async function() { + await page.goto(PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(button => button.parentElement.style.display = 'none', button); + const error = await button.click().catch(err => err); + expect(error.message).toBe('Node is not visible'); + })); + it('should throw for