diff --git a/lib/JSHandle.js b/lib/JSHandle.js index 2190a9d381a..f59201cf8db 100644 --- a/lib/JSHandle.js +++ b/lib/JSHandle.js @@ -189,13 +189,17 @@ class ElementHandle extends JSHandle { * @return {!Promise} */ async _clickablePoint() { - const result = await this._client.send('DOM.getContentQuads', { - objectId: this._remoteObject.objectId - }).catch(debugError); + const [result, layoutMetrics] = await Promise.all([ + this._client.send('DOM.getContentQuads', { + objectId: this._remoteObject.objectId + }).catch(debugError), + this._client.send('Page.getLayoutMetrics'), + ]); if (!result || !result.quads.length) throw new Error('Node is either not visible or not an HTMLElement'); // Filter out quads that have too small area to click into. - const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).filter(quad => computeQuadArea(quad) > 1); + const {clientWidth, clientHeight} = layoutMetrics.layoutViewport; + const quads = result.quads.map(quad => this._fromProtocolQuad(quad)).map(quad => this._intersectQuadWithViewport(quad, clientWidth, clientHeight)).filter(quad => computeQuadArea(quad) > 1); if (!quads.length) throw new Error('Node is either not visible or not an HTMLElement'); // Return the middle point of the first quad. @@ -234,6 +238,19 @@ class ElementHandle extends JSHandle { ]; } + /** + * @param {!Array<{x: number, y: number}>} quad + * @param {number} width + * @param {number} height + * @return {!Array<{x: number, y: number}>} + */ + _intersectQuadWithViewport(quad, width, height) { + return quad.map(point => ({ + x: Math.min(Math.max(point.x, 0), width), + y: Math.min(Math.max(point.y, 0), height), + })); + } + async hover() { await this._scrollIntoViewIfNeeded(); const {x, y} = await this._clickablePoint(); diff --git a/test/click.spec.js b/test/click.spec.js index 47e9768e036..e462ebb3e67 100644 --- a/test/click.spec.js +++ b/test/click.spec.js @@ -50,6 +50,19 @@ module.exports.addTests = function({testRunner, expect}) { ]); expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked'); }); + it_fails_ffox('should click when one of inline box children is outside of viewport', async({page, server}) => { + await page.setContent(` + + woofdoggo + `); + await page.click('span'); + expect(await page.evaluate(() => window.CLICKED)).toBe(42); + }); it('should select the text by triple clicking', async({page, server}) => { await page.goto(server.PREFIX + '/input/textarea.html'); await page.focus('textarea');