Skip to main content
/tayyab/portfolio — zsh
tayyab
TA
// dispatch.read --classified=false --access-level: public

Cypress Tutorial for Beginners 2026: Step-by-Step Guide to Web Test Automation

April 1, 2026 EST. READ: 15 MIN #Quality Assurance

If you're a QA engineer considering test automation for the first time, you're probably asking: Where do I even start?

The answer is surprisingly simple: Start with Cypress.

Cypress is the easiest way to learn test automation in 2026. It's not the most powerful (that's Playwright), and it's not the oldest (that's Selenium). But for beginners? It's the sweet spot—simple enough that you can write your first test in 10 minutes, yet powerful enough to test real applications.

I work with QA teams transitioning from manual testing to automation, and Cypress is always my first recommendation for teams starting from zero. Why? Because it removes the barriers that make other frameworks frustrating for beginners.

Why Cypress for Beginners?

Before we start coding, let's be honest: learning test automation is hard. You're learning a new language (JavaScript or TypeScript), a new framework (Cypress), and completely new concepts (selectors, assertions, waits).

Cypress removes half of that friction.

  • No confusing waits: Cypress automatically waits for elements. You don't need to understand implicit waits, explicit waits, or expected conditions. It just works.
  • The browser is part of your test: Unlike Selenium where tests and browser feel disconnected, Cypress runs inside the browser. You can see what your test is doing in real-time.
  • Debugging is easy: Time-travel debugging lets you step through your test backward and forward. Click any command in the test log and see the page at that exact moment.
  • Error messages are actually helpful: When a test fails, Cypress tells you exactly why. Not a cryptic "element not found" message, but actual guidance.
  • JavaScript, not Java: You're learning JavaScript (or TypeScript), which is the language of the web. This makes test code feel native to the platform you're testing.

Bottom line: Cypress is designed for learning. The other frameworks were designed for power. As a beginner, you need Cypress.

Installation: 5 Minutes Start to Finish

Step 1: Create a Project Directory

mkdir cypress-qa-tutorial
cd cypress-qa-tutorial

Step 2: Initialize Node.js Project

npm init -y

This creates a package.json file that tells Node.js what dependencies your project needs.

Step 3: Install Cypress

npm install --save-dev cypress

This installs Cypress as a development dependency (only needed for testing, not for production).

Step 4: Open Cypress

npx cypress open

A Cypress window opens with example tests already set up. This is it—you're ready to write tests.

Your First Test: 10 Minutes

Cypress created a cypress/e2e/ folder with example tests. Let's create your first test from scratch.

Create a new file: cypress/e2e/first-test.cy.js

describe('My First Test', () => {
  it('should visit a website and verify the title', () => {
    // Navigate to a website
    cy.visit('https://example.com');
    
    // Verify the title contains certain text
    cy.title().should('include', 'Example Domain');
  });
});

That's it. Save the file and click it in the Cypress window. Watch your test run.

What happened?

  1. describe() - Groups tests together (think of it as a folder)
  2. it() - Defines a single test (the description is what shows in the report)
  3. cy.visit() - Navigates to a website
  4. cy.title() - Gets the page title
  5. .should('include', 'Example Domain') - Asserts (checks) that the title includes the text

You just wrote a test. Congratulations.

Understanding Cypress Basics: Commands, Selectors & Assertions

Every Cypress test has three parts: navigate, interact, assert.

Part 1: Navigate (cy.visit)

cy.visit('https://example.com'); // Go to the website

Part 2: Interact (cy.get, cy.click, cy.type)

// Find an element by CSS selector
cy.get('button');

// Find an element with specific text
cy.contains('button', 'Sign Up');

// Click the button
cy.get('button').click();

// Type text into an input field
cy.get('input[type="email"]').type('user@example.com');

Part 3: Assert (should)

// Check if element is visible
cy.get('.success-message').should('be.visible');

// Check if text matches
cy.get('h1').should('have.text', 'Welcome');

// Check if element exists
cy.get('#dashboard').should('exist');

// Check if URL changed
cy.url().should('include', '/dashboard');

Every test follows this pattern: visit → interact → assert.

Real Example: Testing a Login Form

Let's build a realistic test. Create cypress/e2e/login.cy.js:

describe('Login Feature', () => {
  beforeEach(() => {
    // This runs before each test
    cy.visit('https://example.com/login');
  });

  it('should login successfully with valid credentials', () => {
    // Find the email input and type an email
    cy.get('input[data-testid="email"]').type('user@example.com');
    
    // Find the password input and type a password
    cy.get('input[data-testid="password"]').type('password123');
    
    // Find the login button and click it
    cy.get('button[type="submit"]').click();
    
    // Verify we're redirected to dashboard
    cy.url().should('include', '/dashboard');
    
    // Verify a welcome message appears
    cy.get('.welcome-message').should('be.visible');
  });

  it('should show error with invalid credentials', () => {
    // Type invalid email
    cy.get('input[data-testid="email"]').type('wrong@example.com');
    
    // Type wrong password
    cy.get('input[data-testid="password"]').type('wrongpassword');
    
    // Click login
    cy.get('button[type="submit"]').click();
    
    // Verify error message appears
    cy.get('.error-message').should('contain', 'Invalid credentials');
    
    // Verify we're still on login page
    cy.url().should('include', '/login');
  });
});

Key concepts here:

  • beforeEach() - Runs before each test (no need to visit the URL twice)
  • data-testid - A special attribute developers add to make elements easy to find in tests
  • Multiple tests in one describe block - tests run independently
  • Assertions check both behavior (URL changed, message visible) and content (error text)

Finding Elements: Selectors Explained

The hardest part of test automation for beginners is finding elements. Let me demystify it.

Elements can be found by:

1. CSS Class or ID (Most Common)

// Find by ID (# symbol)
cy.get('#submit-button');

// Find by class (. symbol)
cy.get('.login-form');

// Combine multiple classes
cy.get('.btn.btn-primary');

2. Attribute (data-testid is Best Practice)

// Find by data-testid (developers add this for testing)
cy.get('[data-testid="login-button"]');

// Find by placeholder
cy.get('input[placeholder="Enter your email"]');

// Find by type
cy.get('input[type="password"]');

3. Text Content

// Find element containing text
cy.contains('Sign Up');

// Find button containing text
cy.contains('button', 'Submit');

4. Parent-Child Relationships

// Find input inside a form
cy.get('form input[type="email"]');

// Find element inside a container
cy.get('.login-container button');

Pro tip: The best selectors are data-testid attributes because developers control them and they don't change when styling changes. If the website doesn't use data-testid, ask the developers to add them.

Understanding Waits: The Secret Sauce

Here's where Cypress shines for beginners. In Selenium, you write complex wait code. In Cypress, you just write assertions and it waits automatically.

// ❌ DON'T DO THIS (arbitrary wait)
cy.wait(2000); // Wait 2 seconds (what if it takes 2.1?)

// ✅ DO THIS (wait for a condition)
cy.get('.success-message').should('be.visible'); // Waits up to 4 seconds by default

Cypress waits automatically for:

  • Element to exist: cy.get() waits up to 4 seconds
  • Element to be visible: .should('be.visible') waits up to 4 seconds
  • Text to appear: .should('contain', 'text') waits up to 4 seconds
  • URL to change: cy.url().should('include', '/path') waits automatically

This is why Cypress tests are less flaky than Selenium tests.

Organizing Tests: Page Object Model for Beginners

As you write more tests, you'll notice repetition. The Page Object Model (POM) eliminates this.

Create cypress/support/pages/LoginPage.js:

export class LoginPage {
  constructor() {
    this.emailInput = '[data-testid="email"]';
    this.passwordInput = '[data-testid="password"]';
    this.submitButton = 'button[type="submit"]';
    this.errorMessage = '.error-message';
  }

  visit() {
    cy.visit('/login');
  }

  login(email, password) {
    cy.get(this.emailInput).type(email);
    cy.get(this.passwordInput).type(password);
    cy.get(this.submitButton).click();
  }

  verifyErrorMessage(message) {
    cy.get(this.errorMessage).should('contain', message);
  }

  verifyLoginSuccess() {
    cy.url().should('include', '/dashboard');
  }
}

Now your tests are cleaner:

import { LoginPage } from '../support/pages/LoginPage';

describe('Login Tests', () => {
  const loginPage = new LoginPage();

  beforeEach(() => {
    loginPage.visit();
  });

  it('should login successfully', () => {
    loginPage.login('user@example.com', 'password123');
    loginPage.verifyLoginSuccess();
  });

  it('should show error with wrong password', () => {
    loginPage.login('user@example.com', 'wrong');
    loginPage.verifyErrorMessage('Invalid credentials');
  });
});

Benefits:

  • If the login button changes, you update only LoginPage.js
  • Tests are readable: you see what the test does, not how selectors work
  • Less code duplication
  • Easier for team members to write tests

Running Tests: CLI Mode & Debugging

Run Tests in the Browser (Interactive)

npx cypress open

Click any test to run it. You see the browser, the test log, and can inspect elements.

Run Tests in Headless Mode (CI/CD)

npx cypress run

Tests run without opening a browser (faster for automated pipelines).

Run Specific Tests

npx cypress run --spec "cypress/e2e/login.cy.js"

Debug a Failing Test

npx cypress open --headed

Then click the test that's failing. Use the Inspector tool to step through commands and see exactly where it fails.

Real Project Setup: Configuration

Create cypress.config.js at your project root:

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

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000', // Your app's URL
    setupNodeEvents(on, config) {
      // You can use this for advanced features later
    },
  },
});

Now tests can use relative URLs:

// Instead of cy.visit('http://localhost:3000/login')
cy.visit('/login'); // baseUrl is prepended automatically

Adding Tests to Your Workflow: CI/CD Pipeline

Running tests locally is great, but you need them running automatically on every code change. GitHub Actions makes this easy.

Create .github/workflows/cypress-tests.yml:

name: Cypress Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 18
      - run: npm install
      - run: npm run cypress:run

Add to your package.json:

{
  "scripts": {
    "cypress:open": "cypress open",
    "cypress:run": "cypress run"
  }
}

Now every time you push code, tests run automatically. If tests fail, you can see exactly why.

Common Beginner Mistakes (and How to Avoid Them)

Mistake 1: Using Random Waits

// ❌ BAD
cy.get('.button').click();
cy.wait(2000);
cy.get('.result').should('exist');

// ✅ GOOD
cy.get('.button').click();
cy.get('.result').should('be.visible'); // Waits automatically

Mistake 2: Not Using data-testid

// ❌ BAD (breaks if styling changes)
cy.get('.sidebar > div:nth-child(2) > button');

// ✅ GOOD
cy.get('[data-testid="sidebar-button"]');

Mistake 3: Testing Implementation Details Instead of Behavior

// ❌ BAD (testing implementation)
cy.get('.form').should('have.class', 'submitted');

// ✅ GOOD (testing behavior)
cy.get('.success-message').should('be.visible');

Mistake 4: Tests That Depend on Each Other

// ❌ BAD (tests depend on order)
it('should create user', () => { ... });
it('should login as the user created above', () => { ... });

// ✅ GOOD (tests are independent)
beforeEach(() => {
  // Create fresh data before each test
});
it('should login', () => { ... });

Cypress vs Other Frameworks: When to Use Cypress

Use Cypress when:

  • You're new to test automation (start here)
  • You're testing web applications (Cypress is web-only)
  • You want fast feedback and easy debugging
  • You're on a small team with limited test automation experience

Consider Playwright when:

  • You need to test multiple browsers (Chrome, Firefox, Safari simultaneously)
  • You need multi-language support (Python, Java, etc.)
  • You're testing large-scale applications with 100+ tests
  • You need API testing alongside UI testing

For beginners: start with Cypress. You can migrate to Playwright later if needed.

FAQ: Beginner Questions Answered

Q: Does Cypress support Chrome? Firefox? Safari?

A: Cypress supports Chrome and Firefox. Safari support is limited. For testing all three browsers, use Playwright.

Q: Can I test native mobile apps with Cypress?

A: No, Cypress is for web apps only. For native mobile, use Appium.

Q: How do I test multiple users logging in simultaneously?

A: Cypress runs tests sequentially by default. For parallel testing, upgrade your Cypress Cloud plan.

Q: Can I test APIs with Cypress?

A: Yes, use cy.request() to make API calls within tests.

Q: Is Cypress free?

A: Yes. The open-source version is free. Cypress Cloud (for parallelization and reporting) is optional and paid.

Q: What's the best way to find elements?

A: Use data-testid attributes. Ask your developers to add them to elements you test.

Q: My test passes locally but fails in CI. Why?

A: Usually timing issues or environment differences. Use explicit waits (assertions) instead of arbitrary waits.

Q: Can I test cross-domain navigation?

A: Cypress has limitations here. For complex multi-domain flows, Playwright is better.

Next Steps: Your Learning Path

  1. Install Cypress and write your first test (you did this above)
  2. Write login and form tests (build from the examples above)
  3. Implement Page Object Model (organize your tests as you add more)
  4. Set up CI/CD pipeline (run tests on every commit)
  5. Explore Cypress Cloud (optional, for advanced features)

If you get stuck:

You're now ready to start your test automation journey. The fact that you're learning Cypress in 2026 means you're ahead of most QA engineers who are still doing manual testing.

If you want to take the next step and learn advanced automation patterns or migrate to Playwright, I offer test automation framework setup services that include training and hands-on implementation. I can also help your team with QA automation coaching to build sustainable testing practices.

Ready to build a testing strategy for your team?

Related Articles:

Tayyab Akmal
// author

Tayyab Akmal

AI & QA Automation Engineer

6 years of catching critical bugs in fintech, e-commerce, and SaaS — then building the Playwright and Selenium automation that prevents them from shipping again.

// related_dispatches

YOU MIGHT ALSO READ

// feedback_channel

FOUND THIS USEFUL?

Share your thoughts or let's discuss automation testing strategies.

→ Start Conversation
Available for hire