Cypress Expert Skill Quick Reference When to use this skill: - Writing or fixing Cypress E2E or component tests - Setting up Cypress in a new project - Debugging flaky tests - Adding network stubbing / API mocking - Configuring CI pipelines for Cypress - Implementing auth patterns ( ) - Building Page Object Model architecture Quick start: 1. — install 2. — interactive mode (first run generates config) 3. — headless CI mode 4. Read full references in for deep patterns --- Core Philosophy Cypress runs inside the browser . It has native access to the DOM, network requests, and application state.…

, ''))\n expect(price).to.be.greaterThan(0)\n expect(price).to.be.lessThan(1000)\n})\n\n// Alias and assert\ncy.get('[data-testid=\"count\"]').invoke('text').as('count')\ncy.get('@count').should('match', /^\\d+$/)\n```\n\n## Network / Request Assertions\n\n```js\ncy.wait('@myRequest').then(({ request, response }) => {\n // Request\n expect(request.method).to.equal('POST')\n expect(request.body).to.deep.include({ name: 'Alice' })\n expect(request.headers).to.have.property('authorization')\n\n // Response\n expect(response.statusCode).to.equal(201)\n expect(response.body.id).to.be.a('number')\n expect(response.body).to.have.keys(['id', 'name', 'email'])\n})\n\n// Chainable with .its()\ncy.wait('@search')\n .its('request.url')\n .should('include', 'q=cypress')\n\ncy.wait('@getUser')\n .its('response.body.email')\n .should('contain', '@example.com')\n```\n\n## Cookie Assertions\n\n```js\ncy.getCookie('session').should('exist')\ncy.getCookie('session').should('have.property', 'value').and('not.be.empty')\ncy.getCookie('session').its('httpOnly').should('be.true')\ncy.getCookies().should('have.length.greaterThan', 0)\n```\n\n## localStorage Assertions\n\n```js\ncy.window().its('localStorage').invoke('getItem', 'auth_token').should('exist')\ncy.window().its('localStorage').invoke('getItem', 'user').then(JSON.parse).should('have.property', 'id')\n```\n\n## Spy / Stub Assertions\n\n```js\ncy.get('@myStub').should('have.been.called')\ncy.get('@myStub').should('have.been.calledOnce')\ncy.get('@myStub').should('have.been.calledTwice')\ncy.get('@myStub').should('have.been.calledWith', 'expected-arg')\ncy.get('@myStub').should('have.been.calledWithMatch', { key: 'value' })\ncy.get('@myStub').should('not.have.been.called')\n```\n\n## Viewport Assertions\n\n```js\ncy.viewport('iphone-14')\ncy.viewport(375, 812)\n\n// Assert responsive behavior\ncy.get('[data-testid=\"mobile-nav\"]').should('be.visible')\ncy.get('[data-testid=\"desktop-nav\"]').should('not.be.visible')\n```\n\n## Custom Assertion Helpers\n\n```js\n// Assert element is \"ready\" (visible + enabled + not loading)\nconst shouldBeReady = (selector) => {\n cy.get(selector)\n .should('be.visible')\n .and('not.be.disabled')\n .and('not.have.attr', 'aria-busy', 'true')\n}\n\n// Assert form validation error\nconst shouldHaveValidationError = (fieldName, errorText) => {\n cy.get(`[data-testid=\"${fieldName}-error\"]`)\n .should('be.visible')\n .and('contain', errorText)\n}\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5543,"content_sha256":"1a63c3cb61d781e4999ae496bf81a8c4b791875dc1480c9d3fec301f15ea075f"},{"filename":"references/ci.md","content":"# CI/CD Reference — Cypress\n\n## GitHub Actions\n\n### Basic (Sequential)\n\n```yaml\nname: Cypress E2E\n\non:\n push:\n branches: [main, develop]\n pull_request:\n\njobs:\n cypress:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v6\n\n - name: Setup Node\n uses: actions/setup-node@v6\n with:\n node-version: 22\n cache: 'npm'\n\n - name: Install dependencies\n run: npm ci\n\n - name: Build app\n run: npm run build\n\n - name: Start server\n run: npm run start:ci &\n \n - name: Wait for server\n run: npx wait-on http://localhost:3000 --timeout 60000\n\n - name: Run Cypress\n uses: cypress-io/github-action@v6\n with:\n browser: chrome\n headed: false\n env:\n CYPRESS_API_URL: ${{ secrets.CYPRESS_API_URL }}\n \n - name: Upload screenshots\n uses: actions/upload-artifact@v6\n if: failure()\n with:\n name: cypress-screenshots\n path: cypress/screenshots\n \n - name: Upload videos\n uses: actions/upload-artifact@v6\n if: always()\n with:\n name: cypress-videos\n path: cypress/videos\n```\n\n### Parallel with Cypress Cloud\n\n```yaml\nname: Cypress Parallel\n\non: [push, pull_request]\n\njobs:\n cypress-run:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n matrix:\n containers: [1, 2, 3, 4]\n\n steps:\n - uses: actions/checkout@v6\n\n - name: Run Cypress\n uses: cypress-io/github-action@v6\n with:\n start: npm start\n wait-on: 'http://localhost:3000'\n wait-on-timeout: 120\n browser: chrome\n record: true\n parallel: true\n group: 'UI - Chrome'\n env:\n CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}\n GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n```\n\n### Matrix — Multiple Browsers\n\n```yaml\njobs:\n cypress-cross-browser:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n matrix:\n browser: [chrome, firefox, edge]\n steps:\n - uses: actions/checkout@v6\n - uses: cypress-io/github-action@v6\n with:\n start: npm start\n wait-on: 'http://localhost:3000'\n browser: ${{ matrix.browser }}\n record: true\n group: ${{ matrix.browser }}\n env:\n CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}\n```\n\n### Smoke Tests Only on PR\n\n```yaml\njobs:\n smoke:\n runs-on: ubuntu-latest\n if: github.event_name == 'pull_request'\n steps:\n - uses: actions/checkout@v6\n - uses: cypress-io/github-action@v6\n with:\n start: npm start\n wait-on: 'http://localhost:3000'\n spec: 'cypress/e2e/smoke/**/*.cy.js'\n\n full-suite:\n runs-on: ubuntu-latest\n if: github.ref == 'refs/heads/main'\n steps:\n - uses: actions/checkout@v6\n - uses: cypress-io/github-action@v6\n with:\n start: npm start\n wait-on: 'http://localhost:3000'\n```\n\n---\n\n## GitLab CI\n\n```yaml\n# .gitlab-ci.yml\nimage: cypress/browsers:node20-chrome-120\n\nstages:\n - test\n\ncypress:e2e:\n stage: test\n script:\n - npm ci\n - npm start &\n - npx wait-on http://localhost:3000\n - npx cypress run --browser chrome --record --parallel\n parallel: 4\n variables:\n CYPRESS_RECORD_KEY: $CYPRESS_RECORD_KEY\n artifacts:\n when: always\n paths:\n - cypress/videos/\n - cypress/screenshots/\n expire_in: 7 days\n reports:\n junit: cypress/results/junit.xml\n cache:\n key: ${CI_COMMIT_REF_SLUG}\n paths:\n - node_modules/\n - ~/.cache/Cypress\n\ncypress:component:\n stage: test\n script:\n - npm ci\n - npx cypress run --component\n artifacts:\n when: on_failure\n paths:\n - cypress/screenshots/\n```\n\n---\n\n## CircleCI\n\n```yaml\n# .circleci/config.yml\nversion: 2.1\n\norbs:\n cypress: cypress-io/cypress@3\n\nworkflows:\n build-and-test:\n jobs:\n - cypress/run:\n name: 'Cypress E2E'\n start-command: 'npm start'\n wait-on: 'http://localhost:3000'\n cypress-command: 'npx cypress run --record --parallel'\n parallelism: 4\n executor:\n name: 'cypress/default'\n node-version: '22'\n environment:\n CYPRESS_RECORD_KEY: $CYPRESS_RECORD_KEY\n post-steps:\n - store_artifacts:\n path: cypress/screenshots\n - store_artifacts:\n path: cypress/videos\n```\n\n---\n\n## Jenkins\n\n```groovy\n// Jenkinsfile\npipeline {\n agent {\n docker {\n image 'cypress/browsers:node20-chrome-120'\n }\n }\n\n environment {\n CYPRESS_RECORD_KEY = credentials('cypress-record-key')\n HOME = '/root'\n }\n\n stages {\n stage('Install') {\n steps {\n sh 'npm ci'\n }\n }\n\n stage('E2E Tests') {\n steps {\n sh 'npm start &'\n sh 'npx wait-on http://localhost:3000 --timeout 60000'\n sh 'npx cypress run --record --browser chrome'\n }\n post {\n always {\n archiveArtifacts artifacts: 'cypress/videos/**', allowEmptyArchive: true\n archiveArtifacts artifacts: 'cypress/screenshots/**', allowEmptyArchive: true\n junit allowEmptyResults: true, testResults: 'cypress/results/*.xml'\n }\n }\n }\n }\n}\n```\n\n---\n\n## JUnit XML Reports (CI Integration)\n\n```js\n// cypress.config.js\nconst { defineConfig } = require('cypress')\n\nmodule.exports = defineConfig({\n reporter: 'cypress-multi-reporters',\n reporterOptions: {\n reporterEnabled: 'spec, mocha-junit-reporter',\n mochaJunitReporterReporterOptions: {\n mochaFile: 'cypress/results/junit.[hash].xml',\n toConsole: false,\n },\n },\n e2e: {\n setupNodeEvents(on, config) {},\n },\n})\n```\n\n```bash\n# Install reporters\nnpm install --save-dev cypress-multi-reporters mocha-junit-reporter\n```\n\n---\n\n## Docker Images\n\n```bash\n# Official Cypress images\ncypress/base:20 # Node 20, no browsers\ncypress/browsers:node20-chrome-120-ff-121 # with browsers\ncypress/included:15.11.0 # Cypress pre-installed\n\n# Full Docker run\ndocker run -it \\\n -v $PWD:/e2e \\\n -w /e2e \\\n -e CYPRESS_baseUrl=http://host.docker.internal:3000 \\\n cypress/included:15.11.0\n```\n\n---\n\n## Performance Tips\n\n1. **Reuse node_modules cache** using lock file hash as cache key\n2. **Run component tests in parallel** with `--parallel` flag\n3. **Split specs by file** — avoid huge spec files (1 spec ≤ 50 tests)\n4. **Use `--spec` flag** to run subset: `cypress run --spec 'cypress/e2e/auth/**'`\n5. **Disable video in CI** unless needed: `video: false` in config\n6. **Enable `experimentalMemoryManagement`** for long test runs\n7. **Set `numTestsKeptInMemory: 0`** for memory-constrained environments","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6788,"content_sha256":"7140474847ee878837f090061b26d168d28f3057e8fa84c33aa5c7fc03a19f91"},{"filename":"references/commands.md","content":"# cy.* Commands Reference — Cheatsheet\n\n## Navigation\n\n```js\ncy.visit('/path')\ncy.visit('http://example.com', { timeout: 30000 })\ncy.visit('/page', { onBeforeLoad: (win) => { /* stub things */ } })\ncy.go('back')\ncy.go('forward')\ncy.reload()\ncy.reload(true) // force reload (bypass cache)\ncy.url()\ncy.location()\ncy.location('pathname')\ncy.hash()\ncy.title()\n```\n\n## DOM Queries\n\n```js\ncy.get(selector)\ncy.get(selector, { timeout: 10000 })\ncy.contains(text)\ncy.contains(selector, text)\ncy.find(selector) // child command\ncy.children() // direct children\ncy.parent()\ncy.parents(selector)\ncy.closest(selector)\ncy.next(selector)\ncy.prev(selector)\ncy.siblings(selector)\ncy.first()\ncy.last()\ncy.eq(index)\ncy.filter(selector)\ncy.not(selector)\ncy.within(fn)\ncy.wrap(subject)\n```\n\n## Actions\n\n```js\ncy.click()\ncy.click({ force: true }) // click even if obscured\ncy.click(50, 100) // click at specific coords\ncy.dblclick()\ncy.rightclick()\ncy.type('text')\ncy.type('text{enter}') // special keys\ncy.type('{ctrl}a{del}')\ncy.clear() // clear input\ncy.check() // check checkbox\ncy.uncheck()\ncy.check(['value1', 'value2'])\ncy.select('Option Text') // select dropdown by text\ncy.select('value') // select by value\ncy.selectFile(path) // file upload (v9.3+)\ncy.selectFile([path1, path2])\ncy.selectFile(path, { action: 'drag-drop' })\ncy.submit() // submit form\ncy.focus()\ncy.blur()\ncy.hover() // requires cypress-real-events plugin\ncy.scrollTo('bottom')\ncy.scrollTo(x, y)\ncy.scrollIntoView()\ncy.trigger('mouseover')\ncy.trigger('change', { force: true })\n```\n\n## Keyboard Shortcuts (in .type())\n\n```js\ncy.get('input').type('{enter}')\ncy.get('input').type('{esc}')\ncy.get('input').type('{backspace}')\ncy.get('input').type('{del}')\ncy.get('input').type('{tab}')\ncy.get('input').type('{uparrow}')\ncy.get('input').type('{downarrow}')\ncy.get('input').type('{leftarrow}')\ncy.get('input').type('{rightarrow}')\ncy.get('input').type('{home}')\ncy.get('input').type('{end}')\ncy.get('input').type('{ctrl}a')\ncy.get('input').type('{cmd}k')\ncy.get('input').type('{shift}{enter}')\n```\n\n## Assertions\n\n```js\ncy.should('be.visible')\ncy.should('not.be.visible')\ncy.should('exist')\ncy.should('not.exist')\ncy.should('have.text', 'exact text')\ncy.should('contain', 'partial')\ncy.should('have.value', 'input value')\ncy.should('have.attr', 'href', '/path')\ncy.should('have.class', 'active')\ncy.should('not.have.class', 'error')\ncy.should('have.css', 'color', 'rgb(0,0,0)')\ncy.should('be.checked')\ncy.should('be.disabled')\ncy.should('have.length', 5)\ncy.should('be.empty')\ncy.and(...) // chain additional should\n```\n\n## Network\n\n```js\ncy.intercept(method, url)\ncy.intercept(method, url, response)\ncy.intercept(method, url, handler)\ncy.intercept({ method, url, headers, query })\ncy.request(method, url)\ncy.request(method, url, body)\ncy.request({ method, url, body, headers, failOnStatusCode: false })\ncy.wait('@alias')\ncy.wait(['@alias1', '@alias2'])\ncy.wait('@alias', { timeout: 15000 })\n```\n\n## Cookies / Storage\n\n```js\ncy.getCookie(name)\ncy.getCookies()\ncy.getAllCookies()\ncy.setCookie(name, value)\ncy.clearCookie(name)\ncy.clearCookies()\ncy.clearAllCookies()\ncy.clearAllLocalStorage()\ncy.clearAllSessionStorage()\ncy.window()\ncy.document()\ncy.readFile(path)\ncy.writeFile(path, content)\n```\n\n## Time Control\n\n```js\ncy.clock() // freeze time at current moment\ncy.clock(timestamp) // set specific time\ncy.clock(new Date(2024, 0, 1)) // January 1, 2024\ncy.tick(milliseconds) // advance clock\ncy.clock().invoke('restore') // restore real clock\n```\n\n## Tasks and Exec\n\n```js\ncy.task('myTask')\ncy.task('myTask', { arg: 'value' })\ncy.exec('bash command')\ncy.exec('npm run seed', { timeout: 30000, failOnNonZeroExit: false })\n```\n\n## Screenshots and Videos\n\n```js\ncy.screenshot()\ncy.screenshot('name')\ncy.screenshot({ capture: 'fullPage' })\ncy.screenshot({ clip: { x: 0, y: 0, width: 400, height: 300 } })\n```\n\n## Debugging\n\n```js\ncy.pause() // pause test execution\ncy.debug() // log subject to console\ncy.log('message') // log in Cypress runner\ncy.get(selector).debug()\ncy.get(selector).then(($el) => { debugger })\n```\n\n## Fixtures\n\n```js\ncy.fixture('filename.json')\ncy.fixture('filename.json').as('alias')\ncy.fixture('images/logo.png', 'base64')\n```\n\n## Session\n\n```js\ncy.session(id, setup)\ncy.session(id, setup, { validate, cacheAcrossSpecs: true })\ncy.clearAllSavedSessions()\n```\n\n## Viewport\n\n```js\ncy.viewport(1280, 720)\ncy.viewport('iphone-14')\ncy.viewport('ipad-2')\ncy.viewport('macbook-16')\ncy.viewport('samsung-s10')\n```\n\n## Other\n\n```js\ncy.focused() // get focused element\ncy.root() // get root element\ncy.spy(object, 'method')\ncy.stub(object, 'method')\ncy.stub().as('myStub')\ncy.as('alias') // alias current subject\ncy.invoke('methodName') // invoke method on subject\ncy.its('propertyName') // get property of subject\ncy.spread(fn) // spread array as args\ncy.each(fn) // iterate array\ncy.then(fn) // callback with subject\ncy.pipe(fn) // transform subject\ncy.end() // end chain (return undefined)\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5424,"content_sha256":"2dc88b696116a599fed196af2f1b248bcd2331829913d446709367aa353c063e"},{"filename":"references/component-testing.md","content":"# Component Testing Reference — Cypress\n\n## Setup\n\n### Install\n\n```bash\nnpm install --save-dev cypress\nnpx cypress open --component # first run generates config\n```\n\n### cypress.config.js — React + Vite\n\n```js\nconst { defineConfig } = require('cypress')\n\nmodule.exports = defineConfig({\n component: {\n devServer: {\n framework: 'react',\n bundler: 'vite',\n },\n specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',\n viewportWidth: 1280,\n viewportHeight: 720,\n },\n})\n```\n\n### cypress.config.js — React + Webpack (CRA)\n\n```js\nmodule.exports = defineConfig({\n component: {\n devServer: {\n framework: 'create-react-app',\n bundler: 'webpack',\n },\n },\n})\n```\n\n### cypress.config.js — Next.js\n\n```js\nmodule.exports = defineConfig({\n component: {\n devServer: {\n framework: 'next',\n bundler: 'webpack',\n },\n },\n})\n```\n\n### cypress.config.js — Vue 3 + Vite\n\n```js\nmodule.exports = defineConfig({\n component: {\n devServer: {\n framework: 'vue',\n bundler: 'vite',\n },\n },\n})\n```\n\n### cypress.config.js — Angular\n\n```js\nmodule.exports = defineConfig({\n component: {\n devServer: {\n framework: 'angular',\n bundler: 'webpack',\n },\n specPattern: '**/*.cy.ts',\n },\n})\n```\n\n---\n\n## React Component Tests\n\n### Basic Mounting\n\n```jsx\n// src/components/Button.cy.jsx\nimport { mount } from 'cypress/react'\nimport Button from './Button'\n\ndescribe('Button', () => {\n it('renders with text', () => {\n mount(\u003cButton>Click me\u003c/Button>)\n cy.get('button').should('have.text', 'Click me')\n })\n\n it('calls onClick handler', () => {\n const handleClick = cy.stub().as('clickHandler')\n mount(\u003cButton onClick={handleClick}>Submit\u003c/Button>)\n cy.get('button').click()\n cy.get('@clickHandler').should('have.been.calledOnce')\n })\n\n it('is disabled', () => {\n mount(\u003cButton disabled>Submit\u003c/Button>)\n cy.get('button').should('be.disabled')\n })\n})\n```\n\n### With Context / Providers\n\n```jsx\nimport { mount } from 'cypress/react'\nimport { ThemeProvider } from './ThemeContext'\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\nimport UserProfile from './UserProfile'\n\nconst mountWithProviders = (component) => {\n const queryClient = new QueryClient({\n defaultOptions: { queries: { retry: false } },\n })\n return mount(\n \u003cQueryClientProvider client={queryClient}>\n \u003cThemeProvider theme=\"dark\">\n {component}\n \u003c/ThemeProvider>\n \u003c/QueryClientProvider>\n )\n}\n\ndescribe('UserProfile', () => {\n it('shows user data', () => {\n cy.intercept('GET', '/api/user/1', { fixture: 'user.json' }).as('getUser')\n mountWithProviders(\u003cUserProfile userId={1} />)\n cy.wait('@getUser')\n cy.get('[data-testid=\"user-name\"]').should('contain', 'Alice')\n })\n})\n```\n\n### Custom Mount Command\n\n```jsx\n// cypress/support/component.js\nimport { mount } from 'cypress/react'\nimport { MemoryRouter } from 'react-router-dom'\nimport { Provider } from 'react-redux'\nimport { createStore } from './test-utils'\nimport './commands'\nimport '../../src/index.css'\n\nCypress.Commands.add('mount', (component, options = {}) => {\n const {\n routerProps = { initialEntries: ['/'] },\n reduxState = {},\n ...mountOptions\n } = options\n\n const store = createStore(reduxState)\n\n const wrapped = (\n \u003cProvider store={store}>\n \u003cMemoryRouter {...routerProps}>\n {component}\n \u003c/MemoryRouter>\n \u003c/Provider>\n )\n\n return mount(wrapped, mountOptions)\n})\n\n// TypeScript declaration\n// cypress/support/component.d.ts\ndeclare global {\n namespace Cypress {\n interface Chainable {\n mount(\n component: React.ReactNode,\n options?: { routerProps?: object; reduxState?: object }\n ): Chainable\u003cvoid>\n }\n }\n}\n```\n\n### Testing Hooks\n\n```jsx\n// src/hooks/useCounter.cy.jsx\nimport { mount } from 'cypress/react'\nimport useCounter from './useCounter'\n\n// Render a test component that exercises the hook\nfunction CounterComponent({ initial = 0 }) {\n const { count, increment, decrement, reset } = useCounter(initial)\n return (\n \u003cdiv>\n \u003cspan data-testid=\"count\">{count}\u003c/span>\n \u003cbutton data-testid=\"inc\" onClick={increment}>+\u003c/button>\n \u003cbutton data-testid=\"dec\" onClick={decrement}>-\u003c/button>\n \u003cbutton data-testid=\"reset\" onClick={reset}>Reset\u003c/button>\n \u003c/div>\n )\n}\n\ndescribe('useCounter', () => {\n it('increments count', () => {\n mount(\u003cCounterComponent initial={0} />)\n cy.get('[data-testid=\"count\"]').should('have.text', '0')\n cy.get('[data-testid=\"inc\"]').click()\n cy.get('[data-testid=\"count\"]').should('have.text', '1')\n })\n\n it('resets to initial value', () => {\n mount(\u003cCounterComponent initial={5} />)\n cy.get('[data-testid=\"inc\"]').click().click()\n cy.get('[data-testid=\"reset\"]').click()\n cy.get('[data-testid=\"count\"]').should('have.text', '5')\n })\n})\n```\n\n---\n\n## Vue Component Tests\n\n```js\n// src/components/TodoItem.cy.js\nimport { mount } from 'cypress/vue'\nimport TodoItem from './TodoItem.vue'\n\ndescribe('TodoItem', () => {\n it('renders todo text', () => {\n mount(TodoItem, {\n props: {\n todo: { id: 1, text: 'Buy groceries', done: false },\n },\n })\n cy.get('[data-testid=\"todo-text\"]').should('contain', 'Buy groceries')\n })\n\n it('emits toggle event on checkbox click', () => {\n mount(TodoItem, {\n props: {\n todo: { id: 1, text: 'Buy groceries', done: false },\n },\n })\n cy.get('[data-testid=\"todo-checkbox\"]').click()\n cy.wrap(Cypress.vueWrapper.emitted()).should('have.property', 'toggle')\n cy.wrap(Cypress.vueWrapper.emitted('toggle')[0]).should('deep.equal', [1])\n })\n\n it('shows strike-through when done', () => {\n mount(TodoItem, {\n props: {\n todo: { id: 1, text: 'Buy groceries', done: true },\n },\n })\n cy.get('[data-testid=\"todo-text\"]').should('have.class', 'line-through')\n })\n})\n```\n\n---\n\n## Intercepting in Component Tests\n\n```jsx\n// Network stubs work the same in component tests\ndescribe('UserCard with API', () => {\n it('loads and displays user', () => {\n cy.intercept('GET', '/api/users/1', {\n body: { id: 1, name: 'Alice', role: 'Admin' },\n }).as('getUser')\n\n mount(\u003cUserCard userId={1} />)\n cy.wait('@getUser')\n cy.get('[data-testid=\"user-name\"]').should('contain', 'Alice')\n cy.get('[data-testid=\"user-role\"]').should('contain', 'Admin')\n })\n\n it('shows error state on failure', () => {\n cy.intercept('GET', '/api/users/1', {\n statusCode: 500,\n body: { error: 'Server error' },\n }).as('getUserError')\n\n mount(\u003cUserCard userId={1} />)\n cy.wait('@getUserError')\n cy.get('[data-testid=\"error-message\"]').should('be.visible')\n })\n})\n```\n\n---\n\n## Viewport and Responsive Tests\n\n```js\ndescribe('Responsive layout', () => {\n it('shows mobile nav on small screens', () => {\n cy.viewport('iphone-14')\n mount(\u003cNavigation />)\n cy.get('[data-testid=\"hamburger-menu\"]').should('be.visible')\n cy.get('[data-testid=\"desktop-nav\"]').should('not.be.visible')\n })\n\n it('shows full nav on desktop', () => {\n cy.viewport(1280, 720)\n mount(\u003cNavigation />)\n cy.get('[data-testid=\"hamburger-menu\"]').should('not.be.visible')\n cy.get('[data-testid=\"desktop-nav\"]').should('be.visible')\n })\n})\n```\n\n---\n\n## Running Component Tests\n\n```bash\n# Interactive mode\nnpx cypress open --component\n\n# Headless (CI)\nnpx cypress run --component\n\n# Specific spec\nnpx cypress run --component --spec 'src/components/Button.cy.jsx'\n\n# Specific browser\nnpx cypress run --component --browser chrome\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7560,"content_sha256":"effbf7dd45e448c5bab515e731efe0cb50859a7e9aa3fc62ca17e55e56526060"},{"filename":"references/config.md","content":"# Configuration Reference — cypress.config.js\n\n## Complete Example\n\n```js\nconst { defineConfig } = require('cypress')\n\nmodule.exports = defineConfig({\n // ─── Project / Paths ──────────────────────────────────────────\n projectId: 'abc123', // Cypress Cloud project ID\n fixturesFolder: 'cypress/fixtures',\n screenshotsFolder: 'cypress/screenshots',\n videosFolder: 'cypress/videos',\n downloadsFolder: 'cypress/downloads',\n\n // ─── Timeouts ─────────────────────────────────────────────────\n defaultCommandTimeout: 8000, // cy.get, cy.contains, etc.\n execTimeout: 60000, // cy.exec\n taskTimeout: 60000, // cy.task\n pageLoadTimeout: 60000, // cy.visit\n requestTimeout: 10000, // cy.request, cy.intercept waits\n responseTimeout: 30000, // cy.request, cy.intercept response\n\n // ─── Viewport ─────────────────────────────────────────────────\n viewportWidth: 1280,\n viewportHeight: 720,\n\n // ─── Video / Screenshot ───────────────────────────────────────\n video: true,\n videoCompression: 32, // quality 0-51 (lower = better)\n videosFolder: 'cypress/videos',\n screenshotOnRunFailure: true,\n trashAssetsBeforeRuns: true, // clean screenshots/videos before each run\n\n // ─── Test Isolation ───────────────────────────────────────────\n testIsolation: true, // clear cookies/localStorage between tests (default: true)\n\n // ─── Retries ──────────────────────────────────────────────────\n retries: {\n runMode: 2, // CI retries\n openMode: 0, // interactive mode retries\n },\n\n // ─── Reporter ─────────────────────────────────────────────────\n reporter: 'spec', // or 'dot', 'tap', 'json', 'junit'\n reporterOptions: {\n mochaFile: 'cypress/results/junit.[hash].xml',\n },\n\n // ─── Network / Proxy ──────────────────────────────────────────\n chromeWebSecurity: true, // set false to allow cross-origin iframes\n blockHosts: [\n 'analytics.google.com',\n '*.hotjar.com',\n 'cdn.segment.com',\n ],\n\n // ─── Environment Variables ────────────────────────────────────\n env: {\n apiUrl: 'http://localhost:3001',\n adminEmail: '[email protected]',\n // secrets via cypress.env.json (gitignored) or CYPRESS_ prefix env vars\n },\n\n // ─── E2E Config ───────────────────────────────────────────────\n e2e: {\n baseUrl: 'http://localhost:3000',\n specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',\n supportFile: 'cypress/support/e2e.js',\n excludeSpecPattern: ['**/node_modules/**', '**/__snapshots__/**'],\n experimentalRunAllSpecs: true, // v15.9.0+: works for e2e AND component tests\n experimentalMemoryManagement: false, // enable for long test suites\n experimentalFastVisibility: true, // v15.x: faster visibility assertions\n numTestsKeptInMemory: 50, // reduce for memory savings\n\n // v15.10.0+: enforce migration from deprecated Cypress.env()\n // set to false after migrating all Cypress.env() → cy.env() / Cypress.expose()\n allowCypressEnv: false,\n\n setupNodeEvents(on, config) {\n // Plugin registration\n require('@cypress/code-coverage/task')(on, config)\n\n on('task', {\n log(message) {\n console.log(message)\n return null\n },\n table(message) {\n console.table(message)\n return null\n },\n })\n\n on('before:browser:launch', (browser, launchOptions) => {\n if (browser.name === 'chrome') {\n launchOptions.args.push('--disable-gpu')\n launchOptions.args.push('--no-sandbox')\n launchOptions.args.push('--disable-dev-shm-usage')\n }\n return launchOptions\n })\n\n return config\n },\n },\n\n // ─── Component Testing Config ─────────────────────────────────\n component: {\n devServer: {\n framework: 'react', // react | vue | angular | svelte | next | nuxt | solid\n bundler: 'vite', // vite | webpack\n },\n specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',\n supportFile: 'cypress/support/component.js',\n viewportWidth: 500,\n viewportHeight: 500,\n setupNodeEvents(on, config) {\n return config\n },\n },\n})\n```\n\n## TypeScript Configuration\n\n```ts\n// cypress.config.ts\nimport { defineConfig } from 'cypress'\nimport codeCoverageTask from '@cypress/code-coverage/task'\n\nexport default defineConfig({\n e2e: {\n baseUrl: 'http://localhost:3000',\n setupNodeEvents(on, config) {\n codeCoverageTask(on, config)\n return config\n },\n },\n})\n```\n\n## Per-Environment Configuration\n\n```js\n// cypress.config.js\nmodule.exports = defineConfig({\n e2e: {\n baseUrl: process.env.CYPRESS_BASE_URL || 'http://localhost:3000',\n setupNodeEvents(on, config) {\n // Load env-specific config\n const envConfig = require(`./cypress/config/${config.env.ENV || 'local'}.json`)\n return { ...config, ...envConfig }\n },\n },\n})\n\n// cypress/config/local.json\n{\n \"baseUrl\": \"http://localhost:3000\",\n \"env\": { \"apiUrl\": \"http://localhost:3001\" }\n}\n\n// cypress/config/staging.json\n{\n \"baseUrl\": \"https://staging.example.com\",\n \"env\": { \"apiUrl\": \"https://api-staging.example.com\" }\n}\n\n// Run with env: npx cypress run --env ENV=staging\n```\n\n## Common CLI Flags\n\n```bash\n# Run all tests headless\nnpx cypress run\n\n# Run with specific browser\nnpx cypress run --browser chrome\nnpx cypress run --browser firefox\nnpx cypress run --browser edge\nnpx cypress run --browser electron\n\n# Run specific spec(s)\nnpx cypress run --spec 'cypress/e2e/auth/**/*.cy.js'\nnpx cypress run --spec 'cypress/e2e/auth/login.cy.js,cypress/e2e/checkout.cy.js'\n\n# Run with env override\nnpx cypress run --env baseUrl=http://staging.example.com,apiKey=abc123\n\n# Set config override\nnpx cypress run --config viewportWidth=1920,viewportHeight=1080\n\n# Run component tests\nnpx cypress run --component\n\n# Record to Cypress Cloud\nnpx cypress run --record --key your-record-key\n\n# Parallel (with Cypress Cloud)\nnpx cypress run --record --parallel --ci-build-id $CI_BUILD_ID\n\n# Open interactive\nnpx cypress open\nnpx cypress open --browser chrome\nnpx cypress open --component\n```","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7151,"content_sha256":"eb082309588c50ce396ee1f57f5b3efbf9da25a35e123e01e009352e84043691"},{"filename":"references/network.md","content":"# Network Control Reference — cy.intercept\n\n## Request Matching\n\n```js\n// Match by method + URL string\ncy.intercept('GET', '/api/users')\ncy.intercept('POST', '/api/users')\ncy.intercept('PUT', '/api/users/1')\ncy.intercept('DELETE', '/api/users/1')\ncy.intercept('PATCH', '/api/users/1')\n\n// Match any method\ncy.intercept('/api/users')\n\n// Glob patterns (* = any segment, ** = any path)\ncy.intercept('GET', '/api/users/*') // /api/users/1, /api/users/abc\ncy.intercept('GET', '/api/**') // any nested path under /api\ncy.intercept('GET', '/api/users/*/posts') // /api/users/1/posts\n\n// RegExp\ncy.intercept('GET', /\\/api\\/users\\/\\d+/)\ncy.intercept(/\\/api\\/(users|posts)/)\n\n// URL object (most precise)\ncy.intercept({\n method: 'GET',\n url: '/api/search',\n query: { q: 'cypress', page: '1' },\n headers: { 'x-api-version': '2' },\n})\n```\n\n## Static Responses\n\n```js\n// Full response object\ncy.intercept('GET', '/api/users', {\n statusCode: 200,\n headers: {\n 'content-type': 'application/json',\n 'x-total-count': '42',\n },\n body: [{ id: 1, name: 'Alice' }],\n delay: 500, // artificial delay in ms\n throttleKbps: 100, // throttle response bandwidth\n}).as('getUsers')\n\n// Shorthand (body only)\ncy.intercept('GET', '/api/users', [{ id: 1 }]).as('getUsers')\n\n// Fixture file\ncy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')\n\n// Fixture with headers\ncy.intercept('GET', '/api/users', {\n fixture: 'users.json',\n headers: { 'cache-control': 'no-cache' },\n}).as('getUsers')\n\n// Empty response\ncy.intercept('DELETE', '/api/users/1', { statusCode: 204, body: '' }).as('deleteUser')\n\n// Force network error (no response — simulates offline)\ncy.intercept('GET', '/api/data', { forceNetworkError: true }).as('networkFail')\n```\n\n## Dynamic Handler Functions\n\n```js\n// Full control via handler function\ncy.intercept('POST', '/api/orders', (req) => {\n // Inspect request\n console.log(req.body)\n console.log(req.headers)\n console.log(req.url)\n console.log(req.method)\n\n // Reply with static response\n req.reply({\n statusCode: 201,\n body: { id: 999, ...req.body },\n })\n}).as('createOrder')\n\n// Modify and forward to real server\ncy.intercept('GET', '/api/config', (req) => {\n req.headers['x-test-flag'] = 'true'\n req.continue() // forward modified request\n}).as('getConfig')\n\n// Modify response from real server\ncy.intercept('GET', '/api/config', (req) => {\n req.reply((res) => {\n res.body.betaFeature = true // inject flag\n res.headers['x-modified'] = 'true'\n // res.statusCode = 200 // optionally change status\n return res // send modified response\n })\n}).as('getConfig')\n\n// Conditional responses\ncy.intercept('GET', '/api/users/*', (req) => {\n const userId = req.url.split('/').pop()\n if (userId === '999') {\n req.reply({ statusCode: 404, body: { error: 'Not found' } })\n } else {\n req.reply({ fixture: `user-${userId}.json` })\n }\n})\n```\n\n## Request Inspection and Assertions\n\n```js\n// Wait for one call\ncy.wait('@createOrder')\n\n// Wait for multiple calls\ncy.wait(['@getUsers', '@getProducts'])\n\n// Assert request details\ncy.wait('@createOrder').then((interception) => {\n const { request, response } = interception\n\n // Request assertions\n expect(request.method).to.equal('POST')\n expect(request.url).to.include('/api/orders')\n expect(request.body).to.deep.include({ quantity: 2 })\n expect(request.headers).to.have.property('authorization')\n expect(request.headers['content-type']).to.include('application/json')\n\n // Response assertions\n expect(response.statusCode).to.equal(201)\n expect(response.body.id).to.be.a('number')\n})\n\n// Wait for Nth call (0-indexed)\ncy.wait('@search') // first call\ncy.wait('@search') // second call (Cypress tracks call count)\n\n// Get all calls after the fact\ncy.get('@search.all').then((interceptions) => {\n expect(interceptions).to.have.length(3)\n})\n\n// Get specific call\ncy.get('@search.2').then((interception) => {\n expect(interception.request.query.q).to.equal('advanced')\n})\n```\n\n## Aliases and Ordering\n\n```js\n// Multiple intercepts for same URL (last registered wins for stub)\ncy.intercept('GET', '/api/items', { fixture: 'items-empty.json' }).as('getItemsEmpty')\n\n// Override for specific scenario\ndescribe('when items exist', () => {\n beforeEach(() => {\n cy.intercept('GET', '/api/items', { fixture: 'items-full.json' }).as('getItemsFull')\n })\n // The later registration takes precedence\n})\n```\n\n## GraphQL Intercepting\n\n```js\n// Intercept GraphQL by operationName in body\ncy.intercept('POST', '/graphql', (req) => {\n if (req.body.operationName === 'GetUsers') {\n req.reply({ fixture: 'graphql/get-users.json' })\n } else if (req.body.operationName === 'CreateUser') {\n req.reply({ fixture: 'graphql/create-user.json' })\n } else {\n req.continue() // pass other queries through\n }\n}).as('graphql')\n\n// Wait for specific operation\ncy.wait('@graphql').its('request.body.operationName').should('eq', 'GetUsers')\n```\n\n## WebSocket Stubs (Experimental)\n\n```js\n// Intercept WebSocket connection\ncy.intercept('GET', '/ws').as('wsConnection')\n```\n\n## Tips\n\n1. **Register intercepts BEFORE the action that triggers the request**\n2. **Always use `.as()` to name intercepts you plan to `cy.wait()` on**\n3. **Use `req.continue()` to pass requests to real server (spy mode)**\n4. **Delay + throttle simulate slow networks for loading state tests**\n5. **`forceNetworkError` tests offline/error recovery paths**\n6. **Multiple `cy.wait('@alias')` calls advance through sequential requests**\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5550,"content_sha256":"f1064f0f4301a59aa11ae021997fc29e8805f63fecc6bcd139b2f85e08622b1e"},{"filename":"references/patterns.md","content":"# Advanced Patterns Reference\n\n## Visual Regression Testing\n\n```js\n// Install: npm install --save-dev @percy/cypress\n// Add to cypress/support/e2e.js: import '@percy/cypress'\n\ndescribe('Visual Regression', () => {\n it('matches dashboard snapshot', () => {\n cy.intercept('GET', '/api/**', { fixture: 'dashboard-data.json' })\n cy.visit('/dashboard')\n cy.get('[data-testid=\"dashboard-content\"]').should('be.visible')\n cy.percySnapshot('Dashboard - Default View')\n })\n\n it('matches dark mode snapshot', () => {\n cy.visit('/dashboard?theme=dark')\n cy.percySnapshot('Dashboard - Dark Mode', { widths: [375, 768, 1280] })\n })\n})\n\n// Run: PERCY_TOKEN=xxx npx percy exec -- cypress run\n```\n\n## API Testing (Without Browser)\n\n```js\n// Direct cy.request — no browser needed\ndescribe('API Tests', () => {\n let authToken\n\n before(() => {\n cy.request('POST', '/api/auth/login', {\n email: Cypress.env('API_USER'),\n password: Cypress.env('API_PASS'),\n }).then(({ body }) => {\n authToken = body.token\n })\n })\n\n it('GET /api/users returns paginated list', () => {\n cy.request({\n method: 'GET',\n url: '/api/users?page=1&limit=10',\n headers: { Authorization: `Bearer ${authToken}` },\n }).then(({ status, body, headers }) => {\n expect(status).to.equal(200)\n expect(body.data).to.have.length(10)\n expect(body.meta.total).to.be.a('number')\n expect(headers['content-type']).to.include('application/json')\n })\n })\n\n it('POST /api/users creates a user', () => {\n const newUser = {\n name: 'Test User',\n email: `test+${Date.now()}@example.com`,\n role: 'user',\n }\n\n cy.request({\n method: 'POST',\n url: '/api/users',\n headers: { Authorization: `Bearer ${authToken}` },\n body: newUser,\n }).then(({ status, body }) => {\n expect(status).to.equal(201)\n expect(body.id).to.exist\n expect(body.email).to.equal(newUser.email)\n })\n })\n\n it('validates required fields', () => {\n cy.request({\n method: 'POST',\n url: '/api/users',\n headers: { Authorization: `Bearer ${authToken}` },\n body: { name: 'No Email' },\n failOnStatusCode: false, // don't throw on 4xx\n }).then(({ status, body }) => {\n expect(status).to.equal(422)\n expect(body.errors).to.have.property('email')\n })\n })\n})\n```\n\n## Multi-Tab / New Window Handling\n\n```js\n// Cypress doesn't support multiple tabs natively\n// Best approach: remove target=\"_blank\" attribute\nit('opens link in same window', () => {\n cy.get('[data-testid=\"external-link\"]')\n .invoke('removeAttr', 'target')\n .click()\n cy.url().should('include', 'expected-destination')\n})\n\n// Capture new window URL via cy.stub on window.open\nit('calls window.open with correct URL', () => {\n cy.window().then((win) => {\n cy.stub(win, 'open').as('windowOpen')\n })\n cy.get('[data-testid=\"share-btn\"]').click()\n cy.get('@windowOpen').should('be.calledWithMatch', /expected-url/)\n})\n```\n\n## Clipboard Testing\n\n```js\nit('copies text to clipboard', () => {\n cy.window().then((win) => {\n cy.stub(win.navigator.clipboard, 'writeText').resolves().as('clipboard')\n })\n cy.get('[data-testid=\"copy-btn\"]').click()\n cy.get('@clipboard').should('have.been.calledWith', 'Expected copied text')\n cy.get('[data-testid=\"copy-success\"]').should('be.visible')\n})\n```\n\n## Drag and Drop\n\n```js\n// Using cypress-drag-drop plugin\n// npm install --save-dev @4tw/cypress-drag-drop\nimport '@4tw/cypress-drag-drop'\n\nit('reorders items via drag and drop', () => {\n cy.get('[data-testid=\"drag-item-1\"]').drag('[data-testid=\"drop-zone-3\"]')\n cy.get('[data-testid=\"item-list\"]')\n .children()\n .first()\n .should('contain', 'Item 3')\n})\n\n// Native HTML5 drag events\nit('drops file on upload zone', () => {\n cy.get('[data-testid=\"drop-zone\"]').selectFile(\n 'cypress/fixtures/test-file.pdf',\n { action: 'drag-drop' }\n )\n cy.get('[data-testid=\"file-name\"]').should('contain', 'test-file.pdf')\n})\n```\n\n## Date/Time Manipulation\n\n```js\n// Freeze clock to specific time\nit('shows correct date on dashboard', () => {\n const now = new Date('2024-03-15T10:00:00.000Z')\n cy.clock(now)\n cy.visit('/dashboard')\n cy.get('[data-testid=\"today-date\"]').should('contain', 'March 15, 2024')\n})\n\n// Tick time forward\nit('shows session expiry warning', () => {\n cy.clock()\n cy.visit('/dashboard')\n cy.tick(25 * 60 * 1000) // advance 25 minutes\n cy.get('[data-testid=\"session-warning\"]').should('be.visible')\n})\n```\n\n## Spy on Methods\n\n```js\nit('tracks analytics events', () => {\n cy.visit('/checkout', {\n onBeforeLoad(win) {\n cy.spy(win.analytics, 'track').as('analyticsTrack')\n },\n })\n cy.get('[data-testid=\"purchase-btn\"]').click()\n cy.get('@analyticsTrack').should('have.been.calledWith', 'Purchase Completed')\n cy.get('@analyticsTrack').should('have.been.calledWithMatch', {\n event: 'Purchase Completed',\n properties: { value: Cypress.sinon.match.number },\n })\n})\n```\n\n## Scroll and Viewport\n\n```js\n// Scroll to element\ncy.get('[data-testid=\"footer\"]').scrollIntoView()\ncy.get('[data-testid=\"footer\"]').should('be.visible')\n\n// Scroll to position\ncy.scrollTo('bottom')\ncy.scrollTo(0, 500)\n\n// Test sticky header\ncy.scrollTo(0, 300)\ncy.get('[data-testid=\"sticky-header\"]').should('have.class', 'sticky')\n .and('be.visible')\n```\n\n## Download Testing\n\n```js\n// Assert file was downloaded\nit('downloads CSV report', () => {\n const downloadsFolder = Cypress.config('downloadsFolder')\n \n // Clear downloads before test\n cy.task('clearDownloads', downloadsFolder)\n \n cy.get('[data-testid=\"export-csv\"]').click()\n \n // Wait for file to appear\n cy.readFile(`${downloadsFolder}/report.csv`, { timeout: 15000 })\n .should('contain', 'Name,Email,Role')\n})\n\n// cypress.config.js — register task\nsetupNodeEvents(on, config) {\n on('task', {\n clearDownloads(downloadsFolder) {\n const fs = require('fs')\n if (fs.existsSync(downloadsFolder)) {\n fs.rmSync(downloadsFolder, { recursive: true })\n }\n fs.mkdirSync(downloadsFolder, { recursive: true })\n return null\n },\n })\n}\n```\n\n## Testing PWA / Service Workers\n\n```js\n// Disable service workers in tests to avoid caching issues\ncy.visit('/', {\n onBeforeLoad(win) {\n delete win.navigator.__proto__.serviceWorker\n },\n})\n```\n\n## Handling Flaky Third-Party Scripts\n\n```js\n// Block third-party requests that might cause flakiness\ncy.intercept('GET', 'https://analytics.example.com/**', { statusCode: 200 }).as('analytics')\ncy.intercept('GET', 'https://chat-widget.com/embed.js', { body: '' }).as('chatWidget')\n\n// Or block by resource type (requires cypress plugin)\ncy.visit('/', {\n onBeforeLoad(win) {\n // Stub chatbot initialization\n win.Intercom = cy.stub().as('Intercom')\n },\n})\n```\n\n## Database Seeding via Tasks\n\n```js\n// cypress.config.js\nsetupNodeEvents(on, config) {\n on('task', {\n async seedDatabase(scenario) {\n const db = require('./db-connection')\n await db.seed(scenario)\n return null\n },\n async resetDatabase() {\n const db = require('./db-connection')\n await db.truncateAll()\n return null\n },\n async queryDatabase(sql) {\n const db = require('./db-connection')\n return db.query(sql)\n },\n })\n}\n\n// Usage in tests\nbeforeEach(() => {\n cy.task('resetDatabase')\n cy.task('seedDatabase', 'standard-users')\n})\n\nit('shows seeded users', () => {\n cy.task('queryDatabase', 'SELECT count(*) FROM users').then((result) => {\n const count = result.rows[0].count\n cy.visit('/users')\n cy.get('[data-testid=\"user-row\"]').should('have.length', parseInt(count))\n })\n})\n```\n\n## TypeScript Full Setup\n\n```bash\n# Install types\nnpm install --save-dev @types/cypress\n```\n\n```ts\n// cypress/support/e2e.ts\nimport './commands'\n\n// cypress/support/commands.ts\ndeclare global {\n namespace Cypress {\n interface Chainable {\n login(email: string, password: string): Chainable\u003cvoid>\n getByTestId\u003cT = HTMLElement>(\n testId: string,\n options?: Partial\u003cLoggable & Timeoutable & Withinable & Shadow>\n ): Chainable\u003cJQuery\u003cT>>\n }\n }\n}\n\nCypress.Commands.add('login', (email: string, password: string) => {\n cy.session([email, password], () => {\n cy.visit('/login')\n cy.get('[data-testid=\"email\"]').type(email)\n cy.get('[data-testid=\"password\"]').type(password)\n cy.get('[data-testid=\"submit\"]').click()\n cy.url().should('include', '/dashboard')\n })\n})\n\nCypress.Commands.add('getByTestId', (testId: string, options?) => {\n return cy.get(`[data-testid=\"${testId}\"]`, options)\n})\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":8589,"content_sha256":"ee9e7d0a6d8c47bb4b9539227bfc9fc3c3701b531ff3774d1ae8acb9277bb1eb"},{"filename":"references/selectors.md","content":"# Selectors Reference — Cypress\n\n## Stability Hierarchy (Best → Worst)\n\n### Tier 1 — Stable (Recommended)\n\n```js\n// data-testid (purpose-built for testing)\ncy.get('[data-testid=\"submit-button\"]')\ncy.get('[data-testid=\"user-card-123\"]')\n\n// data-cy (alternative attribute)\ncy.get('[data-cy=\"login-form\"]')\n\n// data-test (another common convention)\ncy.get('[data-test=\"header-nav\"]')\n\n// ARIA roles (accessible AND stable)\ncy.get('[role=\"dialog\"]')\ncy.get('[role=\"alert\"]')\ncy.get('[role=\"navigation\"]')\ncy.get('[role=\"main\"]')\ncy.get('[role=\"button\"]')\n\n// ARIA labels\ncy.get('[aria-label=\"Close modal\"]')\ncy.get('[aria-label=\"Search products\"]')\n\n// Semantic form attributes\ncy.get('button[type=\"submit\"]')\ncy.get('input[type=\"email\"]')\ncy.get('input[name=\"username\"]')\ncy.get('label[for=\"email\"]')\n\n// cy.contains — text-based, often stable\ncy.contains('Submit')\ncy.contains('button', 'Submit') // scoped to element type\ncy.contains('[data-testid=\"nav\"]', 'Dashboard') // scoped to parent\n\n// ID (stable if IDs are static, not auto-generated)\ncy.get('#login-form')\ncy.get('#main-content')\n```\n\n### Tier 2 — Acceptable (Use With Care)\n\n```js\n// Semantic HTML elements when structure is stable\ncy.get('nav')\ncy.get('header')\ncy.get('main')\ncy.get('footer')\ncy.get('form')\ncy.get('table')\n\n// HTML attributes that don't change often\ncy.get('a[href=\"/about\"]')\ncy.get('img[alt=\"Logo\"]')\ncy.get('input[placeholder=\"Search...\"]')\n\n// Text content (breaks if copy changes)\ncy.contains('Sign in')\ncy.contains('h1', 'Dashboard')\n```\n\n### Tier 3 — Avoid\n\n```js\n// CSS utility classes (Tailwind, Bootstrap, etc.) — change with styling\ncy.get('.btn-primary') // ❌\ncy.get('.MuiButton-root') // ❌ (MUI generated classes change)\ncy.get('.text-blue-500') // ❌ (Tailwind class)\n\n// Complex CSS selectors — breaks on restructuring\ncy.get('.sidebar > ul > li:first-child > a') // ❌\ncy.get('.card .card-body .card-title') // ❌\n\n// Positional (extremely fragile)\ncy.get('button').eq(2) // ❌ (except for known stable lists)\ncy.get('li:nth-child(3)') // ❌\ncy.get('.items > *:last-child') // ❌\n\n// XPath (never — Cypress is CSS-first)\ncy.xpath('//div[@class=\"container\"]//button') // ❌ (avoid cypress-xpath)\n```\n\n## Scoping and Chaining\n\n```js\n// within() — scope all queries to a parent\ncy.get('[data-testid=\"user-profile\"]').within(() => {\n cy.get('[data-testid=\"name\"]').should('contain', 'Alice')\n cy.get('[data-testid=\"email\"]').should('contain', '@example.com')\n cy.get('[data-testid=\"edit-btn\"]').click()\n})\n\n// find() — search within a subject\ncy.get('table').find('tbody tr').should('have.length', 5)\ncy.get('[data-testid=\"product-card\"]').first().find('[data-testid=\"price\"]')\n\n// parent() / parents()\ncy.get('[data-testid=\"error-icon\"]').parent().should('have.class', 'field-error')\ncy.get('[data-testid=\"tag\"]').parents('[data-testid=\"post-card\"]').first()\n\n// siblings()\ncy.get('[data-testid=\"active-tab\"]').siblings().should('not.have.class', 'active')\n\n// next() / prev()\ncy.get('[data-testid=\"step-2\"]').prev().should('have.class', 'completed')\ncy.get('[data-testid=\"step-1\"]').next().should('have.text', 'Step 2')\n\n// closest() — nearest ancestor matching selector\ncy.get('[data-testid=\"delete-icon\"]').closest('[data-testid=\"list-item\"]').should('exist')\n```\n\n## Querying Multiple Elements\n\n```js\n// Get all matches\ncy.get('[data-testid=\"item\"]') // all items\ncy.get('[data-testid=\"item\"]').first() // first\ncy.get('[data-testid=\"item\"]').last() // last\ncy.get('[data-testid=\"item\"]').eq(2) // 3rd (0-indexed)\n\n// Iterate (only when necessary)\ncy.get('[data-testid=\"item\"]').each(($item, index) => {\n cy.wrap($item).should('be.visible')\n})\n\n// Filter\ncy.get('[data-testid=\"item\"]').filter('[data-status=\"active\"]')\ncy.get('[data-testid=\"item\"]').not('[data-disabled=\"true\"]')\ncy.get('[data-testid=\"item\"]').contains('Featured')\n```\n\n## Adding data-testid to Your App\n\n```html\n\u003c!-- React -->\n\u003cbutton data-testid=\"submit-button\" type=\"submit\">Submit\u003c/button>\n\u003cinput data-testid=\"email-input\" type=\"email\" />\n\u003cdiv data-testid=\"user-card-{id}\">...\u003c/div>\n\n\u003c!-- Vue -->\n\u003cbutton data-testid=\"submit-button\">Submit\u003c/button>\n\u003ctemplate v-for=\"user in users\">\n \u003cdiv :data-testid=\"`user-card-${user.id}`\">...\u003c/div>\n\u003c/template>\n\n\u003c!-- Angular -->\n\u003cbutton data-testid=\"submit-button\">Submit\u003c/button>\n```\n\n```js\n// Strip test IDs from production build (optional but recommended)\n// babel-plugin-react-remove-properties\n{\n \"plugins\": [\n [\"babel-plugin-react-remove-properties\", {\n \"properties\": [\"data-testid\"],\n \"env\": \"production\"\n }]\n ]\n}\n```\n\n## Debugging Selectors\n\n```js\n// Inspect what Cypress found\ncy.get('[data-testid=\"btn\"]').then(($el) => {\n console.log('Element:', $el[0])\n console.log('Text:', $el.text())\n console.log('Classes:', $el.attr('class'))\n})\n\n// Check if selector finds unique element\ncy.get('[data-testid=\"submit\"]').should('have.length', 1)\n\n// Use cy.pause() and Cypress runner's selector playground\ncy.pause()\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5049,"content_sha256":"e5161f32fd7b321622389938abc2b6bfa8feca7f2eda8a911c10eb24faef82b5"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Cypress Expert Skill","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Reference","type":"text"}]},{"type":"paragraph","content":[{"text":"When to use this skill:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Writing or fixing Cypress E2E or component tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Setting up Cypress in a new project","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Debugging flaky tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Adding network stubbing / API mocking","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Configuring CI pipelines for Cypress","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implementing auth patterns (","type":"text"},{"text":"cy.session","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Building Page Object Model architecture","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Quick start:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"npm install --save-dev cypress","type":"text","marks":[{"type":"code_inline"}]},{"text":" — install","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"npx cypress open","type":"text","marks":[{"type":"code_inline"}]},{"text":" — interactive mode (first run generates config)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"npx cypress run","type":"text","marks":[{"type":"code_inline"}]},{"text":" — headless CI mode","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read full references in ","type":"text"},{"text":"{baseDir}/references/","type":"text","marks":[{"type":"code_inline"}]},{"text":" for deep patterns","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Philosophy","type":"text"}]},{"type":"paragraph","content":[{"text":"Cypress runs ","type":"text"},{"text":"inside the browser","type":"text","marks":[{"type":"strong"}]},{"text":". It has native access to the DOM, network requests, and application state. Every command is automatically retried until it passes or times out. This means:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never use ","type":"text","marks":[{"type":"strong"}]},{"text":"cy.wait(3000)","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" — use aliases + ","type":"text"},{"text":"cy.wait('@alias')","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never query DOM immediately after an action","type":"text","marks":[{"type":"strong"}]},{"text":" — Cypress retries automatically","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always assert on outcomes, not implementation","type":"text","marks":[{"type":"strong"}]},{"text":" — test user-visible behavior","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text","marks":[{"type":"strong"}]},{"text":"data-testid","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" attributes","type":"text","marks":[{"type":"strong"}]},{"text":" — decouple tests from styling/structure","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"1. Installation & Configuration","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Install","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"npm install --save-dev cypress\n# or\nyarn add -D cypress\n# or\npnpm add -D cypress","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"cypress.config.js (JavaScript)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"const { defineConfig } = require('cypress')\n\nmodule.exports = defineConfig({\n e2e: {\n baseUrl: 'http://localhost:3000',\n viewportWidth: 1280,\n viewportHeight: 720,\n video: false,\n screenshotOnRunFailure: true,\n defaultCommandTimeout: 8000,\n requestTimeout: 10000,\n responseTimeout: 10000,\n retries: {\n runMode: 2,\n openMode: 0,\n },\n // v15.10.0+ — enforce new cy.env() / Cypress.expose() APIs\n // set after migrating all Cypress.env() calls\n allowCypressEnv: false,\n // v15.x — faster visibility checks\n experimentalFastVisibility: true,\n // v15.9.0+ — run all specs without --parallel flag; now works for component tests too\n experimentalRunAllSpecs: true,\n setupNodeEvents(on, config) {\n return config\n },\n },\n component: {\n devServer: {\n framework: 'react',\n bundler: 'vite',\n },\n experimentalRunAllSpecs: true,\n },\n})","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"cypress.config.ts (TypeScript)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"import { defineConfig } from 'cypress'\n\nexport default defineConfig({\n e2e: {\n baseUrl: 'http://localhost:3000',\n specPattern: 'cypress/e2e/**/*.cy.ts',\n setupNodeEvents(on, config) {\n return config\n },\n },\n})","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"tsconfig for Cypress","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"lib\": [\"es5\", \"dom\"],\n \"types\": [\"cypress\", \"node\"]\n },\n \"include\": [\"**/*.ts\"]\n}","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"2. Selectors (Stability Hierarchy)","type":"text"}]},{"type":"paragraph","content":[{"text":"Use the most stable selector available. Prefer in this order:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// ✅ BEST — semantic, decoupled from style/structure\ncy.get('[data-testid=\"submit-button\"]')\ncy.get('[data-cy=\"login-form\"]')\ncy.get('[data-test=\"user-email\"]')\n\n// ✅ GOOD — ARIA/accessibility selectors\ncy.get('[role=\"dialog\"]')\ncy.get('[aria-label=\"Close modal\"]')\ncy.get('button[type=\"submit\"]')\n\n// ✅ GOOD — cy.contains for text-driven queries\ncy.contains('button', 'Submit')\ncy.contains('[data-testid=\"nav\"]', 'Dashboard')\n\n// ⚠️ FRAGILE — CSS classes tied to styling\ncy.get('.btn-primary') // avoid\ncy.get('.MuiButton-root') // avoid\n\n// ❌ WORST — absolute XPath / positional\ncy.get('div > ul > li:nth-child(3) > a') // never","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Scoped Queries","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"cy.get('[data-testid=\"user-card\"]').within(() => {\n cy.get('[data-testid=\"user-name\"]').should('contain', 'Alice')\n cy.get('[data-testid=\"user-role\"]').should('contain', 'Admin')\n})\n\ncy.get('table').find('tr').should('have.length', 5)","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"3. Assertions","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Should / Expect","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// Chainable assertions\ncy.get('[data-testid=\"title\"]').should('be.visible')\ncy.get('[data-testid=\"title\"]').should('have.text', 'Dashboard')\ncy.get('[data-testid=\"title\"]').should('contain.text', 'Dash')\n\n// Multiple assertions (all retry together)\ncy.get('[data-testid=\"btn\"]')\n .should('be.visible')\n .and('not.be.disabled')\n .and('have.attr', 'type', 'submit')\n\n// Value\ncy.get('input[name=\"email\"]').should('have.value', '[email protected]')\n\n// Length assertions\ncy.get('[data-testid=\"item\"]').should('have.length', 3)\ncy.get('[data-testid=\"item\"]').should('have.length.greaterThan', 0)\n\n// Negative assertions (use carefully — can pass too early)\ncy.get('[data-testid=\"error\"]').should('not.exist')\ncy.get('[data-testid=\"spinner\"]').should('not.be.visible')\n\n// BDD expect style\ncy.get('[data-testid=\"count\"]').invoke('text').then((text) => {\n expect(parseInt(text)).to.be.greaterThan(0)\n})\n\n// URL assertions\ncy.url().should('include', '/dashboard')\ncy.url().should('eq', 'http://localhost:3000/dashboard')\n\n// Alias + should\ncy.get('[data-testid=\"price\"]').invoke('text').as('price')\ncy.get('@price').should('match', /\\$\\d+\\.\\d{2}/)","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Async State Assertions","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// Wait for element to appear (retries automatically)\ncy.get('[data-testid=\"success-message\"]', { timeout: 10000 })\n .should('be.visible')\n\n// Wait for element to disappear\ncy.get('[data-testid=\"loading-spinner\"]').should('not.exist')","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"4. Network Stubbing with cy.intercept","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// Basic stub\ncy.intercept('GET', '/api/users', {\n statusCode: 200,\n body: [\n { id: 1, name: 'Alice', role: 'admin' },\n { id: 2, name: 'Bob', role: 'user' },\n ],\n}).as('getUsers')\n\ncy.visit('/users')\ncy.wait('@getUsers')\ncy.get('[data-testid=\"user-row\"]').should('have.length', 2)\n\n// Fixture file\ncy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')\n\n// Glob/regex patterns\ncy.intercept('GET', '/api/users/*').as('getUser')\ncy.intercept('GET', /\\/api\\/products\\/\\d+/).as('getProduct')\n\n// Dynamic handler\ncy.intercept('POST', '/api/orders', (req) => {\n req.reply({ statusCode: 201, body: { id: 999, ...req.body } })\n}).as('createOrder')\n\n// Modify real server response (spy + transform)\ncy.intercept('GET', '/api/config', (req) => {\n req.reply((res) => {\n res.body.featureFlag = true\n return res\n })\n}).as('getConfig')\n\n// Error simulation\ncy.intercept('GET', '/api/critical', { forceNetworkError: true }).as('networkError')\ncy.intercept('GET', '/api/data', { statusCode: 500, body: { error: 'Server Error' } }).as('serverError')\n\n// Delay (for loading state tests)\ncy.intercept('GET', '/api/data', (req) => {\n req.reply({ delay: 1000, body: { data: [] } })\n}).as('slowRequest')\n\n// Assert request details\ncy.wait('@createOrder').then((interception) => {\n expect(interception.request.body).to.deep.include({ quantity: 2 })\n expect(interception.response.statusCode).to.equal(201)\n})","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"5. Authentication Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"cy.session — Cache Auth State (Recommended)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"Cypress.Commands.add('loginByUI', (email, password) => {\n cy.session(\n [email, password],\n () => {\n cy.visit('/login')\n cy.get('[data-testid=\"email\"]').type(email)\n cy.get('[data-testid=\"password\"]').type(password)\n cy.get('[data-testid=\"submit\"]').click()\n cy.url().should('include', '/dashboard')\n },\n {\n validate() {\n cy.getCookie('session_token').should('exist')\n },\n cacheAcrossSpecs: true,\n }\n )\n})","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"API-Based Auth (Faster)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"Cypress.Commands.add('loginByApi', (email, password) => {\n cy.session(\n ['api', email, password],\n () => {\n cy.request({\n method: 'POST',\n url: '/api/auth/login',\n body: { email, password },\n }).then(({ body }) => {\n window.localStorage.setItem('auth_token', body.token)\n cy.setCookie('session', body.sessionId)\n })\n },\n {\n validate() {\n cy.window().its('localStorage').invoke('getItem', 'auth_token').should('exist')\n },\n }\n )\n})\n\n// Usage — cy.env() for secrets (v15.10.0+, replaces deprecated Cypress.env())\nbeforeEach(() => {\n cy.env(['adminPassword']).then(({ adminPassword }) => {\n cy.loginByApi('[email protected]', adminPassword)\n cy.visit('/dashboard')\n })\n})","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"6. Custom Commands","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// cypress/support/commands.js\nCypress.Commands.add('getByTestId', (testId, options) => {\n return cy.get(`[data-testid=\"${testId}\"]`, options)\n})\n\nCypress.Commands.add('waitForToast', (message) => {\n const selector = '[data-testid=\"toast\"], [role=\"status\"]'\n if (message) {\n cy.get(selector, { timeout: 10000 }).should('contain', message)\n } else {\n cy.get(selector, { timeout: 10000 }).should('be.visible')\n }\n})\n\nCypress.Commands.add('fillForm', (data) => {\n Object.entries(data).forEach(([field, value]) => {\n cy.get(`[name=\"${field}\"]`).clear().type(String(value))\n })\n})\n\n// TypeScript — cypress/support/index.d.ts\ndeclare global {\n namespace Cypress {\n interface Chainable {\n getByTestId(testId: string, options?: Partial\u003cLoggable & Timeoutable>): Chainable\u003cJQuery>\n loginByApi(email: string, password: string): Chainable\u003cvoid>\n loginByUI(email: string, password: string): Chainable\u003cvoid>\n waitForToast(message?: string): Chainable\u003cvoid>\n fillForm(data: Record\u003cstring, string | number>): Chainable\u003cvoid>\n }\n }\n}","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"7. Page Object Model","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// cypress/pages/LoginPage.js\nclass LoginPage {\n visit() { cy.visit('/login'); return this }\n getEmailInput() { return cy.get('[data-testid=\"email-input\"]') }\n getPasswordInput() { return cy.get('[data-testid=\"password-input\"]') }\n getSubmitButton() { return cy.get('[data-testid=\"submit-button\"]') }\n getErrorMessage() { return cy.get('[data-testid=\"error-message\"]') }\n\n login(email, password) {\n this.getEmailInput().clear().type(email)\n this.getPasswordInput().clear().type(password)\n this.getSubmitButton().click()\n return this\n }\n\n assertLoggedIn() { cy.url().should('include', '/dashboard'); return this }\n assertError(message) { this.getErrorMessage().should('contain', message); return this }\n}\n\nexport default new LoginPage()\n\n// Usage\nimport loginPage from '../pages/LoginPage'\n\nit('logs in successfully', () => {\n loginPage.visit().login('[email protected]', 'password123').assertLoggedIn()\n})","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"8. Component Testing","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// cypress/component/Button.cy.jsx\nimport { mount } from 'cypress/react'\nimport Button from '../../src/components/Button'\n\ndescribe('Button', () => {\n it('calls onClick when clicked', () => {\n const onClick = cy.stub().as('clickHandler')\n mount(\u003cButton label=\"Submit\" onClick={onClick} />)\n cy.get('button').click()\n cy.get('@clickHandler').should('have.been.calledOnce')\n })\n\n it('is disabled when loading', () => {\n mount(\u003cButton label=\"Submit\" loading={true} />)\n cy.get('button').should('be.disabled')\n })\n})\n\n// Run component tests\n// npx cypress open --component\n// npx cypress run --component","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"9. Common Patterns","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// Forms\ncy.get('input[name=\"email\"]').clear().type('[email protected]')\ncy.get('select[name=\"country\"]').select('United States')\ncy.get('[data-testid=\"agree\"]').check()\ncy.get('[data-testid=\"file-input\"]').selectFile('cypress/fixtures/doc.pdf')\n\n// File drag & drop\ncy.get('[data-testid=\"drop-zone\"]').selectFile('cypress/fixtures/image.png', {\n action: 'drag-drop',\n})\n\n// Modal handling\ncy.get('[data-testid=\"open-modal\"]').click()\ncy.get('[role=\"dialog\"]').should('be.visible')\ncy.get('[role=\"dialog\"]').within(() => {\n cy.get('[data-testid=\"confirm-btn\"]').click()\n})\ncy.get('[role=\"dialog\"]').should('not.exist')\n\n// Window alerts\ncy.on('window:alert', (text) => { expect(text).to.contain('Success') })\ncy.on('window:confirm', () => true)\n\n// LocalStorage / Cookies\ncy.window().then((win) => { win.localStorage.setItem('key', 'value') })\ncy.setCookie('session', 'abc123')\ncy.clearAllCookies()\ncy.clearAllLocalStorage()\n\n// Date/Time control\ncy.clock(new Date('2024-03-15'))\ncy.tick(25 * 60 * 1000) // advance 25 minutes\n\n// Spy on methods\ncy.visit('/checkout', {\n onBeforeLoad(win) { cy.spy(win.analytics, 'track').as('track') },\n})\ncy.get('@track').should('have.been.calledWith', 'Purchase Completed')","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"10. Flake Prevention","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// ❌ FLAKY\ncy.wait(2000)\ncy.get('[data-testid=\"result\"]').should('exist')\n\n// ✅ STABLE — wait for network alias\ncy.intercept('GET', '/api/results').as('getResults')\ncy.get('[data-testid=\"search-btn\"]').click()\ncy.wait('@getResults')\ncy.get('[data-testid=\"result\"]').should('have.length.greaterThan', 0)\n\n// Test isolation — reset state between tests\nbeforeEach(() => {\n cy.clearAllCookies()\n cy.clearAllLocalStorage()\n cy.clearAllSessionStorage()\n})\n\n// Retries config\nretries: { runMode: 2, openMode: 0 }\n\n// Per-test retry\nit('critical path', { retries: 3 }, () => { ... })","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"11. CI / Parallelization","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"GitHub Actions (Parallel Matrix)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"name: Cypress Tests\non: [push, pull_request]\n\njobs:\n cypress-run:\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: actions/setup-node@v4\n with: { node-version: 22, cache: 'npm' }\n - run: npm ci\n - run: npm start &\n - run: npx wait-on http://localhost:3000 --timeout 60000\n - uses: cypress-io/github-action@v6\n with:\n record: true\n parallel: true\n group: 'UI Tests'\n tag: ${{ github.ref_name }}\n env:\n CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}\n CYPRESS_ADMIN_PASSWORD: ${{ secrets.ADMIN_PASSWORD }} # accessed via cy.env()\n GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n - uses: actions/upload-artifact@v4\n if: failure()\n with:\n name: cypress-screenshots\n path: cypress/screenshots","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"New CLI flags (v15.11.0)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Don't fail the run when no tests are found (useful for conditional spec discovery)\nnpx cypress run --pass-with-no-tests\n\n# Run component tests with experimentalRunAllSpecs (now works for component testing too, v15.9.0+)\nnpx cypress run --component","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Smoke Test Tags","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// Run subset of tests in CI\nconst isSmoke = Cypress.expose('SMOKE') === 'true'\n;(isSmoke ? describe.only : describe)('Checkout', () => { ... })\n// Run: CYPRESS_SMOKE=true npx cypress run","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Docker Compose for CI","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"version: '3.8'\nservices:\n app:\n build: .\n ports:\n - \"3000:3000\"\n cypress:\n image: cypress/included:15.11.0 # updated from 13.x\n depends_on:\n - app\n environment:\n - CYPRESS_baseUrl=http://app:3000\n - CYPRESS_ADMIN_PASSWORD=${ADMIN_PASSWORD} # passed via cy.env()\n volumes:\n - ./:/e2e\n working_dir: /e2e\n command: cypress run --browser chrome","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"12. Environment Variables","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"⚠️ ","type":"text"},{"text":"Breaking change in v15.10.0:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"Cypress.env()","type":"text","marks":[{"type":"code_inline"}]},{"text":" is deprecated and will be removed in Cypress 16.","type":"text"},{"type":"br"},{"text":"Migrate to ","type":"text"},{"text":"cy.env()","type":"text","marks":[{"type":"code_inline"}]},{"text":" for secrets and ","type":"text"},{"text":"Cypress.expose()","type":"text","marks":[{"type":"code_inline"}]},{"text":" for public config values.","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"New API (v15.10.0+)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// cy.env() — for SECRETS (API keys, passwords, tokens)\n// Async, only exposes the values you explicitly request\n// Values are NOT serialized into browser state\ncy.env(['apiKey', 'adminPassword']).then(({ apiKey, adminPassword }) => {\n cy.request({\n method: 'POST',\n url: '/api/auth/login',\n body: { email: '[email protected]', password: adminPassword },\n headers: { Authorization: `Bearer ${apiKey}` },\n })\n})\n\n// Cypress.expose() — for NON-SENSITIVE public config\n// Synchronous, safe to appear in browser state\n// Use for: feature flags, API versions, env labels, base URLs\nconst apiUrl = Cypress.expose('apiUrl')\ncy.visit(apiUrl + '/dashboard')","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"cypress.config.js (v15.10.0+)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"const { defineConfig } = require('cypress')\n\nmodule.exports = defineConfig({\n // Enforce migration — disables legacy Cypress.env() API entirely\n allowCypressEnv: false,\n\n env: {\n apiUrl: 'http://localhost:3001', // non-sensitive — use Cypress.expose()\n adminEmail: '[email protected]', // non-sensitive — use Cypress.expose()\n // secrets (apiKey, adminPassword) come from cypress.env.json or CYPRESS_* OS vars\n // never hardcode secrets here\n },\n})","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"cypress.env.json — secrets only (gitignore this file)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"adminPassword\": \"secret123\",\n \"apiKey\": \"test-key\"\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"CI environment variables (prefix CYPRESS_)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Set secrets as CI env vars — accessed via cy.env()\nCYPRESS_API_KEY=abc123 npx cypress run\nCYPRESS_ADMIN_PASSWORD=secret npx cypress run","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Migration cheatsheet","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":"Old (deprecated)","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"New","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"When","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cypress.env('apiKey')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cy.env(['apiKey']).then(...)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Secrets, inside hooks/tests","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cypress.env('apiUrl')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cypress.expose('apiUrl')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Public config, synchronous access needed","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cypress.env()","type":"text","marks":[{"type":"code_inline"}]},{"text":" (all)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Never use — intentional explicit access only","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"—","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Plugin migration note","type":"text"}]},{"type":"paragraph","content":[{"text":"Popular plugins require updates to drop ","type":"text"},{"text":"Cypress.env()","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@cypress/grep","type":"text","marks":[{"type":"code_inline"}]},{"text":" → upgrade to latest major","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@cypress/code-coverage","type":"text","marks":[{"type":"code_inline"}]},{"text":" → upgrade to latest major","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Review all custom plugins for ","type":"text"},{"text":"Cypress.env()","type":"text","marks":[{"type":"code_inline"}]},{"text":" usage before setting ","type":"text"},{"text":"allowCypressEnv: false","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"13. Fixtures and Data Management","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// cypress/fixtures/user.json\n{\n \"id\": 1,\n \"name\": \"Test User\",\n \"email\": \"[email protected]\",\n \"role\": \"admin\"\n}\n\n// Load fixture\ncy.fixture('user.json').then((user) => {\n cy.get('[data-testid=\"name\"]').should('contain', user.name)\n})\n\n// Use fixture as stub (shorthand)\ncy.intercept('GET', '/api/user', { fixture: 'user.json' }).as('getUser')\n\n// Dynamic data generation\nconst generateUser = (overrides = {}) => ({\n id: Math.random(),\n name: 'Test User',\n email: `test+${Date.now()}@example.com`,\n role: 'user',\n ...overrides,\n})\n\ncy.intercept('GET', '/api/user', generateUser({ role: 'admin' })).as('getUser')\n\n// Seed DB via API task (faster than UI)\nbeforeEach(() => {\n cy.request('POST', '/api/test/reset', { scenario: 'clean-slate' })\n})","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"14. Accessibility Testing","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// Install: npm install --save-dev cypress-axe axe-core\n// Add to cypress/support/e2e.js: import 'cypress-axe'\n\ndescribe('Accessibility', () => {\n beforeEach(() => {\n cy.visit('/')\n cy.injectAxe()\n })\n\n it('has no detectable a11y violations on load', () => {\n cy.checkA11y()\n })\n\n it('has no violations in the modal', () => {\n cy.get('[data-testid=\"open-modal\"]').click()\n cy.get('[role=\"dialog\"]').should('be.visible')\n cy.checkA11y('[role=\"dialog\"]', {\n rules: {\n 'color-contrast': { enabled: false }, // disable specific rules\n },\n })\n })\n\n it('reports violations with details', () => {\n cy.checkA11y(null, null, (violations) => {\n violations.forEach((violation) => {\n cy.log(`${violation.id}: ${violation.description}`)\n violation.nodes.forEach((node) => cy.log(node.html))\n })\n })\n })\n})","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"15. Best Practices Summary","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":"Pattern","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Do","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Don't","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Selectors","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"data-testid","type":"text","marks":[{"type":"code_inline"}]},{"text":" attributes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CSS classes, XPath","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Waiting","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cy.wait('@alias')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cy.wait(3000)","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auth","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"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Login via UI every test","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Assertions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Chain ","type":"text"},{"text":".should()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Implicit ","type":"text"},{"text":"then()","type":"text","marks":[{"type":"code_inline"}]},{"text":" checks","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Data","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"API seeding","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"UI-based test data setup","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Isolation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"beforeEach","type":"text","marks":[{"type":"code_inline"}]},{"text":" resets","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Shared state between tests","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Network","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cy.intercept()","type":"text","marks":[{"type":"code_inline"}]},{"text":" stubs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Real API calls in unit tests","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"16. Debugging","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// Pause execution — opens interactive debugger in Cypress UI\ncy.pause()\n\n// Debug current subject — logs to console\ncy.get('[data-testid=\"el\"]').debug()\n\n// Log custom messages\ncy.log('Current step: submitting checkout form')\n\n// Take screenshot at specific point\ncy.screenshot('before-submit')\n\n// Inspect DOM state\ncy.get('[data-testid=\"form\"]').then(($el) => {\n console.log('Form HTML:', $el.html())\n debugger // opens DevTools when running in interactive mode\n})\n\n// Time travel debugging: Cypress UI → click any command in the log → DOM snapshot appears","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"cy.prompt() — Experimental Natural Language Tests (v15.x)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"js"},"content":[{"text":"// cy.prompt() lets you write tests in plain English\n// Cypress AI interprets intent and generates the necessary commands\n// Enable: set experimentalCyPrompt: true in cypress.config.js\n\ncy.prompt('Click the submit button and verify the success message appears')\ncy.prompt('Fill in the login form with admin credentials and sign in')\ncy.prompt('Verify the product list shows 3 items and the first one is selected')","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Enable with: ","type":"text"},{"text":"experimentalCyPrompt: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"cypress.config.js","type":"text","marks":[{"type":"code_inline"}]},{"text":". Self-healing: if a selector changes, Cypress attempts to re-locate the element by intent rather than failing immediately.","type":"text"}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"References","type":"text"}]},{"type":"paragraph","content":[{"text":"All detailed references in ","type":"text"},{"text":"{baseDir}/references/","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"selectors.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Selector strategies and anti-patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"commands.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Full cy.* command cheatsheet","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"network.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — cy.intercept advanced patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"assertions.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Complete assertion reference","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"config.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Full cypress.config.js options","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ci.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — CI/CD setup guides (GitHub Actions, GitLab, CircleCI, Jenkins)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"component-testing.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — React/Vue/Angular component testing","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Visual regression, API testing, multi-tab, drag/drop","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Examples","type":"text"}]},{"type":"paragraph","content":[{"text":"Working test files in ","type":"text"},{"text":"{baseDir}/examples/","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"auth-flow.cy.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Full auth with cy.session","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"api-intercept.cy.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Network stubbing patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"page-objects.cy.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" — POM implementation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"custom-commands.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Custom command library","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"cypress-agent-skill","author":"@skillopedia","source":{"stars":2012,"repo_name":"openclaw-master-skills","origin_url":"https://github.com/leoyeai/openclaw-master-skills/blob/HEAD/skills/cypress-agent-skill/SKILL.md","repo_owner":"leoyeai","body_sha256":"77cb844391f93f7ddf4e5dd366a61377465a2be20e2a815f256557073a466a12","cluster_key":"735e8718f1037670b32fe3b1ce9054900627759a896a8a73e38cfc64aa009c8e","clean_bundle":{"format":"clean-skill-bundle-v1","source":"leoyeai/openclaw-master-skills/skills/cypress-agent-skill/SKILL.md","attachments":[{"id":"89f5115a-a018-5e6a-8ced-c6d34a72e0de","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/89f5115a-a018-5e6a-8ced-c6d34a72e0de/attachment.md","path":"README.md","size":6673,"sha256":"35a4a03c31a6ece9563f75db09d85892acbcf312ac6c8235424e258ad673f137","contentType":"text/markdown; charset=utf-8"},{"id":"732dc2d0-d1fd-54d3-ba14-2e53d892662b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/732dc2d0-d1fd-54d3-ba14-2e53d892662b/attachment.json","path":"_meta.json","size":293,"sha256":"c13bf30f81c968f07ee4652e6a46da947a66279ade2dd02b952a9d4799844fd9","contentType":"application/json; charset=utf-8"},{"id":"039c1433-41f6-5928-a7cc-12f3a125ceff","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/039c1433-41f6-5928-a7cc-12f3a125ceff/attachment.js","path":"examples/api-intercept.cy.js","size":7433,"sha256":"e4d0ea526ccf35934ac1489f0dd1cc8ecec3f15decf4135b1132b74715303d1c","contentType":"application/javascript; charset=utf-8"},{"id":"8da68351-92e7-5e13-8b0d-fea17f5c5e0c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8da68351-92e7-5e13-8b0d-fea17f5c5e0c/attachment.js","path":"examples/auth-flow.cy.js","size":4941,"sha256":"bbd0a14d1d66d295223f0bebbc9b82f888ced03719afb0016c1f2a7c4b349223","contentType":"application/javascript; charset=utf-8"},{"id":"37d01e17-0252-5207-8903-11838840a0fc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/37d01e17-0252-5207-8903-11838840a0fc/attachment.js","path":"examples/custom-commands.js","size":8595,"sha256":"ef2d68fe2ce49bb76a4c5a3d7c8604767bd3123a062c38f66b6a1eaeb80134a0","contentType":"application/javascript; charset=utf-8"},{"id":"3dcf5819-a190-5916-b75b-91b895036cdf","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3dcf5819-a190-5916-b75b-91b895036cdf/attachment.js","path":"examples/page-objects.cy.js","size":8185,"sha256":"2f978324203d6e73e6110cd4ff2a0c8400c631a9d50b771044ab355c52628d11","contentType":"application/javascript; charset=utf-8"},{"id":"cf4f3b7e-02ad-551d-92f3-c177b6a81a7d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cf4f3b7e-02ad-551d-92f3-c177b6a81a7d/attachment.md","path":"references/assertions.md","size":5543,"sha256":"1a63c3cb61d781e4999ae496bf81a8c4b791875dc1480c9d3fec301f15ea075f","contentType":"text/markdown; charset=utf-8"},{"id":"a1525f14-6cf6-59d8-a5d3-361cb64453ea","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a1525f14-6cf6-59d8-a5d3-361cb64453ea/attachment.md","path":"references/ci.md","size":6788,"sha256":"7140474847ee878837f090061b26d168d28f3057e8fa84c33aa5c7fc03a19f91","contentType":"text/markdown; charset=utf-8"},{"id":"152a9bd6-1a63-5378-add2-7b4eb36259b8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/152a9bd6-1a63-5378-add2-7b4eb36259b8/attachment.md","path":"references/commands.md","size":5424,"sha256":"2dc88b696116a599fed196af2f1b248bcd2331829913d446709367aa353c063e","contentType":"text/markdown; charset=utf-8"},{"id":"0ed03ec9-face-5983-9701-291e7f6612aa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0ed03ec9-face-5983-9701-291e7f6612aa/attachment.md","path":"references/component-testing.md","size":7560,"sha256":"effbf7dd45e448c5bab515e731efe0cb50859a7e9aa3fc62ca17e55e56526060","contentType":"text/markdown; charset=utf-8"},{"id":"1f6e51ce-78b7-58ad-a47f-a4c431b0db56","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1f6e51ce-78b7-58ad-a47f-a4c431b0db56/attachment.md","path":"references/config.md","size":7151,"sha256":"eb082309588c50ce396ee1f57f5b3efbf9da25a35e123e01e009352e84043691","contentType":"text/markdown; charset=utf-8"},{"id":"dc0494f5-080b-5f5c-867e-7299f1482cb5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/dc0494f5-080b-5f5c-867e-7299f1482cb5/attachment.md","path":"references/network.md","size":5550,"sha256":"f1064f0f4301a59aa11ae021997fc29e8805f63fecc6bcd139b2f85e08622b1e","contentType":"text/markdown; charset=utf-8"},{"id":"d4a64310-e6fd-5ea4-b593-904ebb56d5f2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d4a64310-e6fd-5ea4-b593-904ebb56d5f2/attachment.md","path":"references/patterns.md","size":8589,"sha256":"ee9e7d0a6d8c47bb4b9539227bfc9fc3c3701b531ff3774d1ae8acb9277bb1eb","contentType":"text/markdown; charset=utf-8"},{"id":"8d45aff4-caf3-5bfb-a681-5479780b2a50","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8d45aff4-caf3-5bfb-a681-5479780b2a50/attachment.md","path":"references/selectors.md","size":5049,"sha256":"e5161f32fd7b321622389938abc2b6bfa8feca7f2eda8a911c10eb24faef82b5","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"e46bfdf5b2c21a1d572c715cc87842dfdcae8a5a34376ef43b7d4dda47a51253","attachment_count":14,"text_attachments":14,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/cypress-agent-skill/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"testing-qa","category_label":"Testing"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"testing-qa","metadata":{"openclaw":{"emoji":"🧪","install":[{"id":"npm","kind":"node","label":"Install Cypress (npm)","package":"cypress"}],"requires":{"anyBins":["cypress","npx"]}}},"import_tag":"clean-skills-v1","description":"Production-grade Cypress E2E and component testing — selectors, network stubbing, auth, CI parallelization, flake elimination, Page Object Model, and TypeScript support. The complete Cypress skill for AI agents.","user-invocable":true}},"renderedAt":1782980744533}

Cypress Expert Skill Quick Reference When to use this skill: - Writing or fixing Cypress E2E or component tests - Setting up Cypress in a new project - Debugging flaky tests - Adding network stubbing / API mocking - Configuring CI pipelines for Cypress - Implementing auth patterns ( ) - Building Page Object Model architecture Quick start: 1. — install 2. — interactive mode (first run generates config) 3. — headless CI mode 4. Read full references in for deep patterns --- Core Philosophy Cypress runs inside the browser . It has native access to the DOM, network requests, and application state.…