QA How-To
How to Use Playwright keyboard press (2026)
Learn playwright keyboard press with runnable TypeScript, key combinations, focus control, cross-platform shortcuts, debugging, and interview answers.
20 min read | 2,968 words
TL;DR
Use locator.press('Enter') for a key sent to a known control. Use page.keyboard.press() when focus or a page-level shortcut is the behavior under test, then assert the visible result.
Key Takeaways
- Prefer locator.press() when a known element should receive the key.
- Use page.keyboard.press() for global shortcuts and natural focus traversal.
- Use ControlOrMeta for portable Control and Command shortcuts.
- Assert focus before page-level keyboard input when the recipient matters.
- Use fill() for ordinary text and pressSequentially() only for per-character behavior.
- Release every manually held modifier, preferably with try/finally.
- Wait for a visible product outcome instead of adding fixed sleeps.
Playwright keyboard.press() sends a real keyboard-style key sequence to the element that currently owns focus. For most field-specific interactions, the best playwright keyboard press pattern is locator.press() because it locates and focuses the intended element before pressing the key. Use page.keyboard.press() when focus movement itself is the behavior under test or when a shortcut applies to the whole page.
This guide shows the exact TypeScript syntax, supported key names, cross-platform shortcuts, focus rules, event behavior, synchronization, debugging, and maintainable test design. Every example uses Playwright Test and observable assertions rather than fixed sleeps.
TL;DR
import { test, expect } from '@playwright/test';
test('submits search with Enter', async ({ page }) => {
await page.setContent(`
<label for="search">Search</label>
<input id="search">
<output id="result"></output>
<script>
document.querySelector("#search").addEventListener("keydown", event => {
if (event.key === "Enter") {
document.querySelector("#result").textContent = event.target.value;
}
});
</script>
`);
const search = page.getByLabel('Search');
await search.fill('locator reliability');
await search.press('Enter');
await expect(page.locator('#result')).toHaveText('locator reliability');
});
| Situation | Preferred API | Why |
|---|---|---|
| Press a key in a known control | locator.press('Enter') |
Resolves and focuses the target |
| Trigger a page-level shortcut | page.keyboard.press('ControlOrMeta+K') |
Sends to the active page context |
| Enter ordinary text | locator.fill('text') |
Fast, direct, and intention-revealing |
| Exercise per-character handlers | locator.pressSequentially('text') |
Emits a sequence for every character |
| Hold and release a modifier manually | keyboard.down() and keyboard.up() |
Gives control over event boundaries |
Always assert the user-visible result. A completed key call proves that events were dispatched, not that the application handled them correctly.
1. What playwright keyboard press Does
page.keyboard.press(key) is a shortcut for pressing a key down and releasing it. It accepts a logical key such as Enter, Escape, ArrowDown, or F2, a character such as a, or a modifier combination such as Shift+Tab. The optional delay sets the time between keydown and keyup.
The important detail is focus. page.keyboard.press() does not locate a target or move focus for you. The browser dispatches the sequence through the currently focused element, or through the document when no ordinary control owns focus. That makes the API appropriate for global commands, focus traversal, editors, menus, and tests where the active element is part of the contract.
locator.press() adds element targeting. It waits for the locator, focuses the matched element, and presses the supplied key combination. Playwright's current guidance recommends the locator form in most cases because the intended recipient is explicit.
const editor = page.getByRole('textbox', { name: 'Comment' });
await editor.press('Enter');
await editor.press('Shift+Enter');
These two calls may have different application meanings, for example submit and insert a line break. Playwright supplies the events, while the product decides their effect.
Do not confuse a keyboard action with an assertion. The promise resolves when Playwright completes the input operation. Verify the outcome with expect(locator), a URL assertion, a download check, or another product-level signal. This distinction keeps a test from passing when a handler is absent, a command is disabled, or focus is wrong.
2. Set Up a Runnable Playwright Keyboard Test
Create a Playwright Test project and install its browser binaries:
npm init playwright@latest
npx playwright install
Save the following as tests/keyboard-submit.spec.ts. It is self-contained because page.setContent() supplies the application fixture.
import { test, expect } from '@playwright/test';
test('submits a command with Enter', async ({ page }) => {
await page.setContent(`
<main>
<label for="command">Command</label>
<input id="command" autocomplete="off">
<p role="status">Waiting</p>
</main>
<script>
const input = document.querySelector("#command");
const status = document.querySelector("[role=status]");
input.addEventListener("keydown", event => {
if (event.key === "Enter" && input.value.trim()) {
status.textContent = "Ran: " + input.value;
}
});
</script>
`);
const command = page.getByLabel('Command');
await command.fill('run smoke tests');
await command.press('Enter');
await expect(page.getByRole('status')).toHaveText('Ran: run smoke tests');
});
Run it with:
npx playwright test tests/keyboard-submit.spec.ts
The test separates text entry from the special key. fill() is the right default for ordinary strings because it expresses the state you want in the input. press('Enter') is then reserved for behavior that depends on a keyboard event.
A production test normally replaces setContent() with page.goto(). Keep the same interaction pattern: locate by a user-facing label, enter deterministic data, press the meaningful key, and assert an observable result. This arrangement also produces readable traces because each operation has one purpose.
For more locator design context, read the Playwright getByRole guide. Keyboard tests are most reliable when semantic locators identify the intended control before the input begins.
3. Use Correct Key Names and Modifier Syntax
Playwright accepts named keys, code-like key names, single characters, and +-joined shortcuts. Key names include Backspace, Tab, Delete, Escape, ArrowLeft, ArrowRight, ArrowUp, ArrowDown, Home, End, PageUp, PageDown, Insert, Enter, function keys, Digit0 through Digit9, and KeyA through KeyZ.
| Goal | Key string | Typical recipient |
|---|---|---|
| Submit a form or accept a menu item | Enter |
Input, button, menu option |
| Close a dialog or clear a transient UI | Escape |
Dialog or page |
| Move to the next focusable control | Tab |
Currently focused control |
| Move focus backward | Shift+Tab |
Currently focused control |
| Select all across operating systems | ControlOrMeta+A |
Text field or editor |
| Move by one option | ArrowDown |
Listbox, menu, select |
| Delete the character before the caret | Backspace |
Editable control |
ControlOrMeta resolves to Control on Windows and Linux and Meta on macOS. It is the most portable choice when the application follows platform conventions.
const notes = page.getByLabel('Notes');
await notes.press('ControlOrMeta+A');
await notes.press('Backspace');
await expect(notes).toHaveValue('');
await notes.press('Shift+KeyA');
await expect(notes).toHaveValue('A');
A character is case-sensitive. a and A can generate different text. For an explicit uppercase event, Shift+A or Shift+KeyA makes the modifier relationship visible.
Do not create strings such as CmdOrCtrl from another automation library. Playwright's portable modifier is ControlOrMeta. Avoid platform branching unless the application itself intentionally uses different shortcuts. If Windows triggers Control+Enter but macOS triggers Meta+Enter, test those product rules explicitly with project metadata rather than assuming one universal command.
4. Control Focus Before Using page.keyboard.press
Focus is the first diagnostic question when page.keyboard.press() appears to do nothing. A focused input, contenteditable area, button, link, or composite widget can consume the event. The same key can have a different effect when the document body owns focus.
Use locator.focus() when focus setup is part of the test arrangement. Use a click when mouse activation and its focus side effect are relevant. Use locator.press() when you simply need a key delivered to a known element.
import { test, expect } from '@playwright/test';
test('moves through fields with Tab', async ({ page }) => {
await page.setContent(`
<label>First name <input name="first"></label>
<label>Last name <input name="last"></label>
<button>Save</button>
`);
await page.getByLabel('First name').focus();
await page.keyboard.press('Tab');
await expect(page.getByLabel('Last name')).toBeFocused();
await page.keyboard.press('Tab');
await expect(page.getByRole('button', { name: 'Save' })).toBeFocused();
});
This is a good use of the page keyboard because the test is explicitly validating focus order. Replacing both calls with field-specific locator.press('Tab') would repeatedly reset focus and could conceal a broken traversal path.
Before pressing a global shortcut, intentionally establish the starting state. A modal, hidden editor, or browser-managed focus restoration can change the active element. await expect(target).toBeFocused() documents and verifies the prerequisite.
Avoid using document.activeElement through evaluate() as the primary assertion. Playwright's toBeFocused() matcher is readable and retrying. Direct evaluation is still useful for deep diagnostics, such as printing an unexpected active element, but it should not replace the user-facing contract.
5. Test Enter, Tab, Escape, and Arrow-Key Workflows
Keyboard coverage should focus on behavior users depend on. Enter commonly submits, opens, accepts, or selects. Escape commonly closes a dialog, clears a menu, or cancels an edit. Tab validates focus order. Arrow keys navigate listboxes, grids, menus, sliders, and custom controls.
import { test, expect } from '@playwright/test';
test('navigates and selects a command option', async ({ page }) => {
await page.setContent(`
<label for="query">Action</label>
<input id="query" aria-controls="actions" aria-expanded="true">
<ul id="actions" role="listbox">
<li role="option" aria-selected="true">Run unit tests</li>
<li role="option" aria-selected="false">Run browser tests</li>
</ul>
<p role="status"></p>
<script>
const input = document.querySelector("#query");
const options = [...document.querySelectorAll("[role=option]")];
let index = 0;
function render() {
options.forEach((option, position) => {
option.setAttribute("aria-selected", String(position === index));
});
}
input.addEventListener("keydown", event => {
if (event.key === "ArrowDown") {
index = Math.min(index + 1, options.length - 1);
render();
}
if (event.key === "Enter") {
document.querySelector("[role=status]").textContent =
"Selected: " + options[index].textContent;
}
});
</script>
`);
const action = page.getByLabel('Action');
await action.press('ArrowDown');
await expect(page.getByRole('option', { selected: true }))
.toHaveText('Run browser tests');
await action.press('Enter');
await expect(page.getByRole('status'))
.toHaveText('Selected: Run browser tests');
});
Assert intermediate semantic state when it matters. Here, aria-selected proves ArrowDown moved the active option before Enter confirms it. If only the final status were checked, the test would give less useful evidence when navigation failed.
Native controls already implement many keyboard rules. Custom widgets need especially careful keyboard tests because the application owns focus movement, selection state, and event cancellation. Base expected behavior on the product's accessibility contract, not on whatever one browser happens to do with incomplete markup.
6. Build Cross-Platform Keyboard Shortcuts
Use ControlOrMeta for shortcuts that follow the operating system's primary command modifier. It reduces duplicated project logic and keeps intent obvious.
import { test, expect } from '@playwright/test';
test('selects all and replaces field content', async ({ page }) => {
await page.setContent(`
<label for="title">Title</label>
<input id="title" value="Old title">
`);
const title = page.getByLabel('Title');
await title.press('ControlOrMeta+A');
await title.pressSequentially('Updated title');
await expect(title).toHaveValue('Updated title');
});
For pure replacement, fill('Updated title') would be simpler. This sequence is justified only when select-all behavior is the requirement, or when the component handles individual input events in a meaningful way.
Page-level shortcuts need a stable starting point. Click a neutral region or focus the expected host, then verify focus before pressing. A shortcut handler registered on document may still ignore events from inputs to prevent accidental activation while typing. That is application logic worth testing.
await page.getByRole('main').click();
await page.keyboard.press('ControlOrMeta+K');
await expect(page.getByRole('dialog', { name: 'Command palette' }))
.toBeVisible();
Do not force portability when the product specification is platform-specific. If a browser application visibly advertises Ctrl+K on every platform, test Control+K. If it advertises the native primary modifier, ControlOrMeta+K is accurate.
Be cautious with browser or operating-system reserved shortcuts. Automation is strongest for events handled inside the page. A test should not depend on opening browser chrome, switching OS applications, or triggering a native save dialog. Verify the web application's event handler and outcome inside the page boundary.
7. Understand Keyboard Events, Delay, and Text Entry
keyboard.press() produces a keydown followed by keyup. When appropriate, text-related events can also occur. The optional delay is the interval between the two boundary events, not a general wait for the application.
await page.keyboard.press('Enter', { delay: 50 });
Use delay only when press duration is part of the component behavior or a realistic reproduction requires it. Do not add arbitrary delays to fix synchronization. An application result should be awaited with an assertion.
Choose the API by intent:
| API | Event model | Best use |
|---|---|---|
locator.fill(text) |
Sets editable value and dispatches input behavior | Ordinary deterministic entry |
locator.press(key) |
Focuses target and emits one key sequence | Enter, Escape, arrows, shortcuts |
page.keyboard.press(key) |
Emits one sequence to current focus | Global shortcut or focus testing |
locator.pressSequentially(text) |
Sends characters one by one | Per-key validation, autocomplete |
keyboard.insertText(text) |
Dispatches input without keydown or keyup | Text insertion and special characters |
keyboard.down/up |
Separates modifier or key boundaries | Selection, repeat, chord diagnostics |
Do not use press() to type a sentence one character at a time. It obscures intent and lengthens the test. Conversely, fill() is not a substitute when a component only responds to a keydown handler.
Event-level assertions are sometimes justified for a component library. Capture a small event log and verify order or modifier state. Most end-to-end tests should assert the resulting UI instead. An internal event log can remain correct while a reducer, request, or render step fails.
For broader synchronization principles, see the Playwright waiting and stability guide.
8. Use down() and up() for Advanced Keyboard Scenarios
keyboard.down() and keyboard.up() are the lower-level tools for holding a modifier, extending a selection, or reproducing repeat behavior. Always release what you hold, even when the sequence is more complex than a single press.
import { test, expect } from '@playwright/test';
test('selects text with a held Shift key', async ({ page }) => {
await page.setContent(`
<label for="message">Message</label>
<textarea id="message">Hello World!</textarea>
`);
const message = page.getByLabel('Message');
await message.focus();
await message.press('End');
await page.keyboard.down('Shift');
for (let index = 0; index < 6; index += 1) {
await page.keyboard.press('ArrowLeft');
}
await page.keyboard.up('Shift');
await page.keyboard.press('Backspace');
await expect(message).toHaveValue('Hello!');
});
A held modifier affects subsequent keyboard calls until it is released. If an assertion or helper throws before up(), later steps can inherit surprising state. For production helpers, use try/finally around the held interval.
await page.keyboard.down('Shift');
try {
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
} finally {
await page.keyboard.up('Shift');
}
Repeated keyboard.down() calls without a corresponding up can set the event's repeat property after the first keydown. That is useful for specialized component testing, but ordinary navigation should use repeated press() calls because each represents a complete keystroke.
Keep advanced input helpers small. A function named selectPreviousCharacters(count) can be useful in an editor suite, but it should not hide which control receives focus or what final state is asserted. Keyboard choreography is implementation-heavy, so anchor it to a clear user behavior.
9. Synchronize on Outcomes, Not Time
Playwright waits for actions to become actionable, but a key handler can start asynchronous work that finishes after the press returns. Wait for the product outcome with a web-first assertion.
await page.getByLabel('Search').press('Enter');
await expect(page.getByRole('heading', { name: 'Search results' }))
.toBeVisible();
await expect(page.getByRole('listitem')).toHaveCount(3);
Avoid waitForTimeout(). A fixed pause guesses when the application will finish, wastes time on fast runs, and can still fail on a slow worker. An assertion polls for the required state and ends as soon as it succeeds.
If the key initiates navigation, assert the destination:
await page.getByLabel('Site search').press('Enter');
await expect(page).toHaveURL(/\/search\?q=accessibility/);
If it triggers a download or popup, begin waiting before the key so the event cannot race past the listener:
const downloadPromise = page.waitForEvent('download');
await page.keyboard.press('ControlOrMeta+S');
const download = await downloadPromise;
await expect(download.suggestedFilename()).toMatch(/report/);
Only use that shortcut if the page owns it and initiates a web download. Do not attempt to automate a native browser save dialog.
Separate action timeout from assertion timeout when diagnosing failures. A successful press followed by a timed-out assertion means input dispatch completed but the expected state did not appear. The Playwright timeout diagnosis guide explains how to identify the expired budget instead of increasing every timeout.
10. Debug playwright keyboard press Failures
Start by confirming the recipient. Add await expect(locator).toBeFocused() immediately before a page keyboard call. If that fails, investigate the prior click, modal focus trap, rerender, or disabled control.
Next, verify the key string. Use Playwright names such as Escape and ArrowDown, and use ControlOrMeta only for portable primary-modifier behavior. Check case when the command depends on a literal character.
Then inspect the application state and event boundary. Playwright Inspector and Trace Viewer reveal focus, DOM snapshots, actions, console messages, and timing:
npx playwright test tests/keyboard.spec.ts --debug
npx playwright test tests/keyboard.spec.ts --trace on
npx playwright show-trace test-results/path-to-trace.zip
Classify the symptom:
- No event effect: focus or key syntax is probably wrong, or no handler is registered.
- Key works headed but not in CI: hidden responsive duplicates, focus timing, or environment-specific shortcut logic may exist.
- Text changes but submit does not: the component may listen to a different event or require valid state.
- Shortcut works on one OS: a hard-coded
ControlorMetalikely violates portability. - Later tests behave strangely: a modifier may have been held without a matching
up(). - Assertion times out: the handler ran but the expected application result never stabilized.
A quick event logger can help during diagnosis, but remove diagnostic-only code from the committed test. Avoid force as a focus fix. Keyboard input should represent a state a user can reach. If focus cannot reach the target, the test may have exposed a real usability or accessibility problem.
11. Encapsulate Keyboard Behavior Without Hiding Intent
Page objects can expose domain commands while retaining semantic locators and clear assertions.
import { expect, type Locator, type Page } from '@playwright/test';
export class CommandPalette {
readonly dialog: Locator;
readonly query: Locator;
constructor(private readonly page: Page) {
this.dialog = page.getByRole('dialog', { name: 'Command palette' });
this.query = this.dialog.getByRole('textbox', { name: 'Command' });
}
async open(): Promise<void> {
await this.page.getByRole('main').click();
await this.page.keyboard.press('ControlOrMeta+K');
await expect(this.dialog).toBeVisible();
}
async run(command: string): Promise<void> {
await this.query.fill(command);
await this.query.press('Enter');
}
}
The page-level shortcut remains visible inside open(), and the field-specific Enter uses the locator API. This models two distinct focus contracts accurately.
Avoid a generic helper such as pressKey(key) that adds no domain meaning and makes call sites harder to understand. Playwright already supplies that abstraction. Useful helpers express outcomes such as openCommandPalette(), dismissDialog(), or chooseNextOption().
Keep assertions close to the behavior they prove. A page object can assert that a universal workflow completed, while scenario-specific results belong in the test. Do not catch and replace Playwright errors with vague messages. The built-in call log, locator description, and expected-versus-received output are valuable evidence.
Use traces in continuous integration, particularly on retry, and keep tests isolated so a held key or active dialog cannot leak between scenarios. A fresh page per Playwright Test case gives keyboard state a predictable boundary.
Interview Questions and Answers
Q: What is the difference between page.keyboard.press() and locator.press()?
page.keyboard.press() sends a key sequence to the element that currently owns focus. locator.press() resolves the locator, focuses that element, and then sends the sequence. I prefer the locator API for a known control and the page keyboard for global shortcuts or focus traversal.
Q: How do you press Ctrl+A on Windows and Command+A on macOS?
Use ControlOrMeta+A. Playwright resolves ControlOrMeta to Control on Windows and Linux and Meta on macOS. I only branch by platform when the application deliberately defines different behavior.
Q: Why can keyboard.press resolve even when the feature does not work?
The method reports completion of the input operation, not success of the product's event handler and downstream state. The handler could ignore the event, reject invalid data, or fail asynchronously. A strong test asserts the visible result, URL, download, or semantic state after the press.
Q: When would you use keyboard.down() and keyboard.up()?
I use them when a key must remain held across several operations, such as extending a selection with Shift. I wrap the sequence in try/finally so the modifier is always released. For an ordinary keystroke, press() is clearer.
Q: Should a test use fill(), pressSequentially(), or keyboard.press() for text?
Use fill() for normal deterministic text entry. Use pressSequentially() when the application depends on per-character events, such as a specialized autocomplete or validation widget. Use press() for a single special key or shortcut, not for a sentence.
Q: How do you debug a key that works locally but fails in CI?
I first assert which element is focused, then inspect the trace for responsive duplicates, overlays, rerenders, and the exact action sequence. I verify that the shortcut is portable and that no prior helper leaves a modifier held. Finally, I wait on an observable result instead of adding a sleep.
Q: How do you test Tab order correctly?
Focus a known starting control, call page.keyboard.press('Tab'), and assert the next control with toBeFocused(). Continue only across the critical path so the test remains maintainable. Using page keyboard is important because repeatedly targeting locators could reset focus and hide a broken order.
Common Mistakes
- Calling
page.keyboard.press()without establishing or asserting focus. - Using a Selenium-style or third-party shortcut name that Playwright does not support.
- Hard-coding
Controlfor a feature that follows the macOS Meta convention. - Typing long text through repeated
press()calls whenfill()expresses the requirement. - Using
fill()when the feature specifically depends on keydown, keyup, or per-character handlers. - Adding a delay or
waitForTimeout()to hide an asynchronous application race. - Holding Shift, Control, Alt, or Meta without reliably releasing it.
- Asserting only that the input action completed, with no product-level result.
- Using an element-specific press while claiming to test natural Tab traversal.
- Triggering browser or operating-system chrome instead of testing behavior owned by the page.
- Silencing focus failures with forced interactions instead of investigating reachability.
- Building generic keyboard wrappers that obscure the intended recipient and outcome.
A useful review question is: "Could this test pass if the application ignored the key?" If the answer is yes, add an assertion that proves the user-visible contract. Another is: "Would the same key reach the same target after a rerender?" If not, use a locator-scoped press or re-establish focus intentionally.
Conclusion
Use locator.press() for most element-specific keyboard behavior and page.keyboard.press() when current focus, traversal, or a page-level shortcut is the feature under test. Choose valid key names, use ControlOrMeta for portable shortcuts, and reserve down() and up() for scenarios that truly require held state.
The most reliable playwright keyboard press test establishes the recipient, performs one meaningful key action, and waits for an observable outcome. Start with the smallest critical workflow, such as Enter submission or Escape dismissal, then add focus and accessibility coverage where keyboard behavior carries real user risk.
Interview Questions and Answers
What is the difference between page.keyboard.press() and locator.press()?
page.keyboard.press() sends a key sequence to the element that currently owns focus. locator.press() resolves and focuses the target before sending the key. I use the locator API for known controls and page keyboard for global commands or focus traversal.
How do you create a cross-platform primary-modifier shortcut?
I use ControlOrMeta, for example locator.press('ControlOrMeta+A'). It resolves to Control on Windows and Linux and Meta on macOS. I branch only if the product defines platform-specific behavior.
How do you prove that a keyboard action succeeded?
I assert an observable product result after the input, such as visible state, selected option, URL, popup, or download. The resolved press promise proves event dispatch completed, not that the application handled it correctly.
When do you use keyboard.down() and keyboard.up()?
I use them when state must remain held across operations, such as Shift while extending a selection. I release the key in a finally block. A normal complete keystroke remains a press() call.
How do you test keyboard focus order?
I focus a deterministic starting element, use page.keyboard.press('Tab'), and assert the next element with toBeFocused(). I avoid targeting each next element because that would reset focus and could conceal an incorrect natural order.
How do you choose among fill(), pressSequentially(), and press()?
fill() is for ordinary text entry, pressSequentially() is for per-character handler behavior, and press() is for one named key or shortcut. Choosing by intent produces faster tests and clearer traces.
How would you debug a keyboard test that fails only in CI?
I confirm active focus, review the trace for overlays or responsive duplicates, check platform-specific modifiers, and verify no held key leaked from a helper. I then synchronize on an observable outcome rather than adding a delay.
Frequently Asked Questions
How do I press Enter in Playwright?
Use await locator.press('Enter') when a known field or control should receive Enter. Use await page.keyboard.press('Enter') only after establishing focus when the active element is part of the scenario.
How do I press Ctrl+A or Command+A in Playwright?
Use await locator.press('ControlOrMeta+A'). ControlOrMeta resolves to Control on Windows and Linux and Meta on macOS.
Does page.keyboard.press focus an element?
No. It sends the key sequence through the page's current focus. Use locator.press() to target and focus a known element, or call focus() and assert toBeFocused() before a page-level key.
What is the difference between fill and pressSequentially?
fill() is the default for setting normal text quickly and clearly. pressSequentially() sends characters one at a time and is appropriate when per-key application handlers are part of the requirement.
Can Playwright hold down Shift or Control?
Yes. Call page.keyboard.down('Shift'), perform the required key operations, and call page.keyboard.up('Shift'). Use try/finally in reusable helpers so the modifier cannot remain held after a failure.
Why does keyboard press do nothing in my Playwright test?
The most common causes are incorrect focus, the wrong key string, a handler that ignores the current state, or an asynchronous result that is not asserted. Verify focus, inspect the trace, and assert the product outcome.
Should I add delay to keyboard.press?
Only when press duration is genuinely relevant to the component or a specific reproduction. Delay is not a synchronization strategy, so wait for the resulting UI state with a web-first assertion.