0
0
Cypresstesting~15 mins

Test isolation strategies in Cypress - Deep Dive

Choose your learning style9 modes available
Overview - Test isolation strategies
What is it?
Test isolation strategies are ways to make sure each test runs independently without being affected by other tests. This means tests do not share data, state, or side effects. In Cypress, this helps tests stay reliable and easy to understand. It ensures that one test's result does not depend on another test's actions.
Why it matters
Without test isolation, tests can fail unpredictably because they depend on leftover data or changes from previous tests. This makes debugging hard and slows down development. Isolated tests give clear, trustworthy results and speed up fixing problems. They help teams deliver better software faster and with less frustration.
Where it fits
Before learning test isolation, you should understand basic Cypress test writing and how tests run. After mastering isolation, you can learn advanced test design patterns, mocking, and parallel test execution for faster pipelines.
Mental Model
Core Idea
Each test should run like a fresh start, unaffected by anything that happened before or after it.
Think of it like...
Imagine baking cookies in a kitchen: test isolation is like cleaning the kitchen completely before starting each batch so no crumbs or ingredients from the last batch mix in and spoil the new cookies.
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│   Test 1     │   │   Test 2     │   │   Test 3     │
│  (Clean state)│→  │  (Clean state)│→  │  (Clean state)│
└───────────────┘   └───────────────┘   └───────────────┘
Each test starts fresh without leftovers from others.
Build-Up - 6 Steps
1
FoundationUnderstanding test independence
🤔
Concept: Tests should not rely on each other's data or state to avoid unpredictable results.
In Cypress, each test runs in the same browser session by default, but you can write tests so they do not depend on previous tests. For example, avoid sharing variables or data between tests. Each test should set up its own data and clean up after itself.
Result
Tests run reliably and failures point directly to the problem in that test, not another.
Understanding that tests must be independent prevents hidden bugs caused by leftover data or state.
2
FoundationUsing beforeEach for setup
🤔
Concept: beforeEach hooks run before every test to prepare a clean environment.
Use beforeEach() in Cypress to reset the app state or visit a fresh page before each test. For example: beforeEach(() => { cy.clearCookies() cy.clearLocalStorage() cy.visit('/login') })
Result
Each test starts with the app in the same known state.
Using beforeEach ensures consistent starting points, which is key for isolation.
3
IntermediateCleaning up side effects after tests
🤔Before reading on: do you think cleaning up after tests is as important as setting up before tests? Commit to your answer.
Concept: Tests can leave behind changes that affect others, so cleaning up after tests is necessary.
Use afterEach() to remove or reset data created during a test. For example, deleting test users or clearing database entries. This prevents leftover data from causing false failures in later tests.
Result
Tests do not interfere with each other through shared side effects.
Knowing that cleanup is as important as setup helps maintain true isolation and test reliability.
4
IntermediateAvoiding shared state with Cypress commands
🤔Before reading on: do you think sharing variables between tests is safe if you reset the app state? Commit to your answer.
Concept: Sharing variables or state between tests breaks isolation even if the app resets.
Avoid storing data in global variables or closures that persist across tests. Instead, use Cypress commands or fixtures to load fresh data each time. For example, do not do: let token it('test1', () => { token = 'abc' }) it('test2', () => { cy.request({ auth: token }) }) This shares state and can cause flaky tests.
Result
Tests remain independent and easier to debug.
Understanding that shared variables break isolation prevents subtle test failures.
5
AdvancedUsing cy.session for faster isolation
🤔Before reading on: do you think cy.session replaces the need for beforeEach setup? Commit to your answer.
Concept: cy.session caches and restores browser session data to speed up isolated tests without repeating slow setup steps.
Cypress 9.6+ introduced cy.session() to save login or other session data once and restore it for each test. This keeps tests isolated but faster. Example: cy.session('user', () => { cy.visit('/login') cy.get('input').type('user') cy.get('button').click() }) Then tests can reuse this session safely.
Result
Tests run isolated but with improved speed by reusing session state.
Knowing how to balance isolation and speed with cy.session helps build efficient test suites.
6
ExpertHandling external dependencies for isolation
🤔Before reading on: do you think real external services should be called in isolated tests? Commit to your answer.
Concept: Isolated tests avoid calling real external services by mocking or stubbing them.
Tests that depend on APIs or databases can break isolation if they call real services. Use cy.intercept() to stub network requests with fixed responses. This keeps tests fast, reliable, and isolated from external changes. Example: cy.intercept('GET', '/api/data', { fixture: 'data.json' })
Result
Tests do not fail due to external service issues and remain isolated.
Understanding how to mock external dependencies is key to true test isolation in real-world apps.
Under the Hood
Cypress runs tests in the browser and shares the same JavaScript context by default. Without isolation, variables, cookies, local storage, and network state persist across tests. Isolation strategies reset or separate these states so each test runs in a clean environment. Hooks like beforeEach and afterEach run setup and cleanup code to manage this. cy.session caches browser session storage and cookies to restore them quickly between tests.
Why designed this way?
Cypress was designed for fast, reliable end-to-end tests in real browsers. Early versions ran all tests in one session for speed but caused flaky tests due to shared state. Isolation strategies evolved to balance speed and reliability. cy.session was introduced to keep isolation benefits while reducing slow repeated logins or setups.
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│ Test Runner   │→  │ Browser       │→  │ App Under Test│
│ (Runs tests)  │   │ (Shared state)│   │ (UI, API)     │
└───────────────┘   └───────────────┘   └───────────────┘

Isolation resets Browser state (cookies, storage) between tests
using hooks and cy.session caching.
Myth Busters - 4 Common Misconceptions
Quick: Do tests automatically run isolated in Cypress? Commit to yes or no.
Common Belief:Tests in Cypress run completely isolated by default without extra setup.
Tap to reveal reality
Reality:Cypress runs all tests in the same browser session by default, so state can leak between tests unless isolation steps are added.
Why it matters:Assuming automatic isolation leads to flaky tests and confusion when tests fail unpredictably.
Quick: Is sharing variables between tests safe if you clear cookies? Commit to yes or no.
Common Belief:Clearing cookies or local storage is enough to isolate tests, so sharing variables is fine.
Tap to reveal reality
Reality:Shared variables persist in JavaScript memory and can cause tests to depend on each other even if cookies are cleared.
Why it matters:This causes hidden dependencies and flaky tests that are hard to debug.
Quick: Does mocking external APIs reduce test isolation? Commit to yes or no.
Common Belief:Mocking external APIs makes tests less realistic and should be avoided for true isolation.
Tap to reveal reality
Reality:Mocking external APIs actually improves isolation by removing flaky dependencies on external services.
Why it matters:Not mocking leads to slow, unreliable tests that fail due to network or service issues.
Quick: Can cy.session replace all beforeEach setups? Commit to yes or no.
Common Belief:cy.session completely replaces the need for beforeEach hooks in all cases.
Tap to reveal reality
Reality:cy.session speeds up session-related setup but does not replace all setup or cleanup needed for full isolation.
Why it matters:Overusing cy.session without proper setup can cause tests to share unwanted state.
Expert Zone
1
cy.session caches only cookies and local storage, so tests must still reset other state like IndexedDB or server data manually.
2
Using cy.intercept to stub APIs requires careful matching of request details to avoid false positives or missed calls.
3
Test isolation can be balanced with test speed by selectively sharing setup with cy.session while cleaning other state fully.
When NOT to use
Test isolation strategies are less useful for exploratory or manual testing where state persistence helps debugging. For integration tests that verify real service interactions, mocking should be minimized. In those cases, use dedicated test environments instead of full isolation.
Production Patterns
Teams use beforeEach and afterEach hooks to reset app state, cy.session to speed up login flows, and cy.intercept to mock APIs. They combine these with database seeding and cleanup scripts to keep tests isolated and fast in CI pipelines.
Connections
Database Transactions
Both use rollback or reset to keep tests isolated from each other.
Understanding how database transactions rollback changes helps grasp why resetting app state between tests prevents interference.
Functional Programming
Test isolation mirrors pure functions that do not depend on or change outside state.
Knowing pure functions helps understand why tests should avoid shared state and side effects for predictability.
Clean Room Design (Engineering)
Both isolate components to prevent contamination and ensure reliable results.
Seeing test isolation like a clean room shows why strict boundaries and cleanup are essential for quality.
Common Pitfalls
#1Leaving shared variables between tests causes hidden dependencies.
Wrong approach:let userToken it('login', () => { userToken = 'abc' }) it('use token', () => { cy.request({ auth: userToken }) })
Correct approach:it('login', () => { cy.login().then(token => { cy.wrap(token).as('userToken') }) }) it('use token', function() { cy.request({ auth: this.userToken }) })
Root cause:Misunderstanding that JavaScript variables persist across tests and break isolation.
#2Not cleaning up test data after tests causes flaky failures.
Wrong approach:it('creates user', () => { cy.request('POST', '/users', {...}) }) it('checks user', () => { cy.request('GET', '/users') }) // user from previous test remains
Correct approach:afterEach(() => { cy.request('DELETE', '/users/test-user') })
Root cause:Ignoring side effects tests leave on shared resources.
#3Calling real external APIs in tests causes slow and flaky runs.
Wrong approach:it('fetches data', () => { cy.request('/api/data') }) // calls real server
Correct approach:cy.intercept('GET', '/api/data', { fixture: 'data.json' }) it('fetches data', () => { cy.request('/api/data') })
Root cause:Not isolating tests from external dependencies.
Key Takeaways
Test isolation means each test runs independently with no leftover data or state from others.
Using beforeEach and afterEach hooks in Cypress helps set up and clean up the environment for each test.
Avoid sharing variables or state between tests to prevent hidden dependencies and flaky failures.
Mocking external services with cy.intercept improves isolation and test reliability.
cy.session balances isolation and speed by caching session data but does not replace all setup or cleanup.