In a Cypress spec, in what order do before, beforeEach, afterEach and after run?
answer
- Cypress borrows its hooks from Mocha
- Two of the four run only once
- Nesting changes how many hooks fire
- Setup hooks fire outermost first
- Teardown unwinds in the opposite direction
basics
~20 sCypress inherits Mocha's hooks. Each before runs once when its describe block starts; then for every it, beforeEach runs, the test runs, afterEach runs; after runs once when the block ends. Outer blocks wrap inner ones.
solid answer
~50 sCypress's test interface is Mocha's, so the hooks behave exactly as Mocha's do. Within a `describe`, `before` fires once ahead of the first test, `beforeEach` fires before every test, `afterEach` after every test, and `after` once when the block is finished. Hooks accumulate down nested blocks: a test in a nested `context` runs the outer `beforeEach` first and the inner one second, and unwinds the other way, innermost `afterEach` first. Two `beforeEach` hooks in the same block both run, in declaration order. A hook declared outside any block is root-level and applies to the whole file, which is why the support file is the usual place for setup shared by every spec. Cypress drains each hook's command chain before the next runnable starts, so a `cy.visit()` in `beforeEach` is complete when the test body begins.
code
javascript · 27 linesdescribe('Library catalogue', () => {
before(() => {
cy.task('seedCatalogue')
})
beforeEach(() => {
cy.visit('/catalogue')
})
context('search', () => {
beforeEach(() => {
cy.get('[data-testid="catalogue-search"]').type('Dune')
})
it('lists a matching title', () => {
cy.contains('[data-testid="result"]', 'Dune').should('be.visible')
})
it('offers a borrow button on the first result', () => {
cy.get('[data-testid="borrow"]').first().should('be.visible')
})
})
afterEach(() => {
cy.log('one catalogue test finished')
})
})go deeper
Be ready to name all four hooks and say which two run once per block and which two run per test. Interviewers often just ask you to predict the printed order for a short spec.
Explain how hooks stack through nested describe and context blocks, that setup runs outside-in while teardown runs inside-out, and that a hook body is a command chain Cypress drains before the test starts.
Show judgment about what belongs in a hook at all: shared navigation and seeding yes, per-test assertions no, and required resets in beforeEach rather than afterEach because teardown is not guaranteed to run.
Own the convention for the suite: how much setup may live in the support file versus a spec, when duplicated hooks signal that two describe blocks should be two specs, and how the team keeps hook cost off the critical path.
## Cypress does not define these hooks — Mocha does Cypress bundles **Mocha** as its test interface and **Chai** as its assertion library. `describe()`, `context()`, `it()` and `specify()` are Mocha's, and so are the four lifecycle hooks `before`, `beforeEach`, `afterEach` and `after`. `context()` is an alias for `describe()`, and `specify()` is an alias for `it()`, so pick whichever reads better in a library-catalogue spec. Because the vocabulary is Mocha's, the ordering rules below are Mocha's rules. What Cypress adds is that a hook body is itself a command chain: `cy.visit('/catalogue')` inside a `beforeEach` is fully drained before the first line of the test runs, so you never have to await it. ## The order around one test For a single `it()` inside a single `describe()`, the runner walks this sequence: 1. Every `before()` hook for the block runs — **once**, ahead of the first test in that block. 2. Every `beforeEach()` hook runs — **once per test**. 3. The test body runs. 4. Every `afterEach()` hook runs — once per test. 5. Every `after()` hook runs — **once**, after the last test in that block finishes. Declare two `beforeEach()` hooks in the same block and **both** run, in declaration order. Nothing deduplicates them, and a later declaration does not replace an earlier one. ## Nesting: setup runs outside-in, teardown inside-out Hooks accumulate down the tree. A test in a nested `context()` inherits every `beforeEach` above it, and the outermost one goes first; on the way out the innermost `afterEach` goes first. | Hook | How often it runs | Order across nested blocks | |---|---|---| | `before` | once per block | outermost block first | | `beforeEach` | once per test in that block and every block below | outermost first | | `afterEach` | once per test in that block and every block below | innermost first | | `after` | once per block | innermost first | That is why a catalogue spec can put `cy.visit('/catalogue')` on the outer `describe` and the search typing on an inner `context`, and every test in the inner block gets the visit first and the search second — without either block knowing about the other. ## Root-level hooks and the support file - A hook declared **outside** any `describe` is a root-level hook and applies to every test in the spec file. - Cypress loads the **support file** before every spec, so a root-level `beforeEach` written there runs before every test in every spec of that testing type — the usual home for a shared catalogue seed or a shared cookie. - Root-level hooks stack with block-level ones rather than replacing them; a test can easily run three `beforeEach` bodies before its first assertion. ## Setup belongs in `before`/`beforeEach`, not in the teardown pair Cypress documents cleanup in `after`/`afterEach` as an **anti-pattern**, and the reason is availability, not style: - There is **no guarantee** an `after` hook runs. Refresh the runner mid-test in `cypress open` and the teardown body never executes, so whatever it was supposed to reset stays dirty for the next run. - Code in `before`/`beforeEach` always runs ahead of the test, even after that refresh, so a reset placed there is the one that actually happens. - Cypress deliberately leaves **dangling state** — spies, stubs and intercepts are not torn down at the end of a run — so you can keep poking at the catalogue app where the last test left it. A custom `cy.logout()` command in `afterEach` throws that away. ## Declaration position does not change when a hook runs Mocha collects the whole block before running any of it, so a hook is not a statement executed in source order. Two consequences catch people out: - An `afterEach` written **below** the `it()` blocks still runs after each of them; it does not matter whether the hook appears above or below the tests it wraps. - A `beforeEach` written at the bottom of a `describe` still runs before the block's first test. Keep hooks at the top of the block anyway, for readers rather than for the runner. The same collection pass is why a mistyped `describe` body throws when the spec loads rather than when a test runs: the tree is built first, and only then does anything execute. ## Two things hooks cannot do - **They cannot create tests.** The `describe`/`it` tree is built synchronously when the spec file loads, before any hook runs, so `cy.fixture()` or `cy.task()` inside `before()` can supply data *within* a test but cannot generate `it()` blocks. Data that drives test titles has to be available at load time, typically through a static `import`. - **They do not run for a pending test.** A test skipped with Mocha's `it.skip()` or the `xit()` alias, or one with no body at all, never triggers its `beforeEach`.
- In a Cypress spec, what happens if one describe block declares two beforeEach hooks?Both run before every test in that block, in the order they were declared. Mocha does not deduplicate or replace hooks, so the second is additional setup rather than an override. The same is true of two `before` hooks, which both run once ahead of the first test.
- Where do you put a Cypress hook so it runs before every test in every spec file?At the root of the support file, outside any `describe`. Cypress loads the support file before each spec, so a root-level `beforeEach` there applies to every test of that testing type. Keep it cheap: the file is bundled and evaluated once per spec, so heavy imports there slow the whole run.
saying these in an interview costs you the question
- Thinks before runs ahead of every test
- Assumes a nested beforeEach replaces the outer one
- Believes afterEach is guaranteed to run
- Puts required cleanup in after instead of beforeEach
- Cannot say which hooks fire once per block