mirror of
https://github.com/microsoft/playwright.git
synced 2024-09-11 20:37:54 +03:00
feat(scrollIntoView): expose scrollIntoViewIfNeeded in api (#382)
This also replaces isIntersectingViewport with visibleRatio for more flexibility.
This commit is contained in:
parent
58b8e66df8
commit
491eeeef7e
23
docs/api.md
23
docs/api.md
@ -58,15 +58,16 @@
|
||||
* [elementHandle.fill(value)](#elementhandlefillvalue)
|
||||
* [elementHandle.focus()](#elementhandlefocus)
|
||||
* [elementHandle.hover([options])](#elementhandlehoveroptions)
|
||||
* [elementHandle.isIntersectingViewport()](#elementhandleisintersectingviewport)
|
||||
* [elementHandle.ownerFrame()](#elementhandleownerframe)
|
||||
* [elementHandle.press(key[, options])](#elementhandlepresskey-options)
|
||||
* [elementHandle.screenshot([options])](#elementhandlescreenshotoptions)
|
||||
* [elementHandle.scrollIntoViewIfNeeded()](#elementhandlescrollintoviewifneeded)
|
||||
* [elementHandle.select(...values)](#elementhandleselectvalues)
|
||||
* [elementHandle.setInputFiles(...files)](#elementhandlesetinputfilesfiles)
|
||||
* [elementHandle.toString()](#elementhandletostring)
|
||||
* [elementHandle.tripleclick([options])](#elementhandletripleclickoptions)
|
||||
* [elementHandle.type(text[, options])](#elementhandletypetext-options)
|
||||
* [elementHandle.visibleRatio()](#elementhandlevisibleratio)
|
||||
- [class: Frame](#class-frame)
|
||||
* [frame.$(selector)](#frameselector)
|
||||
* [frame.$$(selector)](#frameselector-1)
|
||||
@ -786,9 +787,6 @@ Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
|
||||
This method scrolls element into view if needed, and then uses [page.mouse](#pagemouse) to hover over the center of the element.
|
||||
If the element is detached from DOM, the method throws an error.
|
||||
|
||||
#### elementHandle.isIntersectingViewport()
|
||||
- returns: <[Promise]<[boolean]>> Resolves to true if the element is visible in the current viewport.
|
||||
|
||||
#### elementHandle.ownerFrame()
|
||||
- returns: <[Promise]<[Frame]>> Returns the frame containing the given element.
|
||||
|
||||
@ -816,6 +814,15 @@ If `key` is a single character and no modifier keys besides `Shift` are being he
|
||||
This method scrolls element into view if needed, and then uses [page.screenshot](#pagescreenshotoptions) to take a screenshot of the element.
|
||||
If the element is detached from DOM, the method throws an error.
|
||||
|
||||
#### elementHandle.scrollIntoViewIfNeeded()
|
||||
- returns: <[Promise]> Resolves after the element has been scrolled into view.
|
||||
|
||||
This method tries to scroll element into view, unless it is completely visible as defined by [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)'s ```ratio```. See also [elementHandle.visibleRatio()](#elementhandlevisibleratio).
|
||||
|
||||
Throws when ```elementHandle``` does not point to an element [connected](https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected) to a Document or a ShadowRoot.
|
||||
|
||||
> **NOTE** If javascript is disabled, element is scrolled into view even when already completely visible.
|
||||
|
||||
#### elementHandle.select(...values)
|
||||
- `...values` <...[string]|[ElementHandle]|[Object]> Options to select. If the `<select>` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match.
|
||||
- `value` <[string]> Matches by `option.value`.
|
||||
@ -891,6 +898,11 @@ await elementHandle.type('some text');
|
||||
await elementHandle.press('Enter');
|
||||
```
|
||||
|
||||
#### elementHandle.visibleRatio()
|
||||
- returns: <[Promise]<[number]>> Returns the visible ratio as defined by [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
|
||||
|
||||
Positive ratio means that some part of the element is visible in the current viewport. Ratio equal to one means that element is completely visible.
|
||||
|
||||
### class: Frame
|
||||
|
||||
At every point of time, page exposes its current frame tree via the [page.mainFrame()](#pagemainframe) and [frame.childFrames()](#framechildframes) methods.
|
||||
@ -1954,8 +1966,7 @@ Get the browser context that the page belongs to.
|
||||
- `relativePoint` <[Object]> A point to click relative to the top-left corner of element padding box. If not specified, clicks to some visible point of the element.
|
||||
- x <[number]>
|
||||
- y <[number]>
|
||||
- `modifiers` <[Array]<"Alt"|"Control"|"Meta"|"Shift">> Modifier keys to press. Ensures that only these modifiers are pressed during the click, and then restores current modifiers back. If not specified,
|
||||
currently pressed modifiers are used.
|
||||
- `modifiers` <[Array]<"Alt"|"Control"|"Meta"|"Shift">> Modifier keys to press. Ensures that only these modifiers are pressed during the click, and then restores current modifiers back. If not specified, currently pressed modifiers are used.
|
||||
- `waitFor` <"visible"|"hidden"|"any"|"nowait"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`) or do not wait at all (`nowait`). Defaults to `visible`.
|
||||
- `timeout` <[number]> Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
|
||||
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. The Promise will be rejected if there is no element matching `selector`.
|
||||
|
10
src/dom.ts
10
src/dom.ts
@ -134,7 +134,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return this._page._delegate.getContentFrame(this);
|
||||
}
|
||||
|
||||
async _scrollIntoViewIfNeeded() {
|
||||
async scrollIntoViewIfNeeded() {
|
||||
const error = await this._evaluateInUtility(async (node: Node, pageJavascriptEnabled: boolean) => {
|
||||
if (!node.isConnected)
|
||||
return 'Node is detached from document';
|
||||
@ -168,7 +168,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}
|
||||
|
||||
private async _ensurePointerActionPoint(relativePoint?: types.Point): Promise<types.Point> {
|
||||
await this._scrollIntoViewIfNeeded();
|
||||
await this.scrollIntoViewIfNeeded();
|
||||
if (!relativePoint)
|
||||
return this._clickablePoint();
|
||||
let r = await this._viewportPointAndScroll(relativePoint);
|
||||
@ -464,12 +464,12 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
return this._context._$$('xpath=' + expression, this);
|
||||
}
|
||||
|
||||
isIntersectingViewport(): Promise<boolean> {
|
||||
visibleRatio(): Promise<number> {
|
||||
return this._evaluateInUtility(async (node: Node) => {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||
throw new Error('Node is not of type HTMLElement');
|
||||
const element = node as Element;
|
||||
const visibleRatio = await new Promise(resolve => {
|
||||
const visibleRatio = await new Promise<number>(resolve => {
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
resolve(entries[0].intersectionRatio);
|
||||
observer.disconnect();
|
||||
@ -479,7 +479,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
// there are rafs.
|
||||
requestAnimationFrame(() => {});
|
||||
});
|
||||
return visibleRatio > 0;
|
||||
return visibleRatio;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ export class Screenshotter {
|
||||
await this._page.setViewport(overridenViewport);
|
||||
}
|
||||
|
||||
await handle._scrollIntoViewIfNeeded();
|
||||
await handle.scrollIntoViewIfNeeded();
|
||||
boundingBox = enclosingIntRect(await this._page._delegate.getBoundingBoxForScreenshot(handle));
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,23 @@
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#btn0 { right: 0px; top: 0; }
|
||||
@ -17,6 +34,7 @@
|
||||
#btn9 { right: -90px; top: 225px; }
|
||||
#btn10 { right: -100px; top: 250px; }
|
||||
</style>
|
||||
<div>
|
||||
<button id=btn0>0</button>
|
||||
<button id=btn1>1</button>
|
||||
<button id=btn2>2</button>
|
||||
@ -28,6 +46,7 @@
|
||||
<button id=btn8>8</button>
|
||||
<button id=btn9>9</button>
|
||||
<button id=btn10>10</button>
|
||||
</div>
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
for (const button of Array.from(document.querySelectorAll('button')))
|
||||
|
@ -248,14 +248,13 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ElementHandle.isIntersectingViewport', function() {
|
||||
describe('ElementHandle.visibleRatio', function() {
|
||||
it('should work', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/offscreenbuttons.html');
|
||||
for (let i = 0; i < 11; ++i) {
|
||||
const button = await page.$('#btn' + i);
|
||||
// All but last button are visible.
|
||||
const visible = i < 10;
|
||||
expect(await button.isIntersectingViewport()).toBe(visible);
|
||||
const ratio = await button.visibleRatio();
|
||||
expect(Math.round(ratio * 10)).toBe(10 - i);
|
||||
}
|
||||
});
|
||||
it.skip(FFOX)('should work when Node is removed', async({page, server}) => {
|
||||
@ -263,9 +262,23 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
||||
await page.evaluate(() => delete window['Node']);
|
||||
for (let i = 0; i < 11; ++i) {
|
||||
const button = await page.$('#btn' + i);
|
||||
// All but last button are visible.
|
||||
const visible = i < 10;
|
||||
expect(await button.isIntersectingViewport()).toBe(visible);
|
||||
const ratio = await button.visibleRatio();
|
||||
expect(Math.round(ratio * 10)).toBe(10 - i);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ElementHandle.scrollIntoViewIfNeeded', function() {
|
||||
it('should work', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/offscreenbuttons.html');
|
||||
for (let i = 0; i < 11; ++i) {
|
||||
const button = await page.$('#btn' + i);
|
||||
const before = await button.visibleRatio();
|
||||
expect(Math.round(before * 10)).toBe(10 - i);
|
||||
await button.scrollIntoViewIfNeeded();
|
||||
const after = await button.visibleRatio();
|
||||
expect(Math.round(after * 10)).toBe(10);
|
||||
await page.evaluate(() => window.scrollTo(0, 0));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user