DEV Community

Stephen McCullough
Stephen McCullough

Posted on • Originally published at swm.cc

Cypress Component Isolation Issues

Working on a personal project, I hit a frustrating limitation with Cypress: component state bleeding between tests.

The Problem

Cypress mounts components in the same browser context across tests. Even with beforeEach cleanup, some state persists:

  • Event listeners accumulate
  • CSS-in-JS styles duplicate
  • Global window objects leak between tests
  • Memory usage grows linearly with test count

Example that failed intermittently:

describe('User form', () => {
  beforeEach(() => {
    cy.mount(<UserForm />)
  })

  it('validates email', () => {
    cy.get('[data-testid="email"]').type('invalid')
    cy.get('[data-testid="error"]').should('contain', 'Invalid email')
  })

  it('submits successfully', () => {
    cy.get('[data-testid="email"]').type('valid@example.com')
    cy.get('[data-testid="submit"]').click()
    // Fails intermittently - previous test's error message still in DOM
  })
})
Enter fullscreen mode Exit fullscreen mode

Why This Happens

Cypress reuses the browser instance. Components unmount, but:

  • The iframe stays alive
  • JavaScript heap isn't cleared
  • Event listeners require explicit cleanup
  • Third-party libraries may not clean up properly

The Workaround

Force full remount between tests:

afterEach(() => {
  cy.window().then((win) => {
    win.location.reload()
  })
})
Enter fullscreen mode Exit fullscreen mode

This works but adds ~500ms per test. With 200+ component tests, that's 100 seconds of wasted time.

Playwright Does This Better

Playwright's component testing uses isolated browser contexts per test. Each test gets:

  • Fresh browser context
  • Clean JavaScript heap
  • No state leakage
  • Parallel execution by default

Same test in Playwright:

test('submits successfully', async ({ mount }) => {
  const component = await mount(<UserForm />)
  await component.getByTestId('email').fill('valid@example.com')
  await component.getByTestId('submit').click()
  // Always works - completely isolated from previous test
})
Enter fullscreen mode Exit fullscreen mode

No manual cleanup. No intermittent failures. No performance workaround.

Migration Considerations

Switching to Playwright would mean:

  • Rewriting 200+ Cypress tests (different API)
  • Learning new assertion patterns
  • Different debugging workflow
  • But: genuine test isolation and faster execution

The isolation model is compelling. Cypress is great for E2E, but for component testing, Playwright's architecture is superior.

Might be time to migrate.

Top comments (0)