Why can a Cypress spec that types into a search box break on the upgrade to 16?
answer
- The upgrade changed a default, not an API
- Something about how fast typing is
- Ten milliseconds became zero
- Config, then Keyboard defaults, then zero
- keystrokeDelay restores the old behaviour
basics
~20 sCypress 16 changed .type()'s default keystroke delay from 10 milliseconds to 0, so the whole string now goes in with no gap. A debounced typeahead that used to fire a request per pause now fires once, and timing-dependent assertions change with it.
solid answer
~40 sAs of Cypress 16, `.type()` has no keystroke delay unless you ask for one — the default moved from 10 ms to 0 to speed up typing-heavy runs. The resolved value is read from the `keystrokeDelay` configuration key first, then from `Cypress.Keyboard.defaults()`, and falls back to `0` if neither is set; a per-command `delay` passed to `.type()` is independent of both and wins for that one call. What breaks is anything that depended on the implicit gap: a debounced search that used to emit several requests now emits one, an assertion written against an intermediate value never sees it, and a component that re-renders per keystroke behaves differently. Restoring `keystrokeDelay: 10` gets you green again, but the durable fix is to assert on the settled result rather than on typing speed.
code
javascript · 9 lines// cypress.config.js - blanket restore of the pre-16 gap
module.exports = {
keystrokeDelay: 10,
e2e: { baseUrl: 'http://localhost:4300' },
}
// Better: keep the fast default, slow down only where the delay is the subject
cy.get('[data-cy=catalogue-search]').type('Le Guin', { delay: 50 })
cy.get('[data-cy=suggestion]').should('have.length', 3)go deeper
Know that .type() types with no gap by default in Cypress 16, and that a delay option exists on the command if you need one.
Explain the resolution order — config key, then Cypress.Keyboard.defaults(), then zero — and that a per-command delay is independent of all three.
Show that you read the failure before restoring the default: distinguish a missing intermediate state from a real race the faster typing exposed, and fix each differently.
Own the upgrade policy: whether the suite buys time with a global restore, how long that is allowed to stand, and what evidence closes it out.
This is one of the quieter breaking changes in Cypress 16, because nothing in your spec has to change for the behaviour to change. `.type()` used to leave a 10 ms gap between keystrokes; from 16 it leaves none unless you ask, which makes typing-heavy suites noticeably faster and makes a small number of specs fail in ways that look like flake. ## What the default actually is now The delay a `.type()` call uses is resolved in a fixed order: 1. A `delay` passed to that call — `cy.get('#q').type('Dune', { delay: 10 })`. It is independent of everything below and applies to that command only. 2. Otherwise the `keystrokeDelay` value in the Cypress configuration. 3. Otherwise the value set by `Cypress.Keyboard.defaults({ keystrokeDelay: 10 })`. 4. Otherwise `0`. The order of steps 2 and 3 catches people out: the **configuration wins over the runtime call**, not the other way round. If a support file sets `Cypress.Keyboard.defaults()` and the config file also carries `keystrokeDelay`, the config value is the one in force. Both reject a negative or non-numeric value with an explicit error rather than falling back silently. | Where you set it | Scope | Notes | | --- | --- | --- | | `{ delay: 10 }` on `.type()` | That one command | Independent of the resolved default; the surgical option | | `keystrokeDelay` in the Cypress config | Whole project, or one suite via a test-configuration override | Read first, so it wins over the API call | | `Cypress.Keyboard.defaults({ keystrokeDelay: 10 })` | The spec that runs it, from a support file | The blanket restore for a suite mid-migration | ## Why a catalogue search spec is the one that breaks A typeahead over a library catalogue is usually debounced: the input handler waits a couple of hundred milliseconds after the last keystroke before requesting suggestions. Under Cypress 15, typing `Le Guin` took roughly 70 ms of wall-clock time spread over seven keystrokes; under 16 those seven keystrokes land in one tight burst. Several things follow: - **Fewer intermediate states exist.** A test asserting that a "searching…" indicator appears while the reader is still typing may never catch it. - **Request counts change.** A spec written when three requests went out may now see one, so an assertion counting calls, or a second wait on the same route, fails. - **Per-keystroke re-renders compress.** A results list that re-renders on every keystroke now re-renders once, which changes when an element the chain is holding gets detached. - **Order-of-operations bugs surface.** Application code that assumed a human's typing speed — an autocomplete that races its own responses, a form that validates on a timer — starts losing that race, and the test is telling you something true about the application. That last point is the one worth pausing on. The upgrade did not create the race; it removed the accidental padding that hid it. A spec that only passes with a 10 ms gap between keystrokes is describing behaviour no real reader is guaranteed to reproduce, because a fast typist or a pasted value produces the same burst. ## How to respond Work through it in this order rather than reaching straight for the global restore: 1. **Read what actually failed.** A missing intermediate state and a lost race are different problems with different fixes; only the second is a real defect. 2. **Prefer asserting on the settled result.** Assert that the suggestions for "Le Guin" render, not that a spinner appeared partway through. Assertions on a settled outcome retry until they pass and do not depend on typing speed at all. 3. **Use a per-command `delay` where the delay genuinely is the subject.** A test whose point is that the typeahead debounces correctly is entitled to type slowly: `{ delay: 50 }` on that one command is honest and local. 4. **Reach for `keystrokeDelay: 10` project-wide only as a migration step.** It gets a large suite green in one edit, which is a legitimate thing to want on the day of an upgrade, but it re-hides every race the change exposed and it gives back the speed the change was made to win. ## What it does not change Nothing about the events themselves moved. `.type()` still fires `keydown`, `keypress`, `beforeinput`, `textInput`, `input` and `keyup` per character, still fires `change` on `{enter}` or on blur when the value moved, and still respects cancellation. `.clear()`, which Cypress runs as `.type('{selectall}{del}')`, picks up the same resolved delay, so a suite that restores the delay slows its clears down too. And the readiness wait before typing is untouched: a field that is not ready still blocks the command for as long as it takes, which is why an upgrade that made a suite faster overall can still leave individual slow specs exactly as slow as they were.
- In Cypress 16, if both `keystrokeDelay` and `Cypress.Keyboard.defaults()` are set, which one applies?The configuration value. The resolved delay is read from the `keystrokeDelay` configuration key first, then from `Cypress.Keyboard.defaults()`, and falls back to `0` if neither is set. A support file calling `Cypress.Keyboard.defaults()` therefore has no effect on a project whose config already sets the key — a common surprise mid-migration.
- Is restoring `keystrokeDelay: 10` after upgrading to Cypress 16 an acceptable end state?As a migration step, yes: it gets a large suite green in one edit. As an end state it is weak, because it re-hides every race the change exposed and gives back the speed the change existed to win. The durable move is to assert on settled results, and to keep an explicit per-command `delay` only in the specs whose subject really is typing speed.
saying these in an interview costs you the question
- Says .type() gained a new option in 16
- Thinks Cypress.Keyboard.defaults() overrides the config key
- Treats the new failures as ordinary flake
- Believes the change altered which events fire