Input.ts (15661B)
1 /** 2 * @license 3 * Copyright 2017 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 import type {Protocol} from 'devtools-protocol'; 8 9 import {TouchError} from '../common/Errors.js'; 10 import type {KeyInput} from '../common/USKeyboardLayout.js'; 11 import {createIncrementalIdGenerator} from '../util/incremental-id-generator.js'; 12 13 import type {Point} from './ElementHandle.js'; 14 15 /** 16 * @public 17 */ 18 export interface KeyDownOptions { 19 /** 20 * @deprecated Do not use. This is automatically handled. 21 */ 22 text?: string; 23 /** 24 * @deprecated Do not use. This is automatically handled. 25 */ 26 commands?: string[]; 27 } 28 29 /** 30 * @public 31 */ 32 export interface KeyboardTypeOptions { 33 delay?: number; 34 } 35 36 /** 37 * @public 38 */ 39 export type KeyPressOptions = KeyDownOptions & KeyboardTypeOptions; 40 41 /** 42 * Keyboard provides an api for managing a virtual keyboard. 43 * The high level api is {@link Keyboard."type"}, 44 * which takes raw characters and generates proper keydown, keypress/input, 45 * and keyup events on your page. 46 * 47 * @remarks 48 * For finer control, you can use {@link Keyboard.down}, 49 * {@link Keyboard.up}, and {@link Keyboard.sendCharacter} 50 * to manually fire events as if they were generated from a real keyboard. 51 * 52 * On macOS, keyboard shortcuts like `⌘ A` -\> Select All do not work. 53 * See {@link https://github.com/puppeteer/puppeteer/issues/1313 | #1313}. 54 * 55 * @example 56 * An example of holding down `Shift` in order to select and delete some text: 57 * 58 * ```ts 59 * await page.keyboard.type('Hello World!'); 60 * await page.keyboard.press('ArrowLeft'); 61 * 62 * await page.keyboard.down('Shift'); 63 * for (let i = 0; i < ' World'.length; i++) 64 * await page.keyboard.press('ArrowLeft'); 65 * await page.keyboard.up('Shift'); 66 * 67 * await page.keyboard.press('Backspace'); 68 * // Result text will end up saying 'Hello!' 69 * ``` 70 * 71 * @example 72 * An example of pressing `A` 73 * 74 * ```ts 75 * await page.keyboard.down('Shift'); 76 * await page.keyboard.press('KeyA'); 77 * await page.keyboard.up('Shift'); 78 * ``` 79 * 80 * @public 81 */ 82 export abstract class Keyboard { 83 /** 84 * @internal 85 */ 86 constructor() {} 87 88 /** 89 * Dispatches a `keydown` event. 90 * 91 * @remarks 92 * If `key` is a single character and no modifier keys besides `Shift` 93 * are being held down, a `keypress`/`input` event will also generated. 94 * The `text` option can be specified to force an input event to be generated. 95 * If `key` is a modifier key, `Shift`, `Meta`, `Control`, or `Alt`, 96 * subsequent key presses will be sent with that modifier active. 97 * To release the modifier key, use {@link Keyboard.up}. 98 * 99 * After the key is pressed once, subsequent calls to 100 * {@link Keyboard.down} will have 101 * {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat | repeat} 102 * set to true. To release the key, use {@link Keyboard.up}. 103 * 104 * Modifier keys DO influence {@link Keyboard.down}. 105 * Holding down `Shift` will type the text in upper case. 106 * 107 * @param key - Name of key to press, such as `ArrowLeft`. 108 * See {@link KeyInput} for a list of all key names. 109 * 110 * @param options - An object of options. Accepts text which, if specified, 111 * generates an input event with this text. Accepts commands which, if specified, 112 * is the commands of keyboard shortcuts, 113 * see {@link https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/commands/editor_command_names.h | Chromium Source Code} for valid command names. 114 */ 115 abstract down( 116 key: KeyInput, 117 options?: Readonly<KeyDownOptions>, 118 ): Promise<void>; 119 120 /** 121 * Dispatches a `keyup` event. 122 * 123 * @param key - Name of key to release, such as `ArrowLeft`. 124 * See {@link KeyInput | KeyInput} 125 * for a list of all key names. 126 */ 127 abstract up(key: KeyInput): Promise<void>; 128 129 /** 130 * Dispatches a `keypress` and `input` event. 131 * This does not send a `keydown` or `keyup` event. 132 * 133 * @remarks 134 * Modifier keys DO NOT effect {@link Keyboard.sendCharacter | Keyboard.sendCharacter}. 135 * Holding down `Shift` will not type the text in upper case. 136 * 137 * @example 138 * 139 * ```ts 140 * page.keyboard.sendCharacter('嗨'); 141 * ``` 142 * 143 * @param char - Character to send into the page. 144 */ 145 abstract sendCharacter(char: string): Promise<void>; 146 147 /** 148 * Sends a `keydown`, `keypress`/`input`, 149 * and `keyup` event for each character in the text. 150 * 151 * @remarks 152 * To press a special key, like `Control` or `ArrowDown`, 153 * use {@link Keyboard.press}. 154 * 155 * Modifier keys DO NOT effect `keyboard.type`. 156 * Holding down `Shift` will not type the text in upper case. 157 * 158 * @example 159 * 160 * ```ts 161 * await page.keyboard.type('Hello'); // Types instantly 162 * await page.keyboard.type('World', {delay: 100}); // Types slower, like a user 163 * ``` 164 * 165 * @param text - A text to type into a focused element. 166 * @param options - An object of options. Accepts delay which, 167 * if specified, is the time to wait between `keydown` and `keyup` in milliseconds. 168 * Defaults to 0. 169 */ 170 abstract type( 171 text: string, 172 options?: Readonly<KeyboardTypeOptions>, 173 ): Promise<void>; 174 175 /** 176 * Shortcut for {@link Keyboard.down} 177 * and {@link Keyboard.up}. 178 * 179 * @remarks 180 * If `key` is a single character and no modifier keys besides `Shift` 181 * are being held down, a `keypress`/`input` event will also generated. 182 * The `text` option can be specified to force an input event to be generated. 183 * 184 * Modifier keys DO effect {@link Keyboard.press}. 185 * Holding down `Shift` will type the text in upper case. 186 * 187 * @param key - Name of key to press, such as `ArrowLeft`. 188 * See {@link KeyInput} for a list of all key names. 189 * 190 * @param options - An object of options. Accepts text which, if specified, 191 * generates an input event with this text. Accepts delay which, 192 * if specified, is the time to wait between `keydown` and `keyup` in milliseconds. 193 * Defaults to 0. Accepts commands which, if specified, 194 * is the commands of keyboard shortcuts, 195 * see {@link https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/commands/editor_command_names.h | Chromium Source Code} for valid command names. 196 */ 197 abstract press( 198 key: KeyInput, 199 options?: Readonly<KeyPressOptions>, 200 ): Promise<void>; 201 } 202 203 /** 204 * @public 205 */ 206 export interface MouseOptions { 207 /** 208 * Determines which button will be pressed. 209 * 210 * @defaultValue `'left'` 211 */ 212 button?: MouseButton; 213 /** 214 * Determines the click count for the mouse event. This does not perform 215 * multiple clicks. 216 * 217 * @deprecated Use {@link MouseClickOptions.count}. 218 * @defaultValue `1` 219 */ 220 clickCount?: number; 221 } 222 223 /** 224 * @public 225 */ 226 export interface MouseClickOptions extends MouseOptions { 227 /** 228 * Time (in ms) to delay the mouse release after the mouse press. 229 */ 230 delay?: number; 231 /** 232 * Number of clicks to perform. 233 * 234 * @defaultValue `1` 235 */ 236 count?: number; 237 } 238 239 /** 240 * @public 241 */ 242 export interface MouseWheelOptions { 243 deltaX?: number; 244 deltaY?: number; 245 } 246 247 /** 248 * @public 249 */ 250 export interface MouseMoveOptions { 251 /** 252 * Determines the number of movements to make from the current mouse position 253 * to the new one. 254 * 255 * @defaultValue `1` 256 */ 257 steps?: number; 258 } 259 260 /** 261 * Enum of valid mouse buttons. 262 * 263 * @public 264 */ 265 export const MouseButton = Object.freeze({ 266 Left: 'left', 267 Right: 'right', 268 Middle: 'middle', 269 Back: 'back', 270 Forward: 'forward', 271 }) satisfies Record<string, Protocol.Input.MouseButton>; 272 273 /** 274 * @public 275 */ 276 export type MouseButton = (typeof MouseButton)[keyof typeof MouseButton]; 277 278 /** 279 * The Mouse class operates in main-frame CSS pixels 280 * relative to the top-left corner of the viewport. 281 * 282 * @remarks 283 * Every `page` object has its own Mouse, accessible with {@link Page.mouse}. 284 * 285 * @example 286 * 287 * ```ts 288 * // Using ‘page.mouse’ to trace a 100x100 square. 289 * await page.mouse.move(0, 0); 290 * await page.mouse.down(); 291 * await page.mouse.move(0, 100); 292 * await page.mouse.move(100, 100); 293 * await page.mouse.move(100, 0); 294 * await page.mouse.move(0, 0); 295 * await page.mouse.up(); 296 * ``` 297 * 298 * **Note**: The mouse events trigger synthetic `MouseEvent`s. 299 * This means that it does not fully replicate the functionality of what a normal user 300 * would be able to do with their mouse. 301 * 302 * For example, dragging and selecting text is not possible using `page.mouse`. 303 * Instead, you can use the {@link https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/getSelection | `DocumentOrShadowRoot.getSelection()`} functionality implemented in the platform. 304 * 305 * @example 306 * For example, if you want to select all content between nodes: 307 * 308 * ```ts 309 * await page.evaluate( 310 * (from, to) => { 311 * const selection = from.getRootNode().getSelection(); 312 * const range = document.createRange(); 313 * range.setStartBefore(from); 314 * range.setEndAfter(to); 315 * selection.removeAllRanges(); 316 * selection.addRange(range); 317 * }, 318 * fromJSHandle, 319 * toJSHandle, 320 * ); 321 * ``` 322 * 323 * If you then would want to copy-paste your selection, you can use the clipboard api: 324 * 325 * ```ts 326 * // The clipboard api does not allow you to copy, unless the tab is focused. 327 * await page.bringToFront(); 328 * await page.evaluate(() => { 329 * // Copy the selected content to the clipboard 330 * document.execCommand('copy'); 331 * // Obtain the content of the clipboard as a string 332 * return navigator.clipboard.readText(); 333 * }); 334 * ``` 335 * 336 * **Note**: If you want access to the clipboard API, 337 * you have to give it permission to do so: 338 * 339 * ```ts 340 * await browser 341 * .defaultBrowserContext() 342 * .overridePermissions('<your origin>', [ 343 * 'clipboard-read', 344 * 'clipboard-write', 345 * ]); 346 * ``` 347 * 348 * @public 349 */ 350 export abstract class Mouse { 351 /** 352 * @internal 353 */ 354 constructor() {} 355 356 /** 357 * Resets the mouse to the default state: No buttons pressed; position at 358 * (0,0). 359 */ 360 abstract reset(): Promise<void>; 361 362 /** 363 * Moves the mouse to the given coordinate. 364 * 365 * @param x - Horizontal position of the mouse. 366 * @param y - Vertical position of the mouse. 367 * @param options - Options to configure behavior. 368 */ 369 abstract move( 370 x: number, 371 y: number, 372 options?: Readonly<MouseMoveOptions>, 373 ): Promise<void>; 374 375 /** 376 * Presses the mouse. 377 * 378 * @param options - Options to configure behavior. 379 */ 380 abstract down(options?: Readonly<MouseOptions>): Promise<void>; 381 382 /** 383 * Releases the mouse. 384 * 385 * @param options - Options to configure behavior. 386 */ 387 abstract up(options?: Readonly<MouseOptions>): Promise<void>; 388 389 /** 390 * Shortcut for `mouse.move`, `mouse.down` and `mouse.up`. 391 * 392 * @param x - Horizontal position of the mouse. 393 * @param y - Vertical position of the mouse. 394 * @param options - Options to configure behavior. 395 */ 396 abstract click( 397 x: number, 398 y: number, 399 options?: Readonly<MouseClickOptions>, 400 ): Promise<void>; 401 402 /** 403 * Dispatches a `mousewheel` event. 404 * @param options - Optional: `MouseWheelOptions`. 405 * 406 * @example 407 * An example of zooming into an element: 408 * 409 * ```ts 410 * await page.goto( 411 * 'https://mdn.mozillademos.org/en-US/docs/Web/API/Element/wheel_event$samples/Scaling_an_element_via_the_wheel?revision=1587366', 412 * ); 413 * 414 * const elem = await page.$('div'); 415 * const boundingBox = await elem.boundingBox(); 416 * await page.mouse.move( 417 * boundingBox.x + boundingBox.width / 2, 418 * boundingBox.y + boundingBox.height / 2, 419 * ); 420 * 421 * await page.mouse.wheel({deltaY: -100}); 422 * ``` 423 */ 424 abstract wheel(options?: Readonly<MouseWheelOptions>): Promise<void>; 425 426 /** 427 * Dispatches a `drag` event. 428 * @param start - starting point for drag 429 * @param target - point to drag to 430 */ 431 abstract drag(start: Point, target: Point): Promise<Protocol.Input.DragData>; 432 433 /** 434 * Dispatches a `dragenter` event. 435 * @param target - point for emitting `dragenter` event 436 * @param data - drag data containing items and operations mask 437 */ 438 abstract dragEnter( 439 target: Point, 440 data: Protocol.Input.DragData, 441 ): Promise<void>; 442 443 /** 444 * Dispatches a `dragover` event. 445 * @param target - point for emitting `dragover` event 446 * @param data - drag data containing items and operations mask 447 */ 448 abstract dragOver( 449 target: Point, 450 data: Protocol.Input.DragData, 451 ): Promise<void>; 452 453 /** 454 * Performs a dragenter, dragover, and drop in sequence. 455 * @param target - point to drop on 456 * @param data - drag data containing items and operations mask 457 */ 458 abstract drop(target: Point, data: Protocol.Input.DragData): Promise<void>; 459 460 /** 461 * Performs a drag, dragenter, dragover, and drop in sequence. 462 * @param start - point to drag from 463 * @param target - point to drop on 464 * @param options - An object of options. Accepts delay which, 465 * if specified, is the time to wait between `dragover` and `drop` in milliseconds. 466 * Defaults to 0. 467 */ 468 abstract dragAndDrop( 469 start: Point, 470 target: Point, 471 options?: {delay?: number}, 472 ): Promise<void>; 473 } 474 /** 475 * The TouchHandle interface exposes methods to manipulate touches that have been started 476 * @public 477 */ 478 export interface TouchHandle { 479 /** 480 * Dispatches a `touchMove` event for this touch. 481 * @param x - Horizontal position of the move. 482 * @param y - Vertical position of the move. 483 */ 484 move(x: number, y: number): Promise<void>; 485 /** 486 * Dispatches a `touchend` event for this touch. 487 */ 488 end(): Promise<void>; 489 } 490 /** 491 * The Touchscreen class exposes touchscreen events. 492 * @public 493 */ 494 export abstract class Touchscreen { 495 /** 496 * @internal 497 */ 498 idGenerator = createIncrementalIdGenerator(); 499 /** 500 * @internal 501 */ 502 touches: TouchHandle[] = []; 503 /** 504 * @internal 505 */ 506 constructor() {} 507 508 /** 509 * @internal 510 */ 511 removeHandle(handle: TouchHandle): void { 512 const index = this.touches.indexOf(handle); 513 if (index === -1) { 514 return; 515 } 516 this.touches.splice(index, 1); 517 } 518 519 /** 520 * Dispatches a `touchstart` and `touchend` event. 521 * @param x - Horizontal position of the tap. 522 * @param y - Vertical position of the tap. 523 */ 524 async tap(x: number, y: number): Promise<void> { 525 const touch = await this.touchStart(x, y); 526 await touch.end(); 527 } 528 529 /** 530 * Dispatches a `touchstart` event. 531 * @param x - Horizontal position of the tap. 532 * @param y - Vertical position of the tap. 533 * @returns A handle for the touch that was started. 534 */ 535 abstract touchStart(x: number, y: number): Promise<TouchHandle>; 536 537 /** 538 * Dispatches a `touchMove` event on the first touch that is active. 539 * @param x - Horizontal position of the move. 540 * @param y - Vertical position of the move. 541 * 542 * @remarks 543 * 544 * Not every `touchMove` call results in a `touchmove` event being emitted, 545 * depending on the browser's optimizations. For example, Chrome 546 * {@link https://developer.chrome.com/blog/a-more-compatible-smoother-touch/#chromes-new-model-the-throttled-async-touchmove-model | throttles} 547 * touch move events. 548 */ 549 async touchMove(x: number, y: number): Promise<void> { 550 const touch = this.touches[0]; 551 if (!touch) { 552 throw new TouchError('Must start a new Touch first'); 553 } 554 return await touch.move(x, y); 555 } 556 557 /** 558 * Dispatches a `touchend` event on the first touch that is active. 559 */ 560 async touchEnd(): Promise<void> { 561 const touch = this.touches.shift(); 562 if (!touch) { 563 throw new TouchError('Must start a new Touch first'); 564 } 565 await touch.end(); 566 } 567 }