Skip to content

Commit

Permalink
chore: implement Webdriver BiDi mouse and touchscreen (#10402)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
  • Loading branch information
jrandolf and OrKoN committed Jun 19, 2023
1 parent 4b49212 commit 4f6b0d4
Show file tree
Hide file tree
Showing 17 changed files with 869 additions and 192 deletions.
14 changes: 6 additions & 8 deletions docs/api/puppeteer.frame.type.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,18 @@ class Frame {
type(
selector: string,
text: string,
options?: {
delay: number;
}
options?: Readonly<TypeOptions>
): Promise<void>;
}
```

## Parameters

| Parameter | Type | Description |
| --------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| selector | string | the selector for the element to type into. If there are multiple the first will be used. |
| text | string | text to type into the element |
| options | { delay: number; } | _(Optional)_ takes one option, <code>delay</code>, which sets the time to wait between key presses in milliseconds. Defaults to <code>0</code>. |
| Parameter | Type | Description |
| --------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| selector | string | the selector for the element to type into. If there are multiple the first will be used. |
| text | string | text to type into the element |
| options | Readonly&lt;[TypeOptions](./puppeteer.typeoptions.md)&gt; | _(Optional)_ takes one option, <code>delay</code>, which sets the time to wait between key presses in milliseconds. Defaults to <code>0</code>. |

**Returns:**

Expand Down
14 changes: 6 additions & 8 deletions docs/api/puppeteer.page.type.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,18 @@ class Page {
type(
selector: string,
text: string,
options?: {
delay: number;
}
options?: Readonly<TypeOptions>
): Promise<void>;
}
```

## Parameters

| Parameter | Type | Description |
| --------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| selector | string | A [selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) of an element to type into. If there are multiple elements satisfying the selector, the first will be used. |
| text | string | A text to type into a focused element. |
| options | { delay: number; } | _(Optional)_ have property <code>delay</code> which is the Time to wait between key presses in milliseconds. Defaults to <code>0</code>. |
| Parameter | Type | Description |
| --------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| selector | string | A [selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) of an element to type into. If there are multiple elements satisfying the selector, the first will be used. |
| text | string | A text to type into a focused element. |
| options | Readonly&lt;[TypeOptions](./puppeteer.typeoptions.md)&gt; | _(Optional)_ have property <code>delay</code> which is the Time to wait between key presses in milliseconds. Defaults to <code>0</code>. |

**Returns:**

Expand Down
16 changes: 16 additions & 0 deletions packages/puppeteer-core/src/api/ElementHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,22 @@ export class ElementHandle<
}
}

/**
* @internal
*/
protected async scrollIntoViewIfNeeded(
this: ElementHandle<Element>
): Promise<void> {
if (
await this.isIntersectingViewport({
threshold: 1,
})
) {
return;
}
await this.scrollIntoView();
}

/**
* 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
Expand Down
48 changes: 25 additions & 23 deletions packages/puppeteer-core/src/api/Frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
} from '../common/types.js';
import {TaskManager} from '../common/WaitTask.js';

import {TypeOptions} from './Input.js';
import {JSHandle} from './JSHandle.js';
import {Locator} from './Locator.js';

Expand Down Expand Up @@ -74,6 +75,16 @@ export interface Realm {
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
click(selector: string, options: Readonly<ClickOptions>): Promise<void>;
focus(selector: string): Promise<void>;
hover(selector: string): Promise<void>;
select(selector: string, ...values: string[]): Promise<string[]>;
tap(selector: string): Promise<void>;
type(
selector: string,
text: string,
options?: Readonly<TypeOptions>
): Promise<void>;
}

/**
Expand Down Expand Up @@ -793,12 +804,8 @@ export class Frame {
*
* @param selector - The selector to query for.
*/
async click(
selector: string,
options?: Readonly<ClickOptions>
): Promise<void>;
async click(): Promise<void> {
throw new Error('Not implemented');
click(selector: string, options: Readonly<ClickOptions> = {}): Promise<void> {
return this.isolatedRealm().click(selector, options);
}

/**
Expand All @@ -807,9 +814,8 @@ export class Frame {
* @param selector - The selector to query for.
* @throws Throws if there's no element matching `selector`.
*/
async focus(selector: string): Promise<void>;
async focus(): Promise<void> {
throw new Error('Not implemented');
async focus(selector: string): Promise<void> {
return this.isolatedRealm().focus(selector);
}

/**
Expand All @@ -819,9 +825,8 @@ export class Frame {
* @param selector - The selector to query for.
* @throws Throws if there's no element matching `selector`.
*/
async hover(selector: string): Promise<void>;
async hover(): Promise<void> {
throw new Error('Not implemented');
hover(selector: string): Promise<void> {
return this.isolatedRealm().hover(selector);
}

/**
Expand All @@ -842,9 +847,8 @@ export class Frame {
* @returns the list of values that were successfully selected.
* @throws Throws if there's no `<select>` matching `selector`.
*/
select(selector: string, ...values: string[]): Promise<string[]>;
select(): Promise<string[]> {
throw new Error('Not implemented');
select(selector: string, ...values: string[]): Promise<string[]> {
return this.isolatedRealm().select(selector, ...values);
}

/**
Expand All @@ -853,9 +857,8 @@ export class Frame {
* @param selector - The selector to query for.
* @throws Throws if there's no element matching `selector`.
*/
async tap(selector: string): Promise<void>;
async tap(): Promise<void> {
throw new Error('Not implemented');
tap(selector: string): Promise<void> {
return this.isolatedRealm().tap(selector);
}

/**
Expand All @@ -879,13 +882,12 @@ export class Frame {
* @param options - takes one option, `delay`, which sets the time to wait
* between key presses in milliseconds. Defaults to `0`.
*/
async type(
type(
selector: string,
text: string,
options?: {delay: number}
): Promise<void>;
async type(): Promise<void> {
throw new Error('Not implemented');
options?: Readonly<TypeOptions>
): Promise<void> {
return this.isolatedRealm().type(selector, text, options);
}

/**
Expand Down
34 changes: 14 additions & 20 deletions packages/puppeteer-core/src/api/Page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import type {
FrameAddStyleTagOptions,
FrameWaitForFunctionOptions,
} from './Frame.js';
import {Keyboard, Mouse, Touchscreen} from './Input.js';
import {Keyboard, Mouse, Touchscreen, TypeOptions} from './Input.js';
import type {JSHandle} from './JSHandle.js';
import {Locator} from './Locator.js';

Expand Down Expand Up @@ -2445,9 +2445,8 @@ export class Page extends EventEmitter {
* successfully clicked. The Promise will be rejected if there is no element
* matching `selector`.
*/
click(selector: string, options?: Readonly<ClickOptions>): Promise<void>;
click(): Promise<void> {
throw new Error('Not implemented');
click(selector: string, options?: Readonly<ClickOptions>): Promise<void> {
return this.mainFrame().click(selector, options);
}

/**
Expand All @@ -2463,9 +2462,8 @@ export class Page extends EventEmitter {
* @remarks
* Shortcut for {@link Frame.focus | page.mainFrame().focus(selector)}.
*/
focus(selector: string): Promise<void>;
focus(): Promise<void> {
throw new Error('Not implemented');
focus(selector: string): Promise<void> {
return this.mainFrame().focus(selector);
}

/**
Expand All @@ -2483,9 +2481,8 @@ export class Page extends EventEmitter {
* @remarks
* Shortcut for {@link Page.hover | page.mainFrame().hover(selector)}.
*/
hover(selector: string): Promise<void>;
hover(): Promise<void> {
throw new Error('Not implemented');
hover(selector: string): Promise<void> {
return this.mainFrame().hover(selector);
}

/**
Expand All @@ -2511,9 +2508,8 @@ export class Page extends EventEmitter {
* @remarks
* Shortcut for {@link Frame.select | page.mainFrame().select()}
*/
select(selector: string, ...values: string[]): Promise<string[]>;
select(): Promise<string[]> {
throw new Error('Not implemented');
select(selector: string, ...values: string[]): Promise<string[]> {
return this.mainFrame().select(selector, ...values);
}

/**
Expand All @@ -2529,9 +2525,8 @@ export class Page extends EventEmitter {
* @remarks
* Shortcut for {@link Frame.tap | page.mainFrame().tap(selector)}.
*/
tap(selector: string): Promise<void>;
tap(): Promise<void> {
throw new Error('Not implemented');
tap(selector: string): Promise<void> {
return this.mainFrame().tap(selector);
}

/**
Expand Down Expand Up @@ -2561,10 +2556,9 @@ export class Page extends EventEmitter {
type(
selector: string,
text: string,
options?: {delay: number}
): Promise<void>;
type(): Promise<void> {
throw new Error('Not implemented');
options?: Readonly<TypeOptions>
): Promise<void> {
return this.mainFrame().type(selector, text, options);
}

/**
Expand Down
37 changes: 12 additions & 25 deletions packages/puppeteer-core/src/common/ElementHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,6 @@ export class CDPElementHandle<
}
}

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}> {
Expand Down Expand Up @@ -300,7 +287,7 @@ export class CDPElementHandle<
* If the element is detached from DOM, the method throws an error.
*/
override async hover(this: CDPElementHandle<Element>): Promise<void> {
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
const {x, y} = await this.clickablePoint();
await this.#page.mouse.move(x, y);
}
Expand All @@ -314,7 +301,7 @@ export class CDPElementHandle<
this: CDPElementHandle<Element>,
options: Readonly<ClickOptions> = {}
): Promise<void> {
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
const {x, y} = await this.clickablePoint(options.offset);
await this.#page.mouse.click(x, y, options);
}
Expand All @@ -330,7 +317,7 @@ export class CDPElementHandle<
this.#page.isDragInterceptionEnabled(),
'Drag Interception is not enabled!'
);
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
const start = await this.clickablePoint();
return await this.#page.mouse.drag(start, target);
}
Expand All @@ -339,7 +326,7 @@ export class CDPElementHandle<
this: CDPElementHandle<Element>,
data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1}
): Promise<void> {
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
const target = await this.clickablePoint();
await this.#page.mouse.dragEnter(target, data);
}
Expand All @@ -348,7 +335,7 @@ export class CDPElementHandle<
this: CDPElementHandle<Element>,
data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1}
): Promise<void> {
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
const target = await this.clickablePoint();
await this.#page.mouse.dragOver(target, data);
}
Expand All @@ -357,7 +344,7 @@ export class CDPElementHandle<
this: CDPElementHandle<Element>,
data: Protocol.Input.DragData = {items: [], dragOperationsMask: 1}
): Promise<void> {
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
const destination = await this.clickablePoint();
await this.#page.mouse.drop(destination, data);
}
Expand All @@ -371,7 +358,7 @@ export class CDPElementHandle<
this.#page.isDragInterceptionEnabled(),
'Drag Interception is not enabled!'
);
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
const startPoint = await this.clickablePoint();
const targetPoint = await target.clickablePoint();
await this.#page.mouse.dragAndDrop(startPoint, targetPoint, options);
Expand Down Expand Up @@ -435,26 +422,26 @@ export class CDPElementHandle<
}

override async tap(this: CDPElementHandle<Element>): Promise<void> {
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
const {x, y} = await this.clickablePoint();
await this.#page.touchscreen.touchStart(x, y);
await this.#page.touchscreen.touchEnd();
}

override async touchStart(this: CDPElementHandle<Element>): Promise<void> {
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
const {x, y} = await this.clickablePoint();
await this.#page.touchscreen.touchStart(x, y);
}

override async touchMove(this: CDPElementHandle<Element>): Promise<void> {
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
const {x, y} = await this.clickablePoint();
await this.#page.touchscreen.touchMove(x, y);
}

override async touchEnd(this: CDPElementHandle<Element>): Promise<void> {
await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();
await this.#page.touchscreen.touchEnd();
}

Expand Down Expand Up @@ -552,7 +539,7 @@ export class CDPElementHandle<
needsViewportReset = true;
}

await this.#scrollIntoViewIfNeeded();
await this.scrollIntoViewIfNeeded();

boundingBox = await this.boundingBox();
assert(boundingBox, 'Node is either not visible or not an HTMLElement');
Expand Down

0 comments on commit 4f6b0d4

Please sign in to comment.