100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Testing

Cypress Architecture: Why It's Different

A deeper look at the two-process architecture behind Cypress, how the Node server and browser driver work together, and the trade-offs that result.

FoundationsIntermediate10 min readJul 10, 2026
Analogies

The Two-Process Architecture

Cypress actually runs as two cooperating processes: a Node.js server process that manages the browser, handles file system operations, proxies network traffic, and executes plugin code, and a browser process where your test code and application code both run in the same JavaScript context, driven through the Chrome DevTools Protocol (for Chromium browsers) or an equivalent driver for Firefox and WebKit. This split lets Cypress commands that need OS-level access, like reading a fixture file or writing a screenshot, hop over to the Node process via an internal WebSocket-based communication channel, while DOM-level commands like cy.get() or cy.click() execute directly inside the browser without leaving it.

🏏

Cricket analogy: This split is like a stadium's on-field umpire (browser process) handling live decisions instantly, paired with an off-field third umpire (Node process) with access to replay technology for the calls that need extra resources.

The Proxy Layer and Network Control

All traffic between your browser and your application (or any third-party APIs it calls) passes through an internal Cypress proxy server running in the Node process, which is how features like cy.intercept() can observe, delay, modify, or entirely replace network responses before the browser ever sees them. Because the proxy sits at the network layer rather than mocking at the JavaScript fetch/XHR level, Cypress can intercept requests made from Web Workers, service workers, and even requests Cypress didn't explicitly know about in advance, giving cy.intercept() broader reach than a typical JavaScript-level mocking library.

🏏

Cricket analogy: The proxy layer is like a broadcast production truck that intercepts every camera feed before it reaches viewers, able to insert graphics or delay a replay on any feed, not just the ones it planned for in advance.

javascript
// The proxy intercepts this request at the network layer,
// so cy.intercept() can stub it even though the fetch happens
// deep inside a third-party analytics script.
describe('Checkout flow', () => {
  it('completes checkout even if analytics fails', () => {
    cy.intercept('POST', '**/analytics/track', { statusCode: 500 }).as('trackCall');
    cy.intercept('POST', '/api/checkout', { fixture: 'checkout-success.json' }).as('checkout');

    cy.visit('/cart');
    cy.get('[data-cy="checkout-button"]').click();

    cy.wait('@checkout');
    cy.get('[data-cy="order-confirmation"]').should('be.visible');
  });
});

Because the proxy operates below the JavaScript layer, cy.intercept() can even stub requests fired by third-party scripts you don't control, such as analytics or ad tags, which is far harder to achieve with libraries that only monkey-patch window.fetch.

Same-Origin Constraints and cy.origin()

Historically, Cypress required your application under test to stay on a single origin for the duration of a test, because injecting the test runner's code into the page relies on same-origin access to the browser's document and window objects; navigating to a different origin (like a third-party OAuth login page) inside the same test used to throw a cross-origin error. Modern Cypress solves this with the cy.origin() command, which spins up an isolated context scoped to the new origin, runs a callback of commands inside that boundary, and passes results back to the primary test, letting a single spec legitimately test flows like Google or Auth0 login redirects that leave your app's domain.

🏏

Cricket analogy: cy.origin() is like a player being allowed to briefly join a different franchise for an exhibition match under special dispensation, operating fully within that team's rules before returning to their home side's squad.

cy.origin() runs its callback in a serialized, isolated context: you cannot directly reference outer-scope variables or aliases inside the callback (closures don't cross the boundary), so any data you need to pass in must be explicitly serialized as an argument, and any data you need back must be explicitly returned.

  • Cypress runs as two cooperating processes: a Node.js server process and a browser process sharing the app's JS context.
  • OS-level operations (files, screenshots) route through the Node process via an internal WebSocket channel.
  • All network traffic passes through an internal proxy server, which is how cy.intercept() can observe and stub requests.
  • The proxy operates below the JS layer, letting it intercept even third-party script requests you don't directly control.
  • Same-origin constraints historically blocked navigating to a different domain within one test.
  • cy.origin() solves this by running an isolated, serialized context scoped to the new origin.
  • Data crossing the cy.origin() boundary must be explicitly passed in as arguments and explicitly returned, since closures don't cross it.

Practice what you learned

Was this page helpful?

Topics covered

#Testing#CypressStudyNotes#TestingQA#CypressArchitectureWhyItSDifferent#Cypress#Architecture#Different#Two#StudyNotes#SkillVeris