From 3ba7fba838ac8129ebb73720d665fdc8ed6e88db Mon Sep 17 00:00:00 2001 From: jrandolf <101637635+jrandolf@users.noreply.github.com> Date: Mon, 19 Jun 2023 17:44:39 +0200 Subject: [PATCH] chore: implement Bidi keyboard (#10417) --- docs/api/index.md | 2 +- docs/api/puppeteer.elementhandle.type.md | 10 +- docs/api/puppeteer.frame.type.md | 12 +- docs/api/puppeteer.keyboard.type.md | 10 +- ...ns.md => puppeteer.keyboardtypeoptions.md} | 6 +- docs/api/puppeteer.keypressoptions.md | 4 +- docs/api/puppeteer.page.type.md | 12 +- .../puppeteer-core/src/api/ElementHandle.ts | 11 +- packages/puppeteer-core/src/api/Frame.ts | 6 +- packages/puppeteer-core/src/api/Input.ts | 15 +- packages/puppeteer-core/src/api/Page.ts | 4 +- .../src/common/ElementHandle.ts | 4 +- packages/puppeteer-core/src/common/Input.ts | 4 +- .../src/common/IsolatedWorld.ts | 4 +- .../src/common/bidi/ElementHandle.ts | 18 ++ .../puppeteer-core/src/common/bidi/Input.ts | 278 +++++++++++++++++- .../puppeteer-core/src/common/bidi/Page.ts | 8 +- .../puppeteer-core/src/common/bidi/Sandbox.ts | 4 +- test/TestExpectations.json | 178 +++++++++-- 19 files changed, 510 insertions(+), 80 deletions(-) rename docs/api/{puppeteer.typeoptions.md => puppeteer.keyboardtypeoptions.md} (72%) diff --git a/docs/api/index.md b/docs/api/index.md index f10ff5d0..bc27f71c 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -92,6 +92,7 @@ sidebar_label: API | [InternalNetworkConditions](./puppeteer.internalnetworkconditions.md) | | | [JSCoverageEntry](./puppeteer.jscoverageentry.md) | The CoverageEntry class for JavaScript | | [JSCoverageOptions](./puppeteer.jscoverageoptions.md) | Set of configurable options for JS coverage. | +| [KeyboardTypeOptions](./puppeteer.keyboardtypeoptions.md) | | | [KeyDownOptions](./puppeteer.keydownoptions.md) | | | [LaunchOptions](./puppeteer.launchoptions.md) | Generic launch options that can be passed when launching any browser. | | [LocatorEventObject](./puppeteer.locatoreventobject.md) | | @@ -118,7 +119,6 @@ sidebar_label: API | [SerializedAXNode](./puppeteer.serializedaxnode.md) | Represents a Node and the properties of it that are relevant to Accessibility. | | [SnapshotOptions](./puppeteer.snapshotoptions.md) | | | [TracingOptions](./puppeteer.tracingoptions.md) | | -| [TypeOptions](./puppeteer.typeoptions.md) | | | [Viewport](./puppeteer.viewport.md) | Sets the viewport of the page. | | [WaitForOptions](./puppeteer.waitforoptions.md) | | | [WaitForSelectorOptions](./puppeteer.waitforselectoroptions.md) | | diff --git a/docs/api/puppeteer.elementhandle.type.md b/docs/api/puppeteer.elementhandle.type.md index 073d95ae..5682e3ee 100644 --- a/docs/api/puppeteer.elementhandle.type.md +++ b/docs/api/puppeteer.elementhandle.type.md @@ -12,16 +12,16 @@ To press a special key, like `Control` or `ArrowDown`, use [ElementHandle.press( ```typescript class ElementHandle { - type(text: string, options?: Readonly): Promise; + type(text: string, options?: Readonly): Promise; } ``` ## Parameters -| Parameter | Type | Description | -| --------- | --------------------------------------------------------- | -------------------------------------------------- | -| text | string | | -| options | Readonly<[TypeOptions](./puppeteer.typeoptions.md)> | _(Optional)_ Delay in milliseconds. Defaults to 0. | +| Parameter | Type | Description | +| --------- | ------------------------------------------------------------------------- | -------------------------------------------------- | +| text | string | | +| options | Readonly<[KeyboardTypeOptions](./puppeteer.keyboardtypeoptions.md)> | _(Optional)_ Delay in milliseconds. Defaults to 0. | **Returns:** diff --git a/docs/api/puppeteer.frame.type.md b/docs/api/puppeteer.frame.type.md index bf1e27d2..341ca04c 100644 --- a/docs/api/puppeteer.frame.type.md +++ b/docs/api/puppeteer.frame.type.md @@ -13,18 +13,18 @@ class Frame { type( selector: string, text: string, - options?: Readonly + options?: Readonly ): Promise; } ``` ## 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 | Readonly<[TypeOptions](./puppeteer.typeoptions.md)> | _(Optional)_ takes one option, delay, which sets the time to wait between key presses in milliseconds. Defaults to 0. | +| 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<[KeyboardTypeOptions](./puppeteer.keyboardtypeoptions.md)> | _(Optional)_ takes one option, delay, which sets the time to wait between key presses in milliseconds. Defaults to 0. | **Returns:** diff --git a/docs/api/puppeteer.keyboard.type.md b/docs/api/puppeteer.keyboard.type.md index f3dd2398..b9a09d14 100644 --- a/docs/api/puppeteer.keyboard.type.md +++ b/docs/api/puppeteer.keyboard.type.md @@ -10,16 +10,16 @@ Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in t ```typescript class Keyboard { - type(text: string, options?: Readonly): Promise; + type(text: string, options?: Readonly): Promise; } ``` ## Parameters -| Parameter | Type | Description | -| --------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| text | string | A text to type into a focused element. | -| options | Readonly<[TypeOptions](./puppeteer.typeoptions.md)> | _(Optional)_ An object of options. Accepts delay which, if specified, is the time to wait between keydown and keyup in milliseconds. Defaults to 0. | +| Parameter | Type | Description | +| --------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| text | string | A text to type into a focused element. | +| options | Readonly<[KeyboardTypeOptions](./puppeteer.keyboardtypeoptions.md)> | _(Optional)_ An object of options. Accepts delay which, if specified, is the time to wait between keydown and keyup in milliseconds. Defaults to 0. | **Returns:** diff --git a/docs/api/puppeteer.typeoptions.md b/docs/api/puppeteer.keyboardtypeoptions.md similarity index 72% rename from docs/api/puppeteer.typeoptions.md rename to docs/api/puppeteer.keyboardtypeoptions.md index 340cbc0d..e1dda789 100644 --- a/docs/api/puppeteer.typeoptions.md +++ b/docs/api/puppeteer.keyboardtypeoptions.md @@ -1,13 +1,13 @@ --- -sidebar_label: TypeOptions +sidebar_label: KeyboardTypeOptions --- -# TypeOptions interface +# KeyboardTypeOptions interface #### Signature: ```typescript -export interface TypeOptions +export interface KeyboardTypeOptions ``` ## Properties diff --git a/docs/api/puppeteer.keypressoptions.md b/docs/api/puppeteer.keypressoptions.md index fb147124..72a43e45 100644 --- a/docs/api/puppeteer.keypressoptions.md +++ b/docs/api/puppeteer.keypressoptions.md @@ -7,7 +7,7 @@ sidebar_label: KeyPressOptions #### Signature: ```typescript -export type KeyPressOptions = KeyDownOptions & TypeOptions; +export type KeyPressOptions = KeyDownOptions & KeyboardTypeOptions; ``` -**References:** [KeyDownOptions](./puppeteer.keydownoptions.md), [TypeOptions](./puppeteer.typeoptions.md) +**References:** [KeyDownOptions](./puppeteer.keydownoptions.md), [KeyboardTypeOptions](./puppeteer.keyboardtypeoptions.md) diff --git a/docs/api/puppeteer.page.type.md b/docs/api/puppeteer.page.type.md index 2391e213..bf6e563b 100644 --- a/docs/api/puppeteer.page.type.md +++ b/docs/api/puppeteer.page.type.md @@ -15,18 +15,18 @@ class Page { type( selector: string, text: string, - options?: Readonly + options?: Readonly ): Promise; } ``` ## 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 | Readonly<[TypeOptions](./puppeteer.typeoptions.md)> | _(Optional)_ have property delay which is the Time to wait between key presses in milliseconds. Defaults to 0. | +| 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<[KeyboardTypeOptions](./puppeteer.keyboardtypeoptions.md)> | _(Optional)_ have property delay which is the Time to wait between key presses in milliseconds. Defaults to 0. | **Returns:** diff --git a/packages/puppeteer-core/src/api/ElementHandle.ts b/packages/puppeteer-core/src/api/ElementHandle.ts index 0dddb95c..0868e7be 100644 --- a/packages/puppeteer-core/src/api/ElementHandle.ts +++ b/packages/puppeteer-core/src/api/ElementHandle.ts @@ -34,7 +34,11 @@ import {isString, withSourcePuppeteerURLIfNone} from '../common/util.js'; import {assert} from '../util/assert.js'; import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js'; -import {KeyPressOptions, MouseClickOptions, TypeOptions} from './Input.js'; +import { + KeyPressOptions, + MouseClickOptions, + KeyboardTypeOptions, +} from './Input.js'; import {JSHandle} from './JSHandle.js'; import {ScreenshotOptions} from './Page.js'; @@ -844,7 +848,10 @@ export class ElementHandle< * * @param options - Delay in milliseconds. Defaults to 0. */ - async type(text: string, options?: Readonly): Promise; + async type( + text: string, + options?: Readonly + ): Promise; async type(): Promise { throw new Error('Not implemented'); } diff --git a/packages/puppeteer-core/src/api/Frame.ts b/packages/puppeteer-core/src/api/Frame.ts index b974f284..851220d3 100644 --- a/packages/puppeteer-core/src/api/Frame.ts +++ b/packages/puppeteer-core/src/api/Frame.ts @@ -35,7 +35,7 @@ import { } from '../common/types.js'; import {TaskManager} from '../common/WaitTask.js'; -import {TypeOptions} from './Input.js'; +import {KeyboardTypeOptions} from './Input.js'; import {JSHandle} from './JSHandle.js'; import {Locator} from './Locator.js'; @@ -83,7 +83,7 @@ export interface Realm { type( selector: string, text: string, - options?: Readonly + options?: Readonly ): Promise; } @@ -885,7 +885,7 @@ export class Frame { type( selector: string, text: string, - options?: Readonly + options?: Readonly ): Promise { return this.isolatedRealm().type(selector, text, options); } diff --git a/packages/puppeteer-core/src/api/Input.ts b/packages/puppeteer-core/src/api/Input.ts index 6966a733..2cc73349 100644 --- a/packages/puppeteer-core/src/api/Input.ts +++ b/packages/puppeteer-core/src/api/Input.ts @@ -24,21 +24,27 @@ import {Point} from './ElementHandle.js'; * @public */ export interface KeyDownOptions { + /** + * @deprecated Do not use. This is automatically handled. + */ text?: string; + /** + * @deprecated Do not use. This is automatically handled. + */ commands?: string[]; } /** * @public */ -export interface TypeOptions { +export interface KeyboardTypeOptions { delay?: number; } /** * @public */ -export type KeyPressOptions = KeyDownOptions & TypeOptions; +export type KeyPressOptions = KeyDownOptions & KeyboardTypeOptions; /** * Keyboard provides an api for managing a virtual keyboard. @@ -175,7 +181,10 @@ export class Keyboard { * if specified, is the time to wait between `keydown` and `keyup` in milliseconds. * Defaults to 0. */ - async type(text: string, options?: Readonly): Promise; + async type( + text: string, + options?: Readonly + ): Promise; async type(): Promise { throw new Error('Not implemented'); } diff --git a/packages/puppeteer-core/src/api/Page.ts b/packages/puppeteer-core/src/api/Page.ts index 97948a86..a5198bd0 100644 --- a/packages/puppeteer-core/src/api/Page.ts +++ b/packages/puppeteer-core/src/api/Page.ts @@ -71,7 +71,7 @@ import type { FrameAddStyleTagOptions, FrameWaitForFunctionOptions, } from './Frame.js'; -import {Keyboard, Mouse, Touchscreen, TypeOptions} from './Input.js'; +import {Keyboard, Mouse, Touchscreen, KeyboardTypeOptions} from './Input.js'; import type {JSHandle} from './JSHandle.js'; import {Locator} from './Locator.js'; @@ -2556,7 +2556,7 @@ export class Page extends EventEmitter { type( selector: string, text: string, - options?: Readonly + options?: Readonly ): Promise { return this.mainFrame().type(selector, text, options); } diff --git a/packages/puppeteer-core/src/common/ElementHandle.ts b/packages/puppeteer-core/src/common/ElementHandle.ts index fa31390b..bb626938 100644 --- a/packages/puppeteer-core/src/common/ElementHandle.ts +++ b/packages/puppeteer-core/src/common/ElementHandle.ts @@ -24,7 +24,7 @@ import { Offset, Point, } from '../api/ElementHandle.js'; -import {KeyPressOptions, TypeOptions} from '../api/Input.js'; +import {KeyPressOptions, KeyboardTypeOptions} from '../api/Input.js'; import {Page, ScreenshotOptions} from '../api/Page.js'; import {assert} from '../util/assert.js'; @@ -447,7 +447,7 @@ export class CDPElementHandle< override async type( text: string, - options?: Readonly + options?: Readonly ): Promise { await this.focus(); await this.#page.keyboard.type(text, options); diff --git a/packages/puppeteer-core/src/common/Input.ts b/packages/puppeteer-core/src/common/Input.ts index 64479be4..6139982f 100644 --- a/packages/puppeteer-core/src/common/Input.ts +++ b/packages/puppeteer-core/src/common/Input.ts @@ -28,7 +28,7 @@ import { MouseOptions, MouseWheelOptions, Touchscreen, - TypeOptions, + KeyboardTypeOptions, } from '../api/Input.js'; import {assert} from '../util/assert.js'; @@ -183,7 +183,7 @@ export class CDPKeyboard extends Keyboard { override async type( text: string, - options: Readonly = {} + options: Readonly = {} ): Promise { const delay = options.delay || undefined; for (const char of text) { diff --git a/packages/puppeteer-core/src/common/IsolatedWorld.ts b/packages/puppeteer-core/src/common/IsolatedWorld.ts index 41f25f4a..43bd4cd8 100644 --- a/packages/puppeteer-core/src/common/IsolatedWorld.ts +++ b/packages/puppeteer-core/src/common/IsolatedWorld.ts @@ -18,7 +18,7 @@ import {Protocol} from 'devtools-protocol'; import type {ClickOptions, ElementHandle} from '../api/ElementHandle.js'; import {Realm} from '../api/Frame.js'; -import {TypeOptions} from '../api/Input.js'; +import {KeyboardTypeOptions} from '../api/Input.js'; import {JSHandle} from '../api/JSHandle.js'; import {assert} from '../util/assert.js'; import {Deferred} from '../util/Deferred.js'; @@ -350,7 +350,7 @@ export class IsolatedWorld implements Realm { async type( selector: string, text: string, - options?: Readonly + options?: Readonly ): Promise { const handle = await this.$(selector); assert(handle, `No element found for selector: ${selector}`); diff --git a/packages/puppeteer-core/src/common/bidi/ElementHandle.ts b/packages/puppeteer-core/src/common/bidi/ElementHandle.ts index eb114b56..80869fca 100644 --- a/packages/puppeteer-core/src/common/bidi/ElementHandle.ts +++ b/packages/puppeteer-core/src/common/bidi/ElementHandle.ts @@ -20,7 +20,9 @@ import { ElementHandle as BaseElementHandle, ClickOptions, } from '../../api/ElementHandle.js'; +import {KeyPressOptions, KeyboardTypeOptions} from '../../api/Input.js'; import {assert} from '../../util/assert.js'; +import {KeyInput} from '../USKeyboardLayout.js'; import {Frame} from './Frame.js'; import {JSHandle} from './JSHandle.js'; @@ -143,4 +145,20 @@ export class ElementHandle< await this.scrollIntoViewIfNeeded(); await this.#frame.page().touchscreen.touchEnd(); } + + override async type( + text: string, + options?: Readonly + ): Promise { + await this.focus(); + await this.#frame.page().keyboard.type(text, options); + } + + override async press( + key: KeyInput, + options?: Readonly + ): Promise { + await this.focus(); + await this.#frame.page().keyboard.press(key, options); + } } diff --git a/packages/puppeteer-core/src/common/bidi/Input.ts b/packages/puppeteer-core/src/common/bidi/Input.ts index 8270f27f..574c6d71 100644 --- a/packages/puppeteer-core/src/common/bidi/Input.ts +++ b/packages/puppeteer-core/src/common/bidi/Input.ts @@ -18,23 +18,299 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import {Point} from '../../api/ElementHandle.js'; import { + Keyboard as BaseKeyboard, Mouse as BaseMouse, + Touchscreen as BaseTouchscreen, + KeyDownOptions, + KeyPressOptions, + KeyboardTypeOptions, MouseButton, MouseClickOptions, MouseMoveOptions, MouseOptions, MouseWheelOptions, - Touchscreen as BaseTouchscreen, } from '../../api/Input.js'; +import {KeyInput} from '../USKeyboardLayout.js'; import {BrowsingContext} from './BrowsingContext.js'; const enum InputId { Mouse = '__puppeteer_mouse', + Keyboard = '__puppeteer_keyboard', Wheel = '__puppeteer_wheel', Finger = '__puppeteer_finger', } +const getBidiKeyValue = (key: string) => { + switch (key) { + case '\n': + key = 'Enter'; + break; + } + // Measures the number of code points rather than UTF-16 code units. + if ([...key].length === 1) { + return key; + } + switch (key) { + case 'Unidentified': + return '\uE000'; + case 'Cancel': + return '\uE001'; + case 'Help': + return '\uE002'; + case 'Backspace': + return '\uE003'; + case 'Tab': + return '\uE004'; + case 'Clear': + return '\uE005'; + case 'Return': + return '\uE006'; + case 'Enter': + return '\uE007'; + case 'Shift': + return '\uE008'; + case 'Control': + return '\uE009'; + case 'Alt': + return '\uE00A'; + case 'Pause': + return '\uE00B'; + case 'Escape': + return '\uE00C'; + case ' ': + return '\uE00D'; + case 'PageUp': + return '\uE00E'; + case 'PageDown': + return '\uE00F'; + case 'End': + return '\uE010'; + case 'Home': + return '\uE011'; + case 'ArrowLeft': + return '\uE012'; + case 'ArrowUp': + return '\uE013'; + case 'ArrowRight': + return '\uE014'; + case 'ArrowDown': + return '\uE015'; + case 'Insert': + return '\uE016'; + case 'Delete': + return '\uE017'; + case ';': + return '\uE018'; + case '=': + return '\uE019'; + case '0': + return '\uE01A'; + case '1': + return '\uE01B'; + case '2': + return '\uE01C'; + case '3': + return '\uE01D'; + case '4': + return '\uE01E'; + case '5': + return '\uE01F'; + case '6': + return '\uE020'; + case '7': + return '\uE021'; + case '8': + return '\uE022'; + case '9': + return '\uE023'; + case '*': + return '\uE024'; + case '+': + return '\uE025'; + case ',': + return '\uE026'; + case '-': + return '\uE027'; + case '.': + return '\uE028'; + case '/': + return '\uE029'; + case 'F1': + return '\uE031'; + case 'F2': + return '\uE032'; + case 'F3': + return '\uE033'; + case 'F4': + return '\uE034'; + case 'F5': + return '\uE035'; + case 'F6': + return '\uE036'; + case 'F7': + return '\uE037'; + case 'F8': + return '\uE038'; + case 'F9': + return '\uE039'; + case 'F10': + return '\uE03A'; + case 'F11': + return '\uE03B'; + case 'F12': + return '\uE03C'; + case 'Meta': + return '\uE03D'; + case 'ZenkakuHankaku': + return '\uE040'; + default: + throw new Error(`Unknown key: "${key}"`); + } +}; + +/** + * @internal + */ +export class Keyboard extends BaseKeyboard { + #context: BrowsingContext; + + /** + * @internal + */ + constructor(context: BrowsingContext) { + super(); + this.#context = context; + } + + override async down( + key: KeyInput, + options?: Readonly + ): Promise { + if (options) { + throw new Error('KeyDownOptions are not supported'); + } + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: Bidi.Input.SourceActionsType.Key, + id: InputId.Keyboard, + actions: [ + { + type: Bidi.Input.ActionType.KeyDown, + value: getBidiKeyValue(key), + }, + ], + }, + ], + }); + } + + override async up(key: KeyInput): Promise { + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: Bidi.Input.SourceActionsType.Key, + id: InputId.Keyboard, + actions: [ + { + type: Bidi.Input.ActionType.KeyUp, + value: getBidiKeyValue(key), + }, + ], + }, + ], + }); + } + + override async press( + key: KeyInput, + options: Readonly = {} + ): Promise { + const {delay = 0} = options; + const actions: Bidi.Input.KeySourceAction[] = [ + { + type: Bidi.Input.ActionType.KeyDown, + value: getBidiKeyValue(key), + }, + ]; + if (delay > 0) { + actions.push({ + type: Bidi.Input.ActionType.Pause, + duration: delay, + }); + } + actions.push({ + type: Bidi.Input.ActionType.KeyUp, + value: getBidiKeyValue(key), + }); + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: Bidi.Input.SourceActionsType.Key, + id: InputId.Keyboard, + actions, + }, + ], + }); + } + + override async type( + text: string, + options: Readonly = {} + ): Promise { + const {delay = 0} = options; + // This spread separates the characters into code points rather than UTF-16 + // code units. + const values = [...text].map(getBidiKeyValue); + const actions: Bidi.Input.KeySourceAction[] = []; + if (delay <= 0) { + for (const value of values) { + actions.push( + { + type: Bidi.Input.ActionType.KeyDown, + value, + }, + { + type: Bidi.Input.ActionType.KeyUp, + value, + } + ); + } + } else { + for (const value of values) { + actions.push( + { + type: Bidi.Input.ActionType.KeyDown, + value, + }, + { + type: Bidi.Input.ActionType.Pause, + duration: delay, + }, + { + type: Bidi.Input.ActionType.KeyUp, + value, + } + ); + } + } + await this.#context.connection.send('input.performActions', { + context: this.#context.id, + actions: [ + { + type: Bidi.Input.SourceActionsType.Key, + id: InputId.Keyboard, + actions, + }, + ], + }); + } +} + /** * @internal */ diff --git a/packages/puppeteer-core/src/common/bidi/Page.ts b/packages/puppeteer-core/src/common/bidi/Page.ts index 96948f0a..1683c4df 100644 --- a/packages/puppeteer-core/src/common/bidi/Page.ts +++ b/packages/puppeteer-core/src/common/bidi/Page.ts @@ -58,7 +58,7 @@ import {Connection} from './Connection.js'; import {Frame} from './Frame.js'; import {HTTPRequest} from './HTTPRequest.js'; import {HTTPResponse} from './HTTPResponse.js'; -import {Mouse, Touchscreen} from './Input.js'; +import {Keyboard, Mouse, Touchscreen} from './Input.js'; import {NetworkManager} from './NetworkManager.js'; import {getBidiHandle} from './Realm.js'; import {BidiSerializer} from './Serializer.js'; @@ -130,6 +130,7 @@ export class Page extends PageBase { #emulationManager: EmulationManager; #mouse: Mouse; #touchscreen: Touchscreen; + #keyboard: Keyboard; constructor(browserContext: BrowserContext, info: {context: string}) { super(); @@ -162,6 +163,7 @@ export class Page extends PageBase { ); this.#mouse = new Mouse(this.mainFrame().context()); this.#touchscreen = new Touchscreen(this.mainFrame().context()); + this.#keyboard = new Keyboard(this.mainFrame().context()); } override get accessibility(): Accessibility { @@ -184,6 +186,10 @@ export class Page extends PageBase { return this.#touchscreen; } + override get keyboard(): Keyboard { + return this.#keyboard; + } + override browser(): Browser { return this.#browserContext.browser(); } diff --git a/packages/puppeteer-core/src/common/bidi/Sandbox.ts b/packages/puppeteer-core/src/common/bidi/Sandbox.ts index 83221e4f..81a14589 100644 --- a/packages/puppeteer-core/src/common/bidi/Sandbox.ts +++ b/packages/puppeteer-core/src/common/bidi/Sandbox.ts @@ -16,7 +16,7 @@ import {ClickOptions, ElementHandle} from '../../api/ElementHandle.js'; import {Realm as RealmBase} from '../../api/Frame.js'; -import {TypeOptions} from '../../api/Input.js'; +import {KeyboardTypeOptions} from '../../api/Input.js'; import {JSHandle as BaseJSHandle} from '../../api/JSHandle.js'; import {assert} from '../../util/assert.js'; import {TimeoutSettings} from '../TimeoutSettings.js'; @@ -269,7 +269,7 @@ export class Sandbox implements RealmBase { async type( selector: string, text: string, - options?: Readonly + options?: Readonly ): Promise { const handle = await this.$(selector); assert(handle, `No element found for selector: ${selector}`); diff --git a/test/TestExpectations.json b/test/TestExpectations.json index 880f759b..71661fdf 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -455,6 +455,18 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[click.spec] Page.click should select the text by triple clicking", + "platforms": ["linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[click.spec] Page.click should select the text by triple clicking", + "platforms": ["darwin"], + "parameters": ["webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[Connection.spec] WebDriver BiDi Connection should work", "platforms": ["darwin", "linux", "win32"], @@ -707,6 +719,66 @@ "parameters": ["webDriverBiDi"], "expectations": ["FAIL", "PASS"] }, + { + "testIdPattern": "[keyboard.spec] Keyboard should move with the arrow keys", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should not type canceled events", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should press the meta key", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should press the metaKey", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should send a character with ElementHandle.press", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should send proper codes while typing", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should send proper codes while typing with shift", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should specify repeat property", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should type emoji", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should type into a textarea", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[launcher.spec] Launcher specs Puppeteer Browser.disconnect should reject navigation when browser closes", "platforms": ["darwin", "linux", "win32"], @@ -809,6 +881,36 @@ "parameters": ["firefox", "webDriverBiDi"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[locator.spec] Locator Locator.change should override pre-filled inputs", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[locator.spec] Locator Locator.change should work for contenteditable", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[locator.spec] Locator Locator.change should work for inputs", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[locator.spec] Locator Locator.change should work for pre-filled inputs", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[locator.spec] Locator Locator.change should work if the input becomes enabled later", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[navigation.spec] navigation \"after each\" hook for \"should work with both domcontentloaded and load\"", "platforms": ["darwin", "linux", "win32"], @@ -1065,7 +1167,7 @@ "testIdPattern": "[queryhandler.spec] Query handler tests Text selectors in Page should clear caches", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["PASS"] }, { "testIdPattern": "[requestinterception-experimental.spec] *", @@ -1365,7 +1467,7 @@ "testIdPattern": "[click.spec] Page.click should click offscreen buttons", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], - "expectations": ["TIMEOUT"] + "expectations": ["TIMEOUT", "FAIL"] }, { "testIdPattern": "[click.spec] Page.click should click on checkbox label and toggle", @@ -1415,6 +1517,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL", "PASS"] }, + { + "testIdPattern": "[click.spec] Page.click should select the text by triple clicking", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[cookies.spec] Cookie specs Page.cookies should get cookies from multiple urls", "platforms": ["darwin", "linux", "win32"], @@ -1823,18 +1931,48 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[keyboard.spec] Keyboard should press the meta key", + "platforms": ["linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should press the meta key", + "platforms": ["darwin"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[keyboard.spec] Keyboard should press the metaKey", "platforms": ["linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[keyboard.spec] Keyboard should press the metaKey", + "platforms": ["linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, + { + "testIdPattern": "[keyboard.spec] Keyboard should press the metaKey", + "platforms": ["darwin"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[keyboard.spec] Keyboard should report shiftKey", "platforms": ["darwin"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[keyboard.spec] Keyboard should report shiftKey", + "platforms": ["linux"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[keyboard.spec] Keyboard should send a character with sendCharacter", "platforms": ["darwin", "linux", "win32"], @@ -1865,6 +2003,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[keyboard.spec] Keyboard should type all kinds of characters", + "platforms": ["linux"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[keyboard.spec] Keyboard should type emoji", "platforms": ["darwin", "linux", "win32"], @@ -1997,36 +2141,6 @@ "parameters": ["firefox", "webDriverBiDi"], "expectations": ["SKIP"] }, - { - "testIdPattern": "[locator.spec] Locator Locator.change should override pre-filled inputs", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[locator.spec] Locator Locator.change should work for contenteditable", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[locator.spec] Locator Locator.change should work for inputs", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[locator.spec] Locator Locator.change should work for pre-filled inputs", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[locator.spec] Locator Locator.change should work if the input becomes enabled later", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[mouse.spec] Mouse should reset properly", "platforms": ["darwin", "linux", "win32"],