Resource library

QA How-To

How to Build a Cypress framework from scratch (2026)

Learn how to build a Cypress framework from scratch with runnable examples, sound architecture, test data, parallel execution, reporting, CI, and interview guid

20 min read | 3,010 words

TL;DR

Build a thin vertical slice first, then add typed reuse, isolated data, parallel safety, and CI evidence. Keep business expectations visible and tool mechanics behind small adapters.

Key Takeaways

  • Start with product risks and one representative workflow.
  • Separate test intent, orchestration, data, and tool adapters.
  • Validate configuration and test data before expensive setup.
  • Give every test isolated runtime and application state.
  • Use observable conditions and deterministic cleanup.
  • Report failures with sanitized, reproducible evidence.

To build a Cypress framework from scratch, create a TypeScript project around Cypress configuration, focused specs, reusable domain helpers, deterministic data setup, and CI evidence. The framework should use Cypress retryability and network control instead of sleeps or Selenium-style abstractions.

This guide builds that foundation progressively and explains where teams commonly overengineer Cypress. The result is a runnable layout for UI and API checks that remains understandable as contributors and environments grow.

TL;DR

Layer Put here Keep out
Spec User behavior and assertions Generic plumbing
Support Hooks and focused commands Entire business workflows
API helper Data setup and cleanup UI assertions
Fixture Static readable examples Shared mutable state
Config URLs, retries, tasks Secrets committed to Git

The shortest reliable path is to build one representative flow, enforce isolation, and add reuse only after repetition is visible.

1. Decide What the Cypress Framework Must Prove

Begin with risks and feedback needs, not folders. Identify a small smoke path, critical API checks, supported browsers, and ownership. Cypress is strongest when a test runs inside its command queue and observes the browser plus network. It is not a general-purpose replacement for every performance, mobile, or multi-window test. Document boundaries so framework growth stays intentional.

Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.

A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.

2. Select a Practical Cypress Project Structure

Keep end-to-end specs by product capability, support code small, fixtures explicit, and Node tasks limited to operations that cannot run in the browser. Organizing by feature makes ownership clearer than a page-by-page folder tree. Co-locate helper types when their scope is narrow. Avoid a universal utils directory that becomes a dumping ground and obscures which tests depend on a helper.

Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.

A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.

3. Install Cypress and TypeScript Correctly

Install Cypress locally, commit the lockfile, and invoke it through npm scripts. npx cypress open scaffolds the initial configuration interactively, while npx cypress run is appropriate for CI. Use defineConfig for typed configuration. Keep base URLs environment-specific and provide secrets through protected CI variables or CYPRESS_* environment variables, never source control.

Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.

A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.

4. Build a Cypress Framework from Scratch Configuration

Treat cypress.config.ts as a readable policy file. Configure patterns, viewport only when relevant, retry behavior, and Node event handlers. Do not copy every available option. Cypress merges configuration from files, environment variables, and CLI flags, so print safe effective settings in CI. Separate E2E and component testing concerns if both modes exist.

Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.

A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.

5. Write Specs That Use the Cypress Command Queue

Cypress commands are queued and yield subjects, they do not behave like promises returned from ordinary async functions. Chain commands or use aliases and closures. Do not add async to tests or attempt to await cy.get(). Put assertions near the behavior they prove. Cypress automatically retries queries and linked assertions, so fixed delays usually make tests slower and less reliable.

Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.

A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.

6. Design Selectors and Reusable Commands

Prefer accessible queries where available and stable data-cy hooks for controls whose text changes. Add a custom command only when it represents a frequent, coherent action. Type command declarations in the Cypress namespace. Do not overwrite basic commands merely to hide poor selectors. Application actions are often clearer as plain functions that return a Cypress chain than as a huge global command vocabulary.

Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.

A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.

7. Control Network and Test Data

Use cy.intercept() to observe a request, simulate a precise boundary, or make an assertion deterministic. Do not stub every endpoint in an end-to-end suite, because that can prove a fictional integration. Use cy.request() for fast setup and cleanup. Generate unique records and clean them through supported APIs. Fixtures work best for static payload examples, not mutable cross-test storage.

Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.

A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.

8. Handle Authentication Efficiently

Use cy.session() to cache a validated login procedure across tests when isolation requirements allow it. Validate the session with a cookie or API request, and keep authorization checks in separate tests. Never expose passwords in the command log. Preserve test isolation, and do not make later tests depend on state left by an earlier test even if local execution seems ordered.

Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.

A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.

9. Run Cypress in CI and Diagnose Failures

Start the application, wait for a real health endpoint, then run Cypress headlessly. Keep screenshots for failures and configure video according to the organization's storage policy. Split specs across workers only when data isolation supports it. Record browser and application versions. The ${commonLinks.cypress} helps teams practice explaining retryability, aliases, and intercept behavior.

Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.

A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.

10. Maintain Speed and Reliability as the Suite Grows

Measure spec duration and flaky retry rate. Move repeated UI setup to APIs without bypassing the behavior being tested. Delete obsolete coverage and quarantine only with an owner and expiry date. Review selectors during product changes. Keep smoke checks few and high-value, schedule broader regression separately, and make failure messages describe the business consequence rather than only the missing DOM element.

Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.

A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.

11. How to build a Cypress framework from scratch at Scale

Scaling starts with operational constraints rather than more abstraction. Establish a review checklist for new scenarios, define who owns execution and source data, and publish a compatibility policy. New contributors should be able to run one test locally, inspect resolved configuration, and reproduce a CI failure from its recorded identity. Keep fast validation separate from browser execution so malformed inputs fail in seconds.

Create a change test for every extension point. When a command, helper, data field, selector contract, or reporting hook changes, prove compatible behavior and the intended new behavior. Deprecate old capabilities with a removal date instead of maintaining aliases forever. Sample production risks without copying sensitive production records. Capacity-test representative shards in a controlled environment, then set concurrency from evidence rather than available CPU alone.

Documentation should include one happy path, one rejected input, one application failure, and one cleanup failure. Those examples teach semantics more effectively than a directory diagram. Useful related reading includes Cypress API testing guide, Cypress intercept examples, and end-to-end testing strategy. Together, these guides connect implementation choices to selector stability, data ownership, and delivery feedback.

Finally, treat maintenance as planned engineering. Review durations, recurring failure signatures, retry outcomes, and artifact usefulness. Remove tests whose risk has disappeared, consolidate duplicated intent, and refactor only where measurements or repeated changes justify it. A scalable framework is one whose feedback remains trustworthy as the product changes, not merely one that can enqueue more cases.

12. Release Checklist to build a Cypress framework from scratch

Before calling the first version complete, confirm that a clean checkout can install dependencies and execute a documented smoke command. Verify that configuration errors fail before expensive setup, every scenario can run alone, teardown runs after setup or assertion failures, and artifacts use collision-free names. Run at least two cases concurrently and check that accounts, records, variables, browser state, and files do not leak between them.

Review the negative paths with a maintainer who did not write the implementation. The report should identify the business case, source data, failed operation, environment, and application build without exposing credentials. Confirm that code examples, schemas, keyword or helper documentation, and CI commands match the committed implementation. Assign owners for infrastructure, test data, quarantined cases, and vocabulary changes.

Finally, record the framework boundaries. State which product risks it covers, which test types belong elsewhere, and how contributors propose a new abstraction. Establish a small baseline for duration and repeatability so later changes can be compared honestly. This checklist does not guarantee a perfect architecture, but it proves the framework is installable, diagnosable, isolated, and ready to evolve through evidence.

Runnable setup

npm init -y
npm install -D cypress typescript
npx cypress open

Core example

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

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    specPattern: 'cypress/e2e/**/*.cy.ts',
    supportFile: 'cypress/support/e2e.ts',
    setupNodeEvents(on, config) { return config; }
  },
  retries: { runMode: 1, openMode: 0 },
  video: false
});

// cypress/e2e/login.cy.ts
describe('login', () => {
  beforeEach(() => {
    cy.intercept('POST', '**/api/login').as('login');
    cy.visit('/login');
  });

  it('opens the dashboard for a valid user', () => {
    cy.get('[data-cy=email]').type(Cypress.env('userEmail'));
    cy.get('[data-cy=password]').type(Cypress.env('userPassword'), { log: false });
    cy.contains('button', 'Sign in').click();
    cy.wait('@login').its('response.statusCode').should('eq', 200);
    cy.location('pathname').should('eq', '/dashboard');
  });
});

Example specification or dataset

describe('health API', () => {
  it('returns ready status', () => {
    cy.request('/api/health').its('body.status').should('eq', 'ready');
  });
});

Interview Questions and Answers

Q: What problem does this framework solve?

It creates repeatable, isolated tests while separating business intent from tool mechanics. For build a Cypress framework from scratch, I would first identify the contributors, risks, and feedback time we need. The architecture is justified only if it improves diagnosis and change cost.

Q: How do you prevent flaky tests?

I remove shared state, create deterministic data, use condition-based synchronization, and keep every test independently runnable. Retries are visible diagnostics, not a blanket fix. I classify recurring failures and give each flaky test an owner and removal deadline.

Q: How do you support parallel execution?

Each invocation owns its runtime context, application records, credentials, and artifact paths. I avoid static mutable state and order dependencies, then cap concurrency to the environment capacity. I verify the design with shuffled and repeated runs.

Q: Where should assertions live?

Assertions should remain close to the behavior that owns the expectation. Tool adapters return observations or expose stable query operations, while the scenario layer states the business result. This produces failure messages that explain impact instead of only implementation detail.

Q: How do you manage test data?

I model a minimal named dataset, provision records through supported APIs, and clean up deterministically. Secrets stay in an injected secret store and logs are sanitized. Generated cases retain a seed or exact failing example for reproduction.

Q: What belongs in CI reporting?

The report needs the scenario and case identity, commit, environment, tool version, attempt, duration, failure category, and relevant evidence. Artifacts should be uploaded even when tests fail. A developer must be able to reproduce the failure without guessing hidden inputs.

Q: How would you review framework quality?

I track runtime, retry rate, failure categories, maintenance changes, unused abstractions, and time to diagnose. I also review whether the suite covers current product risks. A large pass count is not evidence of useful coverage.

Common Mistakes

  • Building abstractions before proving two or three real workflows.
  • Sharing mutable drivers, accounts, records, or output files across tests.
  • Hiding business assertions inside generic technical helpers.
  • Using fixed sleeps instead of observable readiness conditions.
  • Logging credentials or sensitive test data in reports.
  • Adding retries without classifying and owning the original failure.
  • Measuring success by test count rather than risk coverage and feedback quality.

Conclusion

To build a Cypress framework from scratch, begin with a narrow, real workflow and an explicit separation between intent, orchestration, data, and tool adapters. Make isolation and failure evidence part of the first implementation, not a later CI enhancement.

Run the example locally, add one high-risk negative case, and review the resulting failure with the people who will maintain it. If they can locate the cause quickly and change the behavior without editing unrelated layers, the framework has a sound foundation.

Interview Questions and Answers

What problem does this framework solve?

It creates repeatable, isolated tests while separating business intent from tool mechanics. For build a Cypress framework from scratch, I would first identify the contributors, risks, and feedback time we need. The architecture is justified only if it improves diagnosis and change cost.

How do you prevent flaky tests?

I remove shared state, create deterministic data, use condition-based synchronization, and keep every test independently runnable. Retries are visible diagnostics, not a blanket fix. I classify recurring failures and give each flaky test an owner and removal deadline.

How do you support parallel execution?

Each invocation owns its runtime context, application records, credentials, and artifact paths. I avoid static mutable state and order dependencies, then cap concurrency to the environment capacity. I verify the design with shuffled and repeated runs.

Where should assertions live?

Assertions should remain close to the behavior that owns the expectation. Tool adapters return observations or expose stable query operations, while the scenario layer states the business result. This produces failure messages that explain impact instead of only implementation detail.

How do you manage test data?

I model a minimal named dataset, provision records through supported APIs, and clean up deterministically. Secrets stay in an injected secret store and logs are sanitized. Generated cases retain a seed or exact failing example for reproduction.

What belongs in CI reporting?

The report needs the scenario and case identity, commit, environment, tool version, attempt, duration, failure category, and relevant evidence. Artifacts should be uploaded even when tests fail. A developer must be able to reproduce the failure without guessing hidden inputs.

How would you review framework quality?

I track runtime, retry rate, failure categories, maintenance changes, unused abstractions, and time to diagnose. I also review whether the suite covers current product risks. A large pass count is not evidence of useful coverage.

Frequently Asked Questions

What should I automate first?

Automate one stable, high-risk workflow with clear expected outcomes. It should exercise the architecture without requiring every planned abstraction.

Should the framework use fixed waits?

No. Synchronize on an observable UI, API, event, or state condition. Fixed waits slow successful runs and still fail when the system takes longer.

How should secrets be handled?

Inject secrets from a CI secret manager or environment variables. Validate their presence early and redact them from command logs, screenshots where possible, and reports.

Can these tests run in parallel?

Yes, after browser state, accounts, records, files, variables, and artifacts are isolated per invocation. Concurrency should also respect the capacity of the test environment.

How much reuse is appropriate?

Extract reuse after repeated intent is visible. Prefer small domain-focused composition and avoid generic helpers that hide control flow or assertions.

What evidence should a failed test capture?

Capture the named case, failed operation, environment, application version, sanitized inputs, and the most relevant screenshot, trace, log, or request identifier.

How do I keep the suite maintainable?

Review flaky failures, runtime, obsolete coverage, unused abstractions, and changing product risks on a schedule. Give framework layers and quarantined tests explicit owners.

Related Guides