diff --git a/lib/ElementHandle.js b/lib/ElementHandle.js
index 9a07e63f..ef356ba1 100644
--- a/lib/ElementHandle.js
+++ b/lib/ElementHandle.js
@@ -55,12 +55,20 @@ class ElementHandle extends JSHandle {
}
async _scrollIntoViewIfNeeded() {
- const error = await this.executionContext().evaluate(element => {
+ const error = await this.executionContext().evaluate(async element => {
if (!element.isConnected)
return 'Node is detached from document';
if (element.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';
- element.scrollIntoViewIfNeeded();
+ const visibleRatio = await new Promise(resolve => {
+ const observer = new IntersectionObserver(entries => {
+ resolve(entries[0].intersectionRatio);
+ observer.disconnect();
+ });
+ observer.observe(element);
+ });
+ if (visibleRatio !== 1.0)
+ element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
return false;
}, this);
if (error)
@@ -70,8 +78,7 @@ class ElementHandle extends JSHandle {
/**
* @return {!Promise<{x: number, y: number}>}
*/
- async _visibleCenter() {
- await this._scrollIntoViewIfNeeded();
+ async _boundingBoxCenter() {
const box = await this._assertBoundingBox();
return {
x: box.x + box.width / 2,
@@ -102,7 +109,8 @@ class ElementHandle extends JSHandle {
}
async hover() {
- const {x, y} = await this._visibleCenter();
+ await this._scrollIntoViewIfNeeded();
+ const {x, y} = await this._boundingBoxCenter();
await this._page.mouse.move(x, y);
}
@@ -110,7 +118,8 @@ class ElementHandle extends JSHandle {
* @param {!Object=} options
*/
async click(options = {}) {
- const {x, y} = await this._visibleCenter();
+ await this._scrollIntoViewIfNeeded();
+ const {x, y} = await this._boundingBoxCenter();
await this._page.mouse.click(x, y, options);
}
@@ -125,7 +134,8 @@ class ElementHandle extends JSHandle {
}
async tap() {
- const {x, y} = await this._visibleCenter();
+ await this._scrollIntoViewIfNeeded();
+ const {x, y} = await this._boundingBoxCenter();
await this._page.touchscreen.tap(x, y);
}
@@ -222,9 +232,7 @@ class ElementHandle extends JSHandle {
needsViewportReset = true;
}
- await this.executionContext().evaluate(function(element) {
- element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
- }, this);
+ await this._scrollIntoViewIfNeeded();
boundingBox = await this._assertBoundingBox();
diff --git a/test/assets/offscreenbuttons.html b/test/assets/offscreenbuttons.html
new file mode 100644
index 00000000..27192336
--- /dev/null
+++ b/test/assets/offscreenbuttons.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/input.spec.js b/test/input.spec.js
index 5a228e99..7fdc2e56 100644
--- a/test/input.spec.js
+++ b/test/input.spec.js
@@ -29,6 +29,29 @@ module.exports.addTests = function({testRunner, expect, DeviceDescriptors}) {
expect(await page.evaluate(() => result)).toBe('Clicked');
});
+ it('should click offscreen buttons', async({page, server}) => {
+ await page.goto(server.PREFIX + '/offscreenbuttons.html');
+ const messages = [];
+ page.on('console', msg => messages.push(msg.text()));
+ for (let i = 0; i < 10; ++i) {
+ // We might've scrolled to click a button - reset to (0, 0).
+ await page.evaluate(() => window.scrollTo(0, 0));
+ await page.click(`#btn${i}`);
+ }
+ expect(messages).toEqual([
+ 'button #0 clicked',
+ 'button #1 clicked',
+ 'button #2 clicked',
+ 'button #3 clicked',
+ 'button #4 clicked',
+ 'button #5 clicked',
+ 'button #6 clicked',
+ 'button #7 clicked',
+ 'button #8 clicked',
+ 'button #9 clicked'
+ ]);
+ });
+
it('should click on checkbox input and toggle', async({page, server}) => {
await page.goto(server.PREFIX + '/input/checkbox.html');
expect(await page.evaluate(() => result.check)).toBe(null);