Skip to content

Cypress Basics

Before writing real tests, you need a working mental model of the pieces that make up a Cypress project. This lesson covers the global cy object, command chaining, spec files, and the default folder structure.

The cy Object and Command Chains

Every Cypress command starts with the global cy object: cy.visit(), cy.get(), cy.click(), and so on. Commands are chainable, each command yields a subject that the next command in the chain can act on.

Importantly, Cypress commands are asynchronous but written to look synchronous. They are enqueued and run later, in order, by the Cypress test runner, not executed immediately when the line is read.

cy.visit('/dashboard');
cy.get('[data-cy="user-menu"]')
  .click()
  .find('[data-cy="logout"]')
  .should('be.visible');

Each line queues a command. Cypress runs them in sequence, automatically retrying .get() and .should() until they pass or time out.

Default Cypress Project Structure

cypress/
  e2e/
    login.cy.js
    checkout.cy.js
  fixtures/
    users.json
  support/
    e2e.js
    commands.js
cypress.config.js
  • cypress/e2e/ holds your spec files, named with a .cy.js (or .cy.ts) suffix.
  • cypress/fixtures/ holds static JSON (or other) test data files.
  • cypress/support/ holds reusable setup code and custom commands.
  • cypress.config.js configures the base URL, viewport, timeouts, and more.

Cypress Basics Cheat Sheet

The essential vocabulary you need before writing your first spec file.

Term Meaning
cy The global Cypress command object
Command A single action, like cy.get() or .click()
Chain Multiple commands linked together with .
Subject The value a command yields to the next command
Spec file A file containing one or more tests (*.cy.js)
Fixture A static JSON/text file used as test data
Support file Code that runs before every spec, e.g. custom commands

Commands Are Asynchronous, Not Synchronous

New Cypress users often try to store a command's result in a plain JavaScript variable, expecting it to hold a value immediately. Because Cypress commands are enqueued and resolved asynchronously, this pattern does not work the way it would with a synchronous function call.

Instead, use .then() to work with a yielded value once it is actually available, or rely on aliases with .as() and cy.get('@alias') for values you need again later in the test.

// Wrong: text is undefined here, the command hasn't run yet
const text = cy.get('[data-cy="title"]').text();

// Right: use .then() to access the resolved value
cy.get('[data-cy="title"]').then(($el) => {
  const text = $el.text();
  expect(text).to.equal('Dashboard');
});

Treat Cypress commands like a queue of instructions, not a series of return values.

Anatomy of a Spec File

A spec file is just a JavaScript (or TypeScript) file that Cypress loads and runs in the browser. It typically contains one describe() block per feature, with one or more it() blocks inside describing individual scenarios.

// cypress/e2e/login.cy.js
describe('Login page', () => {
  beforeEach(() => {
    cy.visit('/login');
  });

  it('shows an error for invalid credentials', () => {
    cy.get('[data-cy="email"]').type('bad@example.com');
    cy.get('[data-cy="password"]').type('wrongpass');
    cy.get('[data-cy="submit"]').click();
    cy.get('[data-cy="error"]').should('contain', 'Invalid credentials');
  });
});

beforeEach() runs before every it() in the same describe(), keeping setup code out of each test.

Common Mistakes

  • Trying to assign a Cypress command's return value directly to a variable instead of using .then().
  • Putting all tests into a single giant spec file instead of organizing by feature.
  • Forgetting that beforeEach() runs before every single test, not just once per file.
  • Not realizing fixtures and support files have their own dedicated folders with specific purposes.

Key Takeaways

  • Every Cypress command starts from the global cy object and can be chained.
  • Commands are asynchronous; use .then() to work with yielded values, never a plain variable assignment.
  • Spec files live in cypress/e2e/ and are organized with describe() and it().
  • Fixtures, support files, and configuration each have a dedicated location in the project.

Pro Tip

If you find yourself reaching for a plain let variable to "save" a value from a Cypress command, that is almost always a signal to use .then() or an alias (.as()) instead.