In Cypress, how do you press the Tab key to move focus between fields?
answer
- Real key events, not simulated ones
- Focus order is the browser's decision
- Named keys live on a constant
- Cypress.Keyboard.Keys.TAB is the key
- The command yields null, not an element
basics
~20 sUse cy.press(Cypress.Keyboard.Keys.TAB). It sends a real keydown and keyup to the browser through the channel Cypress drives it over, so native focus order applies. Cypress's .type() cannot do it, because it does not support {tab}.
solid answer
~40 s`cy.press()` is Cypress's native-key command. It hands the key to the browser process over the automation channel Cypress uses to drive Chrome or Firefox, so the browser moves focus exactly as it would for a real user. Pass a named key from `Cypress.Keyboard.Keys` rather than a raw string, because the underlying values can change between releases: `cy.press(Cypress.Keyboard.Keys.TAB)`. Focus something first, then press, then assert with a fresh query, for example focusing the account picker on a bank statement page and checking that the Download PDF button ends up with focus. Cypress's `.type()` is not a substitute: it synthesizes DOM events inside the page and explicitly does not support `{tab}`, so focus never actually moves. Note that `cy.press()` yields `null`, so you cannot chain `.should('have.focus')` onto it.
code
javascript · 6 linescy.visit('/statements')
cy.get('[data-cy=account-select]').focus()
cy.press(Cypress.Keyboard.Keys.TAB)
cy.get('[data-cy=statement-range]').should('have.focus')
cy.press(Cypress.Keyboard.Keys.TAB)
cy.get('[data-cy=download-pdf]').should('have.focus')go deeper
Remember the command name and the constant: cy.press(Cypress.Keyboard.Keys.TAB). Be ready to say why Cypress's .type() cannot move focus with a tab sequence.
Explain the mechanism, not just the API: a simulated DOM event never earns the browser's default behaviour, so focus order can only be tested with a native key event sent through the browser process.
Be ready to say when a keyboard test is worth the native path, and to spot the failure mode where a synthesized keydown passes on a control a real user could never reach by keyboard.
Own the convention for the suite: which keyboard journeys are exercised natively, how they are guarded on browsers without a native-key channel, and who reviews new keyboard tests against that line.
Cypress runs your spec inside the browser, in the same event loop as the application under test. That gives it direct access to the DOM, but it also means a spec cannot manufacture a *real* key press any more than the application can — a script inside the page can only dispatch events that the browser marks as untrusted. `cy.press()` exists to cross that line. ## Two ways to make a key event - **Simulated, in the page.** Cypress's `.type()` and `.trigger()` build DOM events and dispatch them at an element from inside the page. The application's handlers run, but the browser does not treat the event as a real user action and does not apply its own default behaviour. - **Native, through the browser process.** `cy.press()` sends the key out of the page to the Node process that launched the browser, which forwards it to the browser over the protocol it drives the browser with. The browser then dispatches a genuine `keydown` and `keyup` to whatever currently has focus, and applies its own default behaviour — including moving focus on **Tab**. Because focus order is something the browser decides, only the second route can test it. This is why `.type()` has no `{tab}` sequence: Cypress cannot honestly simulate one from inside the page, so it does not pretend to. ## Using it on a real page On a bank statement screen with an account picker, a date-range field and a **Download PDF** button, the keyboard journey looks like this: ```javascript cy.visit('/statements') cy.get('[data-cy=account-select]').focus() cy.press(Cypress.Keyboard.Keys.TAB) cy.get('[data-cy=statement-range]').should('have.focus') cy.press(Cypress.Keyboard.Keys.TAB) cy.get('[data-cy=download-pdf]').should('have.focus') cy.press(Cypress.Keyboard.Keys.ENTER) ``` Three details matter: 1. **Focus something first.** The key goes to the browser window, not to an element you name, so whatever holds focus receives it. Start the journey with an explicit `.focus()` or a `.click()`. 2. **Assert with a new query.** `cy.press()` yields `null`, so `cy.press(...).should('have.focus')` has nothing to assert against. Query the element you expect to be focused and assert there. 3. **Use the constants.** `Cypress.Keyboard.Keys.TAB`, `.ENTER`, `.ESC`, `.HOME`, `.PAGEDOWN` and the arrow keys are exported so your spec does not hard-code a string whose value could change. ## Where the key actually lands There is no element argument, so the key goes wherever focus is — and in Cypress the page under test lives in a frame inside the runner. Before dispatching, Cypress checks whether that frame is the active one and focuses it if it is not, so a key press never leaks into the runner's own UI and never lands on the wrong document. That is also why an explicit `.focus()` or `.click()` at the start of a keyboard journey is worth writing: it makes the starting point of the tab order explicit instead of inheriting whatever the previous command left focused. ## What cy.press() will not do - **No multi-character strings.** `cy.press('a')` is fine; `cy.press('Hello')` is rejected. Long text belongs in `.type()`. - **No modifier combinations.** There is no syntax for Ctrl+C or Shift+Tab; `.type()` owns `{ctrl}c` and `{shift}b`. - **No function keys.** F1 through F12 are unsupported, because they fire browser shortcuts that can derail the rest of the run. - **Not everywhere.** A WebKit-family run has no native-key channel, so `cy.press()` reports that it is not supported in that browser family. - **Not element-targeted.** There is no `.press()` you can chain off `cy.get()`; it is always a parent command on `cy`. ## Choosing between the two commands | Need | Command | Why | |---|---|---| | Type a customer reference into a field | `.type('AC-4471')` | text entry, many characters, in-page is enough | | Move focus with Tab | `cy.press(Cypress.Keyboard.Keys.TAB)` | focus order is the browser's decision | | Activate the focused Download button with Enter | `cy.press(Cypress.Keyboard.Keys.ENTER)` | exercises the browser's default action | | Send Ctrl+C or Shift+Tab | `.type('{ctrl}c')` | `cy.press()` has no modifier syntax | | Fire a handler on a non-focusable element | `.trigger('keydown', { key: 'k' })` | no real key is needed to test a handler | ## One side effect to know about Dispatching native key events puts the page into the browser's **transient activation** state — the same state a real click or key press produces, which browsers require before allowing things like opening a window or starting a capture. If the application under test cancels the default behaviour of `beforeunload`, that activation can make a later navigation away from the page behave differently than it does with simulated events. It is rarely a problem, but it is the one behavioural difference that can surprise you when converting a `.type()` test to `cy.press()`. The rule of thumb is simple: use `.type()` for content, and `cy.press()` when the thing under test is what the **browser** does with a key rather than what your handler does with it.
- Why can't you chain .should('have.focus') directly onto Cypress's cy.press()?Because `cy.press()` yields `null` rather than an element, so the chained assertion has nothing to run against. Assert with a fresh query instead — `cy.get('[data-cy=download-pdf]').should('have.focus')`. It is an action command with a side effect on the browser, not a query that produces a subject for the next link in the chain.
- Which keys does Cypress's cy.press() refuse, and what do you use instead?F1 through F12 are unsupported, because they trigger browser shortcuts that can stop the rest of the suite from running. Multi-character strings are rejected too, so `cy.press('Hello')` throws. Use Cypress's `.type()` for text, and for modifier sequences such as `{ctrl}c` or `{selectAll}`, which `cy.press()` has no syntax for.
saying these in an interview costs you the question
- Claims Cypress's .type('{tab}') moves focus
- Chains .should('have.focus') straight onto cy.press()
- Thinks press is chained off cy.get() like a click
- Assumes cy.press() takes an element argument
- Believes cy.press() works in every Cypress browser