-
Notifications
You must be signed in to change notification settings - Fork 5.7k
feat(keyboard): add array overload to pressSequentially #40748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
be7eab1
cbabea2
4375b42
d9ed861
4354712
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -308,6 +308,8 @@ In most cases, you should use [`method: Locator.fill`] instead. You only need to | |
|
|
||
| Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text. | ||
|
|
||
| When [`option: namedKeys`] is `true`, anything inside `{}` is treated as a key name (same format as [`method: Keyboard.press`]). Use `{{` and `}}` to type literal brace characters. | ||
|
|
||
| To press a special key, like `Control` or `ArrowDown`, use [`method: Keyboard.press`]. | ||
|
|
||
| **Usage** | ||
|
|
@@ -359,6 +361,14 @@ A text to type into a focused element. | |
|
|
||
| Time to wait between key presses in milliseconds. Defaults to 0. | ||
|
|
||
| ### option: Keyboard.type.namedKeys | ||
| * since: v1.61 | ||
| - `namedKeys` <[boolean]> | ||
|
|
||
| When `true`, anything inside `{}` in the text is treated as a key name (same format as [`method: Keyboard.press`]), | ||
| allowing you to mix regular characters with special keys like `{Enter}`, `{ArrowDown}`, or modifier combos like `{Control+A}`. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having an example that contains both text and special keys would be nice to explain the feature.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| Use `{{` and `}}` to type literal brace characters. Defaults to `false`. | ||
|
|
||
| ## async method: Keyboard.up | ||
| * since: v1.8 | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2103,33 +2103,52 @@ In most cases, you should use [`method: Locator.fill`] instead. You only need to | |||||
|
|
||||||
| Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text. | ||||||
|
|
||||||
| When [`option: namedKeys`] is `true`, anything inside `{}` is treated as a key name (same format as [`method: Locator.press`]). Use `{{` and `}}` to type literal brace characters. | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||||||
|
|
||||||
| To press a special key, like `Control` or `ArrowDown`, use [`method: Locator.press`]. | ||||||
|
|
||||||
| **Usage** | ||||||
|
|
||||||
| ```js | ||||||
| await locator.pressSequentially('Hello'); // Types instantly | ||||||
| await locator.pressSequentially('World', { delay: 100 }); // Types slower, like a user | ||||||
|
|
||||||
| // Mix characters and named keys | ||||||
| await locator.pressSequentially('Hello{Enter}World', { namedKeys: true }); | ||||||
| // Use modifier combos | ||||||
| await locator.pressSequentially('{Control+A}{Delete}Hello', { namedKeys: true }); | ||||||
| ``` | ||||||
|
|
||||||
| ```java | ||||||
| locator.pressSequentially("Hello"); // Types instantly | ||||||
| locator.pressSequentially("World", new Locator.pressSequentiallyOptions().setDelay(100)); // Types slower, like a user | ||||||
|
|
||||||
| // Mix characters and named keys | ||||||
| locator.pressSequentially("Hello{Enter}World", new Locator.pressSequentiallyOptions().setNamedKeys(true)); | ||||||
| ``` | ||||||
|
|
||||||
| ```python async | ||||||
| await locator.press_sequentially("hello") # types instantly | ||||||
| await locator.press_sequentially("world", delay=100) # types slower, like a user | ||||||
|
|
||||||
| # Mix characters and named keys | ||||||
| await locator.press_sequentially("Hello{Enter}World", named_keys=True) | ||||||
| ``` | ||||||
|
|
||||||
| ```python sync | ||||||
| locator.press_sequentially("hello") # types instantly | ||||||
| locator.press_sequentially("world", delay=100) # types slower, like a user | ||||||
|
|
||||||
| # Mix characters and named keys | ||||||
| locator.press_sequentially("Hello{Enter}World", named_keys=True) | ||||||
| ``` | ||||||
|
|
||||||
| ```csharp | ||||||
| await locator.PressSequentiallyAsync("Hello"); // Types instantly | ||||||
| await locator.PressSequentiallyAsync("World", new() { Delay = 100 }); // Types slower, like a user | ||||||
|
|
||||||
| // Mix characters and named keys | ||||||
| await locator.PressSequentiallyAsync("Hello{Enter}World", new() { NamedKeys = true }); | ||||||
| ``` | ||||||
|
|
||||||
| An example of typing into a text field and then submitting the form: | ||||||
|
|
@@ -2168,14 +2187,22 @@ await locator.PressAsync("Enter"); | |||||
| * since: v1.38 | ||||||
| - `text` <[string]> | ||||||
|
|
||||||
| String of characters to sequentially press into a focused element. | ||||||
| String of characters to sequentially press into a focused element. When [`option: namedKeys`] is `true`, anything inside `{}` is treated as a key name (same format as [`method: Locator.press`]). Use `{{` and `}}` to type literal brace characters. | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||||||
|
|
||||||
| ### option: Locator.pressSequentially.delay | ||||||
| * since: v1.38 | ||||||
| - `delay` <[float]> | ||||||
|
|
||||||
| Time to wait between key presses in milliseconds. Defaults to 0. | ||||||
|
|
||||||
| ### option: Locator.pressSequentially.namedKeys | ||||||
| * since: v1.61 | ||||||
| - `namedKeys` <[boolean]> | ||||||
|
|
||||||
| When `true`, anything inside `{}` in the text is treated as a key name (same format as [`method: Locator.press`]), | ||||||
| allowing you to mix regular characters with special keys like `{Enter}`, `{ArrowDown}`, or modifier combos like `{Control+A}`. | ||||||
| Use `{{` and `}}` to type literal brace characters. Defaults to `false`. | ||||||
|
|
||||||
| ### option: Locator.pressSequentially.noWaitAfter = %%-input-no-wait-after-removed-%% | ||||||
| * since: v1.38 | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -103,20 +103,38 @@ export class Keyboard { | |
| await this._raw.sendText(progress, text); | ||
| } | ||
|
|
||
| async apiType(progress: Progress, text: string, options?: { delay?: number }) { | ||
| async apiType(progress: Progress, text: string, options?: { delay?: number, namedKeys?: boolean }) { | ||
| await progress.race(this._page.instrumentation.onBeforeInputAction(this._page, progress.metadata)); | ||
| await this.type(progress, text, options); | ||
| } | ||
|
|
||
| async type(progress: Progress, text: string, options?: { delay?: number }) { | ||
| async type(progress: Progress, text: string, options?: { delay?: number, namedKeys?: boolean }) { | ||
| const delay = (options && options.delay) || undefined; | ||
| for (const char of text) { | ||
| if (usKeyboardLayout.has(char)) { | ||
| await this.press(progress, char, { delay }); | ||
| } else { | ||
| if (delay) | ||
| await progress.wait(delay); | ||
| await this.insertText(progress, char); | ||
| if (options?.namedKeys) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can combine both branches by making
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| for (const token of parseNamedKeys(text)) { | ||
| if (token.type === 'key') { | ||
| if (delay) | ||
| await progress.wait(delay); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps just pass
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| await this.press(progress, token.value, { delay: undefined }); | ||
| } else { | ||
| if (usKeyboardLayout.has(token.value)) { | ||
| await this.press(progress, token.value, { delay }); | ||
| } else { | ||
| if (delay) | ||
| await progress.wait(delay); | ||
| await this.insertText(progress, token.value); | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| for (const char of text) { | ||
| if (usKeyboardLayout.has(char)) { | ||
| await this.press(progress, char, { delay }); | ||
| } else { | ||
| if (delay) | ||
| await progress.wait(delay); | ||
| await this.insertText(progress, char); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -354,6 +372,38 @@ function buildLayoutClosure(layout: keyboardLayout.KeyboardLayout): Map<string, | |
| return result; | ||
| } | ||
|
|
||
| function* parseNamedKeys(text: string): Generator<{ type: 'key' | 'char', value: string }> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we avoid a generator here? It's not like it helps with performance or something.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| let i = 0; | ||
| while (i < text.length) { | ||
| if (text[i] === '{') { | ||
| if (i + 1 < text.length && text[i + 1] === '{') { | ||
| yield { type: 'char', value: '{' }; | ||
| i += 2; | ||
| } else { | ||
| const end = text.indexOf('}', i + 1); | ||
| if (end === -1) { | ||
| yield { type: 'char', value: '{' }; | ||
| i += 1; | ||
| } else { | ||
| yield { type: 'key', value: text.substring(i + 1, end) }; | ||
| i = end + 1; | ||
| } | ||
| } | ||
| } else if (text[i] === '}') { | ||
| if (i + 1 < text.length && text[i + 1] === '}') { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like there is no point to escape
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| yield { type: 'char', value: '}' }; | ||
| i += 2; | ||
| } else { | ||
| yield { type: 'char', value: '}' }; | ||
| i += 1; | ||
| } | ||
| } else { | ||
| yield { type: 'char', value: text[i] }; | ||
| i += 1; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export interface RawTouchscreen { | ||
| tap(progress: Progress, x: number, y: number, modifiers: Set<types.KeyboardModifier>): Promise<void>; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.