fix(page): teach page.click() to click partially offscreen buttons (#2806)

Originally, we use `Element.scrollIntoViewIfNeeded` to make sure
button is on screen before trying to click it.

However, `Element.scrollIntoViewIfNeeded` doesn't work in certain
scenarios, e.g. when element is partially visible and horizontal
scrolling is required to make it fully visible.

This patch polyfills `element.scrollIntoViewIfNeeded` using
IntersectionObserver and `Element.scrollIntoView`.

Fixes #2804.
This commit is contained in:
Andrey Lushnikov
2018-06-26 18:00:55 -07:00
committed by GitHub
parent 6ca43cf761
commit f55d005cbe
3 changed files with 75 additions and 10 deletions

View File

@@ -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();