Learn Cypress - Writing Basic Cypress Tests
Episode 4 of 23

Learn Cypress - Writing Basic Cypress Tests

This episode teaches Cypress's core commands: cy.visit for navigation, cy.get for selecting elements, actions like clicks and typing, assertions with should and contains, form interactions, and debugging techniques directly in the browser.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

The configuration is done. Now it's time to learn Cypress's main language: commands and assertions. Episode 4 covers cy.visit(), cy.get(), basic actions, assertions with .should() and .contains(), form interactions, navigation, and debugging techniques.

These commands are the vocabulary you will use across the entire series. After this episode, you can write simple tests for real pages without copy-pasting from tutorials.

The Basic Flow: visit, get, and Action

Every E2E test follows the same pattern: open the page, select an element, perform an action, then assert the result. Here is a complete example:

JSBasic visit-get-action pattern
describe("Login form", () => {
  it("logs in successfully with valid credentials", () => {
    cy.visit("/login");
 
    cy.get("#email").type("user@example.com");
    cy.get("#password").type("rahasia123");
    cy.get("button[type=submit]").click();
 
    cy.url().should("include", "/dashboard");
  });
});

cy.visit("/login") opens the page using baseUrl. cy.get("#email") selects an element with a CSS selector — here by id. cy.type("...") types text into an input, and cy.click() presses the submit button.

Choosing the Right Selector

Good selectors determine test stability. Order of preference: prefer the data-cy attribute that is specifically provided for testing, then data-testid, then id, then class, then textual via contains. Example of a data-cy selector:

JSUsing data-cy
cy.get("[data-cy=email-input]").type("user@example.com");

cy.get("[data-cy=email-input]") selects the element attached to the data-cy attribute. This approach is far more stable than relying on text or DOM structure that often changes — we will discuss stable selector strategies in episode 13.

Assertions with should and contains

Assertions are the heart of a test: without assertions, a test only runs actions without proving anything.

Built-in Chai Assertions

Cypress ships with Chai assertions that you can use via .should():

JSVarious should assertions
cy.get("h1").should("be.visible");
cy.get("h1").should("have.text", "Dashboard");
cy.get("li.item").should("have.length", 5);
cy.get("input").should("be.enabled");
cy.get("button").should("contain.text", "Save");

.should("be.visible") checks visibility, .should("have.text", "Dashboard") checks the exact text, and .should("have.length", 5) checks the number of elements. Note that have.text matches the full text, while contain.text matches only part of it.

Working with cy.contains

cy.contains() selects an element based on its text — handy for buttons and headings that change dynamically:

JSUsing cy.contains
cy.contains("button", "Send").click();
cy.contains("Saved successfully").should("be.visible");

cy.contains("button", "Send") looks for a button element containing the text "Send". Use it carefully: if the text appears in many places, the test might select the wrong element.

You can move between pages with cy.visit, or follow links like a user:

JSNavigation via link
cy.get("a[href=/products]").click();
cy.url().should("include", "/products");
cy.go("back");
cy.go("forward");

cy.go("back") goes back to the previous page, cy.go("forward") goes forward again. Useful for testing the browser's back-forward flow.

Forms and Interactive Elements

JSComplete form interactions
cy.get("[data-cy=quantity]").clear().type("3");
cy.get("[data-cy=select-type]").select("premium");
cy.get("[data-cy=checkbox-terms]").check();
cy.get("[data-cy=submit]").click();

cy.clear() empties an input before typing, cy.select("premium") picks an option in a dropdown, and cy.check() checks a checkbox. These actions mimic a real user so JavaScript events in the application still fire.

Real-Time Reloading and Debugging in the Browser

.debug() and cy.pause()

When a test fails or behaves oddly, insert a debug point:

JSDebug point in the middle of a test
cy.get("[data-cy=email-input]")
  .type("user@example.com")
  .debug();
 
cy.pause();
cy.get("[data-cy=submit]").click();

.debug() pauses execution and opens the browser console with the current element snapshot. cy.pause() stops the test until you press the continue button in the Test Runner — ideal for stepping through a test command by command.

Using Browser DevTools

Cypress records every command with a DOM snapshot. Click a command in the command log to see the page state at exactly that point. Combine this with browser DevTools to inspect the network, console, and elements — this is the debugging flow that makes Cypress feel like live tooling, not a black box.

Info

Don't leave cy.pause() or .debug() in tests that run in CI — both will hang the run. Keep them only for local debugging, and remove them before committing.

Closing

Episode 4 completed the core Cypress vocabulary: cy.visit for navigation, cy.get and cy.contains for selecting elements, actions like type, click, select, and check, .should() assertions inherited from Chai, back-forward navigation, and debugging with .debug() and cy.pause().

The key takeaways:

  • Basic test pattern: visit, get, action, then assert.
  • data-cy selectors are the most stable choice.
  • .should() exposes Chai assertions; distinguish have.text from contain.text.
  • type, select, and check mimic real users and trigger events.
  • .debug() and cy.pause() for debugging; remove them before CI.

In the next episode, episode 5, we will cover automatic waiting and retries — how Cypress waits for elements without sleeps, handling timing issues, retry-ability on commands and assertions, and best practices for reliable tests. This is the key to why Cypress tests are so much less flaky.

Learn Cypress - Writing Basic Cypress Tests | Learn Cypress