<objective Production-grade Cypress test suites in TypeScript. The failure this prevents: tests written as if Cypress commands ran synchronously (storing in a variable, ), and tests that flake because they wait on instead of a network alias. This skill covers the mental model (command queue, retry-ability), project structure, custom commands, network control with , component testing, cross-origin auth with , and Cypress Cloud / CI integration. </objective Quick Route | You need to... | Go to | |----------------|-------| | Write an E2E spec (load, intercept, assert) | Core Principles + | | Add…

, ''));\n expect(price).to.be.greaterThan(0);\n});\n```\n\n### 2. Retry-ability Is Built-In (For Queries, Not Actions)\n\nCypress automatically retries **queries** (`cy.get`, `cy.find`, `cy.contains`) and **assertions** until they pass or time out. It does **not** retry **actions** (`cy.click`, `cy.type`, `cy.select`):\n\n- `cy.get('.loading').should('not.exist')` waits for the indicator to disappear\n- `cy.get('.item').should('have.length', 5)` waits for 5 items\n- `cy.click()` executes once -- if the element is not actionable, it fails\n\n### 3. Network Control with cy.intercept\n\n`cy.intercept` intercepts HTTP requests at the network layer -- stub responses, wait for requests to complete, assert on request bodies. Mastering it is the difference between flaky and stable tests. Always wait on a network alias or a DOM assertion, never a fixed `cy.wait(ms)`.\n\n### 4. Isolation: Each Test Starts Clean\n\nEvery `it()` runs in fresh browser state -- Cypress clears cookies, localStorage, and sessionStorage between tests by default. Tests must not depend on other tests' state or order. Use `beforeEach` for shared setup, not inter-test dependencies.\n\n### 5. Data Attributes for Test Selectors\n\nUse `data-testid`, `data-cy`, or `data-test`. They survive CSS refactors, class renames, and localization. Configure the preferred attribute in `cypress.config.ts`.\n\n---\n\n## Project Structure & Configuration\n\nStandard layout splits `e2e/`, `component/`, `fixtures/`, and `support/`, with `cypress.config.ts` at the root configuring both runners. Key config choices: `baseUrl` from env (never hardcode for CI), explicit viewport, `retries.runMode: 2` for CI, and the framework/bundler pair under `component.devServer`. Component specs live under `cypress/component/**/*.cy.tsx`.\n\nSee `references/config-and-commands.md` for the directory tree, the complete `cypress.config.ts`, and the `tsconfig.json` additions.\n\n---\n\n## Custom Commands\n\nCustom commands encapsulate repeated actions behind a clean, typed API. Common commands: `login` (via `cy.session` + API, not UI), a `getByTestId` selector shorthand, and assertion helpers like `shouldShowToast`. Declare them in `cypress/support/index.d.ts` (`declare namespace Cypress { interface Chainable { ... } }` with JSDoc `@example`) so they get autocomplete and compile-time checking.\n\n**`cy.session` needs a `validate()` callback.** `cy.session` caches cookies/localStorage/sessionStorage automatically, but without `validate()` the cached session is never re-verified -- a stale or expired token silently reuses a dead session. Always pass a `validate()` that hits an authenticated endpoint (e.g. `cy.request('/api/me').its('status').should('eq', 200)`).\n\n**Retryable lookups use `Cypress.Commands.addQuery()`, and the callback must be a non-arrow `function () {}`** -- Cypress binds `this` to apply the command timeout, so an arrow function silently breaks retry-ability. (Intercept handlers are the opposite: `(req) => {}` is fine there because they do not use `this`.)\n\nSee `references/config-and-commands.md` for the full command definitions, the `validate()` callback, the `addQuery` example, and the TypeScript declarations.\n\n---\n\n## cy.intercept Patterns\n\n`cy.intercept` covers the full network-control surface:\n\n- **Stub a response** — return canned data with `{ statusCode, body }`, then `cy.wait('@alias')`.\n- **Spy without stubbing** — `cy.intercept('POST', '/api/orders').as('createOrder')`, then assert on `interception.request.body` and `interception.response?.statusCode`.\n- **Conditional responses** — drive a closure with `callCount` to simulate polling (202 → 200). The handler arrow function `(req) => { ... }` is correct here.\n- **Network errors** — `{ statusCode: 500 }`, `{ forceNetworkError: true }`, or `req.reply({ delay })` for slow responses.\n- **Modify real responses** — `req.continue((res) => { ...; res.send(); })`.\n- **Fixture-backed** — `{ fixture: 'api-responses/checkout-success.json' }`.\n\nRegister the intercept **before** the action that triggers the request, or the alias never matches.\n\nSee `references/intercept-patterns.md` for runnable code for each, plus cross-origin flows.\n\n---\n\n## Component Testing\n\nComponent testing mounts a single component in a real browser without running the full app -- faster than E2E, more visual feedback than unit tests. Use `cy.mount(\u003cComponent .../>)`, pass `cy.stub()`/`cy.spy()` for callbacks, and assert with the same `cy.contains`/`cy.get` chain you use in E2E. Never use `cy.visit` in a component test. For Vue, use `cy.mount(Component, { props: { ... } })`.\n\nSee `references/component-and-fixtures.md` for a full React `ProductCard` component-test suite.\n\n---\n\n## Data-Driven Testing with Fixtures\n\nThree layers, depending on where the data comes from:\n\n- **Static fixtures** — `cy.fixture('users').as('users')` for JSON that rarely changes; read it via `this.users` in a `beforeEach(function () { ... })`.\n- **Dynamic data via `cy.task`** — register Node-side tasks in `setupNodeEvents` for API calls or DB seeding that must run outside the browser (task bodies run in Node, so `fetch` there is Node's global fetch, not a Cypress API).\n- **Environment-specific config** — merge a per-environment `baseUrl` map in `setupNodeEvents`, selected by `--env ENVIRONMENT=...`.\n\nSee `references/component-and-fixtures.md` for the fixture, `cy.task` seeding, and env-config code.\n\n---\n\n## Cross-Origin Flows\n\nFor legitimate redirects to another domain (SSO, OAuth providers, a separate auth host), wrap the commands that run on the other origin in `cy.origin`. This replaced the old `chromeWebSecurity: false` / `experimentalSessionAndOrigin` escape hatches -- do not disable web security to work around a redirect. This is distinct from third-party payment iframes (Stripe/PayPal), which you stub with `cy.intercept` and never reach into.\n\nSee `references/intercept-patterns.md` (Cross-Origin Flows) for the `cy.origin` example.\n\n---\n\n## CI Integration\n\n**Action version:** pin to `cypress-io/github-action@v7` (latest 7.2.0, May 2026). v7 runs under Node 24 and is the current major; use `@v6` only on a Node 20 runner (the legacy branch).\n\n> **Cypress / Node support:** Current is Cypress 15.x, which supports Node 20, 22, and 24 (Node 18 and 23 dropped). Node 20 removal is a future Cypress 16 / action-v7.2 concern tracking the Node 20 EOL (2026-04-30), not something Cypress 15 did.\n\n- **With Cypress Cloud:** set `projectId`, run `npx cypress run --record --key $CYPRESS_RECORD_KEY`, and parallelize across a container matrix (`fail-fast: false`) for flake detection, Test Replay, and analytics.\n- **Without Cloud:** use `cypress-io/github-action@v7` with `build`/`start`/`wait-on`, and upload `cypress/screenshots` + `cypress/videos` as artifacts on failure.\n\nSee `references/ci-recipes.md` for both complete GitHub Actions workflows.\n\n**Cypress AI (paid Cloud add-on, GA 2026)** ships Auto Heal (selector self-healing), AI Test Generation, and AI Bug Triage. Now GA and worth knowing: `cy.prompt` (English-to-test authoring with runtime self-healing) and **Cloud MCP** (GA May 2026, free on all Cloud plans) — an MCP server that feeds recorded-run errors, stack traces, and Test Replay links to your AI assistant. This overlaps `test-reliability` (selector healing), `ai-bug-triage` (failure clustering), and `ai-test-generation` (authoring). If the team is already on Cypress Cloud, buying the add-on may be cheaper than building the equivalent -- flag it during framework selection.\n\n---\n\n## Anti-Patterns\n\n### 1. cy.wait(milliseconds) for Synchronization\n\n```typescript\n// BAD\ncy.get('[data-testid=\"submit\"]').click();\ncy.wait(3000);\n\n// GOOD -- wait for network\ncy.intercept('POST', '/api/submit').as('submit');\ncy.get('[data-testid=\"submit\"]').click();\ncy.wait('@submit');\n```\n\nOnly acceptable for throttle/debounce testing. Everything else waits on a network alias or a DOM assertion.\n\n### 2. Conditional Testing Based on DOM State\n\nDo not check `$body.find(selector).length > 0` to conditionally act. Control state deterministically -- stub the API that drives the conditional element.\n\n### 3. CSS Selectors Over Data Attributes\n\n`cy.get('.btn.btn-primary > span')` breaks on every CSS refactor. Use `cy.getByTestId('submit')` or `cy.contains('button', 'Place Order')`.\n\n### 4. Sharing State Between Tests\n\nModule-level `let orderId` set in one `it()` and read in another creates order-dependent, parallel-unsafe tests. Each test sets up its own data via `cy.request` or `cy.task` in `beforeEach`.\n\n### 5. Testing Third-Party Iframes\n\nDo not reach into Stripe/PayPal iframes. Mock the payment API with `cy.intercept` and assert on your own UI.\n\n### 6. Not Using cy.session() for Login (or Omitting validate())\n\nUI login in every test is slow and fragile. Use `cy.session` to authenticate via API once and cache it -- with a `validate()` callback so an expired token does not silently reuse a dead session.\n\n### 7. Arrow Function in addQuery\n\nA custom query written with `addQuery('name', (arg) => { ... })` silently loses its retry timeout because Cypress needs `this`. Use `function (arg) { ... }`.\n\n### 8. Running All Tests Serially in CI\n\nParallelize once the suite exceeds 5 minutes -- Cypress Cloud, `cypress-split`, or manual sharding across a CI matrix.\n\n---\n\n## Failure Modes\n\n| Symptom | Likely cause | Fix |\n|---------|--------------|-----|\n| `element is detached from the DOM` | A yielded element was reused after a re-render | Re-query inside `.should`/`.then` instead of holding the old reference |\n| `cy.intercept` did not match / alias never resolves | Wrong method or glob, or intercept registered after the action | Register the intercept before the triggering command; verify method + URL glob |\n| `cross origin` error on redirect | Login or flow crosses to another domain | Wrap the other-origin commands in `cy.origin`; do not disable `chromeWebSecurity` |\n| Session reused but user is logged out | `cy.session` has no `validate()`, token expired | Add a `validate()` callback that hits an authenticated endpoint |\n| Custom query never times out / retries forever | `addQuery` callback is an arrow function | Convert to `function () {}` so Cypress can bind `this` |\n\n---\n\n## Verification\n\nProve the suite runs before calling it done:\n\n- `npx cypress verify` — confirms the Cypress binary is installed and runnable.\n- `npx cypress run --spec \"cypress/e2e/\u003cfile>.cy.ts\"` — should exit 0 (headless, in CI mode).\n- `npx cypress run --component --spec \"cypress/component/\u003cFile>.cy.tsx\"` — component specs exit 0.\n- `npx tsc --noEmit` — custom-command declarations in `index.d.ts` compile against the test files.\n\n---\n\n## Done When\n\n- `cypress.config.ts` exists with a `baseUrl` from env (not hardcoded `localhost` in CI) and explicit `viewportWidth`/`viewportHeight`; `npx cypress verify` passes.\n- Custom commands extracted to `cypress/support/commands.ts` with TypeScript declarations in `cypress/support/index.d.ts`; `npx tsc --noEmit` exits 0.\n- The `login` command uses `cy.session` with a `validate()` callback.\n- No `cy.wait(\u003cnumber>)` for synchronization in the suite (`grep -rn \"cy.wait([0-9]\" cypress/` returns nothing except documented throttle/debounce cases).\n- Component specs live under `cypress/component/**/*.cy.tsx` and run with `npx cypress run --component` exiting 0.\n- E2E specs pass in CI (`npx cypress run` exits 0) with either a recorded Cypress Cloud run (parallel) or local video/screenshot artifacts uploaded on failure.\n\n## Reference Files (in `references/`)\n\n- **config-and-commands.md** — Project directory tree, full `cypress.config.ts`, and custom commands (`cy.session` + `validate()`, the non-arrow `addQuery`) with TypeScript declarations.\n- **intercept-patterns.md** — Every `cy.intercept` recipe (stub, spy, conditional/polling, error simulation, response modification, fixture-backed) plus cross-origin flows with `cy.origin`.\n- **component-and-fixtures.md** — React component-test suite plus data-driven testing (static fixtures, `cy.task` seeding, env-specific config).\n- **ci-recipes.md** — GitHub Actions workflows with and without Cypress Cloud, on `@v7`, with parallelization and artifact upload.\n\n## Related Skills\n\n- **playwright-automation** — Use instead of this skill when the suite is Playwright, not Cypress. Same E2E goals, different runner and API.\n- **test-reliability** — Go here for runtime per-test flake healing, self-healing locators, and quarantine. This skill writes stable tests; that one repairs failing ones.\n- **selector-drift-recovery** — Bulk-regenerate broken selectors after a UI refactor or redesign; this skill is for authoring, not mass repair.\n- **test-migration** — Converting Selenium/other suites to Cypress.\n- **ci-cd-integration** — Pipeline templates for running Cypress in GitHub Actions / GitLab CI, parallelization, and artifact management.\n- **visual-testing** — Visual regression to complement Cypress functional tests; Cypress has no built-in pixel comparison.\n- **unit-testing** — Jest/Vitest for logic that needs no browser; Cypress component tests fill the gap between unit and E2E.\n- **test-data-management** — Seeding, managing, and cleaning up the test data Cypress tests consume.\n- **qa-project-context** — The project context file capturing framework choices, CI platform, and conventions.","attachment_filenames":["references/ci-recipes.md","references/component-and-fixtures.md","references/config-and-commands.md","references/intercept-patterns.md"],"attachments":[{"filename":"references/ci-recipes.md","content":"# Cypress CI Recipes\n\nGitHub Actions workflows for Cypress, with and without Cypress Cloud. The decision prose on when to use Cloud and version-pinning guidance lives in `SKILL.md`.\n\n**Action version:** pin to `cypress-io/github-action@v7` (latest is 7.2.0, May 2026; v7 runs under Node 24, removes the legacy Node 20 path). Use `@v6` only if your runner is still on Node 20 — v6 is the Node-20 legacy branch, not \"current LTS\".\n\n## With Cypress Cloud\n\nSet `projectId` in `cypress.config.ts`. Run with `npx cypress run --record --key $CYPRESS_RECORD_KEY`. Cloud provides parallelization, flake detection, test replay, and analytics.\n\n```yaml\n# GitHub Actions -- Cloud parallelization\njobs:\n cypress:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n matrix:\n containers: [1, 2, 3, 4]\n steps:\n - uses: actions/checkout@v4\n - uses: cypress-io/github-action@v7\n with:\n record: true\n parallel: true\n group: 'E2E Tests'\n env:\n CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}\n```\n\n## Without Cloud\n\n```yaml\n# GitHub Actions -- standalone\nsteps:\n - uses: actions/checkout@v4\n - uses: cypress-io/github-action@v7\n with:\n build: npm run build\n start: npm run start\n wait-on: 'http://localhost:3000'\n browser: chrome\n - uses: actions/upload-artifact@v4\n if: failure()\n with:\n name: cypress-artifacts\n path: |\n cypress/screenshots\n cypress/videos\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1495,"content_sha256":"e14959f9080140244775685bec3e74f748dba6ce38d2a44d875179bfde7af7d9"},{"filename":"references/component-and-fixtures.md","content":"# Component Testing and Data-Driven Fixtures\n\nComponent-mount test code and data-driven testing patterns (static fixtures, `cy.task` seeding, env-specific config). The prose on when component tests beat E2E/unit lives in `SKILL.md`.\n\n## Component Testing\n\nComponent testing mounts a single component in a real browser without running the full application. It is faster than E2E and gives more visual feedback than unit tests.\n\n### React Component Test\n\n```tsx\n// cypress/component/ProductCard.cy.tsx\nimport { ProductCard } from '../../src/components/ProductCard';\n\ndescribe('ProductCard', () => {\n const product = { id: '1', name: 'Widget', price: 29.99, image: '/widget.png' };\n\n it('renders product information', () => {\n cy.mount(\u003cProductCard product={product} onAddToCart={cy.stub()} />);\n\n cy.contains('Widget').should('be.visible');\n cy.contains('$29.99').should('be.visible');\n cy.get('img').should('have.attr', 'src', '/widget.png');\n });\n\n it('calls onAddToCart with product id when button clicked', () => {\n const onAddToCart = cy.stub().as('addToCart');\n cy.mount(\u003cProductCard product={product} onAddToCart={onAddToCart} />);\n\n cy.contains('button', 'Add to Cart').click();\n cy.get('@addToCart').should('have.been.calledOnceWith', '1');\n });\n\n it('shows out of stock state', () => {\n cy.mount(\u003cProductCard product={{ ...product, inStock: false }} onAddToCart={cy.stub()} />);\n\n cy.contains('button', 'Add to Cart').should('be.disabled');\n cy.contains('Out of Stock').should('be.visible');\n });\n});\n```\n\nFor Vue, use `cy.mount(Component, { props: { ... } })` with `cy.spy()` for event assertions. The pattern mirrors React but uses Vue's prop/event conventions.\n\n## Data-Driven Testing with Fixtures\n\n### Static Fixture Data\n\n```typescript\n// Load from cypress/fixtures/users.json\ndescribe('Role-based access', () => {\n beforeEach(function () {\n cy.fixture('users').as('users');\n });\n\n it('admin sees admin panel', function () {\n const admin = this.users.find((u: { role: string }) => u.role === 'admin');\n cy.login(admin.email, admin.password);\n cy.visit('/admin');\n cy.getByTestId('admin-panel').should('be.visible');\n });\n});\n```\n\n### Dynamic Test Data via cy.task\n\nUse `cy.task` for operations that need Node.js context (API calls, database seeding). The task body runs in the Node process, so `fetch` below is Node's global fetch (available on Node 18+, fine for Cypress 15's Node 20+ baseline) -- not a Cypress API:\n\n```typescript\n// cypress.config.ts -- register tasks in setupNodeEvents\non('task', {\n async seedTestUser(role: string) {\n const response = await fetch(`${config.env.API_URL}/test/seed-user`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ role }),\n });\n return response.json();\n },\n});\n\n// In test:\nbeforeEach(() => {\n cy.task('seedTestUser', 'admin').then((user) => {\n cy.login(user.email, user.password);\n });\n});\n```\n\n### Environment-Specific Configuration\n\n```typescript\n// Run with: npx cypress run --env ENVIRONMENT=staging\nsetupNodeEvents(on, config) {\n const envConfig = { local: { baseUrl: 'http://localhost:3000' }, staging: { baseUrl: 'https://staging.example.com' } };\n return { ...config, ...envConfig[config.env.ENVIRONMENT || 'local'] };\n}\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3329,"content_sha256":"0b033f48671cd50d85daec12bb5673d2d285d6807cf328ea8e21e47acbbbc508"},{"filename":"references/config-and-commands.md","content":"# Cypress Config, Project Structure, and Custom Commands\n\nRunnable scaffolding for a Cypress + TypeScript project. The mental model, decision prose, and principles live in `SKILL.md`; this file holds the implementations.\n\n## Project Structure\n\n```\nproject-root/\n├── cypress.config.ts\n├── cypress/\n│ ├── e2e/ # E2E test specs, organized by feature\n│ ├── component/ # Component test specs (*.cy.tsx)\n│ ├── fixtures/ # Static test data (JSON), including api-responses/\n│ ├── support/\n│ │ ├── commands.ts # Custom commands\n│ │ ├── e2e.ts # E2E support file\n│ │ ├── component.ts # Component support file\n│ │ └── index.d.ts # TypeScript declarations for custom commands\n│ └── downloads/ # Git-ignored\n├── cypress.env.json # Git-ignored, environment-specific variables\n└── tsconfig.json\n```\n\n## cypress.config.ts\n\n```typescript\nimport { defineConfig } from 'cypress';\n\nexport default defineConfig({\n projectId: process.env.CYPRESS_PROJECT_ID, // For Cypress Cloud\n\n e2e: {\n baseUrl: process.env.CYPRESS_BASE_URL ?? 'http://localhost:3000',\n specPattern: 'cypress/e2e/**/*.cy.ts',\n supportFile: 'cypress/support/e2e.ts',\n viewportWidth: 1280,\n viewportHeight: 720,\n defaultCommandTimeout: 10000,\n requestTimeout: 15000,\n responseTimeout: 30000,\n video: false, // Enable in CI if needed\n screenshotOnRunFailure: true,\n retries: {\n runMode: 2, // Retries in CI (cypress run)\n openMode: 0, // No retries in interactive mode\n },\n // experimentalRunAllSpecs is no longer needed — stabilized in Cypress 14;\n // the \"Run all specs\" tab is the default in 15.x.\n setupNodeEvents(on, config) {\n // Task plugins, code coverage, etc.\n return config;\n },\n },\n\n component: {\n devServer: {\n framework: 'react', // 'react' | 'vue' | 'angular' | 'svelte'\n bundler: 'vite', // 'vite' | 'webpack'\n },\n specPattern: 'cypress/component/**/*.cy.tsx',\n supportFile: 'cypress/support/component.ts',\n },\n});\n```\n\nAdd `\"types\": [\"cypress\"]` to `tsconfig.json` compilerOptions and include `\"cypress/**/*.ts\"` in the `include` array.\n\n## Custom Commands\n\nCustom commands encapsulate repeated actions and provide a clean API. Always type them for autocomplete and compile-time safety.\n\n### Defining Commands\n\n```typescript\n// cypress/support/commands.ts\n\n// Login command -- avoid UI login in every test\nCypress.Commands.add('login', (email: string, password: string) => {\n cy.session(\n [email, password],\n () => {\n cy.request({\n method: 'POST',\n url: '/api/auth/login',\n body: { email, password },\n }).then((response) => {\n expect(response.status).to.eq(200);\n window.localStorage.setItem('auth_token', response.body.token);\n });\n },\n {\n // Re-verify the cached session before reusing it. Without validate(),\n // a stale/expired token silently reuses a dead session.\n validate() {\n cy.request('/api/me').its('status').should('eq', 200);\n },\n },\n );\n});\n\n// Data attribute selector shorthand\nCypress.Commands.add('getByTestId', (testId: string) => {\n return cy.get(`[data-testid=\"${testId}\"]`);\n});\n\n// Assert toast notification appears and disappears\nCypress.Commands.add('shouldShowToast', (message: string) => {\n cy.get('[role=\"alert\"]')\n .should('be.visible')\n .and('contain.text', message);\n cy.get('[role=\"alert\"]').should('not.exist');\n});\n```\n\n### TypeScript Declarations\n\n```typescript\n// cypress/support/index.d.ts\n\ndeclare namespace Cypress {\n interface Chainable {\n /**\n * Log in via API and cache the session.\n * @example cy.login('[email protected]', 'password123')\n */\n login(email: string, password: string): Chainable\u003cvoid>;\n\n /**\n * Select element by data-testid attribute.\n * @example cy.getByTestId('submit-button').click()\n */\n getByTestId(testId: string): Chainable\u003cJQuery\u003cHTMLElement>>;\n\n /**\n * Assert a toast notification appears with the given message.\n * @example cy.shouldShowToast('Profile updated')\n */\n shouldShowToast(message: string): Chainable\u003cvoid>;\n }\n}\n```\n\n### Retryable Custom Queries\n\nFor retryable element lookups, use `Cypress.Commands.addQuery()` instead of `Cypress.Commands.add()` -- custom queries retry automatically like built-in queries. The query callback **must** be a non-arrow `function () {}`: Cypress binds `this` to apply the command timeout, and an arrow function silently breaks retry-ability.\n\n```typescript\n// cypress/support/commands.ts\nCypress.Commands.addQuery('getByTestId', function (testId: string) {\n const getFn = cy.now('get', `[data-testid=\"${testId}\"]`);\n return (subject) => getFn(subject);\n});\n```\n\nThis is the opposite rule from `cy.intercept` handlers, where an arrow function `(req) => { ... }` is fine -- intercept handlers do not rely on `this`.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5160,"content_sha256":"20ddca89005621939c2b5c6b05cd7447e3d0b7ac03c1790593b1289fc25cf5e6"},{"filename":"references/intercept-patterns.md","content":"# cy.intercept Patterns\n\nNetwork control with `cy.intercept` — stubbing, spying, conditional responses, error simulation, response modification, and fixture-backed responses. The decision prose on when to stub vs. spy lives in `SKILL.md`.\n\n## Stub a Response\n\n```typescript\ncy.intercept('GET', '/api/products', {\n statusCode: 200,\n body: { products: [{ id: '1', name: 'Widget', price: 29.99 }] },\n}).as('getProducts');\n\ncy.visit('/products');\ncy.wait('@getProducts');\ncy.getByTestId('product-card').should('have.length', 1);\n```\n\n## Spy on Requests (No Stubbing)\n\n```typescript\ncy.intercept('POST', '/api/orders').as('createOrder');\n\ncy.getByTestId('place-order').click();\ncy.wait('@createOrder').then((interception) => {\n expect(interception.request.body).to.have.property('items');\n expect(interception.request.body.items).to.have.length(2);\n expect(interception.response?.statusCode).to.eq(201);\n});\n```\n\n## Conditional Responses\n\n```typescript\nlet callCount = 0;\ncy.intercept('GET', '/api/status', (req) => {\n callCount += 1;\n if (callCount \u003c= 2) {\n req.reply({ statusCode: 202, body: { status: 'processing' } });\n } else {\n req.reply({ statusCode: 200, body: { status: 'complete', url: '/download/report.pdf' } });\n }\n}).as('pollStatus');\n\ncy.visit('/jobs/123');\n// The app polls until status flips; wait on the alias for each poll, then assert the UI settled.\ncy.wait('@pollStatus');\ncy.wait('@pollStatus');\ncy.wait('@pollStatus');\ncy.contains('Report ready').should('be.visible');\n```\n\n## Simulate Network Errors\n\n```typescript\n// Simulate server error\ncy.intercept('POST', '/api/checkout', { statusCode: 500, body: { error: 'Internal Server Error' } }).as('checkoutFail');\n\n// Simulate network failure, then verify the error-handling UI\ncy.intercept('POST', '/api/checkout', { forceNetworkError: true }).as('networkError');\n\ncy.getByTestId('place-order').click();\ncy.wait('@networkError');\ncy.contains('Something went wrong. Please try again.').should('be.visible');\n\n// Simulate slow response\ncy.intercept('GET', '/api/dashboard', (req) => {\n req.reply({\n delay: 5000,\n statusCode: 200,\n body: { widgets: [] },\n });\n}).as('slowDashboard');\n```\n\n## Modify Real Response\n\n```typescript\ncy.intercept('GET', '/api/feature-flags', (req) => {\n req.continue((res) => {\n res.body.flags['new-checkout'] = true;\n res.send();\n });\n}).as('featureFlags');\n```\n\n## Using Fixture Files\n\n```typescript\n// Load response from cypress/fixtures/api-responses/checkout-success.json\ncy.intercept('POST', '/api/checkout', { fixture: 'api-responses/checkout-success.json' }).as('checkout');\n```\n\n## Cross-Origin Flows with cy.origin\n\nFor legitimate cross-origin redirects (SSO, OAuth providers, an auth domain separate from your app), wrap the commands that run on the other origin in `cy.origin`. This replaced the old `chromeWebSecurity: false` / `experimentalSessionAndOrigin` flags -- do not disable web security to work around a redirect.\n\n```typescript\ncy.visit('/login');\ncy.contains('button', 'Sign in with SSO').click();\n\ncy.origin('https://auth.example.com', () => {\n cy.get('#username').type('[email protected]');\n cy.get('#password').type('password123', { log: false });\n cy.contains('button', 'Continue').click();\n});\n\n// back on the app origin\ncy.url().should('include', '/dashboard');\n```\n\nThis is distinct from third-party payment iframes (Stripe/PayPal): those you stub with `cy.intercept` and never reach into. `cy.origin` is for redirects to domains you control or trust, not embedded iframes.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3536,"content_sha256":"1c596343e3bee857d78a3e372b9b71fad513dd6888693effc12ab4c0c417030d"}],"content_json":{"type":"doc","content":[{"type":"paragraph","content":[{"text":"\u003cobjective> Production-grade Cypress test suites in TypeScript. The failure this prevents: tests written as if Cypress commands ran synchronously (storing ","type":"text"},{"text":"cy.get()","type":"text","marks":[{"type":"code_inline"}]},{"text":" in a variable, ","type":"text"},{"text":"await cy.click()","type":"text","marks":[{"type":"code_inline"}]},{"text":"), and tests that flake because they wait on ","type":"text"},{"text":"cy.wait(3000)","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead of a network alias. This skill covers the mental model (command queue, retry-ability), project structure, custom commands, network control with ","type":"text"},{"text":"cy.intercept","type":"text","marks":[{"type":"code_inline"}]},{"text":", component testing, cross-origin auth with ","type":"text"},{"text":"cy.origin","type":"text","marks":[{"type":"code_inline"}]},{"text":", and Cypress Cloud / CI integration. \u003c/objective>","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Route","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"You need to...","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Go to","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Write an E2E spec (load, intercept, assert)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Core Principles + ","type":"text"},{"text":"references/intercept-patterns.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add a typed custom command / ","type":"text"},{"text":"cy.session","type":"text","marks":[{"type":"code_inline"}]},{"text":" login","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Custom Commands + ","type":"text"},{"text":"references/config-and-commands.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mount and test a single component","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Component Testing + ","type":"text"},{"text":"references/component-and-fixtures.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Scaffold ","type":"text"},{"text":"cypress.config.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":" / project layout","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Project Structure + ","type":"text"},{"text":"references/config-and-commands.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stub, spy, simulate errors, or poll an API","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cy.intercept Patterns + ","type":"text"},{"text":"references/intercept-patterns.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run in CI / parallelize on Cypress Cloud","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI Integration + ","type":"text"},{"text":"references/ci-recipes.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Handle an SSO / OAuth redirect","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cross-Origin Flows + ","type":"text"},{"text":"references/intercept-patterns.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Discovery Questions","type":"text"}]},{"type":"paragraph","content":[{"text":"Check ","type":"text"},{"text":".agents/qa-project-context.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" first -- if it exists, use it and skip questions already answered there.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Component testing, E2E, or both?","type":"text","marks":[{"type":"strong"}]},{"text":" Component testing mounts individual components in isolation; E2E tests the full app through the browser. Most projects need both. Component testing requires a framework-specific mount (React, Vue, Angular, Svelte).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cypress Cloud?","type":"text","marks":[{"type":"strong"}]},{"text":" Cloud provides parallelization, flake detection, analytics, Test Replay, and the AI add-on. If the team uses it, configure ","type":"text"},{"text":"projectId","type":"text","marks":[{"type":"code_inline"}]},{"text":" and the record key. If not, everything runs locally or in CI without Cloud.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"TypeScript?","type":"text","marks":[{"type":"strong"}]},{"text":" Strongly recommended and the default here -- Cypress supports it natively. All examples use TypeScript.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Framework and bundler?","type":"text","marks":[{"type":"strong"}]},{"text":" React + Vite, Next.js + Webpack, Vue + Vite, Angular -- component-testing config depends on this.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cross-origin auth?","type":"text","marks":[{"type":"strong"}]},{"text":" If login redirects to a separate domain (SSO, OAuth provider), you need ","type":"text"},{"text":"cy.origin","type":"text","marks":[{"type":"code_inline"}]},{"text":". Note it now so the login command is built for it.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Existing suite or fresh start?","type":"text","marks":[{"type":"strong"}]},{"text":" If migrating, start with the flakiest or most critical tests, not a big-bang rewrite (see ","type":"text"},{"text":"test-migration","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Principles","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Commands Are Enqueued, Not Executed Immediately","type":"text"}]},{"type":"paragraph","content":[{"text":"The single most important concept. Cypress commands (","type":"text"},{"text":"cy.get","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cy.click","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cy.type","type":"text","marks":[{"type":"code_inline"}]},{"text":") do not execute when called -- they are added to a queue and run serially, asynchronously. You cannot use ","type":"text"},{"text":"async/await","type":"text","marks":[{"type":"code_inline"}]},{"text":" with Cypress commands, and you cannot store the return value in a variable.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"typescript"},"content":[{"text":"// WRONG -- this looks synchronous but is not\nconst button = cy.get('[data-testid=\"submit\"]'); // button is a Chainable, not an element\nbutton.click(); // works only by accident, via chaining\n\n// CORRECT -- chain commands; use .then() when you need a value\ncy.get('[data-testid=\"submit\"]').click();\n\ncy.get('[data-testid=\"price\"]').invoke('text').then((text) => {\n const price = parseFloat(text.replace('

<objective Production-grade Cypress test suites in TypeScript. The failure this prevents: tests written as if Cypress commands ran synchronously (storing in a variable, ), and tests that flake because they wait on instead of a network alias. This skill covers the mental model (command queue, retry-ability), project structure, custom commands, network control with , component testing, cross-origin auth with , and Cypress Cloud / CI integration. </objective Quick Route | You need to... | Go to | |----------------|-------| | Write an E2E spec (load, intercept, assert) | Core Principles + | | Add…

, ''));\n expect(price).to.be.greaterThan(0);\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Retry-ability Is Built-In (For Queries, Not Actions)","type":"text"}]},{"type":"paragraph","content":[{"text":"Cypress automatically retries ","type":"text"},{"text":"queries","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"cy.get","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cy.find","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cy.contains","type":"text","marks":[{"type":"code_inline"}]},{"text":") and ","type":"text"},{"text":"assertions","type":"text","marks":[{"type":"strong"}]},{"text":" until they pass or time out. It does ","type":"text"},{"text":"not","type":"text","marks":[{"type":"strong"}]},{"text":" retry ","type":"text"},{"text":"actions","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"cy.click","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cy.type","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cy.select","type":"text","marks":[{"type":"code_inline"}]},{"text":"):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cy.get('.loading').should('not.exist')","type":"text","marks":[{"type":"code_inline"}]},{"text":" waits for the indicator to disappear","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cy.get('.item').should('have.length', 5)","type":"text","marks":[{"type":"code_inline"}]},{"text":" waits for 5 items","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cy.click()","type":"text","marks":[{"type":"code_inline"}]},{"text":" executes once -- if the element is not actionable, it fails","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Network Control with cy.intercept","type":"text"}]},{"type":"paragraph","content":[{"text":"cy.intercept","type":"text","marks":[{"type":"code_inline"}]},{"text":" intercepts HTTP requests at the network layer -- stub responses, wait for requests to complete, assert on request bodies. Mastering it is the difference between flaky and stable tests. Always wait on a network alias or a DOM assertion, never a fixed ","type":"text"},{"text":"cy.wait(ms)","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Isolation: Each Test Starts Clean","type":"text"}]},{"type":"paragraph","content":[{"text":"Every ","type":"text"},{"text":"it()","type":"text","marks":[{"type":"code_inline"}]},{"text":" runs in fresh browser state -- Cypress clears cookies, localStorage, and sessionStorage between tests by default. Tests must not depend on other tests' state or order. Use ","type":"text"},{"text":"beforeEach","type":"text","marks":[{"type":"code_inline"}]},{"text":" for shared setup, not inter-test dependencies.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. Data Attributes for Test Selectors","type":"text"}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"data-testid","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"data-cy","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"data-test","type":"text","marks":[{"type":"code_inline"}]},{"text":". They survive CSS refactors, class renames, and localization. Configure the preferred attribute in ","type":"text"},{"text":"cypress.config.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Project Structure & Configuration","type":"text"}]},{"type":"paragraph","content":[{"text":"Standard layout splits ","type":"text"},{"text":"e2e/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"component/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"fixtures/","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":"support/","type":"text","marks":[{"type":"code_inline"}]},{"text":", with ","type":"text"},{"text":"cypress.config.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":" at the root configuring both runners. Key config choices: ","type":"text"},{"text":"baseUrl","type":"text","marks":[{"type":"code_inline"}]},{"text":" from env (never hardcode for CI), explicit viewport, ","type":"text"},{"text":"retries.runMode: 2","type":"text","marks":[{"type":"code_inline"}]},{"text":" for CI, and the framework/bundler pair under ","type":"text"},{"text":"component.devServer","type":"text","marks":[{"type":"code_inline"}]},{"text":". Component specs live under ","type":"text"},{"text":"cypress/component/**/*.cy.tsx","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/config-and-commands.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the directory tree, the complete ","type":"text"},{"text":"cypress.config.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":", and the ","type":"text"},{"text":"tsconfig.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" additions.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Custom Commands","type":"text"}]},{"type":"paragraph","content":[{"text":"Custom commands encapsulate repeated actions behind a clean, typed API. Common commands: ","type":"text"},{"text":"login","type":"text","marks":[{"type":"code_inline"}]},{"text":" (via ","type":"text"},{"text":"cy.session","type":"text","marks":[{"type":"code_inline"}]},{"text":" + API, not UI), a ","type":"text"},{"text":"getByTestId","type":"text","marks":[{"type":"code_inline"}]},{"text":" selector shorthand, and assertion helpers like ","type":"text"},{"text":"shouldShowToast","type":"text","marks":[{"type":"code_inline"}]},{"text":". Declare them in ","type":"text"},{"text":"cypress/support/index.d.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"declare namespace Cypress { interface Chainable { ... } }","type":"text","marks":[{"type":"code_inline"}]},{"text":" with JSDoc ","type":"text"},{"text":"@example","type":"text","marks":[{"type":"code_inline"}]},{"text":") so they get autocomplete and compile-time checking.","type":"text"}]},{"type":"paragraph","content":[{"text":"cy.session","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" needs a ","type":"text","marks":[{"type":"strong"}]},{"text":"validate()","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" callback.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"cy.session","type":"text","marks":[{"type":"code_inline"}]},{"text":" caches cookies/localStorage/sessionStorage automatically, but without ","type":"text"},{"text":"validate()","type":"text","marks":[{"type":"code_inline"}]},{"text":" the cached session is never re-verified -- a stale or expired token silently reuses a dead session. Always pass a ","type":"text"},{"text":"validate()","type":"text","marks":[{"type":"code_inline"}]},{"text":" that hits an authenticated endpoint (e.g. ","type":"text"},{"text":"cy.request('/api/me').its('status').should('eq', 200)","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"Retryable lookups use ","type":"text","marks":[{"type":"strong"}]},{"text":"Cypress.Commands.addQuery()","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":", and the callback must be a non-arrow ","type":"text","marks":[{"type":"strong"}]},{"text":"function () {}","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- Cypress binds ","type":"text"},{"text":"this","type":"text","marks":[{"type":"code_inline"}]},{"text":" to apply the command timeout, so an arrow function silently breaks retry-ability. (Intercept handlers are the opposite: ","type":"text"},{"text":"(req) => {}","type":"text","marks":[{"type":"code_inline"}]},{"text":" is fine there because they do not use ","type":"text"},{"text":"this","type":"text","marks":[{"type":"code_inline"}]},{"text":".)","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/config-and-commands.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the full command definitions, the ","type":"text"},{"text":"validate()","type":"text","marks":[{"type":"code_inline"}]},{"text":" callback, the ","type":"text"},{"text":"addQuery","type":"text","marks":[{"type":"code_inline"}]},{"text":" example, and the TypeScript declarations.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"cy.intercept Patterns","type":"text"}]},{"type":"paragraph","content":[{"text":"cy.intercept","type":"text","marks":[{"type":"code_inline"}]},{"text":" covers the full network-control surface:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stub a response","type":"text","marks":[{"type":"strong"}]},{"text":" — return canned data with ","type":"text"},{"text":"{ statusCode, body }","type":"text","marks":[{"type":"code_inline"}]},{"text":", then ","type":"text"},{"text":"cy.wait('@alias')","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Spy without stubbing","type":"text","marks":[{"type":"strong"}]},{"text":" — ","type":"text"},{"text":"cy.intercept('POST', '/api/orders').as('createOrder')","type":"text","marks":[{"type":"code_inline"}]},{"text":", then assert on ","type":"text"},{"text":"interception.request.body","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"interception.response?.statusCode","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Conditional responses","type":"text","marks":[{"type":"strong"}]},{"text":" — drive a closure with ","type":"text"},{"text":"callCount","type":"text","marks":[{"type":"code_inline"}]},{"text":" to simulate polling (202 → 200). The handler arrow function ","type":"text"},{"text":"(req) => { ... }","type":"text","marks":[{"type":"code_inline"}]},{"text":" is correct here.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Network errors","type":"text","marks":[{"type":"strong"}]},{"text":" — ","type":"text"},{"text":"{ statusCode: 500 }","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"{ forceNetworkError: true }","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"req.reply({ delay })","type":"text","marks":[{"type":"code_inline"}]},{"text":" for slow responses.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Modify real responses","type":"text","marks":[{"type":"strong"}]},{"text":" — ","type":"text"},{"text":"req.continue((res) => { ...; res.send(); })","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fixture-backed","type":"text","marks":[{"type":"strong"}]},{"text":" — ","type":"text"},{"text":"{ fixture: 'api-responses/checkout-success.json' }","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Register the intercept ","type":"text"},{"text":"before","type":"text","marks":[{"type":"strong"}]},{"text":" the action that triggers the request, or the alias never matches.","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/intercept-patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for runnable code for each, plus cross-origin flows.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Component Testing","type":"text"}]},{"type":"paragraph","content":[{"text":"Component testing mounts a single component in a real browser without running the full app -- faster than E2E, more visual feedback than unit tests. Use ","type":"text"},{"text":"cy.mount(\u003cComponent .../>)","type":"text","marks":[{"type":"code_inline"}]},{"text":", pass ","type":"text"},{"text":"cy.stub()","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"cy.spy()","type":"text","marks":[{"type":"code_inline"}]},{"text":" for callbacks, and assert with the same ","type":"text"},{"text":"cy.contains","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"cy.get","type":"text","marks":[{"type":"code_inline"}]},{"text":" chain you use in E2E. Never use ","type":"text"},{"text":"cy.visit","type":"text","marks":[{"type":"code_inline"}]},{"text":" in a component test. For Vue, use ","type":"text"},{"text":"cy.mount(Component, { props: { ... } })","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/component-and-fixtures.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for a full React ","type":"text"},{"text":"ProductCard","type":"text","marks":[{"type":"code_inline"}]},{"text":" component-test suite.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Data-Driven Testing with Fixtures","type":"text"}]},{"type":"paragraph","content":[{"text":"Three layers, depending on where the data comes from:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Static fixtures","type":"text","marks":[{"type":"strong"}]},{"text":" — ","type":"text"},{"text":"cy.fixture('users').as('users')","type":"text","marks":[{"type":"code_inline"}]},{"text":" for JSON that rarely changes; read it via ","type":"text"},{"text":"this.users","type":"text","marks":[{"type":"code_inline"}]},{"text":" in a ","type":"text"},{"text":"beforeEach(function () { ... })","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Dynamic data via ","type":"text","marks":[{"type":"strong"}]},{"text":"cy.task","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" — register Node-side tasks in ","type":"text"},{"text":"setupNodeEvents","type":"text","marks":[{"type":"code_inline"}]},{"text":" for API calls or DB seeding that must run outside the browser (task bodies run in Node, so ","type":"text"},{"text":"fetch","type":"text","marks":[{"type":"code_inline"}]},{"text":" there is Node's global fetch, not a Cypress API).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Environment-specific config","type":"text","marks":[{"type":"strong"}]},{"text":" — merge a per-environment ","type":"text"},{"text":"baseUrl","type":"text","marks":[{"type":"code_inline"}]},{"text":" map in ","type":"text"},{"text":"setupNodeEvents","type":"text","marks":[{"type":"code_inline"}]},{"text":", selected by ","type":"text"},{"text":"--env ENVIRONMENT=...","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/component-and-fixtures.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the fixture, ","type":"text"},{"text":"cy.task","type":"text","marks":[{"type":"code_inline"}]},{"text":" seeding, and env-config code.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Cross-Origin Flows","type":"text"}]},{"type":"paragraph","content":[{"text":"For legitimate redirects to another domain (SSO, OAuth providers, a separate auth host), wrap the commands that run on the other origin in ","type":"text"},{"text":"cy.origin","type":"text","marks":[{"type":"code_inline"}]},{"text":". This replaced the old ","type":"text"},{"text":"chromeWebSecurity: false","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"experimentalSessionAndOrigin","type":"text","marks":[{"type":"code_inline"}]},{"text":" escape hatches -- do not disable web security to work around a redirect. This is distinct from third-party payment iframes (Stripe/PayPal), which you stub with ","type":"text"},{"text":"cy.intercept","type":"text","marks":[{"type":"code_inline"}]},{"text":" and never reach into.","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/intercept-patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Cross-Origin Flows) for the ","type":"text"},{"text":"cy.origin","type":"text","marks":[{"type":"code_inline"}]},{"text":" example.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"CI Integration","type":"text"}]},{"type":"paragraph","content":[{"text":"Action version:","type":"text","marks":[{"type":"strong"}]},{"text":" pin to ","type":"text"},{"text":"cypress-io/github-action@v7","type":"text","marks":[{"type":"code_inline"}]},{"text":" (latest 7.2.0, May 2026). v7 runs under Node 24 and is the current major; use ","type":"text"},{"text":"@v6","type":"text","marks":[{"type":"code_inline"}]},{"text":" only on a Node 20 runner (the legacy branch).","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Cypress / Node support:","type":"text","marks":[{"type":"strong"}]},{"text":" Current is Cypress 15.x, which supports Node 20, 22, and 24 (Node 18 and 23 dropped). Node 20 removal is a future Cypress 16 / action-v7.2 concern tracking the Node 20 EOL (2026-04-30), not something Cypress 15 did.","type":"text"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"With Cypress Cloud:","type":"text","marks":[{"type":"strong"}]},{"text":" set ","type":"text"},{"text":"projectId","type":"text","marks":[{"type":"code_inline"}]},{"text":", run ","type":"text"},{"text":"npx cypress run --record --key $CYPRESS_RECORD_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":", and parallelize across a container matrix (","type":"text"},{"text":"fail-fast: false","type":"text","marks":[{"type":"code_inline"}]},{"text":") for flake detection, Test Replay, and analytics.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Without Cloud:","type":"text","marks":[{"type":"strong"}]},{"text":" use ","type":"text"},{"text":"cypress-io/github-action@v7","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"build","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"start","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"wait-on","type":"text","marks":[{"type":"code_inline"}]},{"text":", and upload ","type":"text"},{"text":"cypress/screenshots","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"cypress/videos","type":"text","marks":[{"type":"code_inline"}]},{"text":" as artifacts on failure.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/ci-recipes.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for both complete GitHub Actions workflows.","type":"text"}]},{"type":"paragraph","content":[{"text":"Cypress AI (paid Cloud add-on, GA 2026)","type":"text","marks":[{"type":"strong"}]},{"text":" ships Auto Heal (selector self-healing), AI Test Generation, and AI Bug Triage. Now GA and worth knowing: ","type":"text"},{"text":"cy.prompt","type":"text","marks":[{"type":"code_inline"}]},{"text":" (English-to-test authoring with runtime self-healing) and ","type":"text"},{"text":"Cloud MCP","type":"text","marks":[{"type":"strong"}]},{"text":" (GA May 2026, free on all Cloud plans) — an MCP server that feeds recorded-run errors, stack traces, and Test Replay links to your AI assistant. This overlaps ","type":"text"},{"text":"test-reliability","type":"text","marks":[{"type":"code_inline"}]},{"text":" (selector healing), ","type":"text"},{"text":"ai-bug-triage","type":"text","marks":[{"type":"code_inline"}]},{"text":" (failure clustering), and ","type":"text"},{"text":"ai-test-generation","type":"text","marks":[{"type":"code_inline"}]},{"text":" (authoring). If the team is already on Cypress Cloud, buying the add-on may be cheaper than building the equivalent -- flag it during framework selection.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Anti-Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. cy.wait(milliseconds) for Synchronization","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"typescript"},"content":[{"text":"// BAD\ncy.get('[data-testid=\"submit\"]').click();\ncy.wait(3000);\n\n// GOOD -- wait for network\ncy.intercept('POST', '/api/submit').as('submit');\ncy.get('[data-testid=\"submit\"]').click();\ncy.wait('@submit');","type":"text"}]},{"type":"paragraph","content":[{"text":"Only acceptable for throttle/debounce testing. Everything else waits on a network alias or a DOM assertion.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Conditional Testing Based on DOM State","type":"text"}]},{"type":"paragraph","content":[{"text":"Do not check ","type":"text"},{"text":"$body.find(selector).length > 0","type":"text","marks":[{"type":"code_inline"}]},{"text":" to conditionally act. Control state deterministically -- stub the API that drives the conditional element.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. CSS Selectors Over Data Attributes","type":"text"}]},{"type":"paragraph","content":[{"text":"cy.get('.btn.btn-primary > span')","type":"text","marks":[{"type":"code_inline"}]},{"text":" breaks on every CSS refactor. Use ","type":"text"},{"text":"cy.getByTestId('submit')","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"cy.contains('button', 'Place Order')","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Sharing State Between Tests","type":"text"}]},{"type":"paragraph","content":[{"text":"Module-level ","type":"text"},{"text":"let orderId","type":"text","marks":[{"type":"code_inline"}]},{"text":" set in one ","type":"text"},{"text":"it()","type":"text","marks":[{"type":"code_inline"}]},{"text":" and read in another creates order-dependent, parallel-unsafe tests. Each test sets up its own data via ","type":"text"},{"text":"cy.request","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"cy.task","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"beforeEach","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. Testing Third-Party Iframes","type":"text"}]},{"type":"paragraph","content":[{"text":"Do not reach into Stripe/PayPal iframes. Mock the payment API with ","type":"text"},{"text":"cy.intercept","type":"text","marks":[{"type":"code_inline"}]},{"text":" and assert on your own UI.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"6. Not Using cy.session() for Login (or Omitting validate())","type":"text"}]},{"type":"paragraph","content":[{"text":"UI login in every test is slow and fragile. Use ","type":"text"},{"text":"cy.session","type":"text","marks":[{"type":"code_inline"}]},{"text":" to authenticate via API once and cache it -- with a ","type":"text"},{"text":"validate()","type":"text","marks":[{"type":"code_inline"}]},{"text":" callback so an expired token does not silently reuse a dead session.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"7. Arrow Function in addQuery","type":"text"}]},{"type":"paragraph","content":[{"text":"A custom query written with ","type":"text"},{"text":"addQuery('name', (arg) => { ... })","type":"text","marks":[{"type":"code_inline"}]},{"text":" silently loses its retry timeout because Cypress needs ","type":"text"},{"text":"this","type":"text","marks":[{"type":"code_inline"}]},{"text":". Use ","type":"text"},{"text":"function (arg) { ... }","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"8. Running All Tests Serially in CI","type":"text"}]},{"type":"paragraph","content":[{"text":"Parallelize once the suite exceeds 5 minutes -- Cypress Cloud, ","type":"text"},{"text":"cypress-split","type":"text","marks":[{"type":"code_inline"}]},{"text":", or manual sharding across a CI matrix.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Failure Modes","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Symptom","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Likely cause","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"element is detached from the DOM","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A yielded element was reused after a re-render","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Re-query inside ","type":"text"},{"text":".should","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":".then","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead of holding the old reference","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cy.intercept","type":"text","marks":[{"type":"code_inline"}]},{"text":" did not match / alias never resolves","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wrong method or glob, or intercept registered after the action","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Register the intercept before the triggering command; verify method + URL glob","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cross origin","type":"text","marks":[{"type":"code_inline"}]},{"text":" error on redirect","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Login or flow crosses to another domain","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wrap the other-origin commands in ","type":"text"},{"text":"cy.origin","type":"text","marks":[{"type":"code_inline"}]},{"text":"; do not disable ","type":"text"},{"text":"chromeWebSecurity","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Session reused but user is logged out","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cy.session","type":"text","marks":[{"type":"code_inline"}]},{"text":" has no ","type":"text"},{"text":"validate()","type":"text","marks":[{"type":"code_inline"}]},{"text":", token expired","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add a ","type":"text"},{"text":"validate()","type":"text","marks":[{"type":"code_inline"}]},{"text":" callback that hits an authenticated endpoint","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Custom query never times out / retries forever","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"addQuery","type":"text","marks":[{"type":"code_inline"}]},{"text":" callback is an arrow function","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Convert to ","type":"text"},{"text":"function () {}","type":"text","marks":[{"type":"code_inline"}]},{"text":" so Cypress can bind ","type":"text"},{"text":"this","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Verification","type":"text"}]},{"type":"paragraph","content":[{"text":"Prove the suite runs before calling it done:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"npx cypress verify","type":"text","marks":[{"type":"code_inline"}]},{"text":" — confirms the Cypress binary is installed and runnable.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"npx cypress run --spec \"cypress/e2e/\u003cfile>.cy.ts\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" — should exit 0 (headless, in CI mode).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"npx cypress run --component --spec \"cypress/component/\u003cFile>.cy.tsx\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" — component specs exit 0.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"npx tsc --noEmit","type":"text","marks":[{"type":"code_inline"}]},{"text":" — custom-command declarations in ","type":"text"},{"text":"index.d.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":" compile against the test files.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Done When","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cypress.config.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":" exists with a ","type":"text"},{"text":"baseUrl","type":"text","marks":[{"type":"code_inline"}]},{"text":" from env (not hardcoded ","type":"text"},{"text":"localhost","type":"text","marks":[{"type":"code_inline"}]},{"text":" in CI) and explicit ","type":"text"},{"text":"viewportWidth","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"viewportHeight","type":"text","marks":[{"type":"code_inline"}]},{"text":"; ","type":"text"},{"text":"npx cypress verify","type":"text","marks":[{"type":"code_inline"}]},{"text":" passes.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Custom commands extracted to ","type":"text"},{"text":"cypress/support/commands.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":" with TypeScript declarations in ","type":"text"},{"text":"cypress/support/index.d.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":"; ","type":"text"},{"text":"npx tsc --noEmit","type":"text","marks":[{"type":"code_inline"}]},{"text":" exits 0.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"login","type":"text","marks":[{"type":"code_inline"}]},{"text":" command uses ","type":"text"},{"text":"cy.session","type":"text","marks":[{"type":"code_inline"}]},{"text":" with a ","type":"text"},{"text":"validate()","type":"text","marks":[{"type":"code_inline"}]},{"text":" callback.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No ","type":"text"},{"text":"cy.wait(\u003cnumber>)","type":"text","marks":[{"type":"code_inline"}]},{"text":" for synchronization in the suite (","type":"text"},{"text":"grep -rn \"cy.wait([0-9]\" cypress/","type":"text","marks":[{"type":"code_inline"}]},{"text":" returns nothing except documented throttle/debounce cases).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Component specs live under ","type":"text"},{"text":"cypress/component/**/*.cy.tsx","type":"text","marks":[{"type":"code_inline"}]},{"text":" and run with ","type":"text"},{"text":"npx cypress run --component","type":"text","marks":[{"type":"code_inline"}]},{"text":" exiting 0.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"E2E specs pass in CI (","type":"text"},{"text":"npx cypress run","type":"text","marks":[{"type":"code_inline"}]},{"text":" exits 0) with either a recorded Cypress Cloud run (parallel) or local video/screenshot artifacts uploaded on failure.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Reference Files (in ","type":"text"},{"text":"references/","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"config-and-commands.md","type":"text","marks":[{"type":"strong"}]},{"text":" — Project directory tree, full ","type":"text"},{"text":"cypress.config.ts","type":"text","marks":[{"type":"code_inline"}]},{"text":", and custom commands (","type":"text"},{"text":"cy.session","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"validate()","type":"text","marks":[{"type":"code_inline"}]},{"text":", the non-arrow ","type":"text"},{"text":"addQuery","type":"text","marks":[{"type":"code_inline"}]},{"text":") with TypeScript declarations.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"intercept-patterns.md","type":"text","marks":[{"type":"strong"}]},{"text":" — Every ","type":"text"},{"text":"cy.intercept","type":"text","marks":[{"type":"code_inline"}]},{"text":" recipe (stub, spy, conditional/polling, error simulation, response modification, fixture-backed) plus cross-origin flows with ","type":"text"},{"text":"cy.origin","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"component-and-fixtures.md","type":"text","marks":[{"type":"strong"}]},{"text":" — React component-test suite plus data-driven testing (static fixtures, ","type":"text"},{"text":"cy.task","type":"text","marks":[{"type":"code_inline"}]},{"text":" seeding, env-specific config).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ci-recipes.md","type":"text","marks":[{"type":"strong"}]},{"text":" — GitHub Actions workflows with and without Cypress Cloud, on ","type":"text"},{"text":"@v7","type":"text","marks":[{"type":"code_inline"}]},{"text":", with parallelization and artifact upload.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Related Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"playwright-automation","type":"text","marks":[{"type":"strong"}]},{"text":" — Use instead of this skill when the suite is Playwright, not Cypress. Same E2E goals, different runner and API.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"test-reliability","type":"text","marks":[{"type":"strong"}]},{"text":" — Go here for runtime per-test flake healing, self-healing locators, and quarantine. This skill writes stable tests; that one repairs failing ones.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"selector-drift-recovery","type":"text","marks":[{"type":"strong"}]},{"text":" — Bulk-regenerate broken selectors after a UI refactor or redesign; this skill is for authoring, not mass repair.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"test-migration","type":"text","marks":[{"type":"strong"}]},{"text":" — Converting Selenium/other suites to Cypress.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ci-cd-integration","type":"text","marks":[{"type":"strong"}]},{"text":" — Pipeline templates for running Cypress in GitHub Actions / GitLab CI, parallelization, and artifact management.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"visual-testing","type":"text","marks":[{"type":"strong"}]},{"text":" — Visual regression to complement Cypress functional tests; Cypress has no built-in pixel comparison.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"unit-testing","type":"text","marks":[{"type":"strong"}]},{"text":" — Jest/Vitest for logic that needs no browser; Cypress component tests fill the gap between unit and E2E.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"test-data-management","type":"text","marks":[{"type":"strong"}]},{"text":" — Seeding, managing, and cleaning up the test data Cypress tests consume.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"qa-project-context","type":"text","marks":[{"type":"strong"}]},{"text":" — The project context file capturing framework choices, CI platform, and conventions.","type":"text"}]}]}]}]},"metadata":{"date":"2026-11-02","name":"cypress-automation","author":"@skillopedia","source":{"stars":239,"repo_name":"qa-skills","origin_url":"https://github.com/petrkindlmann/qa-skills/blob/HEAD/skills/cypress-automation/SKILL.md","repo_owner":"petrkindlmann","body_sha256":"ebe5c9195aa85cf92f66467531f41fa64e1c82b208fff5c6fd7f61bd40d63353","cluster_key":"93ab65709a0a7f8409e7347020faa6fc11adcb310f20bae6022afb3e63a10afa","clean_bundle":{"format":"clean-skill-bundle-v1","source":"petrkindlmann/qa-skills/skills/cypress-automation/SKILL.md","text_bytes":13520,"source_commit":"62434253c2592f27b494609c27f224939c86346d","skills_sh_rank":12238,"attachment_count":4,"text_attachments":4,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/cypress-automation/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"testing-qa","category_label":"Testing"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"testing-qa","metadata":{"author":"kindlmann","version":"2.0","category":"automation"},"import_tag":"skills-sh-top20k-2026-11","description":"Build Cypress test suites in TypeScript: E2E tests, component tests, custom commands, cy.intercept network control, cy.session login, Cypress Cloud, and CI integration. Covers retry-ability, the command queue, cross-origin flows with cy.origin, and data-driven testing with fixtures. Use when: \"write E2E test in Cypress,\" \"Cypress page object / custom command,\" \"cy.,\" \"cy.intercept,\" \"Cypress component test,\" \"Cypress Cloud,\" \"cypress.config.ts.\" Not for: Playwright suites — use playwright-automation; flaky-test healing or quarantine — use test-reliability; bulk selector regeneration after a UI refactor — use selector-drift-recovery; Selenium-to-Cypress conversion — use test-migration. Related: playwright-automation, ci-cd-integration, visual-testing, unit-testing, test-reliability."}},"renderedAt":1793661827695}

<objective Production-grade Cypress test suites in TypeScript. The failure this prevents: tests written as if Cypress commands ran synchronously (storing in a variable, ), and tests that flake because they wait on instead of a network alias. This skill covers the mental model (command queue, retry-ability), project structure, custom commands, network control with , component testing, cross-origin auth with , and Cypress Cloud / CI integration. </objective Quick Route | You need to... | Go to | |----------------|-------| | Write an E2E spec (load, intercept, assert) | Core Principles + | | Add…