Skip to content

feat(keyboard): add array overload to pressSequentially#40748

Merged
dgozman merged 5 commits into
microsoft:mainfrom
SebTardif:feat/press-sequentially-array
May 18, 2026
Merged

feat(keyboard): add array overload to pressSequentially#40748
dgozman merged 5 commits into
microsoft:mainfrom
SebTardif:feat/press-sequentially-array

Conversation

@SebTardif
Copy link
Copy Markdown
Contributor

@SebTardif SebTardif commented May 9, 2026

Summary

  • Adds namedKeys option to Locator.pressSequentially
await locator.pressSequentially('Hello{Enter}World!', { namedKeys: true });

Fixes #40740

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment thread docs/src/api/class-frame.md Outdated
### option: Frame.press.timeout = %%-input-timeout-js-%%
* since: v1.8

## async method: Frame.pressSequentially
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not introduce a discouraged method 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, removed.

Comment thread docs/src/api/class-locator.md Outdated
- `text` <[string]|[Array]<[string]>>

String of characters to sequentially press into a focused element.
String of characters to sequentially press into a focused element, or an array of key names to press sequentially. Key names follow the same format as [`method: Locator.press`].
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we support the following notation: Hello{ArrowRight}{Ctrl+A}world!{Backspace}? Basically, treating anything inside {} as a key from the press method, when passed a namedKeys: true option.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked. Added namedKeys: true option to Locator.pressSequentially and Keyboard.type. {KeyName} treats brace content as a key name for press(), with {{/}} for literal braces.

await locator.pressSequentially('Hello{Enter}World', { namedKeys: true });
await locator.pressSequentially('{Control+A}{Delete}Hello', { namedKeys: true });
await locator.pressSequentially('type {{braces}}', { namedKeys: true }); // types: type {braces}

SebTardif added 3 commits May 11, 2026 17:01
Add string[] overload to Locator.pressSequentially and new
Keyboard.pressSequentially method, as requested in microsoft#40740.

When given an array, each element is treated as a key name
(same format as keyboard.press()) and pressed sequentially
with optional delay between presses.

Fixes microsoft#40740
Add missing documentation for Frame.pressSequentially selector-based
method to fix doclint CI failure.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Rework pressSequentially based on review feedback:
- Drop Frame.pressSequentially (no new discouraged methods)
- Drop Keyboard.pressSequentially and keyboardPressSequentially protocol
- Add namedKeys option to Locator.pressSequentially and Keyboard.type
- {KeyName} syntax treats braces content as key names for press()
- {{ and }} escape to literal brace characters

Fixes: microsoft#40740
@SebTardif SebTardif force-pushed the feat/press-sequentially-array branch from aec15c9 to 4375b42 Compare May 12, 2026 00:07
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment thread docs/src/api/class-keyboard.md Outdated
- `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}`.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread docs/src/api/class-locator.md Outdated
- `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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
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`]).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread docs/src/api/class-locator.md Outdated

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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
When [`option: namedKeys`] is `true`, anything inside `{}` is treated as a key name (same format as [`method: Locator.press`]).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread docs/src/api/class-keyboard.md Outdated

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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
When [`option: namedKeys`] is `true`, anything inside `{}` is treated as a key name (same format as [`method: Keyboard.press`]).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread tests/page/page-keyboard.spec.ts Outdated
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello {World}');
});

it('should type with namedKeys and delay', async ({ page }) => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread tests/page/page-keyboard.spec.ts Outdated
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello\nWorld');
});

it('should type with namedKeys and modifier combos', async ({ page }) => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return result;
}

function* parseNamedKeys(text: string): Generator<{ type: 'key' | 'char', value: string }> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
}
} else if (text[i] === '}') {
if (i + 1 < text.length && text[i + 1] === '}') {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like there is no point to escape } as }} - these both yield the same result. Let's drop that and update docs to only mention {{ for escaping {.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (delay)
await progress.wait(delay);
await this.insertText(progress, char);
if (options?.namedKeys) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can combine both branches by making parseNamedKeys return all "chars" when namedKeys option is not passed. This should simplify the code quite a bit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps just pass delay to this.press() on the next line?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

- Combine namedKeys/non-namedKeys branches into single code path
- Pass delay to press() for named keys
- Replace generator with regular function returning array
- Drop redundant }} escaping, keep only {{ for literal brace
- Simplify docs wording per reviewer suggestions
- Add mixed text+key example to Keyboard.type docs
- Trim tests to two meaningful cases using textarea

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Collaborator

@dgozman dgozman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there!

Comment thread tests/page/page-keyboard.spec.ts Outdated
document.body.appendChild(textarea);
textarea.focus();
});
await page.keyboard.type('Hello{Enter}World', { namedKeys: true });
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's type the following: He{{ll}o{Enter}Wor{ld and see that it produces He{ll}o\nWor{ld

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Updated the test to type He{{ll}o{Enter}Wor{ld and verify it produces He{ll}o\nWor{ld, covering escaped braces, unmatched braces, and named keys.

} else {
for (const token of parseNamedKeys(text, !!options?.namedKeys)) {
if (token.type === 'key') {
if (delay)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why an extra delay here when we pass it to this.press() in the next line? Looks different from how it was before.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Removed the extra progress.wait(delay) before this.press(). Now the named key branch matches the original behavior for layout characters, where press() handles delay internally.

- Remove extraneous progress.wait(delay) before this.press() in the
  named key branch, matching the original behavior for layout characters
  where press() handles delay internally.
- Update namedKeys test to use edge-case input as suggested: escaped
  braces ({{), unmatched }, named key ({Enter}), and unmatched {.
- Regenerate types to sync with doc wording updates.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@github-actions
Copy link
Copy Markdown
Contributor

Test results for "MCP"

2 failed
❌ [chromium] › mcp/annotate.spec.ts:173 › user-initiated annotate downloads zip with feedback.md @mcp-macos-latest-chromium
❌ [chromium] › mcp/annotate.spec.ts:137 › should abort MCP annotation when last screenshot is removed @mcp-windows-latest-chromium

7097 passed, 1104 skipped


Merge workflow run.

@github-actions
Copy link
Copy Markdown
Contributor

Test results for "tests 1"

3 flaky ⚠️ [chromium-library] › library/video.spec.ts:719 › screencast › should work with video+trace `@chromium-ubuntu-22.04-arm-node20`
⚠️ [chromium-page] › page/page-request-continue.spec.ts:756 › propagate headers cross origin redirect after interception `@chromium-ubuntu-22.04-arm-node20`
⚠️ [chromium-library] › library/video.spec.ts:647 › screencast › should capture full viewport `@chromium-ubuntu-22.04-node22`

41925 passed, 850 skipped


Merge workflow run.

Copy link
Copy Markdown
Collaborator

@dgozman dgozman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, merging in. Thank you for the PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: keyboard.pressSequence() for batched named key presses

3 participants