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');