# SkillPatch skill: cypress-skill

A production-grade Cypress automation skill that helps agents write E2E, component, and API tests in JavaScript or TypeScript. It provides structured guidance on test structure, selector strategies, network interception, custom commands, and anti-patterns. Supports both local Cypress execution and TestMu AI cloud (LambdaTest) for cross-browser testing.

You (the agent) were given this URL and asked to use this skill. This is a **public** skill — no SkillPatch account, API key, or prior setup is required. Two ways to use it:

**1. Use it right now** — the skill's complete file tree (SKILL.md + all reference files) is inlined below; read `SKILL.md` first, then follow it, consulting the other files as it directs.

**2. Install the exact package onto disk** (recommended if you can run a shell — this reproduces the skill byte-for-byte, including any binary assets that can't be inlined):

```bash
mkdir -p .claude/skills/cypress-skill
curl -sSL https://skillpatch.dev/install_skill/cypress-skill | tar -xz -C .claude/skills/
```

(`.claude/skills/` is Claude Code's convention; use whatever directory your agent loads skills from.)


---

## Skill files (8)

- `SKILL.md`
- `reference/cloud-integration.md`
- `reference/component-testing.md`
- `reference/custom-commands.md`
- `reference/debugging-flaky.md`
- `reference/playbook.md`
- `scripts/scaffold-project.sh`
- `templates/cypress.config.js`


### `SKILL.md`

````markdown
---
name: cypress-skill
description: >
  Generates production-grade Cypress E2E and component tests in JavaScript
  or TypeScript. Supports local execution and TestMu AI cloud. Use when
  the user asks to write Cypress tests, set up Cypress, test with cy commands,
  or mentions "Cypress", "cy.visit", "cy.get", "cy.intercept". Triggers on:
  "Cypress", "cy.", "component test", "E2E test", "TestMu", "LambdaTest".
languages:
  - JavaScript
  - TypeScript
category: e2e-testing
license: MIT
metadata:
  author: TestMu AI
  version: "1.0"
---

# Cypress Automation Skill

You are a senior QA automation architect specializing in Cypress.

## Step 1 — Execution Target

```
User says "test" / "automate"
│
├─ Mentions "cloud", "TestMu", "LambdaTest", "cross-browser"?
│  └─ TestMu AI cloud via cypress-cli plugin
│
├─ Mentions "locally", "open", "headed"?
│  └─ Local: npx cypress open
│
└─ Ambiguous? → Default local, mention cloud option
```

## Step 2 — Test Type

| Signal | Type | Config |
|--------|------|--------|
| "E2E", "end-to-end", page URL | E2E test | `cypress/e2e/` |
| "component", "React", "Vue" | Component test | `cypress/component/` |
| "API test", "cy.request" | API test via Cypress | `cypress/e2e/api/` |

## Core Patterns

### Command Chaining — CRITICAL

```javascript
// ✅ Cypress chains — no await, no async
cy.visit('/login');
cy.get('#username').type('user@test.com');
cy.get('#password').type('password123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');

// ❌ NEVER use async/await with cy commands
// ❌ NEVER assign cy.get() to a variable for later use
```

### Selector Priority

```
1. cy.get('[data-cy="submit"]')     ← Best practice
2. cy.get('[data-testid="submit"]') ← Also good
3. cy.contains('Submit')            ← Text-based
4. cy.get('#submit-btn')            ← ID
5. cy.get('.btn-primary')           ← Class (fragile)
```

### Anti-Patterns

| Bad | Good | Why |
|-----|------|-----|
| `cy.wait(5000)` | `cy.intercept()` + `cy.wait('@alias')` | Arbitrary waits |
| `const el = cy.get()` | Chain directly | Cypress is async |
| `async/await` with cy | Chain `.then()` if needed | Different async model |
| Testing 3rd party sites | Stub/mock instead | Flaky, slow |
| Single `beforeEach` with everything | Multiple focused specs | Better isolation |

### Basic Test Structure

```javascript
describe('Login', () => {
  beforeEach(() => {
    cy.visit('/login');
  });

  it('should login with valid credentials', () => {
    cy.get('[data-cy="username"]').type('user@test.com');
    cy.get('[data-cy="password"]').type('password123');
    cy.get('[data-cy="submit"]').click();
    cy.url().should('include', '/dashboard');
    cy.get('[data-cy="welcome"]').should('contain', 'Welcome');
  });

  it('should show error for invalid credentials', () => {
    cy.get('[data-cy="username"]').type('wrong@test.com');
    cy.get('[data-cy="password"]').type('wrong');
    cy.get('[data-cy="submit"]').click();
    cy.get('[data-cy="error"]').should('be.visible');
  });
});
```

### Network Interception

```javascript
// Stub API response
cy.intercept('POST', '/api/login', {
  statusCode: 200,
  body: { token: 'fake-jwt', user: { name: 'Test User' } },
}).as('loginRequest');

cy.get('[data-cy="submit"]').click();
cy.wait('@loginRequest').its('request.body').should('deep.include', {
  email: 'user@test.com',
});

// Wait for real API
cy.intercept('GET', '/api/dashboard').as('dashboardLoad');
cy.visit('/dashboard');
cy.wait('@dashboardLoad');
```

### Custom Commands

```javascript
// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
  cy.session([email, password], () => {
    cy.visit('/login');
    cy.get('[data-cy="username"]').type(email);
    cy.get('[data-cy="password"]').type(password);
    cy.get('[data-cy="submit"]').click();
    cy.url().should('include', '/dashboard');
  });
});

// Usage in tests
cy.login('user@test.com', 'password123');
```

### TestMu AI Cloud

```javascript
// cypress.config.js
module.exports = {
  e2e: {
    setupNodeEvents(on, config) {
      // LambdaTest plugin
    },
  },
};

// lambdatest-config.json
{
  "lambdatest_auth": {
    "username": "${LT_USERNAME}",
    "access_key": "${LT_ACCESS_KEY}"
  },
  "browsers": [
    { "browser": "Chrome", "platform": "Windows 11", "versions": ["latest"] },
    { "browser": "Firefox", "platform": "macOS Sequoia", "versions": ["latest"] }
  ],
  "run_settings": {
    "build_name": "Cypress Build",
    "parallels": 5,
    "specs": "cypress/e2e/**/*.cy.js"
  }
}
```

**Run on cloud:**
```bash
npx lambdatest-cypress run
```

## Validation Workflow

1. **No arbitrary waits**: Zero `cy.wait(number)` — use intercepts
2. **Selectors**: Prefer `data-cy` attributes
3. **No async/await**: Pure Cypress chaining
4. **Assertions**: Use `.should()` chains, not manual checks
5. **Isolation**: Each test independent, use `cy.session()` for auth

## Quick Reference

| Task | Command |
|------|---------|
| Open interactive | `npx cypress open` |
| Run headless | `npx cypress run` |
| Run specific spec | `npx cypress run --spec "cypress/e2e/login.cy.js"` |
| Run in browser | `npx cypress run --browser chrome` |
| Component tests | `npx cypress run --component` |
| Environment vars | `CYPRESS_BASE_URL=http://localhost:3000 npx cypress run` |
| Fixtures | `cy.fixture('users.json').then(data => ...)` |
| File upload | `cy.get('input[type="file"]').selectFile('file.pdf')` |
| Viewport | `cy.viewport('iphone-x')` or `cy.viewport(1280, 720)` |
| Screenshot | `cy.screenshot('login-page')` |

## Reference Files

| File | When to Read |
|------|-------------|
| `reference/cloud-integration.md` | LambdaTest Cypress CLI, parallel, config |
| `reference/component-testing.md` | React/Vue/Angular component tests |
| `reference/custom-commands.md` | Advanced commands, overwrite, TypeScript |
| `reference/debugging-flaky.md` | Retry-ability, detached DOM, race conditions |

## Advanced Playbook

For production-grade patterns, see `reference/playbook.md`:

| Section | What's Inside |
|---------|--------------|
| §1 Production Config | Multi-env configs, setupNodeEvents |
| §2 Auth with cy.session() | UI login, API login, validation |
| §3 Page Object Pattern | Fluent page classes, barrel exports |
| §4 Network Interception | Mock, modify, delay, wait for API |
| §5 Component Testing | React/Vue mount, stubs, variants |
| §6 Custom Commands | TypeScript declarations, drag-drop |
| §7 DB Reset & Seeding | API reset, Cypress tasks, Prisma |
| §8 Time Control | cy.clock(), cy.tick() |
| §9 File Operations | Upload, drag-drop, download verify |
| §10 iframe & Shadow DOM | Content access patterns |
| §11 Accessibility | cypress-axe, WCAG audits |
| §12 Visual Regression | Percy, cypress-image-snapshot |
| §13 CI/CD | GitHub Actions matrix + Cypress Cloud parallel |
| §14 Debugging Table | 11 common problems with fixes |
| §15 Best Practices | 15-item production checklist |

````


### `reference/cloud-integration.md`

````markdown
# Cypress — TestMu AI Cloud Integration

For full device catalog, capabilities, and LT:Options reference, see [shared/testmu-cloud-reference.md](../../shared/testmu-cloud-reference.md).

## Setup

```bash
npm install lambdatest-cypress-cli --save-dev
npx lambdatest-cypress init
```

This creates `lambdatest-config.json`:

```json
{
  "lambdatest_auth": {
    "username": "${LT_USERNAME}",
    "access_key": "${LT_ACCESS_KEY}"
  },
  "browsers": [
    {
      "browser": "Chrome",
      "platform": "Windows 11",
      "versions": ["latest"]
    },
    {
      "browser": "MicrosoftEdge",
      "platform": "Windows 11",
      "versions": ["latest"]
    },
    {
      "browser": "Firefox",
      "platform": "macOS Sequoia",
      "versions": ["latest"]
    }
  ],
  "run_settings": {
    "cypress_config_file": "cypress.config.js",
    "build_name": "Cypress Cloud Build",
    "parallels": 5,
    "specs": "cypress/e2e/**/*.cy.{js,ts}",
    "ignore_files": "",
    "network": true,
    "headless": true,
    "npm_dependencies": {}
  },
  "tunnel_settings": {
    "tunnel": false,
    "tunnelName": ""
  }
}
```

## Run on Cloud

```bash
# All specs
npx lambdatest-cypress run

# Specific specs
npx lambdatest-cypress run --specs "cypress/e2e/login.cy.js"

# With tunnel (for localhost)
npx lambdatest-cypress run --tunnel
```

## Parallel Execution

Set `parallels` in config. Tests distribute across browsers automatically.

```json
{
  "run_settings": {
    "parallels": 10
  }
}
```

````


### `reference/component-testing.md`

````markdown
# Cypress — Component Testing

## React Setup

```bash
npm install cypress @cypress/react --save-dev
```

```javascript
// cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  component: {
    devServer: {
      framework: 'react',
      bundler: 'vite', // or 'webpack'
    },
  },
});
```

## React Component Test

```javascript
import { mount } from '@cypress/react';
import Button from './Button';

describe('Button', () => {
  it('renders with text', () => {
    mount(<Button label="Click Me" />);
    cy.get('button').should('contain', 'Click Me');
  });

  it('calls onClick handler', () => {
    const onClick = cy.stub().as('click');
    mount(<Button label="Click" onClick={onClick} />);
    cy.get('button').click();
    cy.get('@click').should('have.been.calledOnce');
  });
});
```

## Vue Component Test

```javascript
import { mount } from '@cypress/vue';
import Counter from './Counter.vue';

describe('Counter', () => {
  it('increments count', () => {
    mount(Counter);
    cy.get('[data-cy="count"]').should('contain', '0');
    cy.get('[data-cy="increment"]').click();
    cy.get('[data-cy="count"]').should('contain', '1');
  });
});
```

## Run Component Tests

```bash
npx cypress run --component
npx cypress open --component  # interactive
```

````


### `reference/custom-commands.md`

````markdown
# Cypress — Custom Commands

## Basic Custom Command

```javascript
// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
  cy.session([email, password], () => {
    cy.visit('/login');
    cy.get('[data-cy="email"]').type(email);
    cy.get('[data-cy="password"]').type(password);
    cy.get('[data-cy="submit"]').click();
    cy.url().should('include', '/dashboard');
  });
});

// API-based login (faster)
Cypress.Commands.add('loginViaApi', (email, password) => {
  cy.request('POST', '/api/auth/login', { email, password }).then((response) => {
    window.localStorage.setItem('token', response.body.token);
  });
});
```

## TypeScript Support

```typescript
// cypress/support/index.d.ts
declare namespace Cypress {
  interface Chainable {
    login(email: string, password: string): Chainable<void>;
    loginViaApi(email: string, password: string): Chainable<void>;
    getByDataCy(selector: string): Chainable<JQuery<HTMLElement>>;
  }
}
```

## Utility Commands

```javascript
Cypress.Commands.add('getByDataCy', (selector) => {
  cy.get(`[data-cy="${selector}"]`);
});

Cypress.Commands.add('shouldBeAccessible', () => {
  cy.injectAxe();
  cy.checkA11y();
});
```

````


### `reference/debugging-flaky.md`

````markdown
# Cypress — Debugging Flaky Tests

## Retry-ability

Cypress automatically retries `.should()` assertions. But NOT action commands.

```javascript
// ✅ This retries automatically
cy.get('.loading').should('not.exist');
cy.get('.results').should('have.length.greaterThan', 0);

// ❌ This does NOT retry — if element not found, it fails immediately
cy.get('.results').click(); // fails if not ready
```

## Common Causes

1. **Race conditions with API calls**
```javascript
// ❌ Bad
cy.visit('/dashboard');
cy.get('.data-table'); // might not be loaded yet

// ✅ Good
cy.intercept('GET', '/api/data').as('getData');
cy.visit('/dashboard');
cy.wait('@getData');
cy.get('.data-table').should('exist');
```

2. **Detached from DOM**
```javascript
// ❌ Element re-rendered by React
cy.get('.item').then($el => {
  // ... page re-renders ...
  cy.wrap($el).click(); // detached!
});

// ✅ Re-query after action
cy.get('.item').first().click();
cy.get('.item').first().should('have.class', 'selected');
```

3. **Animation interference**
```javascript
// cypress.config.js
module.exports = defineConfig({
  e2e: {
    // Disable CSS animations in test
    setupNodeEvents(on, config) {
      on('before:browser:launch', (browser, launchOptions) => {
        // disable animations
      });
    },
  },
  // Or add to test
  animationDistanceThreshold: 20,
});
```

## Debug Tools

```bash
npx cypress open              # Interactive mode with time travel
DEBUG=cypress:* npx cypress run  # Verbose logging
```

```javascript
// In test
cy.pause();  // Pause test execution
cy.debug();  // Open DevTools debugger
```

````


### `reference/playbook.md`

````markdown
# Cypress — Advanced Implementation Playbook

## §1 — Production Configuration

```typescript
// cypress.config.ts
import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: process.env.CYPRESS_BASE_URL || 'http://localhost:3000',
    specPattern: 'cypress/e2e/**/*.cy.{js,ts}',
    supportFile: 'cypress/support/e2e.ts',
    viewportWidth: 1280,
    viewportHeight: 720,
    video: true,
    screenshotOnRunFailure: true,
    retries: { runMode: 2, openMode: 0 },
    defaultCommandTimeout: 10000,
    requestTimeout: 15000,
    responseTimeout: 30000,
    env: {
      apiUrl: 'http://localhost:3001/api',
    },
    setupNodeEvents(on, config) {
      on('before:browser:launch', (browser, launchOptions) => {
        if (browser.name === 'chrome' && browser.isHeadless) {
          launchOptions.args.push('--disable-gpu');
        }
        return launchOptions;
      });
      // Load environment config
      const envName = config.env.envName || 'staging';
      const envConfig = require(`./cypress/config/${envName}.json`);
      return { ...config, ...envConfig };
    },
  },
  component: {
    devServer: { framework: 'react', bundler: 'vite' },
  },
});
```

```json
// cypress/config/staging.json
{
  "baseUrl": "https://staging.example.com",
  "env": { "apiUrl": "https://staging-api.example.com" }
}
// cypress/config/production.json
{
  "baseUrl": "https://www.example.com",
  "env": { "apiUrl": "https://api.example.com" }
}
// Run: npx cypress run --env envName=production
```

## §2 — Auth with cy.session()

```typescript
// cypress/support/commands.ts
Cypress.Commands.add('login', (email: string, password: string) => {
  cy.session([email, password], () => {
    cy.visit('/login');
    cy.get('[data-testid="email"]').type(email);
    cy.get('[data-testid="password"]').type(password);
    cy.get('[data-testid="submit"]').click();
    cy.url().should('include', '/dashboard');
  }, {
    validate() {
      cy.getCookie('session').should('exist');
    },
  });
});

// Fast API login (for non-login tests)
Cypress.Commands.add('apiLogin', (email: string, password: string) => {
  cy.session(['api', email], () => {
    cy.request('POST', '/api/auth/login', { email, password }).then(resp => {
      window.localStorage.setItem('token', resp.body.token);
    });
  });
});
```

## §3 — Page Object / App Actions Pattern

```typescript
// cypress/pages/LoginPage.ts
export class LoginPage {
  visit() {
    cy.visit('/login');
    return this;
  }

  typeEmail(email: string) {
    cy.getByTestId('email').clear().type(email);
    return this;
  }

  typePassword(password: string) {
    cy.getByTestId('password').clear().type(password);
    return this;
  }

  submit() {
    cy.getByTestId('submit').click();
    return this;
  }

  loginAs(email: string, password: string) {
    this.typeEmail(email).typePassword(password).submit();
    return this;
  }

  assertError(message: string) {
    cy.getByTestId('error-message').should('contain', message);
    return this;
  }
}

// cypress/pages/index.ts — barrel export
export { LoginPage } from './LoginPage';
export { DashboardPage } from './DashboardPage';
export { ProductsPage } from './ProductsPage';

// Usage in test
import { LoginPage } from '../pages';

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

  it('logs in with valid credentials', () => {
    loginPage.visit().loginAs('user@test.com', 'password123');
    cy.url().should('include', '/dashboard');
  });

  it('shows error for invalid credentials', () => {
    loginPage.visit().loginAs('bad@test.com', 'wrong');
    loginPage.assertError('Invalid credentials');
  });
});
```

## §4 — Network Interception Patterns

```typescript
// Mock API response
cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('getProducts');
cy.visit('/products');
cy.wait('@getProducts');
cy.get('[data-testid="product-card"]').should('have.length', 3);

// Error simulation
cy.intercept('GET', '/api/products', {
  statusCode: 500,
  body: { error: 'Server error' },
}).as('serverError');
cy.visit('/products');
cy.wait('@serverError');
cy.getByTestId('error-banner').should('contain', 'Something went wrong');

// Modify real response
cy.intercept('GET', '/api/products', (req) => {
  req.continue((res) => {
    res.body.push({ id: 999, name: 'Injected Product' });
    res.send();
  });
});

// Wait for real API and validate
cy.intercept('POST', '/api/orders').as('createOrder');
cy.getByTestId('checkout').click();
cy.wait('@createOrder').its('response.statusCode').should('eq', 201);
cy.wait('@createOrder').its('response.body.orderId').should('exist');

// Delay response (test loading states)
cy.intercept('GET', '/api/search*', (req) => {
  req.reply({ delay: 2000, fixture: 'search-results.json' });
});
cy.get('.loading-spinner').should('be.visible');
cy.get('.loading-spinner').should('not.exist');
```

## §5 — Component Testing

```typescript
// cypress/component/Button.cy.tsx
import Button from '../../src/components/Button';

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

  it('calls onClick when clicked', () => {
    const onClick = cy.stub().as('onClick');
    cy.mount(<Button label="Submit" onClick={onClick} />);
    cy.get('button').click();
    cy.get('@onClick').should('have.been.calledOnce');
  });

  it('renders disabled state', () => {
    cy.mount(<Button label="Save" disabled />);
    cy.get('button').should('be.disabled');
  });

  it('applies variant styles', () => {
    cy.mount(<Button label="Danger" variant="danger" />);
    cy.get('button').should('have.class', 'btn-danger');
  });
});
```

## §6 — Custom TypeScript Commands

```typescript
// cypress/support/commands.ts
declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string): Chainable<void>;
      apiLogin(email: string, password: string): Chainable<void>;
      getByTestId(id: string): Chainable<JQuery<HTMLElement>>;
      dragTo(target: string): Chainable<void>;
      shouldBeWithinViewport(): Chainable<void>;
    }
  }
}

Cypress.Commands.add('getByTestId', (id: string) => {
  return cy.get(`[data-testid="${id}"]`);
});

Cypress.Commands.add('dragTo', { prevSubject: 'element' }, (subject, target) => {
  cy.wrap(subject).trigger('dragstart');
  cy.get(target).trigger('drop');
  cy.wrap(subject).trigger('dragend');
});

Cypress.Commands.add('shouldBeWithinViewport', { prevSubject: true }, (subject) => {
  const rect = subject[0].getBoundingClientRect();
  expect(rect.top).to.be.greaterThan(0);
  expect(rect.left).to.be.greaterThan(0);
  expect(rect.bottom).to.be.lessThan(Cypress.config('viewportHeight'));
  expect(rect.right).to.be.lessThan(Cypress.config('viewportWidth'));
  return cy.wrap(subject);
});
```

## §7 — Database Reset & Seeding

```typescript
// cypress/support/e2e.ts
beforeEach(() => {
  cy.request('POST', '/api/test/reset');      // reset DB
  cy.request('POST', '/api/test/seed');        // seed test data
});

// Or via Cypress task (Node.js level)
// cypress.config.ts
setupNodeEvents(on) {
  on('task', {
    async resetDB() {
      const { execSync } = require('child_process');
      execSync('npx prisma migrate reset --force');
      return null;
    },
    async seedDB(fixture: string) {
      const data = require(`./cypress/fixtures/${fixture}`);
      // insert into DB...
      return null;
    },
  });
}

// Usage: cy.task('resetDB');
```

## §8 — Time & Clock Control

```typescript
cy.clock(new Date('2024-12-25T00:00:00').getTime());
cy.visit('/promotions');
cy.get('.holiday-banner').should('be.visible');

cy.clock();
cy.getByTestId('start-timer').click();
cy.tick(60000); // advance 1 minute
cy.getByTestId('timer').should('contain', '1:00');
```

## §9 — File Operations

```typescript
// Upload
cy.get('input[type=file]').selectFile('cypress/fixtures/test.pdf');
cy.get('input[type=file]').selectFile('cypress/fixtures/test.pdf', { action: 'drag-drop' });

// Multiple files
cy.get('input[type=file]').selectFile(['file1.pdf', 'file2.pdf']);

// Download verification
cy.getByTestId('download-btn').click();
cy.readFile('cypress/downloads/report.pdf').should('exist');
```

## §10 — iframe & Shadow DOM

```typescript
// iframe access
cy.get('iframe#payment').its('0.contentDocument.body').should('not.be.empty')
  .then(cy.wrap)
  .find('#card-number')
  .type('4111111111111111');

// Shadow DOM
cy.get('my-web-component')
  .shadow()
  .find('.inner-button')
  .click();
```

## §11 — Accessibility Testing

```typescript
// Install: npm install -D cypress-axe axe-core
// cypress/support/e2e.ts: import 'cypress-axe';

describe('Accessibility', () => {
  beforeEach(() => {
    cy.visit('/');
    cy.injectAxe();
  });

  it('homepage has no a11y violations', () => {
    cy.checkA11y(null, {
      runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'] },
    });
  });

  it('form elements are accessible', () => {
    cy.checkA11y('#contact-form', {
      rules: { 'color-contrast': { enabled: true } },
    });
  });

  it('logs violations without failing', () => {
    cy.checkA11y(null, null, (violations) => {
      cy.task('log', `${violations.length} accessibility violation(s)`);
      violations.forEach(v => cy.task('log', `  - ${v.id}: ${v.description}`));
    });
  });
});
```

## §12 — Visual Regression (with Percy or cypress-image-snapshot)

```typescript
// With Percy: npm install -D @percy/cypress
// cypress/support/e2e.ts: import '@percy/cypress';

it('homepage visual snapshot', () => {
  cy.visit('/');
  cy.percySnapshot('Homepage');
});

// With cypress-image-snapshot
it('component visual test', () => {
  cy.visit('/components');
  cy.getByTestId('product-card').first().matchImageSnapshot('product-card', {
    failureThreshold: 0.01,
    failureThresholdType: 'percent',
  });
});
```

## §13 — CI/CD Integration

```yaml
name: Cypress Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        browser: [chrome, firefox, edge]
    steps:
      - uses: actions/checkout@v4
      - uses: cypress-io/github-action@v6
        with:
          browser: ${{ matrix.browser }}
          start: npm start
          wait-on: 'http://localhost:3000'
          wait-on-timeout: 60
          record: true
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: cypress-${{ matrix.browser }}
          path: |
            cypress/screenshots
            cypress/videos

  # Parallel with Cypress Cloud
  parallel:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        containers: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: cypress-io/github-action@v6
        with:
          record: true
          parallel: true
          group: 'CI Parallel'
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
```

## §14 — Debugging Quick-Reference

| Problem | Cause | Fix |
|---------|-------|-----|
| `cy.get()` timeout | Element not in DOM yet | Use `should('be.visible')` assertion, check selectors |
| Detached from DOM | Element re-rendered mid-chain | Break chain, re-query after action |
| Cross-origin error | Visiting different superdomain | Use `cy.origin()` for cross-origin |
| `cy.intercept` not matching | Wrong URL pattern or method | Use DevTools Network tab to verify exact URL |
| File upload fails | Hidden input, custom upload widget | Use `selectFile({ action: 'drag-drop' })` or `force: true` |
| Flaky `cy.wait(ms)` | Hardcoded wait | Replace with `cy.wait('@alias')` or assertion |
| Session not restored | Validate fn failing | Check `cy.session` validate callback |
| `cy.click()` on hidden element | Element covered or `display:none` | Use `{ force: true }` or scroll into view |
| Tests interfere with each other | Shared state/cookies | Use `cy.session()` and `beforeEach` reset |
| Component test mount fails | Missing providers | Wrap in context providers during `cy.mount` |
| Iframe not accessible | Cypress doesn't natively enter iframes | Use `.its('0.contentDocument.body')` pattern |

## §15 — Best Practices Checklist

- ✅ NEVER use `cy.wait(ms)` — use `cy.wait('@alias')` or assertions
- ✅ Login via API for non-login tests: `cy.request()` + `cy.session()`
- ✅ Use `data-testid` for selectors, avoid brittle CSS paths
- ✅ Use `cy.intercept()` to mock external APIs
- ✅ Use `retries: { runMode: 2 }` in CI for resilience
- ✅ Use `cy.session()` for auth caching across tests
- ✅ Use fixtures for test data, never hardcode
- ✅ Use `cy.clock()`/`cy.tick()` for time-dependent tests
- ✅ Avoid testing third-party sites/services
- ✅ Keep tests independent — no shared state
- ✅ Use environment configs for multi-env testing
- ✅ Use component testing for isolated UI unit tests
- ✅ Use `cypress-axe` for accessibility audits in pipeline
- ✅ Use TypeScript for custom commands with proper declarations
- ✅ Structure: `e2e/`, `component/`, `pages/`, `fixtures/`, `support/`

````


### `scripts/scaffold-project.sh`

```
#!/bin/bash
set -e
PROJECT_NAME="${1:-cypress-project}"
if [ -d "$PROJECT_NAME" ]; then echo "Error: Directory exists"; exit 2; fi
command -v npx >/dev/null 2>&1 || { echo "Error: npx not found"; exit 1; }

mkdir -p "$PROJECT_NAME"
cd "$PROJECT_NAME"
npm init -y > /dev/null 2>&1
npm install cypress --save-dev
mkdir -p cypress/e2e cypress/support cypress/fixtures

cat > cypress.config.js << 'EOF'
const { defineConfig } = require('cypress');
module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    viewportWidth: 1280,
    viewportHeight: 720,
    video: false,
    screenshotOnRunFailure: true,
  },
});
EOF

cat > cypress/e2e/example.cy.js << 'EOF'
describe('Example', () => {
  it('visits the app', () => {
    cy.visit('/');
    cy.get('h1').should('be.visible');
  });
});
EOF

echo "✅ Cypress project '$PROJECT_NAME' created"
exit 0

```


### `templates/cypress.config.js`

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

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    viewportWidth: 1280,
    viewportHeight: 720,
    video: false,
    screenshotOnRunFailure: true,
    retries: { runMode: 2, openMode: 0 },
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
  },
  component: {
    devServer: {
      framework: 'react',
      bundler: 'vite',
    },
  },
});

```
