The Cypress Test Runner is the desktop app that opens when you run npx cypress open. It's also Cypress's biggest single advantage over every framework that came before it — a real browser running your real app, with a live command log on one side and time-travel snapshots on the other. This lesson walks you through every panel, the Selector Playground, and the Time Travel feature that turns "why did this fail?" into a five-second answer.
Anatomy of the Test Runner UI
Open the runner with npx cypress open, click any spec, and you see four regions:
- Command log (left). Every Cypress command you've called this run, in order, with timing information. Hover any line to time-travel; click to pin.
- Application preview (right). Your real app, rendered in a real browser, at the size your
viewportWidth/viewportHeightconfig specified. - Top toolbar. Browser selector, viewport size dropdown, run/stop/restart buttons, and the Selector Playground crosshair icon.
- URL bar (above the app preview). The current URL Cypress is on. Useful when a test routes through several pages and you want to check where it is.
Get a feel for which panel does what before you start using them — most beginner confusion ("where did my error go?") is just not knowing which panel to look in.
The four panels of the Test Runner
| Left | Right | |
|---|---|---|
| Top half | Command log — every Cypress command in execution order, with pass/fail markers, timings, and hover-to-time-travel | Application preview — your real app rendering in a real browser, fully interactive between tests |
| Bottom half | Test list and stats — selected spec, test count, retry indicators, console links | URL bar + viewport controls — current URL, viewport size dropdown, browser switcher, Selector Playground crosshair |
Time Travel — the killer feature
This is the feature most engineers fall in love with on day one. After a spec runs, hover your mouse over any command in the left-hand log. The application preview on the right rewinds to the DOM state at that exact moment. Move down the list — you watch the page replay, command by command.
// cypress/e2e/checkout.cy.ts
describe("Checkout flow", () => {
it("completes a purchase", () => {
cy.visit("/products");
cy.get("[data-testid='product-card']").first().click();
cy.get("[data-testid='add-to-cart']").click();
cy.get("[data-testid='cart-link']").click();
cy.get("[data-testid='cart-item']").should("have.length", 1);
cy.get("[data-testid='checkout-btn']").click();
cy.get("[data-testid='confirmation']").should("contain", "Thank you");
});
});When this test runs, the command log lists each line. Hover cy.get("[data-testid='cart-link']").click() and the right panel shows the page just before the click — the products page with the cart link highlighted. Hover the next line and you see the cart page loaded. Each command becomes a frame in a step-through animation.
For commands that change the DOM (click, type, select), the runner stores before and after snapshots. A toggle appears in the bottom-right when you pin the command — flip it to compare what the page looked like before the click vs after. Failing tests become solvable by visual inspection.
Pinning a command and inspecting state
Click any command in the log instead of hovering. It pins:
- The DOM snapshot freezes on the right.
- DevTools console reveals the underlying values: matched elements, request/response objects for
cy.intercept, the resolved value of every chainable. - For element commands, the matched DOM is highlighted with a yellow box on the preview pane.
Open the browser DevTools (Cmd+Opt+I / Ctrl+Shift+I) while a command is pinned and you'll see the same yields printed there — Yielded: <input ...>, Coords: { x: 124, y: 320 }, Element: 1. This is how you debug "why did Cypress think this was the right element?" without rerunning the test.
The Selector Playground
Click the crosshair icon in the toolbar. Hover over any element on the page. Cypress generates the best selector it can find, in real time, in a tooltip and in the toolbar text field. Click an element to lock the suggestion.
Cypress's selector priority — the order it tries — is:
data-cy="..."(highest priority)data-test="..."data-testid="..."id="..."class="..."(only first class)- tag-name plus index
- text content (lowest priority)
When you add data-cy or data-testid attributes to your app, the Playground rewards you with stable selectors immediately. When you don't, it falls back to volatile selectors and you'll feel the pain on the next refactor.
Two buttons live next to the suggestion: Copy to clipboard (paste straight into your spec) and Print to console (shows the matched element list in DevTools so you can verify it really uniquely identifies the target).
// What the Playground produces for a "Buy now" button
cy.get("[data-testid='buy-now-button']");
// What it falls back to when the team forgot test IDs
cy.get(".product-card > div:nth-of-type(2) > button");The first selector survives any refactor that doesn't rename the test ID. The second breaks the next time someone adds a wrapper div. Use the Playground as a feedback signal: if it can't suggest a stable selector, your app needs data-testids.
Reading the command log
The command log isn't just a list — every entry tells you four things:
- Number — the command's index in the queue (1, 2, 3…).
- Verb — the Cypress command name (
get,click,should). - Argument — the selector or value passed.
- Status colour — green for pass, red for fail, yellow for retrying.
Click a command and the DevTools console gets a structured breakdown: yielded elements, args, options, return value, network details for intercepts. Multiple commands chain visually with indents — you can see at a glance that .click() is a child of .get(...).
For network commands (cy.intercept, cy.request, cy.wait("@alias")), the log shows the HTTP method, URL, response status, and a "View response" link. You can read the actual JSON body without leaving Cypress.
Failure artefacts: screenshots and videos
The Test Runner's interactive mode is for authoring. Headless mode (npx cypress run) is what CI uses, and it captures evidence automatically:
- Screenshots on failure — saved to
cypress/screenshots/<spec>/<test-name>.png. Captured at the exact moment the failing assertion threw. No configuration needed. - Video of every spec — saved to
cypress/videos/<spec>.mp4. The full test execution at 30fps, including the command log overlay.
Both default to on. To turn them off:
// cypress.config.ts
export default defineConfig({
e2e: {
screenshotOnRunFailure: true, // default
video: false, // disable video to save CI artifact space
},
});Most teams keep screenshots on always and switch video on only for failing specs (a setupNodeEvents recipe in the Cypress commands cheat sheet).
Walking through a real failure
Imagine the dashboard test asserts "Welcome, Alice" but the page actually shows "Welcome, " (empty name). The runner shows a red should("contain", "Welcome, Alice") line. Here's the debugging sequence:
- Hover the failing assertion. The preview shows the dashboard page — and the welcome heading visibly reads "Welcome, ". You've spotted the bug in three seconds.
- Hover the previous command
cy.get("[data-testid='welcome']"). The matched element is highlighted; the text content is empty. So the bug isn't the selector — the data is wrong. - Scroll up the log to find the API call. There's an intercept for
/api/users/me. Click it. The response panel opens in DevTools. The JSON shows{ "name": "" }. The backend returned an empty name. - The test was right to fail — there's a real bug to file. You found it without printing a single
console.log.
Compare the same investigation in Selenium: re-run, log the response, re-run, log the DOM, re-run, take a screenshot, eyeball it. Twenty minutes vs three.
⚠️ Common mistakes
- Forgetting that Time Travel only works for the last completed run. Snapshots are stored in memory. Re-run the test and they're regenerated; close the runner and they're gone. Don't reach for time travel to investigate yesterday's failure — open the saved video or screenshot from CI instead.
- Trusting the Selector Playground's first suggestion in apps without test IDs. When the app has no
data-testid, the Playground falls back to volatile selectors (CSS classes, nth-of-type). Copy-pasting these into specs ships flake into your suite. Use the Playground's signal, not its output: if the suggestion isn'tdata-testid-based, fix the app first, then come back. - Authoring tests in headless mode (
cypress run) and skipping the runner. Headless mode is for CI. The interactive runner is faster, has Time Travel, has the Selector Playground, has live reload. Engineers who author against the headless CLI burn an hour on every flake they could've solved in five minutes.
🎯 Practice task
Use the runner like a senior Cypress engineer would. 20-25 minutes.
- Open your scaffolded project from lesson 2 with
npm run cy:open. Pick the multi-test login spec from lesson 3. - Time Travel drill — let the spec run. Hover every command from top to bottom. Notice how each hover updates the right panel. Pin the
cy.visit("/")line and check the URL bar. Pin the click on the login button and toggle the before/after snapshots. - Selector Playground drill — click the crosshair icon. Hover a few elements on Sauce Demo: the Username field, the password field, the Login button, a product card. Note which selectors come back. The Sauce Demo team uses
data-testattributes everywhere — every suggestion should be a stable[data-test='...']selector. Compare with what the Playground produces againsthttps://example.cypress.io/(which uses class-based selectors mostly). - Force a failure and debug it — change the assertion in your "valid login" test from
cy.url().should("include", "/inventory.html")tocy.url().should("include", "/welcome.html"). Save and re-run. The runner shows red. Use Time Travel to find where things went wrong: hover the command, see the actual URL in the URL bar, confirm your assertion was wrong (not the app). Revert. - Inspect a network call — add a
cy.intercept("POST", "**/login")line at the start of the valid-login test, before anything else. Re-run. In the command log, find the matched intercept and click it. Inspect the request method, URL, and response body in DevTools. - Stretch: disable video in
cypress.config.tsand runnpm run cy:run. Confirmcypress/videos/no longer fills with files butcypress/screenshots/still captures on failure. This is the typical CI configuration for projects that don't need full-run video evidence.
You've now used every panel of the Test Runner. From the next chapter on, Time Travel is your default debugging tool — when something fails, hover before you log. The next chapter dives into the commands themselves: get, find, contains, within, and the selector strategies that keep tests stable across years of UI churn.