0
0
Cypresstesting~15 mins

beforeEach and afterEach hooks in Cypress - Deep Dive

Choose your learning style9 modes available
Overview - beforeEach and afterEach hooks
What is it?
beforeEach and afterEach are special functions in Cypress testing that run code before or after each test in a group. They help set up the environment or clean up after tests automatically. This means you don't have to repeat the same steps in every test. They make tests easier to write and keep organized.
Why it matters
Without beforeEach and afterEach, you would have to write the same setup or cleanup code inside every test, which wastes time and can cause mistakes. These hooks ensure tests start fresh and end clean, preventing one test from affecting another. This leads to more reliable and easier-to-maintain tests, saving developers from chasing confusing bugs.
Where it fits
Before learning these hooks, you should understand basic Cypress test structure and how to write simple tests. After mastering them, you can learn about more advanced hooks like before and after, and how to use fixtures and custom commands to further improve test setup and teardown.
Mental Model
Core Idea
beforeEach runs setup code before every test, and afterEach runs cleanup code after every test, ensuring each test runs in a clean, controlled environment.
Think of it like...
It's like preparing your kitchen before cooking each meal and cleaning up right after, so every meal starts fresh and the kitchen stays tidy.
┌───────────────┐
│ Test Suite    │
├───────────────┤
│ beforeEach()  │
├───────────────┤
│ Test 1        │
├───────────────┤
│ afterEach()   │
├───────────────┤
│ beforeEach()  │
├───────────────┤
│ Test 2        │
├───────────────┤
│ afterEach()   │
└───────────────┘
Build-Up - 7 Steps
1
FoundationBasic Cypress Test Structure
🤔
Concept: Learn how Cypress organizes tests using describe and it blocks.
In Cypress, tests are grouped inside describe blocks. Each test is written inside an it block. For example: describe('My Test Suite', () => { it('does something', () => { // test code here }) })
Result
You get a simple test suite with one test that Cypress can run.
Understanding the basic test structure is essential before adding hooks that run code around these tests.
2
FoundationWhat Are Hooks in Testing?
🤔
Concept: Hooks are special functions that run before or after tests to prepare or clean up the test environment.
Hooks like beforeEach and afterEach let you run code automatically before or after each test. This avoids repeating setup or cleanup code inside every test.
Result
You know hooks help keep tests clean and DRY (Don't Repeat Yourself).
Knowing hooks exist prepares you to write better organized and more reliable tests.
3
IntermediateUsing beforeEach for Setup
🤔Before reading on: do you think beforeEach runs once before all tests or before every single test? Commit to your answer.
Concept: beforeEach runs its code before every test in the current describe block.
Example: describe('Login Tests', () => { beforeEach(() => { cy.visit('/login') }) it('shows login form', () => { cy.get('form').should('be.visible') }) it('accepts user input', () => { cy.get('input[name=username]').type('user') }) }) Here, cy.visit('/login') runs before each test, so both tests start on the login page.
Result
Each test starts fresh on the login page without repeating cy.visit in every it block.
Understanding that beforeEach runs before every test helps prevent tests from interfering with each other.
4
IntermediateUsing afterEach for Cleanup
🤔Before reading on: do you think afterEach runs before or after each test? Commit to your answer.
Concept: afterEach runs its code after every test to clean up or reset things.
Example: describe('Cart Tests', () => { afterEach(() => { cy.clearCookies() }) it('adds item to cart', () => { cy.get('button.add').click() cy.get('.cart-count').should('contain', '1') }) it('removes item from cart', () => { cy.get('button.remove').click() cy.get('.cart-count').should('contain', '0') }) }) Here, cy.clearCookies() runs after each test to clear cookies and avoid leftover data.
Result
Tests do not affect each other by leftover cookies or data.
Knowing afterEach cleans up prevents flaky tests caused by leftover state.
5
IntermediateScope of Hooks in Nested Describes
🤔Before reading on: do you think hooks in outer describe blocks run for inner describe tests? Commit to your answer.
Concept: Hooks run in all parent describe blocks for tests inside nested describes.
Example: describe('Outer Suite', () => { beforeEach(() => { cy.log('Outer beforeEach') }) describe('Inner Suite', () => { beforeEach(() => { cy.log('Inner beforeEach') }) it('runs test', () => { cy.log('Test running') }) }) }) Output order: Outer beforeEach Inner beforeEach Test running Both hooks run before the test.
Result
Hooks from all parent describes run in order before each test.
Understanding hook scope helps organize setup and teardown at different levels.
6
AdvancedAvoiding Common Hook Pitfalls
🤔Before reading on: do you think asynchronous code in beforeEach needs special handling? Commit to your answer.
Concept: Hooks must handle asynchronous commands properly to avoid timing issues.
In Cypress, commands like cy.visit are asynchronous but Cypress manages them automatically. However, if you use custom async code, you must return a promise or use Cypress commands correctly. Wrong example: beforeEach(() => { setTimeout(() => { cy.visit('/home') }, 1000) }) Right example: beforeEach(() => { cy.visit('/home') }) Cypress waits for cy.visit to finish before running tests.
Result
Tests run only after setup completes, avoiding flaky failures.
Knowing Cypress command queueing prevents timing bugs in hooks.
7
ExpertAdvanced Hook Usage and Test Isolation
🤔Before reading on: can afterEach restore application state fully? Commit to your answer.
Concept: afterEach can clean up but some state may require full page reload or database reset for true isolation.
In complex apps, afterEach might clear cookies or local storage, but some data lives on the server or in memory. Experts use beforeEach to reset app state by visiting a clean URL or calling API endpoints to reset data. Example: beforeEach(() => { cy.request('POST', '/reset') cy.visit('/home') }) afterEach(() => { cy.clearCookies() cy.clearLocalStorage() }) This ensures tests do not leak state and remain independent.
Result
Tests are fully isolated, reducing flaky failures and making debugging easier.
Understanding the limits of afterEach cleanup leads to better test isolation strategies.
Under the Hood
Cypress runs tests inside a JavaScript environment where beforeEach and afterEach hooks register functions to run before and after each it block. Cypress queues commands and manages asynchronous execution so hooks complete before tests start or after tests finish. This queueing ensures tests run in a predictable order with setup and teardown tightly controlled.
Why designed this way?
The hooks were designed to reduce repetitive code and improve test reliability by isolating tests. Cypress's command queue and automatic waiting simplify asynchronous handling, avoiding callback hell and timing bugs common in browser testing. Alternatives like manual setup in each test were error-prone and tedious.
┌───────────────┐
│ Test Runner   │
├───────────────┤
│ beforeEach()  │
├───────────────┤
│ Test (it)     │
├───────────────┤
│ afterEach()   │
├───────────────┤
│ beforeEach()  │
├───────────────┤
│ Test (it)     │
├───────────────┤
│ afterEach()   │
└───────────────┘

Cypress queues commands inside each hook and test, running them in order.
Myth Busters - 4 Common Misconceptions
Quick: Does beforeEach run once before all tests or before every test? Commit to your answer.
Common Belief:beforeEach runs only once before all tests in a describe block.
Tap to reveal reality
Reality:beforeEach runs before every single test, not just once.
Why it matters:If you think it runs once, you might put setup code that doesn't reset state properly, causing tests to interfere and fail unpredictably.
Quick: Does afterEach run before or after each test? Commit to your answer.
Common Belief:afterEach runs before each test to prepare the environment.
Tap to reveal reality
Reality:afterEach runs after each test to clean up or reset state.
Why it matters:Misunderstanding this can cause cleanup code to run too early, leaving tests with leftover data and causing flaky failures.
Quick: Can afterEach alone guarantee full test isolation? Commit to your answer.
Common Belief:afterEach always fully cleans up all test data and state.
Tap to reveal reality
Reality:afterEach cleans client-side state but may not reset server or database state; full isolation often requires setup in beforeEach or external resets.
Why it matters:Relying only on afterEach can cause hidden test dependencies and flaky tests that are hard to debug.
Quick: Does Cypress require manual waiting for asynchronous commands in hooks? Commit to your answer.
Common Belief:You must manually handle async code with callbacks or promises in beforeEach and afterEach.
Tap to reveal reality
Reality:Cypress automatically queues and waits for its commands in hooks, so manual async handling is usually unnecessary.
Why it matters:Trying to manually handle async can cause timing bugs or duplicate waits, complicating tests unnecessarily.
Expert Zone
1
Hooks run in the order of nesting: outer beforeEach runs before inner beforeEach, and inner afterEach runs before outer afterEach, which can affect setup and teardown sequencing.
2
Using hooks for heavy setup can slow tests; experts balance between global setup in before and per-test setup in beforeEach for speed and isolation.
3
Cypress commands inside hooks are asynchronous but managed by Cypress's command queue, so mixing native async code without Cypress commands can cause unexpected behavior.
When NOT to use
Avoid using beforeEach and afterEach for very heavy or slow setup tasks that don't need to run before every test; use before and after hooks instead. For tests requiring complex state resets, consider API calls or database resets outside of hooks. Also, avoid hooks when tests are completely independent and setup is trivial.
Production Patterns
In real projects, beforeEach often visits a clean URL or resets app state via API calls. afterEach clears cookies and local storage to prevent leaks. Nested describes use hooks to share setup for related tests. Some teams use custom commands inside hooks for reusable setup steps. CI pipelines rely on hooks to ensure tests run reliably on fresh environments.
Connections
Unit Testing Setup and Teardown
beforeEach and afterEach in Cypress are similar to setup and teardown methods in unit testing frameworks like JUnit or NUnit.
Understanding hooks in Cypress helps grasp how automated tests prepare and clean environments across many testing tools.
Database Transaction Rollbacks
Hooks in testing resemble database transactions that start before a test and rollback after to keep data clean.
Knowing this connection clarifies why test isolation is critical and how hooks help maintain a consistent state.
Cooking Preparation and Cleanup
beforeEach and afterEach are like prepping ingredients before cooking and cleaning dishes after, ensuring each meal is fresh and the kitchen stays clean.
This cross-domain view highlights the universal need for preparation and cleanup in any repeated process.
Common Pitfalls
#1Writing setup code inside each test instead of using beforeEach.
Wrong approach:it('test 1', () => { cy.visit('/page') // test steps }) it('test 2', () => { cy.visit('/page') // test steps })
Correct approach:beforeEach(() => { cy.visit('/page') }) it('test 1', () => { // test steps }) it('test 2', () => { // test steps })
Root cause:Not knowing beforeEach exists or misunderstanding its purpose leads to repetitive and error-prone code.
#2Using asynchronous native code incorrectly inside hooks causing tests to run before setup completes.
Wrong approach:beforeEach(() => { setTimeout(() => { cy.visit('/home') }, 1000) })
Correct approach:beforeEach(() => { cy.visit('/home') })
Root cause:Misunderstanding Cypress's automatic command queue and mixing native async code breaks test timing.
#3Assuming afterEach cleans all state including server-side data.
Wrong approach:afterEach(() => { cy.clearCookies() cy.clearLocalStorage() // no server reset })
Correct approach:beforeEach(() => { cy.request('POST', '/reset') cy.visit('/home') }) afterEach(() => { cy.clearCookies() cy.clearLocalStorage() })
Root cause:Not realizing client-side cleanup is not enough for full test isolation.
Key Takeaways
beforeEach and afterEach hooks run code before and after every test to automate setup and cleanup.
Using these hooks prevents repeating code and keeps tests independent and reliable.
Hooks run in nested describe blocks in order, allowing layered setup and teardown.
Cypress manages asynchronous commands in hooks automatically, avoiding timing issues.
Full test isolation may require combining hooks with API calls or database resets beyond client-side cleanup.