fix: implement click count (#10069)

This commit is contained in:
jrandolf 2023-04-25 13:28:47 +02:00 committed by GitHub
parent 4815676f21
commit 8124a7d5bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 86 additions and 107 deletions

View File

@ -7,14 +7,13 @@ sidebar_label: ClickOptions
#### Signature: #### Signature:
```typescript ```typescript
export interface ClickOptions export interface ClickOptions extends MouseClickOptions
``` ```
**Extends:** [MouseClickOptions](./puppeteer.mouseclickoptions.md)
## Properties ## Properties
| Property | Modifiers | Type | Description | Default | | Property | Modifiers | Type | Description | Default |
| ---------- | --------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------- | -------------- | | -------- | --------------------- | ------------------------------- | --------------------------------------------------------------------------------- | ------- |
| button | <code>optional</code> | [MouseButton](./puppeteer.mousebutton.md) | | 'left' |
| clickCount | <code>optional</code> | number | | <code>1</code> |
| delay | <code>optional</code> | number | Time to wait between <code>mousedown</code> and <code>mouseup</code> in milliseconds. | <code>0</code> |
| offset | <code>optional</code> | [Offset](./puppeteer.offset.md) | Offset for the clickable point relative to the top-left corner of the border box. | | | offset | <code>optional</code> | [Offset](./puppeteer.offset.md) | Offset for the clickable point relative to the top-left corner of the border box. | |

View File

@ -10,23 +10,16 @@ Clicks the first element found that matches `selector`.
```typescript ```typescript
class Frame { class Frame {
click( click(selector: string, options?: Readonly<ClickOptions>): Promise<void>;
selector: string,
options?: {
delay?: number;
button?: MouseButton;
clickCount?: number;
}
): Promise<void>;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------------------------- | -------------------------- | | --------- | ----------------------------------------------------------- | -------------------------- |
| selector | string | The selector to query for. | | selector | string | The selector to query for. |
| options | { delay?: number; button?: [MouseButton](./puppeteer.mousebutton.md); clickCount?: number; } | _(Optional)_ | | options | Readonly&lt;[ClickOptions](./puppeteer.clickoptions.md)&gt; | _(Optional)_ |
**Returns:** **Returns:**

View File

@ -10,17 +10,21 @@ Shortcut for `mouse.move`, `mouse.down` and `mouse.up`.
```typescript ```typescript
class Mouse { class Mouse {
click(x: number, y: number, options?: MouseClickOptions): Promise<void>; click(
x: number,
y: number,
options?: Readonly<MouseClickOptions>
): Promise<void>;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ----------------------------------------------------- | ------------------------------------------- | | --------- | --------------------------------------------------------------------- | ------------------------------------------- |
| x | number | Horizontal position of the mouse. | | x | number | Horizontal position of the mouse. |
| y | number | Vertical position of the mouse. | | y | number | Vertical position of the mouse. |
| options | [MouseClickOptions](./puppeteer.mouseclickoptions.md) | _(Optional)_ Options to configure behavior. | | options | Readonly&lt;[MouseClickOptions](./puppeteer.mouseclickoptions.md)&gt; | _(Optional)_ Options to configure behavior. |
**Returns:** **Returns:**

View File

@ -15,5 +15,6 @@ export interface MouseClickOptions extends MouseOptions
## Properties ## Properties
| Property | Modifiers | Type | Description | Default | | Property | Modifiers | Type | Description | Default |
| -------- | --------------------- | ------ | -------------------------------------------------------------- | ------- | | -------- | --------------------- | ------ | -------------------------------------------------------------- | -------------- |
| count | <code>optional</code> | number | Number of clicks to perform. | <code>1</code> |
| delay | <code>optional</code> | number | Time (in ms) to delay the mouse release after the mouse press. | | | delay | <code>optional</code> | number | Time (in ms) to delay the mouse release after the mouse press. | |

View File

@ -13,6 +13,6 @@ export interface MouseOptions
## Properties ## Properties
| Property | Modifiers | Type | Description | Default | | Property | Modifiers | Type | Description | Default |
| ---------- | --------------------- | ----------------------------------------- | ----------------------------------------- | ------------------- | | ---------- | --------------------- | ----------------------------------------- | ---------------------------------------- | ------------------- |
| button | <code>optional</code> | [MouseButton](./puppeteer.mousebutton.md) | Determines which button will be pressed. | <code>'left'</code> | | button | <code>optional</code> | [MouseButton](./puppeteer.mousebutton.md) | Determines which button will be pressed. | <code>'left'</code> |
| clickCount | <code>optional</code> | number | Determines the click count for the mouse. | <code>1</code> | | clickCount | <code>optional</code> | number | | <code>1</code> |

View File

@ -10,23 +10,16 @@ This method fetches an element with `selector`, scrolls it into view if needed,
```typescript ```typescript
class Page { class Page {
click( click(selector: string, options?: Readonly<ClickOptions>): Promise<void>;
selector: string,
options?: {
delay?: number;
button?: MouseButton;
clickCount?: number;
}
): Promise<void>;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | --------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| selector | string | A <code>selector</code> to search for element to click. If there are multiple elements satisfying the <code>selector</code>, the first will be clicked | | selector | string | A <code>selector</code> to search for element to click. If there are multiple elements satisfying the <code>selector</code>, the first will be clicked |
| options | { delay?: number; button?: [MouseButton](./puppeteer.mousebutton.md); clickCount?: number; } | _(Optional)_ <code>Object</code> | | options | Readonly&lt;[ClickOptions](./puppeteer.clickoptions.md)&gt; | _(Optional)_ <code>Object</code> |
**Returns:** **Returns:**

View File

@ -19,7 +19,7 @@ import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../common/Connection.js'; import {CDPSession} from '../common/Connection.js';
import {ExecutionContext} from '../common/ExecutionContext.js'; import {ExecutionContext} from '../common/ExecutionContext.js';
import {Frame} from '../common/Frame.js'; import {Frame} from '../common/Frame.js';
import {MouseButton} from '../common/Input.js'; import {MouseClickOptions} from '../common/Input.js';
import {WaitForSelectorOptions} from '../common/IsolatedWorld.js'; import {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
import { import {
ElementFor, ElementFor,
@ -76,21 +76,7 @@ export interface Offset {
/** /**
* @public * @public
*/ */
export interface ClickOptions { export interface ClickOptions extends MouseClickOptions {
/**
* Time to wait between `mousedown` and `mouseup` in milliseconds.
*
* @defaultValue `0`
*/
delay?: number;
/**
* @defaultValue 'left'
*/
button?: MouseButton;
/**
* @defaultValue `1`
*/
clickCount?: number;
/** /**
* Offset for the clickable point relative to the top-left corner of the border box. * Offset for the clickable point relative to the top-left corner of the border box.
*/ */

View File

@ -34,20 +34,15 @@ import type {
FrameAddStyleTagOptions, FrameAddStyleTagOptions,
FrameWaitForFunctionOptions, FrameWaitForFunctionOptions,
} from '../common/Frame.js'; } from '../common/Frame.js';
import type { import type {Keyboard, Mouse, Touchscreen} from '../common/Input.js';
Keyboard,
Mouse,
MouseButton,
Touchscreen,
} from '../common/Input.js';
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js'; import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js'; import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js';
import type {Credentials, NetworkConditions} from '../common/NetworkManager.js'; import type {Credentials, NetworkConditions} from '../common/NetworkManager.js';
import { import {
LowerCasePaperFormat, LowerCasePaperFormat,
paperFormats,
ParsedPDFOptions, ParsedPDFOptions,
PDFOptions, PDFOptions,
paperFormats,
} from '../common/PDFOptions.js'; } from '../common/PDFOptions.js';
import type {Viewport} from '../common/PuppeteerViewport.js'; import type {Viewport} from '../common/PuppeteerViewport.js';
import type {Target} from '../common/Target.js'; import type {Target} from '../common/Target.js';
@ -64,7 +59,7 @@ import {assert} from '../util/assert.js';
import type {Browser} from './Browser.js'; import type {Browser} from './Browser.js';
import type {BrowserContext} from './BrowserContext.js'; import type {BrowserContext} from './BrowserContext.js';
import type {ElementHandle} from './ElementHandle.js'; import type {ClickOptions, ElementHandle} from './ElementHandle.js';
import type {JSHandle} from './JSHandle.js'; import type {JSHandle} from './JSHandle.js';
/** /**
@ -2319,14 +2314,7 @@ export class Page extends EventEmitter {
* successfully clicked. The Promise will be rejected if there is no element * successfully clicked. The Promise will be rejected if there is no element
* matching `selector`. * matching `selector`.
*/ */
click( click(selector: string, options?: Readonly<ClickOptions>): Promise<void>;
selector: string,
options?: {
delay?: number;
button?: MouseButton;
clickCount?: number;
}
): Promise<void>;
click(): Promise<void> { click(): Promise<void> {
throw new Error('Not implemented'); throw new Error('Not implemented');
} }

View File

@ -445,7 +445,7 @@ export class CDPElementHandle<
*/ */
override async click( override async click(
this: CDPElementHandle<Element>, this: CDPElementHandle<Element>,
options: ClickOptions = {} options: Readonly<ClickOptions> = {}
): Promise<void> { ): Promise<void> {
await this.#scrollIntoViewIfNeeded(); await this.#scrollIntoViewIfNeeded();
const {x, y} = await this.clickablePoint(options.offset); const {x, y} = await this.clickablePoint(options.offset);

View File

@ -16,7 +16,7 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {ElementHandle} from '../api/ElementHandle.js'; import {type ClickOptions, ElementHandle} from '../api/ElementHandle.js';
import {HTTPResponse} from '../api/HTTPResponse.js'; import {HTTPResponse} from '../api/HTTPResponse.js';
import {Page, WaitTimeoutOptions} from '../api/Page.js'; import {Page, WaitTimeoutOptions} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
@ -30,7 +30,6 @@ import {
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
import {FrameManager} from './FrameManager.js'; import {FrameManager} from './FrameManager.js';
import {getQueryHandlerAndSelector} from './GetQueryHandler.js'; import {getQueryHandlerAndSelector} from './GetQueryHandler.js';
import {MouseButton} from './Input.js';
import { import {
IsolatedWorld, IsolatedWorld,
IsolatedWorldChart, IsolatedWorldChart,
@ -944,11 +943,7 @@ export class Frame {
*/ */
async click( async click(
selector: string, selector: string,
options: { options: Readonly<ClickOptions> = {}
delay?: number;
button?: MouseButton;
clickCount?: number;
} = {}
): Promise<void> { ): Promise<void> {
return this.worlds[PUPPETEER_WORLD].click(selector, options); return this.worlds[PUPPETEER_WORLD].click(selector, options);
} }

View File

@ -342,7 +342,10 @@ export interface MouseOptions {
*/ */
button?: MouseButton; button?: MouseButton;
/** /**
* Determines the click count for the mouse. * @deprecated Use {@link MouseClickOptions.count}.
*
* Determines the click count for the mouse event. This does not perform
* multiple clicks.
* *
* @defaultValue `1` * @defaultValue `1`
*/ */
@ -357,6 +360,12 @@ export interface MouseClickOptions extends MouseOptions {
* Time (in ms) to delay the mouse release after the mouse press. * Time (in ms) to delay the mouse release after the mouse press.
*/ */
delay?: number; delay?: number;
/**
* Number of clicks to perform.
*
* @defaultValue `1`
*/
count?: number;
} }
/** /**
@ -694,15 +703,22 @@ export class Mouse {
async click( async click(
x: number, x: number,
y: number, y: number,
options: MouseClickOptions = {} options: Readonly<MouseClickOptions> = {}
): Promise<void> { ): Promise<void> {
const {delay} = options; const {delay, count = 1, clickCount = count} = options;
const actions: Array<Promise<void>> = []; if (count < 1) {
const {position} = this.#state; throw new Error('Click must occur a positive number of times.');
if (position.x !== x || position.y !== y) {
actions.push(this.move(x, y));
} }
actions.push(this.down(options)); const actions: Array<Promise<void>> = [this.move(x, y)];
if (clickCount === count) {
for (let i = 1; i < count; ++i) {
actions.push(
this.down({...options, clickCount: i}),
this.up({...options, clickCount: i})
);
}
}
actions.push(this.down({...options, clickCount}));
if (typeof delay === 'number') { if (typeof delay === 'number') {
await Promise.all(actions); await Promise.all(actions);
actions.length = 0; actions.length = 0;
@ -710,7 +726,7 @@ export class Mouse {
setTimeout(resolve, delay); setTimeout(resolve, delay);
}); });
} }
actions.push(this.up(options)); actions.push(this.up({...options, clickCount}));
await Promise.all(actions); await Promise.all(actions);
} }

View File

@ -16,7 +16,7 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import type {ElementHandle} from '../api/ElementHandle.js'; import type {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
import {JSHandle} from '../api/JSHandle.js'; import {JSHandle} from '../api/JSHandle.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {createDeferredPromise} from '../util/DeferredPromise.js'; import {createDeferredPromise} from '../util/DeferredPromise.js';
@ -26,7 +26,6 @@ import {CDPSession} from './Connection.js';
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
import {Frame} from './Frame.js'; import {Frame} from './Frame.js';
import {FrameManager} from './FrameManager.js'; import {FrameManager} from './FrameManager.js';
import {MouseButton} from './Input.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js'; import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {TimeoutSettings} from './TimeoutSettings.js'; import {TimeoutSettings} from './TimeoutSettings.js';
@ -306,7 +305,7 @@ export class IsolatedWorld {
async click( async click(
selector: string, selector: string,
options: {delay?: number; button?: MouseButton; clickCount?: number} options: Readonly<ClickOptions> = {}
): Promise<void> { ): Promise<void> {
const handle = await this.$(selector); const handle = await this.$(selector);
assert(handle, `No element found for selector: ${selector}`); assert(handle, `No element found for selector: ${selector}`);

View File

@ -20,7 +20,7 @@ import {Protocol} from 'devtools-protocol';
import type {Browser} from '../api/Browser.js'; import type {Browser} from '../api/Browser.js';
import type {BrowserContext} from '../api/BrowserContext.js'; import type {BrowserContext} from '../api/BrowserContext.js';
import {ElementHandle} from '../api/ElementHandle.js'; import {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
import {HTTPRequest} from '../api/HTTPRequest.js'; import {HTTPRequest} from '../api/HTTPRequest.js';
import {HTTPResponse} from '../api/HTTPResponse.js'; import {HTTPResponse} from '../api/HTTPResponse.js';
import {JSHandle} from '../api/JSHandle.js'; import {JSHandle} from '../api/JSHandle.js';
@ -62,7 +62,7 @@ import {
FrameWaitForFunctionOptions, FrameWaitForFunctionOptions,
} from './Frame.js'; } from './Frame.js';
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js'; import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js';
import {Keyboard, Mouse, MouseButton, Touchscreen} from './Input.js'; import {Keyboard, Mouse, Touchscreen} from './Input.js';
import {WaitForSelectorOptions} from './IsolatedWorld.js'; import {WaitForSelectorOptions} from './IsolatedWorld.js';
import {MAIN_WORLD} from './IsolatedWorlds.js'; import {MAIN_WORLD} from './IsolatedWorlds.js';
import { import {
@ -1551,11 +1551,7 @@ export class CDPPage extends Page {
override click( override click(
selector: string, selector: string,
options: { options: Readonly<ClickOptions> = {}
delay?: number;
button?: MouseButton;
clickCount?: number;
} = {}
): Promise<void> { ): Promise<void> {
return this.mainFrame().click(selector, options); return this.mainFrame().click(selector, options);
} }

View File

@ -155,9 +155,18 @@ describe('Page.click', function () {
const text = const text =
"This is the text that we are going to try to select. Let's see how it goes."; "This is the text that we are going to try to select. Let's see how it goes.";
await page.keyboard.type(text); await page.keyboard.type(text);
await page.click('textarea'); await page.evaluate(() => {
await page.click('textarea', {clickCount: 2}); (window as any).clicks = [];
await page.click('textarea', {clickCount: 3}); window.addEventListener('click', event => {
return (window as any).clicks.push(event.detail);
});
});
await page.click('textarea', {count: 3});
expect(
await page.evaluate(() => {
return (window as any).clicks;
})
).toMatchObject({0: 1, 1: 2, 2: 3});
expect( expect(
await page.evaluate(() => { await page.evaluate(() => {
const textarea = document.querySelector('textarea'); const textarea = document.querySelector('textarea');
@ -328,7 +337,7 @@ describe('Page.click', function () {
}); });
}); });
const button = (await page.$('button'))!; const button = (await page.$('button'))!;
await button!.click({clickCount: 2}); await button!.click({count: 2});
expect(await page.evaluate('double')).toBe(true); expect(await page.evaluate('double')).toBe(true);
expect(await page.evaluate('result')).toBe('Clicked'); expect(await page.evaluate('result')).toBe('Clicked');
}); });