Resource library

QA How-To

Cypress Modern Test Architecture Complete Guide (2026)

Use this Cypress modern test architecture complete guide to design isolated, typed, balanced, and CI-ready browser automation for reliable QA delivery.

28 min read | 3,688 words

TL;DR

A modern Cypress architecture is a set of enforceable boundaries: capability-based specs, API-driven setup, isolated identities and data, typed domain helpers, stable selectors, and risk-based CI orchestration. Start with a small vertical slice, prove it runs independently, then scale using runtime evidence.

Key Takeaways

  • Organize tests around business capabilities and observable contracts, not page classes or arbitrary test types.
  • Use API setup, independent users, and test isolation so every spec can run alone or in any order.
  • Keep selectors, domain tasks, and reusable queries explicit, typed, and close to the behavior they support.
  • Split long specs using historical duration while preserving coherent ownership and useful failure context.
  • Run fast pull-request checks first, then expand browser and environment coverage according to product risk.
  • Treat retries, screenshots, videos, and cloud orchestration as diagnostic tools rather than substitutes for deterministic tests.
  • Measure first-attempt reliability, runtime distribution, and diagnosis time instead of celebrating raw test counts.

A useful cypress modern test architecture complete guide must explain how tests create state, express user intent, run independently, and produce evidence when a release is at risk. The goal is a suite engineers can trust locally and in continuous integration.

This guide builds a runnable TypeScript foundation for a fictional account application. You will create an authenticated account-settings test, isolate its data, extract typed support code, split execution by purpose, and add CI. The examples use public Cypress APIs.

Treat the code as a vertical slice. Replace routes and response shapes with your application.s contracts, but keep the boundaries: tests describe behavior, tasks arrange state, queries locate domain UI, and CI selects coverage by risk.

TL;DR: Cypress Modern Test Architecture Complete Guide

Concern Modern default Warning sign
Organization Business capability One giant page-object tree
Setup API or task before UI action Repeated UI login and seed journeys
Isolation Unique data and independent identities Ordered tests or shared mutable users
Reuse Small typed queries and domain tasks Universal commands with hidden state
Selectors Accessible semantics or explicit test contracts CSS layout chains
CI Risk tiers plus runtime evidence Every browser and spec on every commit
Diagnosis Aliased requests and retained failure artifacts Blind retries and fixed waits

The architecture has four layers. Product tests state observable guarantees. Domain helpers arrange business state. Cypress adapters handle browser mechanics. Infrastructure selects environments, secrets, artifacts, and workers. Product intent remains visible at the top.

What You Will Build

By the end, you will have:

  • A Cypress end-to-end configuration with TypeScript, environment boundaries, and deterministic defaults.
  • Capability-based folders for account settings, typed support code, fixtures, and API tasks.
  • A test that creates its own user, authenticates without repeating the login UI, updates a display name, and verifies the network and screen result.
  • A reusable typed query that participates in Cypress retry behavior without hiding assertions.
  • A tag-free smoke script and a broader regression script based on explicit spec patterns.
  • A GitHub Actions workflow that installs locked dependencies, starts the app, waits for readiness, and preserves failure evidence.
  • A review model for deciding what belongs in Cypress and what belongs in unit, component, API, contract, or exploratory testing.

The architecture stays intentionally small. A framework proves its value when the second and twentieth feature fit naturally, not when the first feature is surrounded by abstractions.

Prerequisites

Use a maintained Node.js LTS release supported by your application and current Cypress release. The examples assume Node.js 22, npm, TypeScript, a web application available at http://localhost:4173, and test-only API endpoints in a nonproduction environment. Pin actual dependency versions through package-lock.json instead of copying a version number that will age.

Install Cypress and TypeScript locally:

npm install --save-dev cypress typescript start-server-and-test
npx cypress open

Choose E2E Testing in the Launchpad and accept the generated support files. Your application needs these illustrative endpoints:

POST /api/test/users        create an isolated test user
POST /api/test/sessions     return a browser session token
PATCH /api/account/profile  update the authenticated profile
GET /api/account/profile    return the authenticated profile

Protect test-only endpoints at the network and application layers. Never expose a seed or session endpoint in production. Put CI credentials in the CI secret store, pass only the minimum secret to Cypress, and use synthetic test data.

Verify the prerequisites with node --version, npm --version, and npx cypress verify. Start the application and confirm curl --fail http://localhost:4173 exits successfully before continuing.

Step 1: Define the Cypress Modern Test Architecture Complete Guide Boundaries

Begin with a dependency rule, not a directory diagram. A spec may call a domain task and a UI query. A domain task may call cy.request. Neither helper should decide that a test passed. Assertions stay in the spec unless a helper's named purpose is itself an assertion.

Create this structure:

cypress/
  e2e/
    smoke/
      account-profile.cy.ts
    regression/
      account-security.cy.ts
  fixtures/
    profile.json
  support/
    commands.ts
    e2e.ts
    queries.ts
    tasks/
      account.ts
cypress.config.ts

Organize e2e around capabilities such as account, billing, search, and permissions. The smoke and regression folders are execution policies. If a capability needs many specs, nest the capability under each policy or select it through your CI manifest. Do not create folders named positive, negative, or misc; they obscure ownership.

Use page objects sparingly. A page object that exposes every button and forwards every Cypress command creates another UI implementation to maintain. Prefer a small task such as createAccountUser() when setup crosses several requests, and a query such as getProfileForm() when a stable domain region improves readability. Keep one-off actions in the test.

Verification: run find cypress -maxdepth 4 -type f | sort after creating files during implementation. Reviewers should be able to predict where a failing account-profile test and its account setup live. If two folders look equally plausible, revise the ownership rule before the suite grows.

Step 2: Configure Deterministic Browser Execution

Create a minimal configuration. Make the base URL overridable for CI, keep test isolation enabled, and use retries only to collect evidence on CI. Do not put secrets directly in this file.

// cypress.config.ts
import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    baseUrl: process.env.CYPRESS_BASE_URL ?? 'http://localhost:4173',
    specPattern: 'cypress/e2e/**/*.cy.{ts,tsx}',
    supportFile: 'cypress/support/e2e.ts',
    testIsolation: true,
    setupNodeEvents(on, config) {
      on('before:browser:launch', (browser, launchOptions) => {
        if (browser.family === 'chromium' && browser.isHeadless) {
          launchOptions.args.push('--window-size=1280,720')
        }
        return launchOptions
      })
      return config
    },
  },
  viewportWidth: 1280,
  viewportHeight: 720,
  defaultCommandTimeout: 8000,
  requestTimeout: 10000,
  responseTimeout: 30000,
  retries: { runMode: 1, openMode: 0 },
  video: false,
  screenshotOnRunFailure: true,
})

Cypress already retries queries and assertions. Raising every timeout or adding sleeps weakens feedback. Set global limits to values appropriate for the environment, then use a targeted timeout only when a documented operation legitimately takes longer. A CI retry can reveal intermittent behavior, but report the first attempt as the reliability signal.

Import support modules from one entry point:

// cypress/support/e2e.ts
import './commands'
import './queries'

afterEach(() => {
  cy.clearAllCookies()
  cy.clearAllLocalStorage()
})

The explicit cleanup is defense in depth. Cypress test isolation resets the browser context before each test. It does not repair shared server records, reused inboxes, or application state held outside the page.

Verification: run npx cypress run --browser electron --spec 'cypress/e2e/**/*.cy.ts'. With an empty spec tree Cypress may report no specs, which confirms the pattern was evaluated. After Step 5, the same command must discover and run the account test.

Step 3: Build Typed, API-Driven Test Data

UI setup is slow and couples a profile test to registration and login screens. Create the user through a protected test API, return the exact identity, and generate a unique email for each test.

// cypress/support/tasks/account.ts
export type TestUser = {
  id: string
  email: string
  password: string
}

type CreateUserResponse = { id: string; email: string }
type SessionResponse = { token: string }

export function createAccountUser(): Cypress.Chainable<TestUser> {
  const suffix = `${Date.now()}-${Cypress._.random(100000, 999999)}`
  const input = {
    email: `qa+${suffix}@example.test`,
    password: `Test-${suffix}!`,
  }

  return cy
    .request<CreateUserResponse>('POST', '/api/test/users', input)
    .then(({ status, body }) => {
      expect(status).to.eq(201)
      return { id: body.id, email: body.email, password: input.password }
    })
}

export function createSession(user: TestUser): Cypress.Chainable<string> {
  return cy
    .request<SessionResponse>('POST', '/api/test/sessions', {
      email: user.email,
      password: user.password,
    })
    .then(({ status, body }) => {
      expect(status).to.eq(201)
      return body.token
    })
}

This is a domain task because it speaks in account concepts while using Cypress transport. The response types document only fields the test consumes. Runtime contract tests should separately validate the full service schema. If your backend supports a deterministic cleanup endpoint, record created IDs and clean them after the test run. A short-lived isolated environment with automated expiry is often safer than teardown that hides failure evidence.

Use a worker or run identifier when parallel jobs share an environment. Timestamp plus randomness reduces collisions, but a server-issued unique ID is stronger. Never rely on a single automation@example.com account for mutable tests.

Verification: temporarily call createAccountUser().then((user) => expect(user.id).to.be.a('string')) from a focused spec. Run it twice and confirm the emails differ and both records are valid. Remove ad hoc logging of passwords or tokens before committing.

Step 4: Authenticate Through a Typed Custom Command

A login command should establish browser state using a documented mechanism, not reproduce the login screen in every test. Keep one or two dedicated UI login tests for the actual form and redirect behavior.

// cypress/support/commands.ts
import { createSession, type TestUser } from './tasks/account'

declare global {
  namespace Cypress {
    interface Chainable {
      loginAs(user: TestUser): Chainable<void>
    }
  }
}

Cypress.Commands.add('loginAs', (user: TestUser) => {
  cy.session(
    ['account-user', user.id],
    () => {
      createSession(user).then((token) => {
        cy.setCookie('session', token, {
          httpOnly: true,
          sameSite: 'lax',
          secure: false,
        })
      })
    },
    {
      validate() {
        cy.request('/api/account/profile')
          .its('status')
          .should('eq', 200)
      },
    },
  )
})

export {}

Match cookie options to the application under test. A browser script normally cannot set an HTTP-only cookie, but Cypress automation can establish it for test setup. If your application stores a token differently, use the real nonproduction session contract. Do not invent local storage state that production never reads.

The session key contains the user ID, so Cypress will not restore one person's state for another. Validation detects expired or invalid server sessions and rebuilds them. The command makes authentication explicit in the test while hiding transport detail that adds no product meaning.

Multi-user workflows need separate identities and explicit transitions. Do not switch a global token while two browser contexts are assumed to coexist. The dedicated Cypress test isolation for multi-user workflows tutorial covers safe patterns for actor changes and server state.

Verification: create a user, call cy.loginAs(user), visit /account/profile, and assert the page shows that user's email. Repeat with a second user. If either sees the other's data, stop and fix identity partitioning before adding tests.

Step 5: Write a Vertical-Slice Product Test

Now write one test whose setup, action, network contract, and visible outcome are obvious. Register the intercept before the action, alias the response, and assert a user-relevant result.

// cypress/e2e/smoke/account-profile.cy.ts
import { createAccountUser } from '../../support/tasks/account'

describe('account profile', () => {
  it('updates the signed-in user display name', () => {
    createAccountUser().then((user) => {
      cy.loginAs(user)
      cy.visit('/account/profile')

      cy.intercept('PATCH', '/api/account/profile').as('updateProfile')

      cy.get('[data-cy=display-name]').clear().type('Morgan QA')
      cy.contains('button', 'Save profile').click()

      cy.wait('@updateProfile').then(({ request, response }) => {
        expect(request.body).to.deep.include({ displayName: 'Morgan QA' })
        expect(response?.statusCode).to.eq(200)
      })

      cy.get('[role=status]')
        .should('be.visible')
        .and('contain.text', 'Profile saved')
      cy.get('[data-cy=display-name]').should('have.value', 'Morgan QA')
    })
  })
})

The test does not assert every request header, CSS class, or intermediate loading frame. It verifies the change sent to the server and the durable UI result. A service-level test should cover authorization, validation combinations, persistence, and schema edge cases faster. A component test can cover local form rendering. Cypress owns the integrated browser behavior.

Use accessible roles, labels, and visible names when they are stable and unambiguous. An explicit data-cy attribute is appropriate for a domain control whose label may be localized or duplicated. Treat it as a maintained contract. Never use a chain such as .form > div:nth-child(2) input.

Verification: run npx cypress run --browser electron --spec cypress/e2e/smoke/account-profile.cy.ts. The command should report one passing test. Change the expected request name to prove the assertion fails for the intended reason, then restore it. A test that cannot fail meaningfully is not finished.

Step 6: Add a Retryable Typed Query Without Hiding Intent

Reusable Cypress code falls into three categories. A query locates or derives browser state and should preserve retry behavior. A command performs an action. A plain function builds data or calculates a value without needing the Cypress queue. Choose the narrowest category.

Create a custom query for a labeled settings region:

// cypress/support/queries.ts
declare global {
  namespace Cypress {
    interface Chainable<Subject = unknown> {
      getSettingsRegion(name: string): Chainable<JQuery<HTMLElement>>
    }
  }
}

Cypress.Commands.addQuery('getSettingsRegion', function (name: string) {
  return () => {
    const regions = Cypress.$('[data-cy=settings-region]')
    return regions.filter((_, element) => {
      return element.getAttribute('aria-label') === name
    })
  }
})

export {}

Use it without embedding an assertion in the query:

cy.getSettingsRegion('Profile')
  .should('be.visible')
  .within(() => {
    cy.get('[data-cy=display-name]').should('have.value', 'Morgan QA')
  })

addQuery is suitable because Cypress can reevaluate the returned function until the downstream assertion passes or times out. A normal custom command that captures the DOM once can lose that semantic. Keep queries synchronous and idempotent. Do not enqueue cy.* commands inside the query callback.

If a selector appears in only one test, leave it there. Extraction is justified by domain meaning or repeated behavior, not by the mere presence of a CSS selector. For deeper implementation rules, use the Cypress query commands TypeScript tutorial.

Verification: render the region after a short application-controlled request and confirm cy.getSettingsRegion('Profile').should('be.visible') retries successfully. Misspell the name and verify the failure points to the query and downstream assertion, not an unrelated timeout.

Step 7: Split Specs by Runtime and Ownership

A suite becomes slow unevenly. One spec may contain three expensive data journeys while ten others finish quickly. File count is therefore a poor proxy for worker balance. Preserve capability ownership, but split a long file when its historical duration dominates a worker or its failures have unrelated causes.

Use explicit scripts for local intent:

{
  "scripts": {
    "cy:open": "cypress open --e2e",
    "cy:smoke": "cypress run --e2e --spec 'cypress/e2e/smoke/**/*.cy.ts'",
    "cy:regression": "cypress run --e2e --spec 'cypress/e2e/{smoke,regression}/**/*.cy.ts'"
  }
}

Smoke coverage should protect a small set of release-critical capabilities: application availability, authentication, a core read, and a core write. Regression expands risk coverage. It should not become a dumping ground for every permutation better covered by API or unit tests.

Collect per-spec duration from the CI reporter or orchestration service over several representative runs. Split by coherent scenario groups, then compare the slowest worker and total wall time. Do not fabricate an equal number of tests per file. Historical performance, setup cost, and failure ownership matter more. The guide to splitting Cypress specs by historical duration provides the complete balancing workflow.

Verification: run both npm scripts. Smoke must discover only the smoke tree. Regression must discover both trees. Record discovered spec names in the CI log so an incorrect glob cannot silently skip coverage.

Step 8: Orchestrate the Suite in CI

Build once, start the same artifact that you intend to validate, and let a readiness check gate Cypress. This GitHub Actions workflow runs smoke coverage on pull requests and manual invocations.

name: cypress-smoke

on:
  pull_request:
  workflow_dispatch:

jobs:
  smoke:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run build
      - name: Run Cypress smoke tests
        env:
          CYPRESS_BASE_URL: http://127.0.0.1:4173
          CYPRESS_TEST_API_KEY: ${{ secrets.CYPRESS_TEST_API_KEY }}
        run: >-
          npx start-server-and-test
          'npm run preview -- --host 127.0.0.1 --port 4173'
          http://127.0.0.1:4173
          cy:smoke
      - name: Upload failure screenshots
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: cypress-screenshots-${{ github.run_id }}
          path: cypress/screenshots
          if-no-files-found: ignore
          retention-days: 7

The shell starts the application, waits for the URL, runs the named npm script, and shuts the server down. Adjust the health URL so it proves dependencies are ready, not merely that a port is open. Sanitize screenshots and request logs because CI artifacts can expose customer data or tokens.

For parallel execution, each worker needs the same build, browser, environment contract, and independent data. Native CI matrices can assign static manifests. Cypress Cloud can record, balance, and parallelize specs when configured with a project ID and protected record key. Do not put record keys in source or print them. The Cypress Cloud Smart Orchestration setup guide explains recording, parallel groups, and verification.

Verification: open a test pull request and inspect the log. Confirm the build completes, the readiness gate passes, the expected spec list appears, and the job exits nonzero after a deliberate failing assertion. Confirm a failure screenshot is retained for seven days and contains no secret. Then restore the assertion.

Step 9: Measure and Govern the Architecture

Architecture needs feedback. Capture first-attempt pass rate, retried pass rate, median and tail spec duration, worker idle time, failure category, and median diagnosis time. These measures answer whether tests are stable, balanced, and useful. Raw test count does not.

Classify failures before changing code:

  1. Product defect: the application violated the expected behavior.
  2. Test defect: selector, data, assertion, or helper was wrong.
  3. Environment defect: a dependency, deployment, browser, or network failed.
  4. Unknown: evidence was insufficient, which is itself an architecture problem.

Review quarantine weekly. A quarantined test should have an owner, reason, issue, and removal date. Quarantine is a temporary release-policy decision, not a permanent suite. Never convert repeated failures into unlimited retries.

Add review rules that keep boundaries healthy. New tests must state their user risk, create independent data, avoid fixed waits, and prove their selector contract. New helpers must identify whether they are queries, actions, tasks, or pure functions. New CI lanes must explain the risk they cover and their runtime cost.

Verification: after ten representative CI runs, create a simple report of spec duration and first-attempt outcome. Verify the slowest specs have owners and known setup costs. Sample one failure artifact and ask another engineer to diagnose it without rerunning locally. If they cannot identify the failed contract, improve aliases, assertions, or retained evidence.

Choosing the Right Test Layer

Cypress should not carry the entire quality strategy. Place a check at the lowest layer that can faithfully reproduce the risk, then keep a small number of higher-layer checks for integration confidence.

Risk Primary layer Cypress role
Pure validation combinations Unit One visible validation journey
Component rendering states Component Integrated page state where valuable
API schema and authorization Contract or API Verify browser request and user outcome
Session, routing, and browser storage Cypress E2E Primary integrated coverage
Payment provider behavior Provider sandbox and contract A few critical browser journeys
Visual layout across viewports Visual review or visual testing Functional assertions and selected screenshots
Novel usability risk Exploratory testing Reproducible regression after learning

This portfolio prevents end-to-end duplication. If thirty validation combinations can run below the browser, Cypress needs only representative flows proving wiring, accessibility, and displayed errors. If authentication depends on cookies, redirects, and origin policy, Cypress is the right place for integrated behavior.

For a broader framework build, see how to build a Cypress framework from scratch. Use that foundation selectively. More files, base classes, or wrappers do not automatically create better architecture.

The Complete Series

Use these focused tutorials when one architectural concern needs implementation depth:

Choose the article matching your bottleneck. Runtime problems need duration evidence, state leaks need isolation, repetitive lookup may need a query, and worker coordination may need orchestration.

Troubleshooting

Problem: A test passes alone but fails in the suite -> Search for shared users, mutable fixtures, server records, cached sessions with broad keys, and imported application singletons. Run the failing spec after each neighbor and verify every test creates its own prerequisites.

Problem: Cypress waits for an element that is visibly present -> Inspect whether the selector targets a detached element, hidden duplicate, iframe, or shadow boundary. Prefer an accessible or explicit domain selector and let a Cypress query retry. Do not add cy.wait(2000).

Problem: An intercept never matches -> Register it before the triggering action, inspect the actual method and URL, and avoid an overly narrow query-string assumption. Alias the route and inspect the yielded interception.

Problem: CI cannot authenticate but local tests can -> Compare base URL, secure-cookie requirements, origin, clock, secret availability, and test endpoint network access. Verify the secret exists without printing its value.

Problem: Parallel workers collide -> Include run and worker identity in generated data, remove shared mutable accounts, and allocate independent external resources. Confirm cleanup cannot delete another worker's records.

Problem: Retried tests hide instability -> Report initial and final outcomes separately. Classify every retry, retain first-failure evidence, and fix the cause rather than raising the retry count.

Where To Go Next

Start with the weakest boundary in your current suite. If tests depend on order, implement the multi-user and test-isolation workflow. If CI workers finish at different times, follow the historical-duration spec splitting tutorial. If support code breaks retryability, build a typed Cypress query command. If static distribution has reached its limit, evaluate Cypress Cloud Smart Orchestration.

Apply one change to a single capability and run it locally and in CI. Expand only after it works under independent and parallel execution.

Interview Questions and Answers

Q1: What defines modern Cypress test architecture?

It separates product intent, domain setup, browser mechanics, and CI policy. Tests create independent state, use typed and narrowly scoped helpers, and assert observable outcomes. The suite can run in any order and produces evidence that makes failures diagnosable.

Q2: Why avoid a large page-object hierarchy?

A page-object hierarchy often mirrors markup and hides simple user behavior behind forwarding methods. That increases maintenance without improving intent. I use small domain tasks and retryable queries where reuse or domain meaning justifies them, while keeping one-off actions visible in the test.

Q3: How do you make Cypress tests independent?

Each test creates its own users and records, authenticates with an identity-specific session key, and avoids relying on prior tests. Browser test isolation is enabled, and server or external state is partitioned by run and worker. I verify independence by running specs alone, reordered, repeated, and in parallel.

Q4: When should setup use the UI?

Use the UI when the setup behavior is the subject of the test, such as a login form or registration journey. For unrelated prerequisites, use a protected API or task so failures remain focused and execution stays fast. The API must create realistic state through a supported nonproduction contract.

Q5: What is the difference between a custom query and command?

A query synchronously reads browser state and can be reevaluated by Cypress for downstream retryable assertions. A command queues an action or workflow. I use addQuery only for idempotent lookup logic and avoid queuing Cypress commands inside its callback.

Q6: How do you decide what runs on a pull request?

I select a small smoke portfolio based on release-critical risk and fast feedback. Broader regression, browser variation, and expensive integrations run at appropriate later gates. I track runtime and escaped defects so the policy changes when evidence changes.

Q7: Are retries part of a reliable architecture?

A limited CI retry can capture evidence and reduce interruption from a known intermittent dependency, but it is not a fix. I report first-attempt reliability separately and classify retry outcomes. Repeated instability gets an owner and remediation, not a larger retry budget.

Q8: How do you scale Cypress across CI workers?

First make tests isolated and data collision-safe. Then collect historical spec durations and distribute coherent specs to reduce the slowest worker. A cloud orchestration service can add dynamic balancing and recording, but identical builds, protected keys, and independent resources remain required.

Best Practices and Common Mistakes

Do create unique business data, keep assertions near intent, register intercepts before actions, and validate failure artifacts. Do measure first attempts and duration distribution. Do keep one UI test for critical setup surfaces while using APIs for unrelated prerequisites.

Avoid ordered tests, shared mutable accounts, arbitrary sleeps, layout selectors, and universal helpers with hidden state. Do not assert every response field in a UI journey or use Cypress for exhaustive pure logic. Do not expose tokens in configuration, videos, screenshots, or logs.

Treat selectors as product contracts. Accessible names are excellent when language and uniqueness are stable. Explicit data-cy attributes are excellent when a domain control needs a locale-independent contract. CSS hierarchy is brittle unless layout itself is the requirement.

Keep support code simple. A helper should remove incidental detail while preserving tested behavior. If a reviewer must open five files to understand an assertion, the abstraction costs too much. Delete obsolete helpers and fixtures.

Finally, rehearse failure. Break an intercept, expire a session, and create a data collision in a safe branch. Confirm the logs and screenshots identify each cause. Green execution proves coverage only once; useful failure evidence proves the architecture every time something changes.

Conclusion

The cypress modern test architecture complete guide is ultimately a guide to boundaries. Organize by capability, arrange state through protected contracts, isolate every identity and record, preserve Cypress retry behavior, and choose CI coverage from risk and runtime evidence. These decisions make the suite easier to extend and far easier to trust.

Build one vertical slice before designing a framework for the whole product. Make the account-profile example run alone, after another test, and across parallel workers. Then use the complete series to solve the specific scaling problem you can measure.

Interview Questions and Answers

What defines a modern Cypress test architecture?

It separates product intent, domain setup, Cypress mechanics, and CI policy. Tests create independent state and use typed, narrow helpers. The design supports arbitrary order, parallel execution, and useful failure diagnosis.

Why do you prefer API-driven setup for most E2E tests?

It avoids coupling an unrelated test to setup screens and reduces runtime. The API task creates realistic state through a protected nonproduction contract. I still keep focused UI coverage for login, registration, and other important setup behavior.

How do you test a workflow with multiple users?

I create a distinct identity for every actor and key sessions by user identity. State transitions occur through explicit login changes or supported server actions, and data is partitioned by run and worker. I verify that no actor can observe another actor's private state accidentally.

What makes a Cypress custom query different from a custom command?

A custom query synchronously reads browser state and can be reevaluated during Cypress retry cycles. A custom command queues an action or workflow. Query callbacks must be idempotent and should not enqueue other Cypress commands.

How do you choose selectors in Cypress?

I prefer stable accessible semantics and visible names when they match the user contract. I use an explicit `data-cy` attribute when text is ambiguous or localized. I avoid CSS layout chains because they couple tests to incidental structure.

How do you reduce Cypress CI duration?

First I remove redundant browser coverage and replace UI setup with domain APIs. Then I measure historical spec duration, split coherent long specs, and balance workers. Parallel execution comes after isolation and collision-safe data are proven.

How do you handle flaky Cypress tests?

I classify the failure as product, test, environment, or insufficient evidence. I retain first-failure artifacts and report retry outcomes separately. Quarantine is time-bound and owned, while fixed waits and unlimited retries are rejected.

What should not be tested through Cypress E2E?

Exhaustive pure logic, schema combinations, and service authorization generally belong in unit, contract, or API tests. Cypress should cover risks that need a real browser or integrated user journey. A small amount of overlap is useful for wiring confidence, but broad duplication slows feedback.

Frequently Asked Questions

What is a modern Cypress test architecture?

It is a layered test design that separates product scenarios, domain setup, browser mechanics, and CI policy. Tests own their data, run independently, use typed narrow helpers, and generate useful failure evidence.

How should I organize Cypress test folders?

Organize primarily by business capability, such as account, billing, and search. Use smoke and regression groupings only as explicit execution policies, and avoid vague folders such as miscellaneous or positive tests.

Should Cypress tests use page objects?

Use them only when they express a stable domain surface and reduce meaningful duplication. Small domain tasks and retryable queries often preserve test intent better than a large hierarchy that mirrors every element on a page.

How do I prevent Cypress tests from sharing state?

Enable test isolation, create unique users and records per test, and key cached sessions by identity. Partition server and external resources by run and worker, then prove independence through reordered and parallel runs.

When should I use cy.request for test setup?

Use `cy.request` through a domain task when a prerequisite is not the behavior under test. Keep dedicated browser tests for important setup screens such as login, and protect all test-only API endpoints from production access.

How should I split a slow Cypress suite?

Collect historical per-spec duration and balance workers using measured runtime while preserving coherent feature ownership. Equal file or test counts do not guarantee equal work.

Should Cypress retries be enabled in CI?

A small retry allowance can preserve diagnostic evidence, but first-attempt outcomes must remain visible. Retries are not a substitute for fixing test, product, or environment instability.

What metrics show whether Cypress architecture is healthy?

Track first-attempt reliability, final outcome, median and tail spec duration, worker idle time, failure category, and diagnosis time. Raw test count and final pass rate alone hide important risks.

Related Guides