Cypress Interview Questions (with Sample Answers)
21 Cypress interview questions with sample answers for 2026 QA and SDET loops, plus the architecture question that separates users from understanders.
Almost every Cypress interview question is downstream of one architectural fact: Cypress runs inside the browser, in the same event loop as the application under test. Selenium and Playwright drive the browser from outside it. Cypress lives in it.
That single decision explains the features people love (automatic retry, time-travel debugging, direct access to the network layer and application state) and every limitation that frustrates them, including the tab restrictions, the origin rules, and the shape of the command queue. Interviewers who ask well are checking whether you understand the causation. Candidates who have only followed tutorials know the symptoms as a list of unrelated rules.
Below are 21 questions grouped by depth, with the answers that land. If you’re preparing for a broader loop, this is one spoke of the 2026 QA & SDET interview questions guide.
Fundamentals
What is Cypress, and how does its architecture differ from Selenium and Playwright?
Cypress is an open-source, JavaScript-based end-to-end testing framework. The architectural difference is where the test code executes.
Selenium sends commands to a browser driver over the W3C WebDriver protocol, an HTTP wire protocol, out-of-process. Playwright talks to the browser over a DevTools-style protocol, also out-of-process. Cypress bundles your test code and runs it in the browser alongside your application.
The consequences follow directly:
- Direct access. Cypress can reach the DOM, the network layer, and application state synchronously, because it is in the same process. That’s what makes
cy.interceptand time-travel debugging straightforward rather than exotic. - Shared event loop. Cypress shares the browser’s single-threaded event loop with your app, which is why it cannot natively drive multiple tabs.
- Browser sandbox rules apply. Cypress is subject to the same-origin policy the way any in-page script is, which is why cross-origin navigation needs special handling.
What a great answer includes:
- Naming in-process versus out-of-process as the root distinction rather than listing features
- Connecting at least one limitation back to that root cause
- Noting that this is a genuine trade-off, not a defect. The same property buys the debugging experience
Explain Cypress’s automatic waiting. What does it not wait for?
Cypress retries commands and assertions until they pass or the timeout expires (4 seconds by default for most assertions). You rarely write explicit waits for element readiness.
The retry model has a specific rule worth knowing: Cypress only retries the last command before an assertion. In a chain like cy.get('.list').find('.item').should('have.length', 3), Cypress retries .find(), not cy.get(). If .list itself is re-rendered, the chain can hold a stale reference. Restructuring to cy.get('.list .item').should('have.length', 3) retries the whole query.
Automatic waiting does not cover:
- Arbitrary async work that produces no DOM or network signal
- Network requests you haven’t declared: use
cy.intercept()pluscy.wait('@alias')to make a request an explicit dependency - Backend eventual consistency: Cypress cannot know your queue drained
What a great answer includes: the last-command-only retry rule, and the instinct to make waits explicit through aliases rather than raising timeouts.
Why can’t you use async/await with Cypress commands?
Cypress commands are not Promises, though they look like them. Calling cy.get() does not execute anything. It enqueues a command. Cypress runs the queue after the test function returns.
This is why const el = await cy.get('.foo') does not give you an element, and why a value assigned inside .then() isn’t available on the next line of synchronous code. The queue hasn’t run yet when that line is evaluated.
Work with the queue rather than against it: chain with .then(), alias with .as() and retrieve with cy.get('@alias'), or use cy.wrap() to bring a plain value into the chain.
What a great answer includes: the word enqueue. Candidates who say “Cypress commands are asynchronous” have half the picture; the queue is the mechanism, and it explains the failure mode.
What’s the difference between cy.get(), cy.find(), and cy.contains()?
cy.get()queries from the document root using a CSS selector..find()is chained off an existing subject and searches within it.cy.contains()matches on text content, optionally scoped by selector:cy.contains('button', 'Submit').
cy.contains() returns the first match in DOM order, which surprises people on pages with repeated text.
What a great answer includes: a preference for data-cy attributes over CSS classes or text for anything load-bearing, so that restyling and copy edits don’t break the suite.
How do fixtures work, and when do you use them?
cy.fixture() loads static data from cypress/fixtures. It’s most useful combined with cy.intercept() to serve deterministic responses:
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
cy.visit('/users')
cy.wait('@getUsers')
cy.get('[data-cy=user-row]').should('have.length', 3)
What a great answer includes: the trade-off. Fixtures make tests fast and deterministic while decoupling them from the real contract, so a fixture-heavy suite can pass while the API is broken. Mention contract tests or a thin layer of real-backend smoke tests as the counterweight.
Practical scenarios
How do you handle cross-origin navigation?
Cypress requires a single origin per test, with cy.origin() as the escape hatch. Wrap the interaction with the other domain in a callback:
cy.visit('https://app.example.com')
cy.get('[data-cy=sso-login]').click()
cy.origin('https://auth.provider.com', () => {
cy.get('#username').type('user@example.com')
cy.get('#password').type(Cypress.env('PASSWORD'))
cy.get('button[type=submit]').click()
})
cy.get('[data-cy=dashboard]').should('be.visible')
Two things to raise unprompted. The callback is executed in a separate context, so variables from the enclosing scope are not available. You pass data in via the args option instead. And cy.origin() still has rough edges around cross-origin document.cookie and some third-party identity providers, which is why many teams bypass the SSO UI entirely and authenticate programmatically instead.
What a great answer includes: the closure-scope gotcha, and a preference for programmatic login over driving someone else’s login page in the first place.
How do you handle authentication efficiently across a suite?
Logging in through the UI in a beforeEach is the most common source of slow suites. Better options, roughly in order:
cy.session(): caches and restores session state (cookies, localStorage, sessionStorage) across tests, so the login runs once and is restored thereafter.- Programmatic login:
cy.request()against the auth endpoint, then set the resulting token yourself. - Seeded test users with pre-provisioned state, so tests don’t need to build fixtures through the UI.
What a great answer includes: naming cy.session() specifically, and the reasoning that UI login should be tested once, in a test about login, rather than in every test as setup.
A test passes locally and fails in CI. How do you debug it?
A structured answer beats a list of tricks:
- Get the artifact. Cypress records video and screenshots on failure in CI by default. Look before theorizing.
- Check viewport and timing. CI runs headless at a configured viewport, often narrower and slower than your machine. Elements below the fold and animations that finish faster locally are frequent culprits.
- Look for hidden ordering dependencies. A test that only passes after another test has run is sharing state: a database row, a cached session, a stubbed route.
- Check for un-awaited network calls. If the assertion doesn’t wait on an aliased request, it races.
- Reproduce with the same container rather than guessing at environment drift.
What a great answer includes: resisting the urge to raise the timeout. Raising a timeout converts a fast failure into a slow one without fixing the race.
How do you test a file upload or download?
Uploads use cy.selectFile(), which works with real file paths, fixtures, or constructed Blob objects, and supports drag-and-drop targets via the action: 'drag-drop' option.
Downloads are the harder half, because the browser’s download behavior is outside the page. The common approach is to configure a known downloads folder, trigger the download, then assert on the file from the Node side using a task. Many teams instead assert that the request returns the right headers and status, and treat the browser’s file-writing as out of scope.
What a great answer includes: recognizing that a download test often tests the browser more than the application, and scoping accordingly.
How do you handle a flaky test suite?
Distinguish causes rather than reaching for retries:
- Race conditions: assertions that don’t wait for the thing they depend on. Fix with aliases and retrying assertions.
- Shared state: tests that pass in isolation and fail in sequence. Fix with per-test data setup or teardown.
- Genuine application nondeterminism: animations, timers, polling. Control it: stub the clock with
cy.clock(), or disable animations in the test environment. - Infrastructure: a slow CI runner or a flaky dependency.
Cypress supports test retries, which are useful as a signal, an alert that a test needs attention, and harmful as a permanent silencer.
What a great answer includes: treating retries as instrumentation rather than a fix, and the diagnostic instinct to run the failing test in isolation first.
How do you structure a growing Cypress suite?
Cypress’s documentation discourages the classic Page Object Model in favor of custom commands and app actions. The reasoning: page objects tend to accumulate a parallel abstraction of the UI that drifts from the real thing.
In practice most large suites land on a blend: custom commands in cypress/support/commands.js for repeated interactions (cy.login(), cy.seedOrder()), plus lightweight selector modules to avoid scattering data-cy strings across specs.
What a great answer includes: an opinion with a reason. Either position is defensible; “we use page objects because that’s how we’ve always done it” is not.
Advanced and senior-level
Why can’t Cypress control multiple browser tabs, and what do you do instead?
Because Cypress runs in the same event loop as the application, it has no mechanism to drive a second top-level browsing context. This is architectural, not a missing feature.
The standard approaches:
- Assert the intent, not the outcome. Verify the link has
target="_blank"and the correcthref, and test the destination page in its own test. - Remove the target attribute in the test so navigation stays in the current tab.
- Test the destination directly with
cy.visit().
What a great answer includes: naming the architectural cause, and the judgment that “a new tab opened” is usually the browser’s responsibility rather than the application’s.
How does parallelization work in Cypress, and what does it cost?
Cypress parallelizes by distributing spec files across multiple CI machines. Load balancing across those machines is coordinated by Cypress Cloud, which is a paid service above its free tier. The orchestration is not purely local.
This is a real budget question at scale, and it’s a common reason teams evaluate Playwright, which parallelizes across worker processes on a single machine without a coordinating service.
What a great answer includes: knowing that parallelization is spec-level rather than test-level, so a single enormous spec file cannot be split and becomes the long pole in the run.
When would you choose Cypress component testing over end-to-end tests?
Component testing mounts a single component in a real browser with a dev server, giving you real rendering and real CSS without booting the full application. It suits component libraries and design systems, edge-case states that are painful to reach through the full app, and fast feedback during development.
End-to-end tests remain necessary for anything crossing component boundaries: routing, authentication, real data flow, integration with the backend.
What a great answer includes: the observation that component tests are much faster and much less likely to catch integration bugs, so a suite made only of component tests will be green and uninformative.
How do you decide between Cypress and Playwright in 2026?
An honest answer names conditions rather than declaring a winner.
Cypress is a strong choice when the team is front-end-heavy and JavaScript-only, when the debugging experience matters for adoption, and when component testing is a first-class need.
Playwright is the better fit when you need multi-tab or multi-origin flows as a routine matter, cross-browser coverage including WebKit, parallelization without a paid coordinator, or language bindings beyond JavaScript.
What a great answer includes: the recognition that a large existing suite has switching costs that usually dominate the technical comparison. For a deeper treatment, see Playwright vs Cypress vs Selenium.
What are Cypress’s debugging tools?
- Time-travel. The Command Log snapshots DOM state at each step; hovering replays it.
.debug()pauses and exposes the current subject in the console.cy.pause()halts execution and lets you step forward interactively.- Browser DevTools work normally, because the test runs in the browser.
cy.screenshot()and video for CI artifacts.
What a great answer includes: naming time-travel as the feature that follows from the in-process architecture, closing the loop back to the first question.
How do you seed and clean up test data?
Ranked by reliability:
- API seeding via
cy.request()before the test — fast and independent of the UI. - Database tasks through
cy.task(), which runs in Node and can reach infrastructure the browser cannot. - UI seeding: slowest and most brittle; acceptable only when the setup path is the thing under test.
Prefer creating uniquely-named data per test over cleaning up shared data. Cleanup that runs after a failed test often doesn’t run at all.
What a great answer includes: the point about failed tests skipping cleanup, a detail that only shows up once you’ve operated a suite.
How do you manage configuration across environments?
cypress.config.js holds base configuration; environment-specific values come from cypress.env.json, CYPRESS_* environment variables, or --env flags, read in tests via Cypress.env(). Credentials belong in CI secrets, never in a committed config file.
What a great answer includes: flagging that cypress.env.json should be gitignored, and that hardcoded credentials in a spec file are a finding rather than a style preference.
What does cy.intercept() do beyond stubbing?
It observes and modifies network traffic. Beyond returning fixtures it can wait on requests via aliases, assert on request bodies and headers, modify a request in flight, delay or force a network error to test error states, and match on method, URL pattern, or a predicate.
Forcing failures is the underused half. Error and empty states are where most bugs hide, and they’re hard to reach with a healthy backend.
What a great answer includes: using cy.intercept() to create failure conditions, not only to avoid them.
How would you introduce Cypress to a team with no automated tests?
A credible answer is about sequencing, not tooling:
- Start with one high-value happy path, usually the flow that generates revenue.
- Wire it into CI immediately. A suite that isn’t blocking anything decays.
- Establish
data-cyconventions before the suite grows, because retrofitting selectors is expensive. - Add tests as bugs are fixed, so coverage tracks real failure history.
- Set an explicit flake policy from day one.
What a great answer includes: treating trust as the constraint. A suite people ignore is worse than no suite, because it costs maintenance and returns nothing.
What are Cypress’s real limitations?
A candidate who can list these honestly is more useful than one who advocates:
- No native multi-tab control
- Same-origin constraints, partially mitigated by
cy.origin() - JavaScript and TypeScript only
- Spec-level parallelization coordinated by a paid service
- No native mobile app support
- Safari support has historically lagged the other browsers
What a great answer includes: framing these as consequences of the in-process design rather than as a list of complaints, which is where this article started.
How to prepare
Build something rather than reading a question bank. Stand up a small app with a login, a list view, and a form, then write a suite against it that runs in CI. The questions above map almost one-to-one onto problems you’ll hit: the command queue will confuse you, an aliased request will fix a race, and you’ll want a second tab you can’t have.
Come prepared with one specific story about a flaky test you diagnosed: cause, fix, and what you changed to prevent recurrence. It’s the most common follow-up in a Cypress loop and the hardest to improvise.
If your loop covers more than Cypress, this is one spoke of the 2026 QA & SDET interview questions guide.
Preparing for the whole loop, not just Cypress? Cracking the QA & SDET Interview covers all six rounds, three scored mock interviews, per-question rubrics, and printable cheat sheets. Get the guide.
Related reading
- QA & SDET Interview Questions. The full loop, six rounds.
- Playwright Interview Questions. The sibling spoke.
- Selenium Interview Questions. The third framework spoke.
- Playwright vs Cypress vs Selenium. The framework decision.
- Testing in the Age of AI. Where automation is heading.
- Database Testing. The layer below your E2E suite.
- How to Write a Bug Report. What to do with what your suite finds.