Getting Started with Cypress: A Step-by-Step Guide to Setup and First Test (Part 1)

A comprehensive guide to setting up Cypress and creating your first automated test for YouTube search

Over the past decade of working in quality engineering, I've experimented with dozens of test automation frameworks. But few have managed to combine power, simplicity, and developer experience quite like Cypress.

In this three-part series, I'll walk you through everything you need to know to become proficient with Cypress testing. This first post focuses on getting started with Cypress, understanding its architecture, and building your first practical test that automates a YouTube search.

Why Cypress?

Before we dive into the setup, it's worth understanding what makes Cypress different from other testing tools like Selenium, Playwright, or TestCafe:

  1. Executes in the browser: Unlike Selenium which operates through a driver, Cypress runs directly inside the browser, giving it complete access to everything in your application.

  2. Time travel debugging: Cypress takes snapshots at each step of test execution, allowing you to see exactly what happened at each stage.

  3. Automatic waiting: No more explicit waits or sleep commands. Cypress automatically waits for elements to become available, animations to complete, and more.

  4. Real-time reloads: Tests reload in real-time as you modify your test files, creating a tight feedback loop during development.

  5. Network traffic control: Intercept, stub, and modify network requests without server modifications.

With that context, let's get started with the installation and setup.

Prerequisites

To follow along, you'll need:

  • Node.js (v18 or higher recommended)
  • npm or yarn
  • A code editor (VS Code recommended)
  • Basic JavaScript knowledge

Setting Up Your Cypress Project

Let's start by creating a new project directory and initializing it:

mkdir cypress-youtube-test
cd cypress-youtube-test
npm init -y

Now, install Cypress as a dev dependency:

npm install cypress --save-dev

Once the installation completes, open Cypress for the first time to generate its folder structure:

npx cypress open

This command opens the Cypress Test Runner and creates the initial directory structure. Close the Test Runner for now.

Your project structure should now look something like this:

cypress-youtube-test/
├── cypress/
│   ├── e2e/
│   ├── fixtures/
│   ├── support/
│   └── videos/
├── cypress.config.js
├── node_modules/
├── package-lock.json
└── package.json

Let's understand each of these folders:

  • e2e/: Contains your test files (previously called integration/)
  • fixtures/: Contains static data used by your tests
  • support/: Contains reusable utility functions and global configurations
  • videos/: Contains videos of test runs

Understanding Cypress Configuration

Open the cypress.config.js file. This is where you'll configure Cypress for your project. Let's modify it to suit our needs:

const { defineConfig } = require("cypress");

module.exports = defineConfig({
  e2e: {
    baseUrl: 'https://www.youtube.com',
    viewportWidth: 1280,
    viewportHeight: 720,
    defaultCommandTimeout: 10000,
    videoCompression: 15,
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
  },
});

This configuration sets YouTube as our base URL, specifies a standard HD viewport size, increases the default command timeout, and configures video compression.

Writing Your First Cypress Test

Now, let's create our first test that will automate a YouTube search. Create a new file in the cypress/e2e directory called youtube-search.cy.js:

describe('YouTube Search Functionality', () => {
  beforeEach(() => {
    // Visit the YouTube homepage before each test
    cy.visit('/');
    
    // Accept cookies if the dialog appears
    cy.get('body').then($body => {
      if ($body.find('[aria-label="Accept the use of cookies and other data for the purposes described"]').length > 0) {
        cy.get('[aria-label="Accept the use of cookies and other data for the purposes described"]').click();
      }
    });
  });

  it('should search for a specific term and verify results', () => {
    // Type the search term
    cy.get('input#search').type('Cypress test automation');
    
    // Click the search button
    cy.get('#search-icon-legacy').click();
    
    // Wait for search results to load
    cy.url().should('include', '/results?search_query=Cypress+test+automation');
    
    // Verify that search results are displayed
    cy.get('#contents ytd-video-renderer').should('be.visible');
    
    // Verify that at least 5 results appear
    cy.get('#contents ytd-video-renderer').should('have.length.at.least', 5);
    
    // Verify that the search results contain our search term
    cy.get('#contents ytd-video-renderer').first().find('#video-title')
      .should('contain.text', 'Cypress');
  });

  it('should filter search results by upload date', () => {
    // Type the search term
    cy.get('input#search').type('Cypress test automation');
    
    // Click the search button
    cy.get('#search-icon-legacy').click();
    
    // Click on the Filters button
    cy.get('[aria-label="Search filters"]').click();
    
    // Click on "This month" under Upload date
    cy.contains('Upload date').parent().contains('This month').click();
    
    // Verify the URL contains the filter parameter
    cy.url().should('include', 'sp=CAI%253D');
    
    // Verify that filtered results are displayed
    cy.get('#contents ytd-video-renderer').should('be.visible');
  });
});

This test performs the following actions:

  1. Visits the YouTube homepage before each test
  2. Handles the cookie consent dialog if it appears
  3. Searches for "Cypress test automation"
  4. Verifies search results contain the search term
  5. Applies a filter to narrow down results
  6. Verifies the filtered results are displayed

Understanding Cypress Commands and Assertions

Let's break down some key Cypress components used in our test:

Commands

  • cy.visit('/'): Navigates to the base URL (YouTube)
  • cy.get('selector'): Selects elements using CSS selectors
  • cy.type('text'): Types text into an input field
  • cy.click(): Clicks on an element
  • cy.url(): Gets the current URL
  • cy.contains('text'): Finds elements containing specific text

Assertions

  • .should('be.visible'): Verifies an element is visible
  • .should('have.length.at.least', 5): Verifies at least 5 elements exist
  • .should('contain.text', 'Cypress'): Verifies text content
  • .should('include', 'search_query'): Verifies URL contains specific text

Handling Dynamic Elements and Waits

One complexity in web testing is dealing with dynamic elements. Cypress handles this elegantly with automatic waiting. For example:

// Cypress will automatically wait for this element to appear (up to defaultCommandTimeout)
cy.get('#contents ytd-video-renderer').should('be.visible');

For more complex scenarios, you might need custom waiting logic:

// Custom waiting logic for a loading indicator to disappear
cy.get('#loading-indicator', { timeout: 15000 }).should('not.exist');

// Wait for a network request to complete
cy.intercept('GET', '**/search*').as('searchResults');
cy.wait('@searchResults');

Running Your Tests

You can run your tests in two ways:

1. Using the Cypress Test Runner (GUI mode)

npx cypress open

This opens the Cypress Test Runner, where you can select and run individual tests. The Test Runner shows a live view of your test execution, which is incredibly useful for debugging.

2. Using Headless Mode (CI-friendly)

npx cypress run

This runs all tests headlessly, which is ideal for continuous integration environments. It records videos and screenshots (on failures) by default.

To run a specific test file:

npx cypress run --spec "cypress/e2e/youtube-search.cy.js"

Debugging Your Tests

Cypress offers several powerful debugging features:

Time Travel Debugging

In the Test Runner, you can hover over each command to see the state of the application at that point in time. This is invaluable for understanding why tests fail.

Console Output

You can use cy.log() to add messages to the Cypress command log:

cy.log('About to search for Cypress test automation');

Browser DevTools

Since Cypress runs directly in the browser, you can use browser DevTools to inspect elements, check network requests, and debug JavaScript:

// Use .debug() to pause execution and inspect
cy.get('#search-icon-legacy').debug().click();

Handling Iframes and Shadow DOM

YouTube, like many modern applications, uses Shadow DOM and iframes. Cypress can handle these with special commands:

// Accessing elements in Shadow DOM
cy.get('my-component').shadow().find('.inside-shadow-dom');

// Working with iframes
cy.frameLoaded('#my-iframe');
cy.iframe('#my-iframe').find('.inside-iframe').should('be.visible');

Note: For iframe support, you may need to install and use the cypress-iframe plugin.

Advanced Selectors and Resilient Tests

To create more resilient tests, it's best to use data attributes instead of relying on CSS classes or complex selectors:

// Add these to your application's HTML
<button data-cy="search-button">Search</button>

// Then in your tests
cy.get('[data-cy=search-button]').click();

This makes your tests less brittle to UI changes.

Setting Up package.json Scripts

Let's add some convenient npm scripts to our package.json:

"scripts": {
  "cy:open": "cypress open",
  "cy:run": "cypress run",
  "test": "cypress run",
  "test:chrome": "cypress run --browser chrome",
  "test:firefox": "cypress run --browser firefox",
  "test:search": "cypress run --spec 'cypress/e2e/youtube-search.cy.js'"
}

Now you can run commands like npm run cy:open or npm run test:search.

Handling Network Requests with Intercept

One of the most powerful features of Cypress is the ability to intercept, spy on, and stub network requests. This is useful for testing how your application handles different API responses:

// Spy on search API requests
cy.intercept('GET', '**/search*').as('searchRequest');

// Perform search
cy.get('input#search').type('Cypress test automation');
cy.get('#search-icon-legacy').click();

// Wait for the request to complete and assert on it
cy.wait('@searchRequest').its('response.statusCode').should('eq', 200);

Screenshots and Videos

Cypress automatically captures screenshots on test failures and records videos of test runs. You can also manually capture screenshots:

// Take a screenshot at a specific point
cy.screenshot('after-search-results');

// Screenshot a specific element
cy.get('#contents').screenshot('search-results-only');

Best Practices for Cypress Tests

  1. Keep tests independent: Each test should not depend on the state from another test.

  2. Use before/beforeEach hooks wisely: Set up the initial state in beforeEach for consistent test runs.

  3. Use aliases for readability:

    cy.get('input#search').as('searchInput');
    cy.get('@searchInput').type('Cypress test automation');
  4. Avoid using .wait(milliseconds): Instead, wait for specific events or elements.

  5. Handle flaky elements properly: Use proper waiting mechanisms rather than retrying manually.

Conclusion and Next Steps

In this first part of our Cypress series, we've set up a Cypress project from scratch and created our first test that performs a YouTube search. We've covered the basics of Cypress commands, assertions, and test execution.

In the next part, we'll refactor our tests to use the Page Object Model pattern, making them more maintainable and scalable. We'll also explore more advanced Cypress features for handling complex UI interactions.

Until then, try expanding the YouTube search test to include:

  • Testing search suggestions as you type
  • Clicking on a search result and verifying the video page loads
  • Testing different search filters (upload date, view count, etc.)

Feel free to share your implementations in the comments below!


Part 2 of this series, "Building Maintainable Cypress Tests with Page Object Model," will be published on March 24, 2025.

Enjoying this?