skip to content

Why does Cypress reject testIsolation: false on an it block but accept it on describe?

level: middleimportance: nice to knowfreq 28%

answer

  1. Overrides ride on a second argument
  2. The reset happens between tests, not inside
  3. One option refuses the finer of two levels
  4. Overrides are undone once the test ends
  5. One override key was removed in 16

basics

~20 s

Because the browser-context reset happens at the boundary between tests, not inside one. Cypress resolves it per suite, so a per-test value would be ambiguous, and it errors that testIsolation can only be overridden from a suite-level override.

solid answer

~50 s

Cypress takes an optional config object as the second argument to `describe`, `context`, `it` and `specify`, and most writable options work at either level. `testIsolation` is the exception: it is suite-level only, and an `it`-level override fails with "The `testIsolation` configuration can only be overridden from a suite-level override." The reason is timing — the reset happens in the gap between two tests, so the setting describes a boundary rather than a test, and Cypress resolves it from the enclosing block. The same timing is why `Cypress.config()` cannot change `testIsolation`, `viewportWidth`, `viewportHeight` or `blockHosts` during test execution. As of Cypress 16, overriding `env` in one of these objects was removed; use `expose: { KEY: value }` and read it with `Cypress.expose()`. Declared keys are applied at test start and restored once the test finishes, and an invalid override fails the test rather than being ignored.

code

javascript · 15 lines
javascript
describe('Borrowing flow', { testIsolation: false, expose: { region: 'eu' } }, () => {
  before(() => {
    cy.visit('/catalogue')
  })

  it('adds a title to the basket', { defaultCommandTimeout: 10000 }, () => {
    cy.get('[data-testid="borrow"]').first().click()
    cy.contains('[data-testid="basket"]', '1 title').should('be.visible')
  })

  it('still shows the basket in the next test', () => {
    expect(Cypress.expose('region')).to.eq('eu')
    cy.contains('[data-testid="basket"]', '1 title').should('be.visible')
  })
})

go deeper

for a junior

Recall that Cypress lets a suite or test carry its own settings as a second argument, and that this is how one slow spec gets a longer timeout without changing the project config.

for a middle

Explain which options are writable at run time, why testIsolation is restricted to the suite level, and when the declared keys are applied and restored around each test.

for a senior

Be ready to say when a narrow override is the right fix and when it is hiding a defect — a block-level timeout bump that masks a genuinely slow catalogue page is a smell, not a configuration.

for a principal

Own where deviations are allowed to live: which options a spec author may override locally, which must go through the project config with review, and how the team stops overrides from accumulating unexplained.

## The second argument to `describe` and `it` Cypress accepts an optional configuration object as the **second** argument to a suite or test: `describe(name, config, fn)`, `context(name, config, fn)`, `it(name, config, fn)` and `specify(name, config, fn)`. The keys in that object take effect only for that suite or test and are restored afterwards, which makes it the right tool when one catalogue spec needs a slower `defaultCommandTimeout` or a different `viewportWidth` without moving the project's baseline. Only a fixed set of options is writable this way — `animationDistanceThreshold`, `baseUrl`, `blockHosts`, `defaultCommandTimeout`, `includeShadowDom`, `keystrokeDelay`, `numTestsKeptInMemory`, `pageLoadTimeout`, `redirectionLimit`, `requestTimeout`, `responseTimeout`, `retries`, `screenshotOnRunFailure`, `scrollBehavior`, `slowTestThreshold`, `taskTimeout`, `testIsolation`, `viewportHeight`, `viewportWidth` and `waitForAnimations`. Everything else is read-only at run time, and an unwritable key produces an error naming the option rather than being quietly ignored. ## Why `testIsolation` is a suite-only key `testIsolation` carries a stricter rule than the rest: it may be overridden **only at the suite level**. Put `{ testIsolation: false }` on an `it()` and Cypress fails the test with > The `testIsolation` configuration can only be overridden from a suite-level override. The reason is *when* the reset happens. Cypress performs the browser-context reset around the test boundary — it decides whether to navigate to `about:blank` by looking at whether the **next** test has isolation on. A per-test value would therefore be a statement about the gap before the test, not about the test itself, and two adjacent `it()` blocks could disagree about the same boundary. Making it a property of the enclosing `describe` keeps the answer unambiguous for every gap inside the block. Two related restrictions follow from the same timing: - `Cypress.config()` cannot set `testIsolation` while a test is executing, and it likewise refuses `viewportWidth`, `viewportHeight` and `blockHosts`, because a change there would land on the next test rather than the current one. - Component testing rejects `testIsolation` entirely: it always resets, and there is nothing to configure. ## `env` overrides are gone in 16 — use `expose` Overriding `env` in a suite- or test-level configuration object was **removed in Cypress 16.0.0**. Attempting it raises an error telling you to switch to `expose: { KEY: value }`. Note the narrow scope of the removal: the `env` **config key** in `cypress.config.js` and the `--env` CLI flag both still exist and still feed `cy.env()`. It is only the per-suite and per-test override that is gone. The replacement splits by sensitivity: - **`expose`** declares public, non-sensitive values — feature flags, a plugin's settings, a region code — read back synchronously with `Cypress.expose()`. Exposed values are reachable from the browser context, so never put a token or a password there. - **`cy.env()`** is the command for sensitive values; it stays in Node and is asynchronous. `expose` overrides merge across nesting, with test-level keys beating suite-level ones for the same key. ## When an override reverts The lifecycle is the part candidates get wrong. Override keys are applied **at test start** and restored **after each test completes** — including keys a hook mutated during that test. So: 1. A key declared in a `describe` override is in force for every test in the block, including inside its hooks. 2. A hook may change the value during a test, and the whole test sees the change. 3. Once the test ends, the declared keys go back to what they were, so the next test is unaffected. 4. A value you set at run time that was **not** declared in an override is not restored for you. ## What an invalid override looks like The overrides are validated, and the error tells you which level it came from — for example, *the config passed to your suite-level overrides has the following validation error*, followed by the specific problem. Three distinct refusals are worth recognising: - **read-only**: the option can never be overridden at run time, at any level. - **suite-only**: the option is valid but must sit on a `describe` or `context`, which is the `testIsolation` case. - **a type or value error**: `{ retries: '1' }` fails because `retries` takes a number or an object, not a string, and `{ baseUrl: 'not_an_http_url' }` fails because the value is not a URL. All three fail the affected test rather than being silently dropped, and a bad override on a `describe` fails every test in it — so a typo in a suite override is a whole-block outage, not a quiet no-op. ## A short checklist - Reach for the second argument when the deviation belongs to a block, not the project. - Put `testIsolation` on the `describe`, never on the `it`. - Replace any remaining per-test `env` override with `expose`, and keep secrets out of it. - Prefer a narrow override on one block over widening the project default so one spec can pass.

  • How long does a suite-level Cypress config override stay in force?
    Declared keys are applied at test start and restored after each test in the block completes, so the block's tests all see them and nothing leaks past the block. If a hook mutates one of those keys during a test, the rest of that test sees the new value and the declared value returns for the next test.
  • In Cypress 16, how do you pass a public value to one describe block and read it in the tests?
    Put `expose: { key: value }` in the block's config object and read it synchronously with `Cypress.expose('key')`. Suite- and test-level `expose` objects merge, with the test-level key winning. Keep it to non-sensitive data: exposed values are readable from the browser context, so tokens and passwords belong in `cy.env()` instead.

saying these in an interview costs you the question

  • Tries to disable test isolation on a single it
  • Thinks any config key can be overridden per test
  • Still writes env inside a per-test override object
  • Believes an override persists after the block ends