Skip to content
Back to skills

Cypress Expert

ASecurity

Expert in Cypress for end-to-end testing, component testing, and modern web application testing. Use when writing, running, or improving tests with cypress.

  • 9 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 8, 2026
developmentjavascripttypescriptgojavareactnodetestingdebugginggitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill cypress-expert --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cypress Expert?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cypress Expert
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-cypress-expert/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-cypress-expert)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cypress-expert
description: Expert in Cypress for end-to-end testing, component testing, and modern web application testing. Use when writing, running, or improving tests with cypress.
license: CC-BY-NC-SA-4.0
metadata:
  risk: unknown
  source: community
  kind: mode
  category: testing
  tags: [cypress, testing, e2e, automation, javascript, typescript]
---

# Cypress Expert Mode

You are an expert in Cypress, the JavaScript end-to-end testing framework. You help teams build fast, reliable tests with best practices for modern web applications.

## Configuration

### cypress.config.ts

```typescript
import { defineConfig } from "cypress";

export default defineConfig({
  projectId: "your-project-id",

  e2e: {
    baseUrl: "http://localhost:3000",
    specPattern: "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}",
    supportFile: "cypress/support/e2e.ts",
    viewportWidth: 1280,
    viewportHeight: 720,
    video: true,
    screenshotOnRunFailure: true,
    defaultCommandTimeout: 10000,
    requestTimeout: 10000,
    responseTimeout: 30000,
    retries: {
      runMode: 2,
      openMode: 0,
    },
    experimentalRunAllSpecs: true,

    setupNodeEvents(on, config) {
      // Task registration
      on("task", {
        log(message) {
          console.log(message);
          return null;
        },
        seedDatabase(data) {
          // Seed database logic
          return null;
        },
        clearDatabase() {
          // Clear database logic
          return null;
        },
      });

      // Code coverage
      require("@cypress/code-coverage/task")(on, config);

      return config;
    },
  },

  component: {
    devServer: {
      framework: "react",
      bundler: "vite",
    },
    specPattern: "src/**/*.cy.{js,jsx,ts,tsx}",
    supportFile: "cypress/support/component.ts",
  },

  env: {
    apiUrl: "http://localhost:3001/api",
    coverage: true,
  },
});
```

### Support Files

```typescript
// cypress/support/e2e.ts
import "./commands";
import "@cypress/code-coverage/support";

// Handle uncaught exceptions
Cypress.on("uncaught:exception", (err, runnable) => {
  // Return false to prevent test failure on uncaught exceptions
  if (err.message.includes("ResizeObserver loop")) {
    return false;
  }
  return true;
});

// Log API calls for debugging
beforeEach(() => {
  cy.intercept("**/*").as("apiCall");
});
```

```typescript
// cypress/support/commands.ts
import "@testing-library/cypress/add-commands";

declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string): Chainable<void>;
      logout(): Chainable<void>;
      apiLogin(email: string, password: string): Chainable<void>;
      dataCy(value: string): Chainable<JQuery<HTMLElement>>;
      getByRole(role: string, options?: { name?: string | RegExp }): Chainable<JQuery<HTMLElement>>;
      shouldBeVisible(): Chainable<JQuery<HTMLElement>>;
      shouldNotExist(): Chainable<JQuery<HTMLElement>>;
      waitForApi(alias: string): Chainable<void>;
      mockApi(method: string, url: string, response: object): Chainable<void>;
    }
  }
}

// Login via UI
Cypress.Commands.add("login", (email: string, password: string) => {
  cy.visit("/login");
  cy.get("[data-cy=email-input]").type(email);
  cy.get("[data-cy=password-input]").type(password);
  cy.get("[data-cy=submit-button]").click();
  cy.url().should("include", "/dashboard");
});

// Login via API (faster for setup)
Cypress.Commands.add("apiLogin", (email: string, password: string) => {
  cy.request({
    method: "POST",
    url: `${Cypress.env("apiUrl")}/auth/login`,
    body: { email, password },
  }).then((response) => {
    window.localStorage.setItem("token", response.body.token);
  });
});

// Logout
Cypress.Commands.add("logout", () => {
  cy.window().then((win) => {
    win.localStorage.removeItem("token");
  });
  cy.visit("/login");
});

// Data-cy selector shorthand
Cypress.Commands.add("dataCy", (value: string) => {
  return cy.get(`[data-cy="${value}"]`);
});

// Get by role
Cypress.Commands.add("getByRole", (role: string, options?: { name?: string | RegExp }) => {
  if (options?.name) {
    return cy.get(`[role="${role}"]`).filter(`:contains("${options.name}")`);
  }
  return cy.get(`[role="${role}"]`);
});

// Visibility assertions
Cypress.Commands.add("shouldBeVisible", { prevSubject: true }, (subject) => {
  cy.wrap(subject).should("be.visible");
});

Cypress.Commands.add("shouldNotExist", { prevSubject: true }, (subject) => {
  cy.wrap(subject).should("not.exist");
});

// Wait for API with assertion
Cypress.Commands.add("waitForApi", (alias: string) => {
  cy.wait(`@${alias}`).its("response.statusCode").should("be.oneOf", [200, 201]);
});

// Mock API helper
Cypress.Commands.add("mockApi", (method: string, url: string, response: object) => {
  cy.intercept(method, url, response).as("mockedApi");
});
```

## Page Objects

### Base Page

```typescript
// cypress/pages/BasePage.ts
export class BasePage {
  visit(url: string) {
    cy.visit(url);
    return this;
  }

  getByTestId(testId: string) {
    return cy.get(`[data-testid="${testId}"]`);
  }

  getByDataCy(dataCy: string) {
    return cy.get(`[data-cy="${dataCy}"]`);
  }

  clickButton(name: string) {
    cy.contains("button", name).click();
    return this;
  }

  fillInput(label: string, value: string) {
    cy.contains("label", label).parent().find("input").clear().type(value);
    return this;
  }

  selectOption(label: string, option: string) {
    cy.contains("label", label).parent().find("select").select(option);
    return this;
  }

  assertUrl(expectedUrl: string) {
    cy.url().should("include", expectedUrl);
    return this;
  }

  assertToast(message: string) {
    cy.get('[role="alert"]').should("contain", message);
    return this;
  }

  waitForPageLoad() {
    cy.get('[data-loading="true"]').should("not.exist");
    return this;
  }
}
```

### Login Page

```typescript
// cypress/pages/LoginPage.ts
import { BasePage } from "./BasePage";

export class LoginPage extends BasePage {
  // Selectors
  private selectors = {
    emailInput: '[data-cy="email-input"]',
    passwordInput: '[data-cy="password-input"]',
    submitButton: '[data-cy="submit-button"]',
    errorMessage: '[data-cy="error-message"]',
    forgotPasswordLink: '[data-cy="forgot-password"]',
    rememberMeCheckbox: '[data-cy="remember-me"]',
  };

  visit() {
    super.visit("/login");
    return this;
  }

  typeEmail(email: string) {
    cy.get(this.selectors.emailInput).clear().type(email);
    return this;
  }

  typePassword(password: string) {
    cy.get(this.selectors.passwordInput).clear().type(password);
    return this;
  }

  clickSubmit() {
    cy.get(this.selectors.submitButton).click();
    return this;
  }

  checkRememberMe() {
    cy.get(this.selectors.rememberMeCheckbox).check();
    return this;
  }

  login(email: string, password: string) {
    this.typeEmail(email);
    this.typePassword(password);
    this.clickSubmit();
    return this;
  }

  assertError(message: string) {
    cy.get(this.selectors.errorMessage).should("contain", message);
    return this;
  }

  assertLoginSuccess() {
    cy.url().should("include", "/dashboard");
    return this;
  }

  clickForgotPassword() {
    cy.get(this.selectors.forgotPasswordLink).click();
    return this;
  }
}

export const loginPage = new LoginPage();
```

### Dashboard Page

```typescript
// cypress/pages/DashboardPage.ts
import { BasePage } from "./BasePage";

export class DashboardPage extends BasePage {
  private selectors = {
    welcomeMessage: '[data-cy="welcome-message"]',
    userMenu: '[data-cy="user-menu"]',
    logoutButton: '[data-cy="logout-button"]',
    statsCard: '[data-cy="stats-card"]',
    recentActivity: '[data-cy="recent-activity"]',
    searchInput: '[data-cy="search-input"]',
    notificationBell: '[data-cy="notification-bell"]',
  };

  visit() {
    super.visit("/dashboard");
    return this;
  }

  assertWelcomeMessage(name: string) {
    cy.get(this.selectors.welcomeMessage).should("contain", name);
    return this;
  }

  openUserMenu() {
    cy.get(this.selectors.userMenu).click();
    return this;
  }

  logout() {
    this.openUserMenu();
    cy.get(this.selectors.logoutButton).click();
    cy.url().should("include", "/login");
    return this;
  }

  getStatsCards() {
    return cy.get(this.selectors.statsCard);
  }

  assertStatsValue(cardName: string, value: string) {
    cy.get(this.selectors.statsCard).contains(cardName).parent().should("contain", value);
    return this;
  }

  search(query: string) {
    cy.get(this.selectors.searchInput).type(query).type("{enter}");
    return this;
  }

  getNotificationCount() {
    return cy.get(this.selectors.notificationBell).find(".badge");
  }
}

export const dashboardPage = new DashboardPage();
```

## Test Patterns

### Authentication Tests

```typescript
// cypress/e2e/auth/login.cy.ts
import { loginPage } from "../../pages/LoginPage";
import { dashboardPage } from "../../pages/DashboardPage";

describe("Login", () => {
  beforeEach(() => {
    cy.task("clearDatabase");
    cy.task("seedDatabase", { users: [{ email: "test@example.com", password: "password123" }] });
    loginPage.visit();
  });

  it("should login with valid credentials", () => {
    loginPage.login("test@example.com", "password123").assertLoginSuccess();

    dashboardPage.assertWelcomeMessage("Test User");
  });

  it("should show error with invalid credentials", () => {
    loginPage.login("wrong@example.com", "wrongpassword").assertError("Invalid email or password");
  });

  it("should validate required fields", () => {
    loginPage.clickSubmit();

    cy.get('[data-cy="email-input"]').should("have.attr", "aria-invalid", "true");
    cy.get('[data-cy="password-input"]').should("have.attr", "aria-invalid", "true");
  });

  it("should persist session with remember me", () => {
    loginPage.checkRememberMe().login("test@example.com", "password123");

    // Clear session storage but not local storage
    cy.window().then((win) => win.sessionStorage.clear());
    cy.reload();

    // Should still be logged in
    dashboardPage.assertWelcomeMessage("Test User");
  });
});
```

### API Mocking Tests

```typescript
// cypress/e2e/dashboard/data.cy.ts
describe("Dashboard Data", () => {
  beforeEach(() => {
    cy.apiLogin("test@example.com", "password123");
  });

  it("should display data from API", () => {
    cy.intercept("GET", "/api/dashboard/stats", {
      statusCode: 200,
      body: {
        totalUsers: 100,
        activeUsers: 85,
        revenue: 50000,
      },
    }).as("getStats");

    cy.visit("/dashboard");
    cy.wait("@getStats");

    cy.dataCy("stats-total-users").should("contain", "100");
    cy.dataCy("stats-active-users").should("contain", "85");
    cy.dataCy("stats-revenue").should("contain", "$50,000");
  });

  it("should handle API errors gracefully", () => {
    cy.intercept("GET", "/api/dashboard/stats", {
      statusCode: 500,
      body: { error: "Internal Server Error" },
    }).as("getStatsError");

    cy.visit("/dashboard");
    cy.wait("@getStatsError");

    cy.dataCy("error-message").should("be.visible");
    cy.dataCy("retry-button").should("be.visible");
  });

  it("should show loading state", () => {
    cy.intercept("GET", "/api/dashboard/stats", (req) => {
      req.on("response", (res) => {
        res.setDelay(2000);
      });
    }).as("getStatsSlow");

    cy.visit("/dashboard");
    cy.dataCy("loading-spinner").should("be.visible");
    cy.wait("@getStatsSlow");
    cy.dataCy("loading-spinner").should("not.exist");
  });

  it("should refresh data on button click", () => {
    let callCount = 0;
    cy.intercept("GET", "/api/dashboard/stats", (req) => {
      callCount++;
      req.reply({
        body: { totalUsers: callCount * 10 },
      });
    }).as("getStats");

    cy.visit("/dashboard");
    cy.wait("@getStats");
    cy.dataCy("stats-total-users").should("contain", "10");

    cy.dataCy("refresh-button").click();
    cy.wait("@getStats");
    cy.dataCy("stats-total-users").should("contain", "20");
  });
});
```

### Form Testing

```typescript
// cypress/e2e/forms/user-form.cy.ts
describe("User Form", () => {
  beforeEach(() => {
    cy.apiLogin("admin@example.com", "adminpass");
    cy.visit("/users/new");
  });

  it("should create a new user", () => {
    cy.intercept("POST", "/api/users", {
      statusCode: 201,
      body: { id: 1, name: "New User", email: "new@example.com" },
    }).as("createUser");

    cy.dataCy("name-input").type("New User");
    cy.dataCy("email-input").type("new@example.com");
    cy.dataCy("role-select").select("Editor");
    cy.dataCy("submit-button").click();

    cy.wait("@createUser").its("request.body").should("deep.include", {
      name: "New User",
      email: "new@example.com",
      role: "editor",
    });

    cy.url().should("include", "/users/1");
    cy.get('[role="alert"]').should("contain", "User created successfully");
  });

  it("should validate email format", () => {
    cy.dataCy("email-input").type("invalid-email");
    cy.dataCy("submit-button").click();

    cy.dataCy("email-error").should("contain", "Please enter a valid email");
  });

  it("should prevent duplicate emails", () => {
    cy.intercept("POST", "/api/users", {
      statusCode: 409,
      body: { error: "Email already exists" },
    }).as("createUser");

    cy.dataCy("name-input").type("Test User");
    cy.dataCy("email-input").type("existing@example.com");
    cy.dataCy("submit-button").click();

    cy.wait("@createUser");
    cy.dataCy("form-error").should("contain", "Email already exists");
  });

  it("should handle file upload", () => {
    cy.dataCy("avatar-input").selectFile("cypress/fixtures/avatar.png");
    cy.dataCy("avatar-preview").should("be.visible");
  });
});
```

### Data-Driven Testing

```typescript
// cypress/e2e/data-driven/validation.cy.ts
interface TestCase {
  description: string;
  input: { email: string; password: string };
  expectedError?: string;
  shouldSucceed?: boolean;
}

const testCases: TestCase[] = [
  {
    description: "valid credentials",
    input: { email: "user@example.com", password: "ValidPass123!" },
    shouldSucceed: true,
  },
  {
    description: "empty email",
    input: { email: "", password: "password123" },
    expectedError: "Email is required",
  },
  {
    description: "invalid email format",
    input: { email: "invalid-email", password: "password123" },
    expectedError: "Please enter a valid email",
  },
  {
    description: "short password",
    input: { email: "user@example.com", password: "123" },
    expectedError: "Password must be at least 8 characters",
  },
  {
    description: "password without uppercase",
    input: { email: "user@example.com", password: "password123!" },
    expectedError: "Password must contain an uppercase letter",
  },
];

describe("Form Validation", () => {
  beforeEach(() => {
    cy.visit("/register");
  });

  testCases.forEach(({ description, input, expectedError, shouldSucceed }) => {
    it(`handles ${description}`, () => {
      if (input.email) {
        cy.dataCy("email-input").type(input.email);
      }
      if (input.password) {
        cy.dataCy("password-input").type(input.password);
      }

      cy.dataCy("submit-button").click();

      if (shouldSucceed) {
        cy.url().should("include", "/dashboard");
      } else if (expectedError) {
        cy.get('[role="alert"]').should("contain", expectedError);
      }
    });
  });
});
```

### Component Testing

```typescript
// src/components/Button/Button.cy.tsx
import { Button } from './Button';

describe('Button Component', () => {
  it('renders with default props', () => {
    cy.mount(<Button>Click me</Button>);
    cy.get('button').should('have.text', 'Click me');
  });

  it('handles click events', () => {
    const onClick = cy.stub().as('onClick');
    cy.mount(<Button onClick={onClick}>Click</Button>);

    cy.get('button').click();
    cy.get('@onClick').should('have.been.calledOnce');
  });

  it('applies variant classes', () => {
    cy.mount(<Button variant="primary">Primary</Button>);
    cy.get('button').should('have.class', 'btn-primary');

    cy.mount(<Button variant="secondary">Secondary</Button>);
    cy.get('button').should('have.class', 'btn-secondary');
  });

  it('shows loading state', () => {
    cy.mount(<Button loading>Loading</Button>);
    cy.get('button').should('be.disabled');
    cy.get('[data-cy="spinner"]').should('be.visible');
  });

  it('renders as a link when href is provided', () => {
    cy.mount(<Button href="/about">About</Button>);
    cy.get('a').should('have.attr', 'href', '/about');
  });

  it('is accessible', () => {
    cy.mount(<Button aria-label="Submit form">Submit</Button>);
    cy.get('button').should('have.attr', 'aria-label', 'Submit form');
  });
});
```

```typescript
// src/components/Modal/Modal.cy.tsx
import { Modal } from './Modal';

describe('Modal Component', () => {
  it('opens and closes correctly', () => {
    const onClose = cy.stub().as('onClose');
    cy.mount(
      <Modal isOpen={true} onClose={onClose} title="Test Modal">
        <p>Modal content</p>
      </Modal>
    );

    cy.get('[role="dialog"]').should('be.visible');
    cy.get('[data-cy="modal-title"]').should('have.text', 'Test Modal');
    cy.contains('Modal content').should('be.visible');

    cy.get('[data-cy="close-button"]').click();
    cy.get('@onClose').should('have.been.calledOnce');
  });

  it('closes on escape key', () => {
    const onClose = cy.stub().as('onClose');
    cy.mount(
      <Modal isOpen={true} onClose={onClose} title="Test">
        Content
      </Modal>
    );

    cy.get('body').type('{esc}');
    cy.get('@onClose').should('have.been.calledOnce');
  });

  it('closes on backdrop click', () => {
    const onClose = cy.stub().as('onClose');
    cy.mount(
      <Modal isOpen={true} onClose={onClose} title="Test">
        Content
      </Modal>
    );

    cy.get('[data-cy="modal-backdrop"]').click({ force: true });
    cy.get('@onClose').should('have.been.calledOnce');
  });

  it('traps focus within modal', () => {
    cy.mount(
      <Modal isOpen={true} onClose={() => {}} title="Test">
        <input data-cy="input-1" />
        <input data-cy="input-2" />
        <button data-cy="button">Submit</button>
      </Modal>
    );

    cy.dataCy('input-1').focus();
    cy.dataCy('button').focus();
    cy.realPress('Tab');
    cy.focused().should('have.attr', 'data-cy', 'close-button');
  });
});
```

## CI/CD Integration

```yaml
# .github/workflows/cypress.yml
name: Cypress Tests

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

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        containers: [1, 2, 3, 4]

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Start server
        run: npm run start &

      - name: Cypress run
        uses: cypress-io/github-action@v6
        with:
          wait-on: "http://localhost:3000"
          record: true
          parallel: true
          group: "E2E Tests"
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SPLIT: ${{ strategy.job-total }}
          SPLIT_INDEX: ${{ strategy.job-index }}

      - name: Upload screenshots
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: cypress-screenshots-${{ matrix.containers }}
          path: cypress/screenshots

      - name: Upload videos
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: cypress-videos-${{ matrix.containers }}
          path: cypress/videos
```

## Best Practices

1. **Selectors**
   - Use data-cy/data-testid attributes
   - Avoid CSS classes and complex selectors
   - Use Testing Library queries when possible

2. **Commands**
   - Create reusable custom commands
   - Use API for test setup (faster than UI)
   - Keep commands focused and composable

3. **Waits**
   - Never use arbitrary cy.wait(ms)
   - Wait for specific elements or API calls
   - Use retry-ability built into commands

4. **Test Isolation**
   - Reset state before each test
   - Don't rely on test order
   - Mock external services

5. **Performance**
   - Use API login instead of UI login
   - Parallelize test runs
   - Use fixtures for large data sets

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.