Cypress Into The Testing Verse

Mon Oct 09 2023

5 min read

Cypress Into The Testing Verse

In this story, I'll share the insights and notes I've gathered while studying Cypress.

What is Cypress?

  • NPM package: Cypress is an NPM package, just like any other dependency you'd add to your web project.
  • Agnostic: It's framework-agnostic, which means you can seamlessly integrate it into any web project, regardless of the underlying framework.
  • End to End (E2E) Testing: Cypress is primarily used for E2E testing. It doesn't require an in-depth understanding of your project's structure or codebase. Instead, it focuses on the end-user experience, interactions, and journeys within your application. caring about is the end result, interaction, journey, user experience with the app.
  • Component Testing: Could also be used to test components from all the popular frameworks (more about this in another verse)

Installation

> node --version # should be something > 16.x
> npm install cypress

Running Cypress

Cypress could be run in two modes, GUI or in terminal.

Exploring Cypress in GUI Mode

  • Test Case List: Lists all test cases and the individual commands executed step by step.
  • Selector Playground Suggests selector query strings when you hover over elements, simplifying element selection.
  • Snapshot Take a snapshot of the whole page before/after each command.
  • Auto watch the file changes and re-runs the tests.
  • Time Travel Allows you to navigate through the state and mutations of your app during the Cypress test, showing before and after states.
  • Logging Double-clicking on a command displays details about the DOM elements affected it and their content, aiding in debugging.
> npx cypress open

Running Cypress in Terminal Mode

You can also run Cypress from the terminal, which is useful for automating tests e.g. while deployment, recording videos of the whole test, and capturing screenshots of failed tests:

> npx cypress run

Visit web pages

cy.visit('localhost:3000'); // local project testing
cy.visit('https://example.cypress.io/todo'); // remote webpage testing

Selectors

  • Cypress provides a collection of methods for targeting elements on your page.
  • get() method accept any valid CSS selector.
// get all list `<li>` items in the page.
cy.get('li');

// get the first item on the list
cy.get('li').first();
cy.get('li:first'); // equivalent CSS selector

// get the last item on the list
cy.get('li').last();
cy.get('li:last'); // equivalent CSS selector

Selecting an Element by Index

// Note that indexing starts at 0.
cy.get('li').eq(3);

// Alternatively, you can use the `nth-child` CSS selector.
cy.get('li:nth-child(4)');

It's recommended to add data attributes specifically for testing purposes to select DOM elements reliably, especially when CSS may change over time or for frameworks like tailwind or bootstrap where named classes may not be available.

<input class="new-todo" data-test="new-todo" />
cy.get('input[data-test="new-todo"]');

Selecting an Element by Text

// Select a button by its text.
cy.contains('Submit').click();

// Select a list item by its text instead of index.
cy.contains('li', 'Item 3').click();

get() vs find()

  • get() select all matching elements in the document.
  • find() searches and select all matching element in children of the selected DOM element only.
// get the checkbox inside a todo item.
cy.get('li.todo').first().find('.toggle');

// get all the checkbox elements in the document.
cy.get('.toggle');

Traversing the DOM

You can traverse the DOM in various directions from the currently selected element, including navigating to the next, previous, parent, or children elements.

cy.contains('.product h2', 'Product 2')
  .parent() // Navigate to the parent div element containing the product details
  .find('button') // Find the "Add to Cart" button within the parent
  .click();

Interactions

Cypress provides a set of methods to simulates user actions on the DOM elements.

Typing Text inside an Input Field

cy.get('input[data-test="new-todo"]')
  .click()
  .type('Go to gym!') // text to type in the input field
  .type('{enter}'); //  press the enter key

Clicking a Button

cg.contains('submit').click();

Toggling a Checkbox

cy.get('li.todo').first().find('.toggle').first().check();

Using click() vs check() for checkbox.

// `click` will toggle the state of the checkbox,
// making it unstable for testing checkboxes if you don't know their current state.
cy.get('[data-test="item-checkbox"]').click();

// Set checked to true
cy.get('[data-test="item-checkbox"]').check();

// Set checked to false.
cy.get('[data-test="item-checkbox"]').uncheck();

Handling display none and disabled Elements

Cypress uses JavaScript to execute commands like click. However, if the element you're trying to interact with has display: none or is disabled, Cypress will detect this and fail the test. To bypass this, you can use the force option:

cy.click({ force: true });

Assertions

Assertions are where you ensure that your application functions correctly as expected.

Checking Element Visibility

cy.get('li').last().should('exist');
cy.get('li').last().should('be.visible');

Validating Input Field Values

cy.get('.new-todo').should('have.value', 'todo item #2');

// Alternatively, you can use 'contains' to check for text content within elements

cy.contains('.new-todo', 'todo item #2');
cy.get('.new-todo').contains('todo item #2');

// To check if an input field is empty
cy.get('.new-todo').should('have.value', '');

Verifying New List Items

// ... code to add new item here ...
cy.get('li.todo').should('have.length', 2);

// Alternatively, you can use 'should' to check if a specific item exists
cy.contain('item #2').should('exist');

Check that the first element is checked

cy.get('li.todo').first().should('have.class', 'completed');

// or
cy.get('li.todo').first().find('.toggle').first().should('be.checked');

Check that all the other elements are not checked

cy.get('li.todo').not(':first-child').should('not.have.class', 'completed');

Timeouts and Retry

  • should() method will continue retrying the assertion and try to ensure it passes before jumping to the next command, until timeout, then it will fail.
  • For Example, we do an action on the page, and we expect to see a success message displayed.
<div class="success-message" style="display: none;">
  Your message has been sent successfully.
</div>

should() retry check for this element visibility until timeout.

cy.get('.success-message').should('be.visible');

We can also change the default timeout for specific command.

cy.get('.success-message', { timeout: 10000 }).should('be.visible');