skip to content

Why can `cypress run --config defaultCommandTimeout=15000` leave your e2e timeout unchanged?

level: seniorimportance: should knowfreq 44%

answer

  1. The flag is not the last word
  2. Two merges happen, in order
  3. Root-level flags land before flattening
  4. Nested JSON reaches inside the block
  5. Root-illegal keys are auto-nested for you

basics

~10 s

Because --config merges onto the root of the configuration being resolved, and Cypress then spreads the e2e block over that root. A defaultCommandTimeout declared inside the block overwrites the flag, silently, with no warning.

solid answer

~50 s

`--config` values land at the **root** of the object Cypress is resolving, and the testing-type block is flattened over the root afterwards — so a shared key that is also declared inside `e2e` beats the flag, with no warning. Keys that are illegal at the root behave differently: the CLI moves `baseUrl`, `specPattern`, `supportFile`, `testIsolation`, `excludeSpecPattern` and `slowTestThreshold` into the testing-type block for you, so `--config baseUrl=https://qa.admin.example.com` does win. The fix for a shared key is the nested JSON form, `--config '{"e2e":{"defaultCommandTimeout":15000}}'`, which merges into the block itself; or stop declaring the key in the block and leave it at the root of the file. Confirm the outcome by reading `Cypress.config('defaultCommandTimeout')` back inside a spec, or by checking the resolved settings in `cypress open`, rather than trusting that the flag was applied — nothing in the run output says it was overwritten.

code

bash · 8 lines
bash
# Loses when the e2e block declares the same key: lands on the root, then is overwritten
npx cypress run --e2e --config defaultCommandTimeout=15000

# Wins: the JSON form merges into the e2e block itself
npx cypress run --e2e --config '{"e2e":{"defaultCommandTimeout":15000}}'

# Wins: baseUrl is illegal at the root, so the CLI nests it into the block for you
npx cypress run --e2e --config baseUrl=https://qa.admin.example.com

go deeper

for a junior

Know that cypress run --config key=value exists and sets configuration for that run. The subtleties of which layer beats which are not expected of you yet.

for a middle

Explain the resolution order that produces this: the flag merges onto the root, and the testing-type block is flattened over the root afterwards, so the block is the more specific site.

for a senior

An interviewer wants the diagnosis, not the trivia — how you notice that a flag silently lost, how you read the resolved value back, and which of the three fixes you would apply to a suite other people maintain.

for a principal

The angle to own is why a per-run override that can be silently ignored is a hazard for a shared suite, and what convention keeps command-line and file-declared settings from contradicting each other.

## Where a `--config` value actually lands `cypress run --config key=value` does not inject the value into the testing-type block. It parses the flag into an object — either comma-separated `key=value` pairs or a JSON string — and merges it onto the **root** of the configuration Cypress is assembling. The testing-type block is flattened over that root *afterwards*. So for a key that is declared inside `e2e`, the sequence is: 1. the config file supplies `e2e.defaultCommandTimeout: 12000`; 2. `--config defaultCommandTimeout=15000` sets the **root** key to 15000; 3. flattening spreads the `e2e` block over the root, and 12000 overwrites 15000; 4. the run uses **12000**, with no warning and no error. The flag was not rejected. It was applied, and then overwritten by a more specific declaration site. ## Two classes of key behave differently This is the part that makes the behaviour look arbitrary until you see the rule. Some keys are **illegal at the root of a config file** — `baseUrl`, `specPattern`, `supportFile`, `excludeSpecPattern`, `slowTestThreshold` and `testIsolation`, plus `indexHtmlFile` and `justInTimeCompile` for component runs. Because they cannot legitimately mean anything at the root, the CLI assumes a root-level flag was meant for the testing type being run and **nests it into the block** before resolution starts. It therefore merges *into* the block and wins. | Key on the CLI | Where the flag lands | Beats a value in the `e2e` block? | |---|---|---| | `baseUrl`, `specPattern`, `supportFile`, `testIsolation` | nested into the block | yes | | `defaultCommandTimeout`, `viewportWidth`, `retries`, `videosFolder` | the root | only if the block does not declare it | So `--config baseUrl=https://qa.admin.example.com` reliably repoints an admin-console suite at a different environment, while `--config defaultCommandTimeout=15000` against the same suite may do nothing at all. Same flag, same file, opposite outcome — decided entirely by whether the key is legal at the root. ## Making the override stick Three fixes, in rough order of how disruptive they are: - **Pass the nested form.** `--config` accepts a JSON object, and an object under `e2e` is merged into the block rather than onto the root: `cypress run --config '{"e2e":{"defaultCommandTimeout":15000}}'`. Note that a dotted key such as `--config e2e.defaultCommandTimeout=15000` does **not** work: it produces a key literally named `e2e.defaultCommandTimeout`, which is not a known option and is dropped. - **Move the key out of the block.** If the value never needed to differ between testing types, declaring it at the root of the file leaves the CLI flag as the most specific site, and it wins. - **Set it inside the spec.** `Cypress.config('defaultCommandTimeout', 15000)` applies for the rest of that spec file, which is the right tool when only some specs need the longer wait. ## Why the order is what it is The two-step shape is not an accident. A configuration file has to be able to say "e2e runs use a longer timeout than component runs", and the only way to express that is for the block to be more specific than the root — which means the block must be applied last. A command-line flag, meanwhile, is a *root-level* input by construction: `--config defaultCommandTimeout=15000` names a key, not a testing type, so Cypress has nowhere else to put it. The collision is the price of letting one file describe two testing types. The auto-nesting for root-illegal keys is the escape hatch Cypress built for the half of the problem it could solve unambiguously. When a key **cannot** mean anything at the root, there is exactly one interpretation of the flag, so the CLI applies it. For a shared key like `defaultCommandTimeout` both interpretations are legitimate, so it does not guess — and you get the surprise. ## Confirming what actually resolved The reason this costs an afternoon is that nothing tells you the flag lost. Two cheap checks close the loop: - Read the value back where it matters — `Cypress.config('defaultCommandTimeout')` inside a spec returns the resolved number, and a mismatch against the flag is the whole diagnosis. - Open the project and look at the resolved settings, which mark each value's origin: default, config file, `CYPRESS_` environment variable, command line, or `setupNodeEvents`. On a multi-tenant admin console this bites hardest on the settings a CI job wants to vary per pipeline: a nightly job that raises `defaultCommandTimeout` because the shared staging environment is slower under load, or a smoke job that narrows `retries`. Both are shared keys; both are the kind a team declares inside `e2e` because that is where the rest of the e2e settings live; and both therefore fail silently on the command line. The generalisable lesson is that a command-line override is not automatically the last word. In Cypress it is one input into a resolution that still has a flattening step to run, and the flattening step is more specific than the flag. When a suite's behaviour ignores a flag you passed, look for a second declaration of the same key one level deeper in the file.

  • Which keys does the Cypress CLI move into the testing-type block automatically?
    The ones that are invalid at the root of a config file: `baseUrl`, `specPattern`, `supportFile`, `excludeSpecPattern`, `slowTestThreshold` and `testIsolation`, plus `indexHtmlFile` and `justInTimeCompile` for component runs. Since they cannot mean anything at the root, the CLI assumes you meant them for the testing type being run and nests them, which is why they override the block.
  • How do you check which value a Cypress run actually resolved for a key?
    Read it back inside a spec with `Cypress.config('<key>')`, which returns the flattened value the run is using. In `cypress open`, the project settings list every resolved value and mark its origin — default, config file, `CYPRESS_` environment variable, command line, or `setupNodeEvents` — which turns a guess into an observation.

saying these in an interview costs you the question

  • Says --config always beats the config file
  • Thinks the flag was rejected rather than overwritten
  • Retries with different comma syntax instead of nesting
  • Assumes every config key behaves the same on the CLI
  • Blames the CI environment before reading the e2e block