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. |
|
| [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). |
|
| [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. |
|
| [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. |
|
| [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. |
|
| [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. |
|
| [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');
|
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
|
* 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
|
* element is an SVG, we check if the svg owner element is in the viewport
|
||||||
@ -822,6 +843,8 @@ export class ElementHandle<
|
|||||||
threshold?: number;
|
threshold?: number;
|
||||||
}
|
}
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
await this.assertConnectedElement();
|
||||||
|
|
||||||
const {threshold = 0} = options ?? {};
|
const {threshold = 0} = options ?? {};
|
||||||
const svgHandle = await this.#asSVGElementHandle(this);
|
const svgHandle = await this.#asSVGElementHandle(this);
|
||||||
const intersectionTarget: ElementHandle<Element> = svgHandle
|
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
|
* Returns true if an element is an SVGElement (included svg, path, rect
|
||||||
* etc.).
|
* etc.).
|
||||||
|
@ -231,43 +231,19 @@ export class CDPElementHandle<
|
|||||||
return this.#frameManager.frame(nodeInfo.node.frameId);
|
return this.#frameManager.frame(nodeInfo.node.frameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #scrollIntoViewIfNeeded(
|
override async scrollIntoView(
|
||||||
this: CDPElementHandle<Element>
|
this: CDPElementHandle<Element>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const error = await this.evaluate(
|
await this.assertConnectedElement();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.client.send('DOM.scrollIntoViewIfNeeded', {
|
await this.client.send('DOM.scrollIntoViewIfNeeded', {
|
||||||
objectId: this.remoteObject().objectId,
|
objectId: this.remoteObject().objectId,
|
||||||
});
|
});
|
||||||
} catch (_err) {
|
} catch (error) {
|
||||||
|
debugError(error);
|
||||||
// Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported
|
// Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported
|
||||||
await this.evaluate(
|
await this.evaluate(async (element): Promise<void> => {
|
||||||
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({
|
element.scrollIntoView({
|
||||||
block: 'center',
|
block: 'center',
|
||||||
inline: 'center',
|
inline: 'center',
|
||||||
@ -276,11 +252,21 @@ export class CDPElementHandle<
|
|||||||
// breaking change in Puppeteer yet so we'll ignore the line.
|
// breaking change in Puppeteer yet so we'll ignore the line.
|
||||||
behavior: 'instant',
|
behavior: 'instant',
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
|
||||||
this.#page.isJavaScriptEnabled()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #scrollIntoViewIfNeeded(
|
||||||
|
this: CDPElementHandle<Element>
|
||||||
|
): Promise<void> {
|
||||||
|
if (
|
||||||
|
await this.isIntersectingViewport({
|
||||||
|
threshold: 1,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.scrollIntoView();
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getOOPIFOffsets(
|
async #getOOPIFOffsets(
|
||||||
|
@ -509,6 +509,12 @@
|
|||||||
"parameters": ["cdp", "firefox"],
|
"parameters": ["cdp", "firefox"],
|
||||||
"expectations": ["FAIL"]
|
"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",
|
"testIdPattern": "[click.spec] Page.click should select the text by triple clicking",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
@ -116,6 +116,18 @@ describe('Page.click', function () {
|
|||||||
await Promise.all([page.click('a'), page.waitForNavigation()]);
|
await Promise.all([page.click('a'), page.waitForNavigation()]);
|
||||||
expect(page.url()).toBe(server.PREFIX + '/wrappedlink.html#clicked');
|
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 () => {
|
it('should click when one of inline box children is outside of viewport', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user