Bhavik Bamania

Programming

Understanding Cypress Basics: Core Features and Syntax Explained

Bhavik Bamania

6 min read

Share On:

Copy linkShare on FacebookShare on LinkedInShare on ThreadsShare on X
Cypress Basics: Core Features & Syntax for Beginners

Cypress is one of the most popular end-to-end (E2E) testing frameworks, known for its simplicity, power, and developer-friendly features. In this article, we'll explore Cypress's core features and syntax, helping you build a strong foundation for writing reliable E2E tests. This guide is written for beginners, with simple explanations and practical examples to make Cypress accessible to everyone.

Introduction

This article is part of our Cypress Tutorial Series designed to help beginners master Cypress, starting from the basics. If you're new to Cypress, we recommend starting with the previous article, A Beginner's Guide to Cypress: End-to-End Testing Made Easy. It introduces Cypress, explains its benefits, and walks you through the setup process.

By the end of this series, you'll have the skills to confidently write, execute, and debug Cypress tests for modern web applications.


Why Cypress?

Modern web applications involve a lot of dynamic and user-driven interactions. Testing these interactions manually is tedious and error-prone. Cypress allows you to:

  • Automate user interactions, such as form submissions, dropdown selections, and navigation.
  • Validate UI elements and ensure they function as expected.
  • Test edge cases and user flows to ensure the reliability of your application.

Now let's set up the project and get started.


Setting Up the Demo Project 

Step 1: Create a Vite React Project

To follow along with this guide, you need a sample application to test. Create a new React project using Vite:

npm create vite@latest cypress-demo -- --template react-ts
cd cypress-demo
npm install
Note: The extra --before --template is required on npm 7 and above. Without it, npm swallows the flag and Vite asks you to pick a template interactively.

Step 2: Create the Login Form Component

For this tutorial, we'll test a simple login form. Create a LoginForm component:

src/components/LoginForm.tsx:

import React, { useState } from 'react';

const LoginForm: React.FC = () => {
  const [username, setUsername] = useState<string>('');
  const [password, setPassword] = useState<string>('');
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    alert(`Welcome, ${username}!`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        id="username"
        type="text"
        placeholder="Username"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <input
        id="password"
        type="password"
        placeholder="Password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">Login</button>
    </form>
  );
};

export default LoginForm;

Then render it in your app by replacing the contents of src/App.tsx:

import LoginForm from './components/LoginForm';

function App() {
  return <LoginForm />;
}

export default App;

Start the development server and keep it running in a separate terminal:

npm run dev

The app is now available at http://localhost:5173.

Step 3: Install Cypress

Install Cypress as a development dependency:

npm install cypress --save-dev

Cypress ships with its own TypeScript type definitions, so there's no separate types package to install.

Step 4: Open Cypress

Initialize Cypress and open its interactive runner:

npx cypress open

Choose E2E Testing when prompted. Cypress creates a cypress folder and a cypress.config.ts file in your project with default configurations.

Step 5: Configure Cypress

Set a baseUrl so you don't have to repeat the full address in every test. Update cypress.config.ts:

import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:5173',
  },
});

To give your test files proper TypeScript support, create cypress/tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM"],
    "types": ["cypress"]
  },
  "include": ["**/*.ts"]
}

Why a separate tsconfig? Keeping Cypress types scoped to the cypress folder prevents them from clashing with your app's types, or with other test runners like Jest that define the same globals.

Now that the project is ready, let's dive into writing test cases.

Writing Cypress Test Cases

Cypress uses a BDD-style syntax with describe and it blocks:

  • describe: Groups related test cases.
  • it: Defines individual test cases.

Let's write a basic test suite for our LoginForm. Create cypress/e2e/login.cy.ts.

Creating Test Suites and Tests

A test suite is a collection of related tests, and each test validates specific functionality. Here’s an example:

describe('LoginForm Tests', () => {
  it('should render the login form', () => {
    cy.visit('/'); // Visit the app (uses baseUrl)
    cy.get('form').should('be.visible'); // Assert the form is visible
  });

  it('should allow users to enter username and password', () => {
    cy.visit('/'); // Each test starts fresh, so visit again
    cy.get('#username').type('testuser'); // Type into username
    cy.get('#password').type('password123'); // Type into password
    cy.get('#username').should('have.value', 'testuser'); // Assert value
    cy.get('#password').should('have.value', 'password123');
  });
});

Notice that both tests call cy.visit(). Cypress starts every test with a clean, blank page, so a test can't rely on a page loaded by the test before it. We'll see a cleaner way to handle this with beforeEach later in the article.

Let's break down the code above to understand each command and its usage.

Visiting Pages

The cy.visit() command is used to load a specific URL. Because we set baseUrl, a relative path is enough:

cy.visit('/');
cy.url().should('eq', 'http://localhost:5173/');

This command ensures the target page loads successfully and is ready for testing.

Selecting Elements

Cypress provides powerful selectors to locate elements on the page. The examples in this section are general patterns you can apply to any app. Some selectors, like #submit-button, don't exist in our login form.

Using .get()

The .get() command locates elements by id, class, or other attributes:

cy.get('#submit-button').click();

Using .find()

The .find() command narrows the search scope to elements inside a specific parent element:

cy.get('.form').find('input').type('Hello!');
Tip: .type() works on a single element only. If your selector matches several elements, such as .find('input') in a form with two inputs, Cypress will throw an error. Use a more specific selector, or chain .first() or .eq(index).

Selecting by Text

Use cy.contains() to locate elements by their text content:

cy.contains('Login').click();

This command is especially useful for buttons, links, and other text-based elements.

Checking for Partial Text

cy.contains() matches partial text, which is handy for dynamic content:

cy.contains('Welcome').should('exist');

This ensures that elements containing partial text, such as greetings or messages, are present.

Assertions

Assertions validate that elements behave as expected.

Implicit Assertions

These are built into Cypress commands like .should():

cy.get('#username').should('be.visible');

Explicit Assertions

Cypress bundles the Chai assertion library, so you can use expect directly for more complex checks:

cy.get('#title').then((element) => {
  expect(element.text()).to.equal('Welcome Back!');
});

Dropdown Selections

Test dropdowns by selecting a value and asserting the result:

cy.get('select').select('Option 1');
cy.get('select').should('have.value', 'Option 1');

Cypress Test Isolation

Each Cypress test runs in isolation. Cypress resets the browser state (cookies, local storage, and the page itself) between tests.

describe('Test Isolation Example', () => {
  it('should set a cookie', () => {
    cy.setCookie('session', '12345');
    cy.getCookie('session').should('exist');
  });

  it('should not retain the cookie', () => {
    cy.getCookie('session').should('not.exist');
  });
});

Practical Example: Testing the Login Form

Combining everything, here’s a complete example:

describe('LoginForm Tests', () => {
  beforeEach(() => {
    cy.visit('http://localhost:5173'); // Replace with your app's URL
  });

  it('should render the login form', () => {
    cy.get('form').should('be.visible'); // Assert the form is visible
  });

  it('should allow users to type in the fields', () => {
    cy.get('#username').type('testuser'); // Type into username
    cy.get('#password').type('password123'); // Type into password
    cy.get('#username').should('have.value', 'testuser'); // Assert value
    cy.get('#password').should('have.value', 'password123');
  });

  it('should display an alert on submit', () => {
    cy.get('#username').type('testuser');
    cy.get('#password').type('password123');
    cy.get('button[type="submit"]').click();
    // Listen for the alert
    cy.on('window:alert', (text) => {
      expect(text).to.contains('Welcome, testuser!');
    });
  });
});

Why does the order matter? cy.on() only catches events that happen after it is registered. Setting it up before the click guarantees Cypress is listening when the alert fires. In the next article, you'll learn an even more reliable approach: using a stub to assert that the alert was actually called.

Conclusion

In this article, we explored Cypress's core features and syntax, including how to create test suites, select elements, and make assertions. By following along with the React-based demo project, you've gained hands-on experience with Cypress.

In the next article, we'll cover advanced features like stubs, spies, and handling network requests. Stay tuned, and happy testing!

CypressTypeScriptTSXEnd-to-End TestingCypress Tutorial

Share On:

Copy linkShare on FacebookShare on LinkedInShare on ThreadsShare on X

Written by Bhavik Bamania

Bhavik Bamania is a software engineer and writer exploring frontend engineering, web architecture, technology, history, psychology, astrology, and culture.

This site uses cookies for basic analytics (page views, no ads). You can accept or decline.