feat: add ElementHandle.scrollIntoView (#10005)
This commit is contained in:
parent
656b562c74
commit
0d556a71d6
@ -70,6 +70,7 @@ The constructor for this class is marked as internal. Third-party code should no
|
||||
| [isIntersectingViewport(this, options)](./puppeteer.elementhandle.isintersectingviewport.md) | | Resolves to true if the element is visible in the current viewport. If an element is an SVG, we check if the svg owner element is in the viewport instead. See https://crbug.com/963246. |
|
||||
| [press(key, options)](./puppeteer.elementhandle.press.md) | | Focuses the element, and then uses [Keyboard.down()](./puppeteer.keyboard.down.md) and [Keyboard.up()](./puppeteer.keyboard.up.md). |
|
||||
| [screenshot(this, options)](./puppeteer.elementhandle.screenshot.md) | | This method scrolls element into view if needed, and then uses [Page.screenshot()](./puppeteer.page.screenshot_2.md) to take a screenshot of the element. If the element is detached from DOM, the method throws an error. |
|
||||
| [scrollIntoView(this)](./puppeteer.elementhandle.scrollintoview.md) | | Scrolls the element into view using either the automation protocol client or by calling element.scrollIntoView. |
|
||||
| [select(values)](./puppeteer.elementhandle.select.md) | | Triggers a <code>change</code> and <code>input</code> event once all the provided options have been selected. If there's no <code><select></code> element matching <code>selector</code>, the method throws an error. |
|
||||
| [tap(this)](./puppeteer.elementhandle.tap.md) | | This method scrolls element into view if needed, and then uses [Touchscreen.tap()](./puppeteer.touchscreen.tap.md) to tap in the center of the element. If the element is detached from DOM, the method throws an error. |
|
||||
| [toElement(tagName)](./puppeteer.elementhandle.toelement.md) | | Converts the current handle to the given element type. |
|
||||
|
25
docs/api/puppeteer.elementhandle.scrollintoview.md
Normal file
25
docs/api/puppeteer.elementhandle.scrollintoview.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
sidebar_label: ElementHandle.scrollIntoView
|
||||
---
|
||||
|
||||
# ElementHandle.scrollIntoView() method
|
||||
|
||||
Scrolls the element into view using either the automation protocol client or by calling element.scrollIntoView.
|
||||
|
||||
#### Signature:
|
||||
|
||||
```typescript
|
||||
class ElementHandle {
|
||||
scrollIntoView(this: ElementHandle<Element>): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------------------------------------------------------------ | ----------- |
|
||||
| this | [ElementHandle](./puppeteer.elementhandle.md)<Element> | |
|
||||
|
||||
**Returns:**
|
||||
|
||||
Promise<void>
|
@ -811,6 +811,27 @@ export class ElementHandle<
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected async assertConnectedElement(): Promise<void> {
|
||||
const error = await this.evaluate(
|
||||
async (element): Promise<string | undefined> => {
|
||||
if (!element.isConnected) {
|
||||
return 'Node is detached from document';
|
||||
}
|
||||
if (element.nodeType !== Node.ELEMENT_NODE) {
|
||||
return 'Node is not of type HTMLElement';
|
||||
}
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to true if the element is visible in the current viewport. If an
|
||||
* element is an SVG, we check if the svg owner element is in the viewport
|
||||
@ -822,6 +843,8 @@ export class ElementHandle<
|
||||
threshold?: number;
|
||||
}
|
||||
): Promise<boolean> {
|
||||
await this.assertConnectedElement();
|
||||
|
||||
const {threshold = 0} = options ?? {};
|
||||
const svgHandle = await this.#asSVGElementHandle(this);
|
||||
const intersectionTarget: ElementHandle<Element> = svgHandle
|
||||
@ -846,6 +869,14 @@ export class ElementHandle<
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the element into view using either the automation protocol client
|
||||
* or by calling element.scrollIntoView.
|
||||
*/
|
||||
async scrollIntoView(this: ElementHandle<Element>): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if an element is an SVGElement (included svg, path, rect
|
||||
* etc.).
|
||||
|
@ -231,58 +231,44 @@ export class CDPElementHandle<
|
||||
return this.#frameManager.frame(nodeInfo.node.frameId);
|
||||
}
|
||||
|
||||
async #scrollIntoViewIfNeeded(
|
||||
override async scrollIntoView(
|
||||
this: CDPElementHandle<Element>
|
||||
): Promise<void> {
|
||||
const error = await this.evaluate(
|
||||
async (element): Promise<string | undefined> => {
|
||||
if (!element.isConnected) {
|
||||
return 'Node is detached from document';
|
||||
}
|
||||
if (element.nodeType !== Node.ELEMENT_NODE) {
|
||||
return 'Node is not of type HTMLElement';
|
||||
}
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
await this.assertConnectedElement();
|
||||
|
||||
try {
|
||||
await this.client.send('DOM.scrollIntoViewIfNeeded', {
|
||||
objectId: this.remoteObject().objectId,
|
||||
});
|
||||
} catch (_err) {
|
||||
} catch (error) {
|
||||
debugError(error);
|
||||
// Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported
|
||||
await this.evaluate(
|
||||
async (element, pageJavascriptEnabled): Promise<void> => {
|
||||
const visibleRatio = async () => {
|
||||
return await new Promise(resolve => {
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
resolve(entries[0]!.intersectionRatio);
|
||||
observer.disconnect();
|
||||
});
|
||||
observer.observe(element);
|
||||
});
|
||||
};
|
||||
if (!pageJavascriptEnabled || (await visibleRatio()) !== 1.0) {
|
||||
element.scrollIntoView({
|
||||
block: 'center',
|
||||
inline: 'center',
|
||||
// @ts-expect-error Chrome still supports behavior: instant but
|
||||
// it's not in the spec so TS shouts We don't want to make this
|
||||
// breaking change in Puppeteer yet so we'll ignore the line.
|
||||
behavior: 'instant',
|
||||
});
|
||||
}
|
||||
},
|
||||
this.#page.isJavaScriptEnabled()
|
||||
);
|
||||
await this.evaluate(async (element): Promise<void> => {
|
||||
element.scrollIntoView({
|
||||
block: 'center',
|
||||
inline: 'center',
|
||||
// @ts-expect-error Chrome still supports behavior: instant but
|
||||
// it's not in the spec so TS shouts We don't want to make this
|
||||
// breaking change in Puppeteer yet so we'll ignore the line.
|
||||
behavior: 'instant',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async #scrollIntoViewIfNeeded(
|
||||
this: CDPElementHandle<Element>
|
||||
): Promise<void> {
|
||||
if (
|
||||
await this.isIntersectingViewport({
|
||||
threshold: 1,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await this.scrollIntoView();
|
||||
}
|
||||
|
||||
async #getOOPIFOffsets(
|
||||
frame: Frame
|
||||
): Promise<{offsetX: number; offsetY: number}> {
|
||||
|
@ -509,6 +509,12 @@
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[click.spec] Page.click should scroll and click with disabled javascript",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[click.spec] Page.click should select the text by triple clicking",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
|
@ -116,6 +116,18 @@ describe('Page.click', function () {
|
||||
await Promise.all([page.click('a'), page.waitForNavigation()]);
|
||||
expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked');
|
||||
});
|
||||
it('should scroll and click with disabled javascript', async () => {
|
||||
const {page, server} = getTestState();
|
||||
|
||||
await page.setJavaScriptEnabled(false);
|
||||
await page.goto(server.PREFIX + '/wrappedlink.html');
|
||||
const body = await page.waitForSelector('body');
|
||||
await body!.evaluate(el => {
|
||||
el.style.paddingTop = '3000px';
|
||||
});
|
||||
await Promise.all([page.click('a'), page.waitForNavigation()]);
|
||||
expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked');
|
||||
});
|
||||
it('should click when one of inline box children is outside of viewport', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user