{
  "doc": {
    "id": "app/guides/migration/playwright-to-cypress",
    "title": "Migrate from Playwright to Cypress: Complete Migration Guide",
    "description": "Step-by-step guide to migrate end-to-end and component tests from Playwright to Cypress. Compare syntax, learn retry-ability, network stubbing, auth patterns, CI parallelization, and Cloud features.",
    "section": "app",
    "source_path": "/llm/markdown/app/guides/migration/playwright-to-cypress.md",
    "version": "3cf5b86b3403f604bdf7f3e35025c3bc3865e02c",
    "updated_at": "2026-05-07T17:44:31.931Z",
    "headings": [
      {
        "id": "app/guides/migration/playwright-to-cypress#migrating-from-playwright-to-cypress",
        "text": "Migrating from Playwright to Cypress",
        "level": 1
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#what-youll-learn",
        "text": "What you'll learn",
        "level": 5
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#quick-conversion-example",
        "text": "Quick conversion example",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#installing-cypress",
        "text": "Installing Cypress",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#configuration-migration",
        "text": "Configuration migration",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#config-file-structure",
        "text": "Config file structure",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#common-translations",
        "text": "Common translations",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#cli-command-line-migration",
        "text": "CLI/Command line migration",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#base-commands",
        "text": "Base commands",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#common-flag-translations",
        "text": "Common flag translations",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#passing-environment-variables",
        "text": "Passing environment variables",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#browser-installation",
        "text": "Browser installation",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#how-each-framework-handles-browsers",
        "text": "How each framework handles browsers",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#browser-versions-and-ci",
        "text": "Browser versions and CI",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#docker-images",
        "text": "Docker images",
        "level": 4
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#circleci-orb",
        "text": "CircleCI orb",
        "level": 4
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#browser-launch-options",
        "text": "Browser launch options",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#launch-arguments-extensions-and-preferences",
        "text": "Launch arguments, extensions, and preferences",
        "level": 4
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#test-structure-and-syntax-migration",
        "text": "Test structure and syntax migration",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#execution-model-and-retry-ability",
        "text": "Execution model and retry-ability",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#async-await-vs-command-chaining",
        "text": "Async/await vs command chaining",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#locators-and-selectors",
        "text": "Locators and selectors",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#prefer-stable-selectors",
        "text": "Prefer stable selectors",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#getbyrole-and-getbylabel",
        "text": "getByRole() and getByLabel()",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#interactions",
        "text": "Interactions",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#click-type-and-clear",
        "text": "Click, type, and clear",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#checkboxes-radios-and-selects",
        "text": "Checkboxes, radios, and selects",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#hover",
        "text": "Hover",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#mobile-viewport-testing",
        "text": "Mobile viewport testing",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#assertions",
        "text": "Assertions",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#visibility-and-existence",
        "text": "Visibility and existence",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#text-attributes-and-url",
        "text": "Text, attributes, and URL",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#waiting-retries-and-timeouts",
        "text": "Waiting, retries, and timeouts",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#network-spying-and-stubbing",
        "text": "Network spying and stubbing",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#spy",
        "text": "Spy",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#stub-a-response",
        "text": "Stub a response",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#modify-a-response",
        "text": "Modify a response",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#authentication-patterns",
        "text": "Authentication patterns",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#cross-origin",
        "text": "Cross-origin",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#screenshots",
        "text": "Screenshots",
        "level": 1
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#file-uploads-and-downloads",
        "text": "File uploads and downloads",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#upload",
        "text": "Upload",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#downloads",
        "text": "Downloads",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#clock-and-time-control",
        "text": "Clock and time control",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#advancing-time",
        "text": "Advancing time",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#pausing-at-a-specific-time",
        "text": "Pausing at a specific time",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#changing-the-system-time-mid-test",
        "text": "Changing the system time mid-test",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#overriding-only-specific-functions",
        "text": "Overriding only specific functions",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#multiple-tabs-and-windows",
        "text": "Multiple tabs and windows",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#iframes",
        "text": "Iframes",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#shadow-dom",
        "text": "Shadow DOM",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#dialogs",
        "text": "Dialogs",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#alert",
        "text": "alert()",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#confirm",
        "text": "confirm()",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#prompt",
        "text": "prompt()",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#beforeunload",
        "text": "beforeunload",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#print-dialogs",
        "text": "print dialogs",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#api-testing",
        "text": "API testing",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#test-generation",
        "text": "Test generation",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#component-testing",
        "text": "Component Testing",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#framework-and-bundler-support",
        "text": "Framework and bundler support",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#reusable-code-patterns",
        "text": "Reusable code patterns",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#page-object-model",
        "text": "Page object model",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#parameterized-tests",
        "text": "Parameterized tests",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#custom-commands",
        "text": "Custom commands",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#ci-parallelization",
        "text": "CI parallelization",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#key-architectural-difference",
        "text": "Key architectural difference",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#smart-orchestration-in-cypress-cloud",
        "text": "Smart Orchestration in Cypress Cloud",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#debugging-with-test-replay",
        "text": "Debugging with Test Replay",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#handling-flaky-tests",
        "text": "Handling flaky tests",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#flaky-test-management-in-cypress-cloud",
        "text": "Flaky test management in Cypress Cloud",
        "level": 3
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#accessibility-testing",
        "text": "Accessibility testing",
        "level": 2
      },
      {
        "id": "app/guides/migration/playwright-to-cypress#cheat-sheet",
        "text": "Cheat sheet",
        "level": 2
      }
    ]
  },
  "chunks": [
    {
      "id": "app/guides/migration/playwright-to-cypress#what-youll-learn",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "What you'll learn",
      "heading_level": 5,
      "content_markdown": "##### What you'll learn\n\n*   How Cypress executes tests: command chaining, retry-ability, and what replaces async/await\n*   Configuration, CLI flags, and browser installation\n*   Locators, interactions, assertions, and network stubbing mapped side by side\n*   Authentication, cross-origin testing, and multi-tab patterns\n*   Clock control, dialogs, iframes, and file handling\n*   Page objects, parameterized tests, and custom commands\n*   Parallelization, Smart Orchestration, Test Replay, and accessibility testing in Cypress Cloud\n",
      "section": "app",
      "anchors": [
        "what-youll-learn"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 92
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#quick-conversion-example",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Quick conversion example",
      "heading_level": 2,
      "content_markdown": "## Quick conversion example\n\nHere's a side-by-side comparison showing how Playwright tests translate to Cypress:\n\nBefore: Playwright\n\ntest.ts\n\n```\nimport { test, expect } from '@playwright/test'test.describe('Authorization', () => {  test('signs up', async ({ page }) => {    // Navigate    await page.goto('/signup')    // Fill form    await page.getByLabel('Email').fill('user@email.com')    await page.getByLabel('Confirm Email').fill('user@email.com')    await page.getByLabel('Password').fill('testPassword1234')    // Submit    await page.getByRole('button', { name: 'Create new account' }).click()    // Assert navigation    await expect(page).toHaveURL(/\\/signup\\/success$/)  })})\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ndescribe('Authorization', () => {  it('signs up', () => {    // Navigate    cy.visit('/signup')    // Fill form    cy.get('[data-testid=\"email\"]').type('user@email.com')    cy.get('[data-testid=\"confirm-email\"]').type('user@email.com')    cy.get('[data-testid=\"password\"]').type('testPassword1234')    // Submit    cy.contains('button', 'Create new account').click()    // Assert navigation    cy.url().should('include', 'signup/success')  })})\n```\n",
      "section": "app",
      "anchors": [
        "quick-conversion-example"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 137
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#installing-cypress",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Installing Cypress",
      "heading_level": 2,
      "content_markdown": "## Installing Cypress\n\nBefore migrating your tests, add Cypress to your project as a dev dependency.\n\n*   npm\n*   yarn\n*   pnpm\n\n```\nnpm install cypress --save-dev\n```\n\n```\nyarn add cypress --dev\n```\n\n```\npnpm add --save-dev cypress\n```\n\nUnlike Playwright, Cypress does not download its own browser binaries. It uses browsers already installed on your machine. Electron is bundled with Cypress and available by default, so you can run tests immediately after installation without any additional setup. Once installed, open the Cypress app to complete initial configuration:\n\n*   npm\n*   yarn\n*   pnpm\n\n```\nnpx cypress open\n```\n\n```\nyarn cypress open\n```\n\n```\npnpm cypress open\n```\n\nThe Cypress App will walk you through choosing a testing type (end-to-end or component) and generating a starter configuration file. After setup, Cypress creates a `cypress.config.js` (or `.ts`) file and a `cypress/` directory in your project root.\n",
      "section": "app",
      "anchors": [
        "installing-cypress"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 196
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#configuration-migration",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Configuration migration",
      "heading_level": 2,
      "content_markdown": "## Configuration migration\n\nBoth Playwright and Cypress use a `defineConfig`\\-based configuration file at the root of your project. The key structural difference is that Cypress scopes most E2E-specific options under an `e2e` object, rather than a flat `use` block.\n\n### Config file structure\n\nThe configuration file location is configurable using the `--config-file` command line flag or the `configFile` Module API option.\n\n|  | Playwright | Cypress |\n| --- | --- | --- |\n| Default config file | `playwright.config.ts` | `cypress.config.ts` |\n| Test files | `testDir: './tests'` | `e2e.specPattern` (glob) |\n| E2E-specific options | `use: { ... }` | `e2e: { ... }` |\n| Node event hooks | `globalSetup` / `globalTeardown` | `e2e.setupNodeEvents` |\n\nBefore: Playwright\n\nplaywright.config.ts\n\n```\nimport { defineConfig } from '@playwright/test'export default defineConfig({  testDir: './tests',  testExclude: '**/*.cy.ts',  outputDir: './test-results',  retries: process.env.CI ? 2 : 0,  timeout: 30000,  reporter: 'html',  use: {    baseURL: 'http://localhost:3000',    viewport: { width: 1280, height: 720 },    video: 'off',    screenshot: 'only-on-failure',  },})\n```\n\nAfter: Cypress\n\ncypress.config.ts\n\n```\nimport { defineConfig } from 'cypress'export default defineConfig({  viewportWidth: 1280,  viewportHeight: 720,  video: false,  videosFolder: './test-results/videos',  reporter: 'html',  screenshotsFolder: './test-results/screenshots',  e2e: {    baseUrl: 'http://localhost:3000',    specPattern: 'tests/**/*.cy.ts',    excludeSpecPattern: '**/*.ts',    retries: {      runMode: 2,      openMode: 0,    },    defaultCommandTimeout: 30000,    screenshotOnRunFailure: true,  },})\n```\n\nSee [Cypress Configuration](/llm/markdown/app/references/configuration.md) for the full option reference.\n\n### Common translations\n\n| Playwright | Cypress | Notes |\n| --- | --- | --- |\n| `forbidOnly` | use `mocha/no-exclusive-tests` lint rule | [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) |\n| `headless` | `--headless` or `--headed` CLI flag | Default headless in `cypress run` |\n| `outputDir` | `screenshotsFolder` / `videosFolder` | Separate options per asset type |\n| `reporter` | `reporter` | See [Reporters](/llm/markdown/app/tooling/reporters.md). |\n| `retries` | `retries.runMode` / `retries.openMode` | Cypress separates CI and interactive mode retries |\n| `testDir` | `specPattern` | Cypress uses a glob, not a path. |\n| `testIgnore` | `excludeSpecPattern` | Glob string or array of globs. |\n| `timeout` | `defaultCommandTimeout` | [`defaultCommandTimeout`](/llm/markdown/app/references/configuration.md#Timeouts) is per command, not test. |\n| `use.baseURL` | `e2e.baseUrl` | Must be under `e2e`, not top-level |\n| `use.channel` | `--browser <name>:<channel>` on the CLI | See [Launching Browsers](/llm/markdown/app/references/launching-browsers.md). |\n| `use.proxy` | OS environment variables | See [Proxy Configuration](/llm/markdown/app/references/proxy-configuration.md) |\n| `use.screenshot` | `screenshotOnRunFailure` | Default is `true` |\n| `use.video` | `video` | Default is `false` |\n| `use.viewport` | `viewportWidth` / `viewportHeight` | Set at top level or scoped under testing type |\n",
      "section": "app",
      "anchors": [
        "configuration-migration"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 536
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#config-file-structure",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Config file structure",
      "heading_level": 3,
      "content_markdown": "### Config file structure\n\nThe configuration file location is configurable using the `--config-file` command line flag or the `configFile` Module API option.\n\n|  | Playwright | Cypress |\n| --- | --- | --- |\n| Default config file | `playwright.config.ts` | `cypress.config.ts` |\n| Test files | `testDir: './tests'` | `e2e.specPattern` (glob) |\n| E2E-specific options | `use: { ... }` | `e2e: { ... }` |\n| Node event hooks | `globalSetup` / `globalTeardown` | `e2e.setupNodeEvents` |\n\nBefore: Playwright\n\nplaywright.config.ts\n\n```\nimport { defineConfig } from '@playwright/test'export default defineConfig({  testDir: './tests',  testExclude: '**/*.cy.ts',  outputDir: './test-results',  retries: process.env.CI ? 2 : 0,  timeout: 30000,  reporter: 'html',  use: {    baseURL: 'http://localhost:3000',    viewport: { width: 1280, height: 720 },    video: 'off',    screenshot: 'only-on-failure',  },})\n```\n\nAfter: Cypress\n\ncypress.config.ts\n\n```\nimport { defineConfig } from 'cypress'export default defineConfig({  viewportWidth: 1280,  viewportHeight: 720,  video: false,  videosFolder: './test-results/videos',  reporter: 'html',  screenshotsFolder: './test-results/screenshots',  e2e: {    baseUrl: 'http://localhost:3000',    specPattern: 'tests/**/*.cy.ts',    excludeSpecPattern: '**/*.ts',    retries: {      runMode: 2,      openMode: 0,    },    defaultCommandTimeout: 30000,    screenshotOnRunFailure: true,  },})\n```\n\nSee [Cypress Configuration](/llm/markdown/app/references/configuration.md) for the full option reference.\n",
      "section": "app",
      "anchors": [
        "config-file-structure"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 236
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#common-translations",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Common translations",
      "heading_level": 3,
      "content_markdown": "### Common translations\n\n| Playwright | Cypress | Notes |\n| --- | --- | --- |\n| `forbidOnly` | use `mocha/no-exclusive-tests` lint rule | [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) |\n| `headless` | `--headless` or `--headed` CLI flag | Default headless in `cypress run` |\n| `outputDir` | `screenshotsFolder` / `videosFolder` | Separate options per asset type |\n| `reporter` | `reporter` | See [Reporters](/llm/markdown/app/tooling/reporters.md). |\n| `retries` | `retries.runMode` / `retries.openMode` | Cypress separates CI and interactive mode retries |\n| `testDir` | `specPattern` | Cypress uses a glob, not a path. |\n| `testIgnore` | `excludeSpecPattern` | Glob string or array of globs. |\n| `timeout` | `defaultCommandTimeout` | [`defaultCommandTimeout`](/llm/markdown/app/references/configuration.md#Timeouts) is per command, not test. |\n| `use.baseURL` | `e2e.baseUrl` | Must be under `e2e`, not top-level |\n| `use.channel` | `--browser <name>:<channel>` on the CLI | See [Launching Browsers](/llm/markdown/app/references/launching-browsers.md). |\n| `use.proxy` | OS environment variables | See [Proxy Configuration](/llm/markdown/app/references/proxy-configuration.md) |\n| `use.screenshot` | `screenshotOnRunFailure` | Default is `true` |\n| `use.video` | `video` | Default is `false` |\n| `use.viewport` | `viewportWidth` / `viewportHeight` | Set at top level or scoped under testing type |\n",
      "section": "app",
      "anchors": [
        "common-translations"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 248
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#cli-command-line-migration",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "CLI/Command line migration",
      "heading_level": 2,
      "content_markdown": "## CLI/Command line migration\n\nPlaywright and Cypress use different commands and flag structures. This section covers the most common translations.\n\n### Base commands\n\n| Task | Playwright | Cypress |\n| --- | --- | --- |\n| Run all tests (headless) | `npx playwright test` | `cypress run` |\n| Open interactive mode | `npx playwright test --ui` | `cypress open` |\n| Run in headed mode | `npx playwright test --headed` | `cypress run --headed` |\n\n### Common flag translations\n\n| Playwright | Cypress |\n| --- | --- |\n| `--config=playwright.config.ts` | `--config-file cypress.config.ts` |\n| `npx playwright test todo.spec.ts` | `cypress run --spec \"todo.cy.ts\"` |\n| `--fail-on-flaky-tests` | `detect-flake-but-always-fail` in config with [Experimental Test Retries](/llm/markdown/app/references/experiments.md#Experimental-Test-Retries) |\n| `--forbid-only` | `mocha/no-exclusive-tests` [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) rule |\n| `--grep \"pattern\"` | `--expose grep=\"pattern\"` from [`@cypress/grep`](https://github.com/cypress-io/cypress/tree/develop/npm/grep) |\n| `--grep-invert \"pattern\"` | `--expose grep=\"-pattern\"` from [`@cypress/grep`](https://github.com/cypress-io/cypress/tree/develop/npm/grep) |\n| `--last-failed` | [Spec Prioritization](/llm/markdown/cloud/features/smart-orchestration/spec-prioritization.md) |\n| `--max-failures=5` | `--auto-cancel-after-failures=5` |\n| `--project=chromium` | [`--project='./front-end'`](/llm/markdown/app/references/command-line.md#cypress-open-project-lt-project-path-gt) |\n| `--quiet` | `--quiet` |\n| `--retries=2` | `--config retries=2` |\n| `--reporter=html` | `--reporter html` |\n| `--shard=1/4` | `--parallel` |\n| `--fully-parallel` | `--parallel` |\n| `--workers=4` | `--parallel` |\n| `--trace on` | `--record` automatically records [Test Replay](/llm/markdown/cloud/features/test-replay.md) |\n\n### Passing environment variables\n\nIn Cypress, use `cy.env()` for sensitive values (credentials, tokens, secrets) or `Cypress.expose()` for non-sensitive configuration (feature flags, public URLs, API versions). These must be set in the Node.js environment within `setupNodeEvents` in your Cypress configuration file.\n\nBefore: Playwright\n\ntest.ts\n\n```\ntest('logs in', async ({ page }) => {  await page.getByLabel('User Name').fill(process.env.USER_NAME)  await page.getByLabel('Password').fill(process.env.PASSWORD)})\n```\n\nAfter: Cypress\n\ncypress.config.ts\n\n```\nexport default defineConfig({  expose: {    API_VERSION: 'v2',  },  env: {    USER_NAME: process.env.USER_NAME,    PASSWORD: process.env.PASSWORD,  },})\n```\n\ntest.cy.ts\n\n```\nit('logs in', () => {  cy.env(['USER_NAME', 'PASSWORD']).then(({ USER_NAME, PASSWORD }) => {    const apiVersion = Cypress.expose('API_VERSION')    cy.visit(`/login/${apiVersion}`)    cy.get('[data-testid=\"username\"]').type(USER_NAME)    cy.get('[data-testid=\"password\"]').type(PASSWORD)  })})\n```\n\nSee [cy.env()](/llm/markdown/api/commands/env.md) and [Cypress.expose()](/llm/markdown/api/cypress-api/expose.md) for the full reference.\n",
      "section": "app",
      "anchors": [
        "cli-command-line-migration"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 416
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#base-commands",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Base commands",
      "heading_level": 3,
      "content_markdown": "### Base commands\n\n| Task | Playwright | Cypress |\n| --- | --- | --- |\n| Run all tests (headless) | `npx playwright test` | `cypress run` |\n| Open interactive mode | `npx playwright test --ui` | `cypress open` |\n| Run in headed mode | `npx playwright test --headed` | `cypress run --headed` |\n",
      "section": "app",
      "anchors": [
        "base-commands"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 77
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#common-flag-translations",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Common flag translations",
      "heading_level": 3,
      "content_markdown": "### Common flag translations\n\n| Playwright | Cypress |\n| --- | --- |\n| `--config=playwright.config.ts` | `--config-file cypress.config.ts` |\n| `npx playwright test todo.spec.ts` | `cypress run --spec \"todo.cy.ts\"` |\n| `--fail-on-flaky-tests` | `detect-flake-but-always-fail` in config with [Experimental Test Retries](/llm/markdown/app/references/experiments.md#Experimental-Test-Retries) |\n| `--forbid-only` | `mocha/no-exclusive-tests` [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) rule |\n| `--grep \"pattern\"` | `--expose grep=\"pattern\"` from [`@cypress/grep`](https://github.com/cypress-io/cypress/tree/develop/npm/grep) |\n| `--grep-invert \"pattern\"` | `--expose grep=\"-pattern\"` from [`@cypress/grep`](https://github.com/cypress-io/cypress/tree/develop/npm/grep) |\n| `--last-failed` | [Spec Prioritization](/llm/markdown/cloud/features/smart-orchestration/spec-prioritization.md) |\n| `--max-failures=5` | `--auto-cancel-after-failures=5` |\n| `--project=chromium` | [`--project='./front-end'`](/llm/markdown/app/references/command-line.md#cypress-open-project-lt-project-path-gt) |\n| `--quiet` | `--quiet` |\n| `--retries=2` | `--config retries=2` |\n| `--reporter=html` | `--reporter html` |\n| `--shard=1/4` | `--parallel` |\n| `--fully-parallel` | `--parallel` |\n| `--workers=4` | `--parallel` |\n| `--trace on` | `--record` automatically records [Test Replay](/llm/markdown/cloud/features/test-replay.md) |\n",
      "section": "app",
      "anchors": [
        "common-flag-translations"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 167
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#passing-environment-variables",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Passing environment variables",
      "heading_level": 3,
      "content_markdown": "### Passing environment variables\n\nIn Cypress, use `cy.env()` for sensitive values (credentials, tokens, secrets) or `Cypress.expose()` for non-sensitive configuration (feature flags, public URLs, API versions). These must be set in the Node.js environment within `setupNodeEvents` in your Cypress configuration file.\n\nBefore: Playwright\n\ntest.ts\n\n```\ntest('logs in', async ({ page }) => {  await page.getByLabel('User Name').fill(process.env.USER_NAME)  await page.getByLabel('Password').fill(process.env.PASSWORD)})\n```\n\nAfter: Cypress\n\ncypress.config.ts\n\n```\nexport default defineConfig({  expose: {    API_VERSION: 'v2',  },  env: {    USER_NAME: process.env.USER_NAME,    PASSWORD: process.env.PASSWORD,  },})\n```\n\ntest.cy.ts\n\n```\nit('logs in', () => {  cy.env(['USER_NAME', 'PASSWORD']).then(({ USER_NAME, PASSWORD }) => {    const apiVersion = Cypress.expose('API_VERSION')    cy.visit(`/login/${apiVersion}`)    cy.get('[data-testid=\"username\"]').type(USER_NAME)    cy.get('[data-testid=\"password\"]').type(PASSWORD)  })})\n```\n\nSee [cy.env()](/llm/markdown/api/commands/env.md) and [Cypress.expose()](/llm/markdown/api/cypress-api/expose.md) for the full reference.\n",
      "section": "app",
      "anchors": [
        "passing-environment-variables"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 145
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#browser-installation",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Browser installation",
      "heading_level": 2,
      "content_markdown": "## Browser installation\n\nPlaywright and Cypress take different approaches to browser management. Understanding the difference will help you set up CI correctly.\n\n### How each framework handles browsers\n\nPlaywright downloads and manages its own isolated browser binaries. Running `npx playwright install` fetches Playwright-specific builds of Chromium, Firefox, and WebKit that are separate from any browsers installed on your system.\n\nCypress uses browsers already installed on the machine. There are no browser binaries to download. If Chrome is installed locally, Cypress will find and use it. This means browser setup is a CI concern rather than a per-project dependency.\n\nBefore: Playwright\n\n```\nnpx playwright install chromium webkit\n```\n\nAfter: Cypress\n\nTo verify which browsers Cypress can find:\n\n```\n cypress info\n```\n\n### Browser versions and CI\n\nBecause Cypress uses system browsers, you have explicit control over which browser version your tests run against.\n\n#### Docker images\n\nCypress publishes official Docker images with specific browser versions pre-installed. Image tags encode the exact versions of Node, Chrome, Firefox, and Edge, so your CI environment is reproducible and version changes are explicit.\n\n**Node + browsers, without Cypress**\n\n```\ncypress/browsers:node-22.19.0-chrome-139.0.7258.154-1-ff-142.0.1-edge-139.0.3405.125-1\n```\n\n**Node + browsers + Cypress pre-installed**\n\n```\ncypress/included:cypress-15.1.0-node-22.19.0-chrome-139.0.7258.154-1-ff-142.0.1-edge-139.0.3405.125-1\n```\n\nUse `cypress/browsers` when you want to control the Cypress version through your `package.json`. Use `cypress/included` when you want a fully self-contained image. If you need a specific combination of versions not covered by the published images, use [`cypress/factory`](https://github.com/cypress-io/cypress-docker-images/tree/master/factory) to build a custom image.\n\nSee [cypress-docker-images](https://github.com/cypress-io/cypress-docker-images) for all available tags.\n\n#### CircleCI orb\n\nIf you use CircleCI, the [Cypress CircleCI orb](https://github.com/cypress-io/circleci-orb) handles installation, caching, and test execution. Pass `install-browsers: true` to install Chrome, Firefox, and Edge into the CI environment.\n\n.circleci/config.yml\n\n```\nversion: 2.1orbs:  cypress: cypress-io/cypress@6workflows:  build:    jobs:      - cypress/run:          install-browsers: true          start-command: 'npm start'          cypress-command: 'npx cypress run --browser chrome'\n```\n\nSee [CircleCI](/llm/markdown/app/continuous-integration/circleci.md) for full setup documentation.\n\n### Browser launch options\n\nPlaywright's `launchOptions` in `playwright.config.ts` maps to the `before:browser:launch` event in Cypress, which you configure inside `setupNodeEvents` of your Cypress configuration file. Both let you modify how the browser starts. See the [Browser Launch Event](/llm/markdown/api/node-events/browser-launch-api.md) for the full reference.\n\n#### Launch arguments, extensions, and preferences\n\nCypress exposes the `before:browser:launch` event.\n\nBefore: Playwright\n\nplaywright.config.ts\n\n```\nexport default defineConfig({  use: {    launchOptions: {      args: ['--disable-gpu'],    },  },})\n```\n\nAfter: Cypress\n\ncypress.config.ts\n\n```\nexport default defineConfig({  e2e: {    setupNodeEvents(on) {      on('before:browser:launch', (browser, launchOptions) => {        if (browser.family === 'chromium' && browser.name !== 'electron') {          launchOptions.args.push('--disable-gpu')        }        return launchOptions      })    },  },})\n```\n\nThe `launchOptions` object exposes three properties depending on the browser:\n\n| Capability | Playwright | Cypress |\n| --- | --- | --- |\n| Browser launch arguments | `launchOptions.args` | `launchOptions.args` via `before:browser:launch` |\n| Firefox preferences | `launchOptions.firefoxUserPrefs` | `launchOptions.preferences` via `before:browser:launch` |\n| Chrome/Edge extensions | `args` flags in `launchPersistentContext()` | `launchOptions.extensions` via `before:browser:launch` |\n\nSee the [Browser Launch Event](/llm/markdown/api/node-events/browser-launch-api.md) for the full reference and per-browser examples.\n",
      "section": "app",
      "anchors": [
        "browser-installation"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 632
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#how-each-framework-handles-browsers",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "How each framework handles browsers",
      "heading_level": 3,
      "content_markdown": "### How each framework handles browsers\n\nPlaywright downloads and manages its own isolated browser binaries. Running `npx playwright install` fetches Playwright-specific builds of Chromium, Firefox, and WebKit that are separate from any browsers installed on your system.\n\nCypress uses browsers already installed on the machine. There are no browser binaries to download. If Chrome is installed locally, Cypress will find and use it. This means browser setup is a CI concern rather than a per-project dependency.\n\nBefore: Playwright\n\n```\nnpx playwright install chromium webkit\n```\n\nAfter: Cypress\n\nTo verify which browsers Cypress can find:\n\n```\n cypress info\n```\n",
      "section": "app",
      "anchors": [
        "how-each-framework-handles-browsers"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 131
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#browser-versions-and-ci",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Browser versions and CI",
      "heading_level": 3,
      "content_markdown": "### Browser versions and CI\n\nBecause Cypress uses system browsers, you have explicit control over which browser version your tests run against.\n\n#### Docker images\n\nCypress publishes official Docker images with specific browser versions pre-installed. Image tags encode the exact versions of Node, Chrome, Firefox, and Edge, so your CI environment is reproducible and version changes are explicit.\n\n**Node + browsers, without Cypress**\n\n```\ncypress/browsers:node-22.19.0-chrome-139.0.7258.154-1-ff-142.0.1-edge-139.0.3405.125-1\n```\n\n**Node + browsers + Cypress pre-installed**\n\n```\ncypress/included:cypress-15.1.0-node-22.19.0-chrome-139.0.7258.154-1-ff-142.0.1-edge-139.0.3405.125-1\n```\n\nUse `cypress/browsers` when you want to control the Cypress version through your `package.json`. Use `cypress/included` when you want a fully self-contained image. If you need a specific combination of versions not covered by the published images, use [`cypress/factory`](https://github.com/cypress-io/cypress-docker-images/tree/master/factory) to build a custom image.\n\nSee [cypress-docker-images](https://github.com/cypress-io/cypress-docker-images) for all available tags.\n\n#### CircleCI orb\n\nIf you use CircleCI, the [Cypress CircleCI orb](https://github.com/cypress-io/circleci-orb) handles installation, caching, and test execution. Pass `install-browsers: true` to install Chrome, Firefox, and Edge into the CI environment.\n\n.circleci/config.yml\n\n```\nversion: 2.1orbs:  cypress: cypress-io/cypress@6workflows:  build:    jobs:      - cypress/run:          install-browsers: true          start-command: 'npm start'          cypress-command: 'npx cypress run --browser chrome'\n```\n\nSee [CircleCI](/llm/markdown/app/continuous-integration/circleci.md) for full setup documentation.\n",
      "section": "app",
      "anchors": [
        "browser-versions-and-ci"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 243
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#docker-images",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Docker images",
      "heading_level": 4,
      "content_markdown": "#### Docker images\n\nCypress publishes official Docker images with specific browser versions pre-installed. Image tags encode the exact versions of Node, Chrome, Firefox, and Edge, so your CI environment is reproducible and version changes are explicit.\n\n**Node + browsers, without Cypress**\n\n```\ncypress/browsers:node-22.19.0-chrome-139.0.7258.154-1-ff-142.0.1-edge-139.0.3405.125-1\n```\n\n**Node + browsers + Cypress pre-installed**\n\n```\ncypress/included:cypress-15.1.0-node-22.19.0-chrome-139.0.7258.154-1-ff-142.0.1-edge-139.0.3405.125-1\n```\n\nUse `cypress/browsers` when you want to control the Cypress version through your `package.json`. Use `cypress/included` when you want a fully self-contained image. If you need a specific combination of versions not covered by the published images, use [`cypress/factory`](https://github.com/cypress-io/cypress-docker-images/tree/master/factory) to build a custom image.\n\nSee [cypress-docker-images](https://github.com/cypress-io/cypress-docker-images) for all available tags.\n",
      "section": "app",
      "anchors": [
        "docker-images"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 136
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#circleci-orb",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "CircleCI orb",
      "heading_level": 4,
      "content_markdown": "#### CircleCI orb\n\nIf you use CircleCI, the [Cypress CircleCI orb](https://github.com/cypress-io/circleci-orb) handles installation, caching, and test execution. Pass `install-browsers: true` to install Chrome, Firefox, and Edge into the CI environment.\n\n.circleci/config.yml\n\n```\nversion: 2.1orbs:  cypress: cypress-io/cypress@6workflows:  build:    jobs:      - cypress/run:          install-browsers: true          start-command: 'npm start'          cypress-command: 'npx cypress run --browser chrome'\n```\n\nSee [CircleCI](/llm/markdown/app/continuous-integration/circleci.md) for full setup documentation.\n",
      "section": "app",
      "anchors": [
        "circleci-orb"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 77
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#browser-launch-options",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Browser launch options",
      "heading_level": 3,
      "content_markdown": "### Browser launch options\n\nPlaywright's `launchOptions` in `playwright.config.ts` maps to the `before:browser:launch` event in Cypress, which you configure inside `setupNodeEvents` of your Cypress configuration file. Both let you modify how the browser starts. See the [Browser Launch Event](/llm/markdown/api/node-events/browser-launch-api.md) for the full reference.\n\n#### Launch arguments, extensions, and preferences\n\nCypress exposes the `before:browser:launch` event.\n\nBefore: Playwright\n\nplaywright.config.ts\n\n```\nexport default defineConfig({  use: {    launchOptions: {      args: ['--disable-gpu'],    },  },})\n```\n\nAfter: Cypress\n\ncypress.config.ts\n\n```\nexport default defineConfig({  e2e: {    setupNodeEvents(on) {      on('before:browser:launch', (browser, launchOptions) => {        if (browser.family === 'chromium' && browser.name !== 'electron') {          launchOptions.args.push('--disable-gpu')        }        return launchOptions      })    },  },})\n```\n\nThe `launchOptions` object exposes three properties depending on the browser:\n\n| Capability | Playwright | Cypress |\n| --- | --- | --- |\n| Browser launch arguments | `launchOptions.args` | `launchOptions.args` via `before:browser:launch` |\n| Firefox preferences | `launchOptions.firefoxUserPrefs` | `launchOptions.preferences` via `before:browser:launch` |\n| Chrome/Edge extensions | `args` flags in `launchPersistentContext()` | `launchOptions.extensions` via `before:browser:launch` |\n\nSee the [Browser Launch Event](/llm/markdown/api/node-events/browser-launch-api.md) for the full reference and per-browser examples.\n",
      "section": "app",
      "anchors": [
        "browser-launch-options"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 229
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#launch-arguments-extensions-and-preferences",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Launch arguments, extensions, and preferences",
      "heading_level": 4,
      "content_markdown": "#### Launch arguments, extensions, and preferences\n\nCypress exposes the `before:browser:launch` event.\n\nBefore: Playwright\n\nplaywright.config.ts\n\n```\nexport default defineConfig({  use: {    launchOptions: {      args: ['--disable-gpu'],    },  },})\n```\n\nAfter: Cypress\n\ncypress.config.ts\n\n```\nexport default defineConfig({  e2e: {    setupNodeEvents(on) {      on('before:browser:launch', (browser, launchOptions) => {        if (browser.family === 'chromium' && browser.name !== 'electron') {          launchOptions.args.push('--disable-gpu')        }        return launchOptions      })    },  },})\n```\n\nThe `launchOptions` object exposes three properties depending on the browser:\n\n| Capability | Playwright | Cypress |\n| --- | --- | --- |\n| Browser launch arguments | `launchOptions.args` | `launchOptions.args` via `before:browser:launch` |\n| Firefox preferences | `launchOptions.firefoxUserPrefs` | `launchOptions.preferences` via `before:browser:launch` |\n| Chrome/Edge extensions | `args` flags in `launchPersistentContext()` | `launchOptions.extensions` via `before:browser:launch` |\n\nSee the [Browser Launch Event](/llm/markdown/api/node-events/browser-launch-api.md) for the full reference and per-browser examples.\n",
      "section": "app",
      "anchors": [
        "launch-arguments-extensions-and-preferences"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 173
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#test-structure-and-syntax-migration",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Test structure and syntax migration",
      "heading_level": 2,
      "content_markdown": "## Test structure and syntax migration\n\nPlaywright imports `test` from `@playwright/test`. Cypress uses Mocha's BDD interface, which provides `it()` and `describe()` globally. `test` is not available in Cypress and must be replaced.\n\n| Playwright | Cypress |\n| --- | --- |\n| `test('name', () => {})` | `it('name', () => {})` |\n| `test.only('name', () => {})` | `it.only('name', () => {})` |\n| `test.skip('name', () => {})` | `it.skip('name', () => {})` |\n| `test.describe('group', () => {})` | `describe('group', () => {})` |\n| `test.describe.only('group', () => {})` | `describe.only('group', () => {})` |\n\nBefore: Playwright\n\ntest.ts\n\n```\nimport { test, expect } from '@playwright/test'test.describe('checkout', () => {  test('completes purchase', async ({ page }) => { ... })  test.only('applies discount', async ({ page }) => { ... })  test.skip('handles expired card', async ({ page }) => { ... })})\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ndescribe('checkout', () => {  it('completes purchase', () => { ... })  it.only('applies discount', () => { ... })  it.skip('handles expired card', () => { ... })})\n```\n",
      "section": "app",
      "anchors": [
        "test-structure-and-syntax-migration"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 232
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#execution-model-and-retry-ability",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Execution model and retry-ability",
      "heading_level": 2,
      "content_markdown": "## Execution model and retry-ability\n\nPlaywright code typically awaits each action and may use explicit waits for specific conditions. Cypress commands are enqueued and automatically retry assertions until they pass or timeout.\n\n**What this means for your tests:**\n\n| Playwright | Cypress |\n| --- | --- |\n| `await page.waitForSelector(...)` | `cy.get(...)` retries until it finds the element |\n| `await expect(locator)...` | `cy.get(...).should(...)` retries automatically |\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.getByTestId('submit').click()await page.waitForSelector('[data-testid=\"toast\"]')await expect(page.getByTestId('toast')).toHaveText('Saved!')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.get('[data-testid=\"submit\"]').click()cy.get('[data-testid=\"toast\"]').should('have.text', 'Saved!')\n```\n\nLearn more about Cypress [retry-ability](/llm/markdown/app/core-concepts/retry-ability.md).\n",
      "section": "app",
      "anchors": [
        "execution-model-and-retry-ability"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 119
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#async-await-vs-command-chaining",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Async/await vs command chaining",
      "heading_level": 2,
      "content_markdown": "## Async/await vs command chaining\n\nPlaywright uses async/await patterns.\n\nBefore: Playwright\n\ntest.ts\n\n```\nconst button = await page.getByRole('button', { name: 'Submit' })await button.click()const toast = await page.getByTestId('toast')await expect(toast).toHaveText('Success')\n```\n\nCypress uses command chaining without async/await.\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.get('button').contains('Submit').click()cy.get('[data-testid=\"toast\"]').should('have.text', 'Success')\n```\n\nSee Cypress's command model in the [Introduction to Cypress](/llm/markdown/app/core-concepts/introduction-to-cypress.md#The-Cypress-Command-Queue).\n",
      "section": "app",
      "anchors": [
        "async-await-vs-command-chaining"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 68
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#locators-and-selectors",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Locators and selectors",
      "heading_level": 2,
      "content_markdown": "## Locators and selectors\n\n### Prefer stable selectors\n\nUse a stable selector strategy (often `data-*`) as described in Cypress [Best Practices](/llm/markdown/app/core-concepts/best-practices.md#Selecting-Elements).\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.getByTestId('email').fill('user@email.com')await page.getByRole('button', { name: 'Sign in' }).click()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.get('[data-testid=\"email\"]').type('user@email.com')cy.contains('button', 'Sign in').click()\n```\n\n### getByRole() and getByLabel()\n\nIf you want `getByRole()`/`getByLabel()` ergonomics in Cypress, install [Cypress Testing Library](https://github.com/testing-library/cypress-testing-library).\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.getByRole('button', { name: 'Save' }).click()await page.getByLabel('First name').fill('Jane')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.findByRole('button', { name: 'Save' }).click()cy.findByLabelText('First name').type('Jane')\n```\n",
      "section": "app",
      "anchors": [
        "locators-and-selectors"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 108
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#prefer-stable-selectors",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Prefer stable selectors",
      "heading_level": 3,
      "content_markdown": "### Prefer stable selectors\n\nUse a stable selector strategy (often `data-*`) as described in Cypress [Best Practices](/llm/markdown/app/core-concepts/best-practices.md#Selecting-Elements).\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.getByTestId('email').fill('user@email.com')await page.getByRole('button', { name: 'Sign in' }).click()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.get('[data-testid=\"email\"]').type('user@email.com')cy.contains('button', 'Sign in').click()\n```\n",
      "section": "app",
      "anchors": [
        "prefer-stable-selectors"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 51
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#getbyrole-and-getbylabel",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "getByRole() and getByLabel()",
      "heading_level": 3,
      "content_markdown": "### getByRole() and getByLabel()\n\nIf you want `getByRole()`/`getByLabel()` ergonomics in Cypress, install [Cypress Testing Library](https://github.com/testing-library/cypress-testing-library).\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.getByRole('button', { name: 'Save' }).click()await page.getByLabel('First name').fill('Jane')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.findByRole('button', { name: 'Save' }).click()cy.findByLabelText('First name').type('Jane')\n```\n",
      "section": "app",
      "anchors": [
        "getbyrole-and-getbylabel"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 52
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#interactions",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Interactions",
      "heading_level": 2,
      "content_markdown": "## Interactions\n\n### Click, type, and clear\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.locator('#username').fill('jane')await page.locator('#password').fill('secret')await page.getByRole('button', { name: 'Log in' }).click()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.get('#username').clear()cy.get('#username').type('jane')cy.get('#password').clear()cy.get('#password').type('secret')cy.contains('button', 'Log in').click()\n```\n\n### Checkboxes, radios, and selects\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.getByLabel('Subscribe').check()await page.getByLabel('Daily').check()await page.getByLabel('Country').selectOption('US')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.get('[data-testid=\"subscribe\"]').check()cy.get('[data-testid=\"daily\"]').check()cy.get('[data-testid=\"country\"]').select('US')\n```\n\n### Hover\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.getByTestId('menu').hover()await page.getByRole('menuitem', { name: 'Settings' }).click()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.get('[data-testid=\"menu\"]').trigger('mouseover')cy.contains('[role=\"menuitem\"]', 'Settings').click()\n```\n\n### Mobile viewport testing\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.setViewportSize({ width: 375, height: 812 })await page.goto('/')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.viewport(375, 812)cy.visit('/')\n```\n\nSee [`cy.viewport()`](/llm/markdown/api/commands/viewport.md).\n",
      "section": "app",
      "anchors": [
        "interactions"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 128
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#assertions",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Assertions",
      "heading_level": 2,
      "content_markdown": "## Assertions\n\n### Visibility and existence\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait expect(page.getByText('Welcome')).toBeVisible()await expect(page.getByTestId('spinner')).toBeHidden()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.contains('Welcome').should('be.visible')cy.get('[data-testid=\"spinner\"]').should('not.be.visible')\n```\n\n### Text, attributes, and URL\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait expect(page.getByTestId('toast')).toHaveText('Saved!')await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled()await expect(page).toHaveURL(/\\/dashboard$/)\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.get('[data-testid=\"toast\"]').should('have.text', 'Saved!')cy.contains('button', 'Save').should('be.disabled')cy.url().should('match', /\\/dashboard$/)\n```\n\nSee Cypress assertions at [Assertions](/llm/markdown/app/references/assertions.md).\n",
      "section": "app",
      "anchors": [
        "assertions"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 69
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#text-attributes-and-url",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Text, attributes, and URL",
      "heading_level": 3,
      "content_markdown": "### Text, attributes, and URL\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait expect(page.getByTestId('toast')).toHaveText('Saved!')await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled()await expect(page).toHaveURL(/\\/dashboard$/)\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.get('[data-testid=\"toast\"]').should('have.text', 'Saved!')cy.contains('button', 'Save').should('be.disabled')cy.url().should('match', /\\/dashboard$/)\n```\n\nSee Cypress assertions at [Assertions](/llm/markdown/app/references/assertions.md).\n",
      "section": "app",
      "anchors": [
        "text-attributes-and-url"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 43
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#waiting-retries-and-timeouts",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Waiting, retries, and timeouts",
      "heading_level": 2,
      "content_markdown": "## Waiting, retries, and timeouts\n\nPlaywright often uses explicit waits. Cypress will retry most DOM queries and assertions until they pass or time out.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.getByRole('button', { name: 'Save' }).click()await page.waitForResponse('**/api/profile')await expect(page.getByTestId('toast')).toHaveText('Saved!')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.intercept('POST', '/api/profile').as('saveProfile')cy.contains('button', 'Save').click()cy.wait('@saveProfile')cy.get('[data-testid=\"toast\"]').should('have.text', 'Saved!')\n```\n\nLearn more about Cypress retry-ability at [Retry-ability](/llm/markdown/app/core-concepts/retry-ability.md).\n",
      "section": "app",
      "anchors": [
        "waiting-retries-and-timeouts"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 71
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#network-spying-and-stubbing",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Network spying and stubbing",
      "heading_level": 2,
      "content_markdown": "## Network spying and stubbing\n\n### Spy\n\nBefore: Playwright\n\ntest.ts\n\n```\nconst [response] = await Promise.all([  page.waitForResponse('**/api/users'),  page.getByRole('button', { name: 'Load users' }).click(),])expect(response.ok()).toBeTruthy()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.intercept('GET', '/api/users').as('users')cy.contains('button', 'Load users').click()cy.wait('@users').its('response.statusCode').should('eq', 200)\n```\n\n### Stub a response\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.route('**/api/projects', async (route) => {  await route.fulfill({    status: 200,    contentType: 'application/json',    body: JSON.stringify([{ id: '1' }, { id: '2' }]),  })})\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.intercept('GET', '/api/projects', {  statusCode: 200,  body: [{ id: '1' }, { id: '2' }],}).as('projects')\n```\n\n### Modify a response\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.route('**/api/profile', async (route) => {  const response = await route.fetch()  const json = await response.json()  json.plan = 'enterprise'  await route.fulfill({ response, json })})\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.intercept('GET', '/api/profile', (req) => {  req.continue((res) => {    res.body.plan = 'enterprise'  })}).as('profile')\n```\n\nSee [Network Requests](/llm/markdown/app/guides/network-requests.md) and [`cy.intercept()`](/llm/markdown/api/commands/intercept.md).\n",
      "section": "app",
      "anchors": [
        "network-spying-and-stubbing"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 185
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#stub-a-response",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Stub a response",
      "heading_level": 3,
      "content_markdown": "### Stub a response\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.route('**/api/projects', async (route) => {  await route.fulfill({    status: 200,    contentType: 'application/json',    body: JSON.stringify([{ id: '1' }, { id: '2' }]),  })})\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.intercept('GET', '/api/projects', {  statusCode: 200,  body: [{ id: '1' }, { id: '2' }],}).as('projects')\n```\n",
      "section": "app",
      "anchors": [
        "stub-a-response"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 67
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#modify-a-response",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Modify a response",
      "heading_level": 3,
      "content_markdown": "### Modify a response\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.route('**/api/profile', async (route) => {  const response = await route.fetch()  const json = await response.json()  json.plan = 'enterprise'  await route.fulfill({ response, json })})\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.intercept('GET', '/api/profile', (req) => {  req.continue((res) => {    res.body.plan = 'enterprise'  })}).as('profile')\n```\n\nSee [Network Requests](/llm/markdown/app/guides/network-requests.md) and [`cy.intercept()`](/llm/markdown/api/commands/intercept.md).\n",
      "section": "app",
      "anchors": [
        "modify-a-response"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 73
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#authentication-patterns",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Authentication patterns",
      "heading_level": 2,
      "content_markdown": "## Authentication patterns\n\nPlaywright commonly reuses auth via `storageState`. In Cypress, use [`cy.session()`](/llm/markdown/api/commands/session.md) to cache cookies and storage between tests.\n\nBefore: Playwright\n\nplaywright.config.ts\n\n```\nimport { defineConfig } from '@playwright/test'export default defineConfig({  use: {    storageState: 'storageState.json',  },})\n```\n\nauth.setup.ts\n\n```\nimport { test as setup } from '@playwright/test'setup('authenticate', async ({ page }) => {  await page.goto('/login')  await page.getByLabel('Username').fill('jane')  await page.getByLabel('Password').fill('secret')  await page.getByRole('button', { name: 'Log in' }).click()  await page.context().storageState({ path: 'storageState.json' })})\n```\n\nAfter: Cypress\n\ncypress.config.ts\n\n```\nconst login = () => {  cy.visit('/login')  cy.get('#username').type('jane')  cy.get('#password').type('secret')  cy.contains('button', 'Log in').click()}beforeEach(() => {  cy.session('jane', login)})\n```\n",
      "section": "app",
      "anchors": [
        "authentication-patterns"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 125
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#cross-origin",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Cross-origin",
      "heading_level": 2,
      "content_markdown": "## Cross-origin\n\nCypress enforces a same-origin policy by default. Use `cy.origin()` to run commands against a different origin within the same test. Any commands targeting a secondary origin must be scoped inside the `cy.origin()` callback.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.goto('https://site-a.example')await page.goto('https://site-b.example')await expect(page.getByRole('banner')).toBeVisible()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.visit('https://site-a.example')cy.origin('https://site-b.example', () => {  cy.visit('https://site-b.example')  cy.get('[role=\"banner\"]').should('be.visible')})\n```\n\nSee [`cy.origin()`](/llm/markdown/api/commands/origin.md) for complete documentation.\n",
      "section": "app",
      "anchors": [
        "cross-origin"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 80
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#file-uploads-and-downloads",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "File uploads and downloads",
      "heading_level": 2,
      "content_markdown": "## File uploads and downloads\n\n### Upload\n\nBefore: Playwright\n\n```\nawait page.setInputFiles('input[type=\"file\"]', 'fixtures/avatar.png')\n```\n\nAfter: Cypress\n\n```\ncy.get('input[type=\"file\"]').selectFile('cypress/fixtures/avatar.png')\n```\n\nSee [`cy.selectFile()`](/llm/markdown/api/commands/selectfile.md).\n\n### Downloads\n\nIn Cypress, a common approach is to trigger the download, then assert the file exists in the configured downloads folder.\n\nBefore: Playwright\n\n```\nconst download = await page.waitForEvent('download')await page.getByRole('button', { name: 'Export CSV' }).click()const path = await download.path()\n```\n\nAfter: Cypress\n\n```\ncy.contains('button', 'Export CSV').click()cy.readFile('cypress/downloads/export.csv')\n```\n\nSee [`cy.readFile()`](/llm/markdown/api/commands/readfile.md).\n",
      "section": "app",
      "anchors": [
        "file-uploads-and-downloads"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 95
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#downloads",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Downloads",
      "heading_level": 3,
      "content_markdown": "### Downloads\n\nIn Cypress, a common approach is to trigger the download, then assert the file exists in the configured downloads folder.\n\nBefore: Playwright\n\n```\nconst download = await page.waitForEvent('download')await page.getByRole('button', { name: 'Export CSV' }).click()const path = await download.path()\n```\n\nAfter: Cypress\n\n```\ncy.contains('button', 'Export CSV').click()cy.readFile('cypress/downloads/export.csv')\n```\n\nSee [`cy.readFile()`](/llm/markdown/api/commands/readfile.md).\n",
      "section": "app",
      "anchors": [
        "downloads"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 67
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#clock-and-time-control",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Clock and time control",
      "heading_level": 2,
      "content_markdown": "## Clock and time control\n\nBoth Playwright and Cypress let you replace native browser time functions with a fake clock that you control. This allows tests to set a specific date, skip waiting for timers, and test time-sensitive UI behavior without real delays. The two tools take different approaches to time control.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.clock.setFixedTime(new Date('2024-02-02T10:00:00')) awaitpage.goto('/') awaitexpect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.clock(new Date('2024-02-02T10:00:00')) cy.visit('/')cy.get('[data-testid=\"current-time\"]').should('have.text', '2/2/2024, 10:00:00AM')\n```\n\n### Advancing time\n\nTo simulate the passage of time and trigger scheduled timers, Playwright uses `fastForward` or `runFor`. Cypress uses `cy.tick()`.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.clock.install() await page.goto('/') awaitpage.getByRole('button').click()// Skip forward 5 minutes, firing all timers that fall within the windowawait page.clock.fastForward('05:00')await expect(page.getByText('Session expired')).toBeVisible()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.clock()cy.visit('/')cy.getByRole('button').click()// cy.tick() takes milliseconds; 5 minutes = 300,000 mscy.tick(5 * 60 * 1000)cy.contains('Session expired').should('be.visible')\n```\n\n### Pausing at a specific time\n\nPlaywright supports `pauseAt`, which lets the clock run naturally from its installed time and then pause automatically when it reaches a target timestamp. For Cypress to reach a specific point in time, calculate the delta between the installed time and the target time and pass that to `cy.tick()`.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.clock.install({ time: new Date('2024-02-02T08:00:00') })await page.goto('/')// Clock runs naturally until it reaches 10:00 AM, then pausesawait page.clock.pauseAt(new Date('2024-02-02T10:00:00'))await expect(page.getByTestId('current-time')).toHaveText(  '2/2/2024, 10:00:00 AM')// Advance another 30 minutes manuallyawait page.clock.fastForward('30:00')await expect(page.getByTestId('current-time')).toHaveText(  '2/2/2024, 10:30:00 AM')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.clock(new Date('2024-02-02T08:00:00'))cy.visit('/')// Advance 2 hours (from 08:00 to 10:00) manuallycy.tick(2 * 60 * 60 * 1000)cy.get('[data-testid=\"current-time\"]').should(  'have.text',  '2/2/2024, 10:00:00 AM')// Advance another 30 minutescy.tick(30 * 60 * 1000)cy.get('[data-testid=\"current-time\"]').should(  'have.text',  '2/2/2024, 10:30:00 AM')\n```\n\n### Changing the system time mid-test\n\nBoth tools allow you to change the current time without triggering timers. In Playwright this is `page.clock.setSystemTime()`. In Cypress it is `clock.setSystemTime()` on the yielded clock object.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.clock.install({ time: new Date('2024-02-02T10:00:00') })await page.goto('/')// Jump to a new time without firing any scheduled timersawait page.clock.setSystemTime(new Date('2024-02-02T10:30:00'))\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.clock(new Date('2024-02-02T10:00:00'))cy.visit('/')cy.clock().then((clock) => {  // Jump to a new time without firing any scheduled timers  clock.setSystemTime(new Date('2024-02-02T10:30:00'))})\n```\n\n### Overriding only specific functions\n\nCypress allows you to limit which native functions the clock replaces. This is useful when you only need to control `Date` without affecting timer behavior, or vice versa.\n\n```\n// Override only Date, leave setTimeout and setInterval running normallycy.clock(Date.UTC(2024, 1, 2), ['Date'])// Override only timers, leave Date as the system clockcy.clock(null, ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'])\n```\n",
      "section": "app",
      "anchors": [
        "clock-and-time-control"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 544
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#advancing-time",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Advancing time",
      "heading_level": 3,
      "content_markdown": "### Advancing time\n\nTo simulate the passage of time and trigger scheduled timers, Playwright uses `fastForward` or `runFor`. Cypress uses `cy.tick()`.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.clock.install() await page.goto('/') awaitpage.getByRole('button').click()// Skip forward 5 minutes, firing all timers that fall within the windowawait page.clock.fastForward('05:00')await expect(page.getByText('Session expired')).toBeVisible()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.clock()cy.visit('/')cy.getByRole('button').click()// cy.tick() takes milliseconds; 5 minutes = 300,000 mscy.tick(5 * 60 * 1000)cy.contains('Session expired').should('be.visible')\n```\n",
      "section": "app",
      "anchors": [
        "advancing-time"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 87
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#pausing-at-a-specific-time",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Pausing at a specific time",
      "heading_level": 3,
      "content_markdown": "### Pausing at a specific time\n\nPlaywright supports `pauseAt`, which lets the clock run naturally from its installed time and then pause automatically when it reaches a target timestamp. For Cypress to reach a specific point in time, calculate the delta between the installed time and the target time and pass that to `cy.tick()`.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.clock.install({ time: new Date('2024-02-02T08:00:00') })await page.goto('/')// Clock runs naturally until it reaches 10:00 AM, then pausesawait page.clock.pauseAt(new Date('2024-02-02T10:00:00'))await expect(page.getByTestId('current-time')).toHaveText(  '2/2/2024, 10:00:00 AM')// Advance another 30 minutes manuallyawait page.clock.fastForward('30:00')await expect(page.getByTestId('current-time')).toHaveText(  '2/2/2024, 10:30:00 AM')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.clock(new Date('2024-02-02T08:00:00'))cy.visit('/')// Advance 2 hours (from 08:00 to 10:00) manuallycy.tick(2 * 60 * 60 * 1000)cy.get('[data-testid=\"current-time\"]').should(  'have.text',  '2/2/2024, 10:00:00 AM')// Advance another 30 minutescy.tick(30 * 60 * 1000)cy.get('[data-testid=\"current-time\"]').should(  'have.text',  '2/2/2024, 10:30:00 AM')\n```\n",
      "section": "app",
      "anchors": [
        "pausing-at-a-specific-time"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 172
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#changing-the-system-time-mid-test",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Changing the system time mid-test",
      "heading_level": 3,
      "content_markdown": "### Changing the system time mid-test\n\nBoth tools allow you to change the current time without triggering timers. In Playwright this is `page.clock.setSystemTime()`. In Cypress it is `clock.setSystemTime()` on the yielded clock object.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.clock.install({ time: new Date('2024-02-02T10:00:00') })await page.goto('/')// Jump to a new time without firing any scheduled timersawait page.clock.setSystemTime(new Date('2024-02-02T10:30:00'))\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.clock(new Date('2024-02-02T10:00:00'))cy.visit('/')cy.clock().then((clock) => {  // Jump to a new time without firing any scheduled timers  clock.setSystemTime(new Date('2024-02-02T10:30:00'))})\n```\n",
      "section": "app",
      "anchors": [
        "changing-the-system-time-mid-test"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 105
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#overriding-only-specific-functions",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Overriding only specific functions",
      "heading_level": 3,
      "content_markdown": "### Overriding only specific functions\n\nCypress allows you to limit which native functions the clock replaces. This is useful when you only need to control `Date` without affecting timer behavior, or vice versa.\n\n```\n// Override only Date, leave setTimeout and setInterval running normallycy.clock(Date.UTC(2024, 1, 2), ['Date'])// Override only timers, leave Date as the system clockcy.clock(null, ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'])\n```\n",
      "section": "app",
      "anchors": [
        "overriding-only-specific-functions"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 81
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#multiple-tabs-and-windows",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Multiple tabs and windows",
      "heading_level": 2,
      "content_markdown": "## Multiple tabs and windows\n\nYou can handle multi-tab workflows using the [`@cypress/puppeteer`](https://www.npmjs.com/package/@cypress/puppeteer) plugin, which gives Cypress access to the underlying browser via Puppeteer. This approach requires Puppeteer as a dependency.\n\nBefore: Playwright\n\ntest.ts\n\n```\n// Start waiting for the new tab before clicking.const newPagePromise = page.context().waitForEvent('page')await page.getByText('open new tab').click()const newPage = await newPagePromiseawait newPage.waitForLoadState()await newPage.getByRole('button', { name: 'Confirm' }).click()\n```\n\nAfter: Cypress\n\ncypress.config.ts\n\n```\nimport { defineConfig } from 'cypress'import { setup, retry } from '@cypress/puppeteer'export default defineConfig({  e2e: {    setupNodeEvents(on) {      setup({        on,        onMessage: {          async switchToTabAndGetContent(browser) {            const page = await retry(async () => {              const pages = await browser.pages()              const page = pages.find((p) => p.url().includes('page-2.html'))              if (!page) throw new Error('Could not find page')              return page            })            await page.bringToFront()            const paragraph = await page.waitForSelector('p')            return await page.evaluate((el) => el.textContent, paragraph)          },        },      })    },  },})\n```\n\nIn your support file, import the command once:\n\n```\n// cypress/support/e2e.tsimport '@cypress/puppeteer/support'\n```\n\ntest.cy.ts\n\n```\ncy.visit('/')cy.contains('button', 'Open new tab').click()cy.puppeteer('switchToTabAndGetContent').should(  'equal',  'You said: Hello from Page 1')\n```\n",
      "section": "app",
      "anchors": [
        "multiple-tabs-and-windows"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 221
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#iframes",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Iframes",
      "heading_level": 2,
      "content_markdown": "## Iframes\n\nCypress has no equivalent single command. The standard approach is to get the iframe element, access its `contentDocument.body`, and then wrap that body element with `cy.wrap()` so you can use Cypress commands on its contents. The `.should('not.be.empty')` assertion ensures Cypress waits for the frame's document to load before proceeding.\n\nBefore: Playwright\n\ntest.ts\n\n```\n// Locate and fill a field inside an iframeconst frame = page.frameLocator('#payment-frame')await frame.getByLabel('Card number').fill('4242424242424242')await frame.getByLabel('Expiry').fill('12/26')await frame.getByRole('button', { name: 'Pay now' }).click()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.get('#payment-frame')  .its('0.contentDocument.body')  .should('not.be.empty')  .then(cy.wrap)  .within(() => {    cy.get('input[aria-label=\"Card number\"]').type('4242424242424242')    cy.get('input[aria-label=\"Expiry\"]').type('12/26')    cy.contains('button', 'Pay now').click()  })\n```\n\nIf you interact with the same iframe across multiple tests, extract the frame access into a custom command to avoid repeating the boilerplate:\n\ncypress/support/commands.ts\n\n```\nCypress.Commands.add('getIframeBody', (selector: string) => {  return cy    .get(selector)    .its('0.contentDocument.body')    .should('not.be.empty')    .then(cy.wrap)})// In your testcy.getIframeBody('#payment-frame').within(() => {  cy.contains('button', 'Pay now').click()})\n```\n",
      "section": "app",
      "anchors": [
        "iframes"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 188
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#dialogs",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Dialogs",
      "heading_level": 2,
      "content_markdown": "## Dialogs\n\n### alert()\n\nCypress always accepts `alert()` dialogs.\n\nBefore: Playwright\n\ntest.ts\n\n```\n// Register a handler to inspect the alert text before acceptingpage.on('dialog', async (dialog) => {  expect(dialog.type()).toBe('alert')  expect(dialog.message()).toBe('Unsaved changes will be lost.')  await dialog.accept()})await page.getByRole('button', { name: 'Leave page' }).click()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\n// Cypress auto-accepts alerts. Listen to assert on the message.cy.on('window:alert', (message) => {  expect(message).to.eq('Unsaved changes will be lost.')})cy.get('button').contains('Leave page').click()\n```\n\n### confirm()\n\nBefore: Playwright\n\ntest.ts\n\n```\n// Accept the confirmationpage.on('dialog', (dialog) => dialog.accept())await page.getByRole('button', { name: 'Delete account' }).click()await expect(page.getByText('Account deleted')).toBeVisible()// Dismiss the confirmationpage.on('dialog', (dialog) => dialog.dismiss())await page.getByRole('button', { name: 'Delete account' }).click()await expect(page.getByText('Account deleted')).not.toBeVisible()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\n// Cypress auto-accepts confirm(). No handler needed to test the accepted path.cy.get('button').contains('Delete account').click()cy.contains('Account deleted').should('be.visible')// Return false to dismiss the confirmationcy.on('window:confirm', () => false)cy.get('button').contains('Delete account').click()cy.contains('Account deleted').should('not.exist')\n```\n\nIf you need to assert on the confirmation message text and control acceptance in the same test, combine both:\n\n```\ncy.on('window:confirm', (message) => {  expect(message).to.eq('Are you sure you want to delete your account?')  return false // return true or omit the return to accept})cy.get('button').contains('Delete account').click()\n```\n\nFor tests that need to verify the stub was called, use `cy.stub()` instead of `cy.on()`. This lets you use Sinon assertions to confirm the dialog was triggered the expected number of times.\n\n```\nconst confirmStub = cy.stub().as('confirmDialog').returns(false)cy.on('window:confirm', confirmStub)cy.get('button').contains('Delete account').click()cy.get('@confirmDialog').should('have.been.calledOnce')\n```\n\n### prompt()\n\nIn Cypress, you must stub `window.prompt` before the page loads using the `onBeforeLoad` callback in `cy.visit()`. This gives you full control over the return value and lets you assert that the prompt was called.\n\nBefore: Playwright\n\ntest.ts\n\n```\npage.on('dialog', async (dialog) => {  expect(dialog.type()).toBe('prompt')  expect(dialog.message()).toBe('Enter your name:')  await dialog.accept('Ada Lovelace')})await page.getByRole('button', { name: 'Set name' }).click()await expect(page.getByTestId('greeting')).toHaveText('Hello, Ada Lovelace!')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.visit('/', {  onBeforeLoad(win) {    cy.stub(win, 'prompt').as('promptDialog').returns('Ada Lovelace')  },})cy.get('button').contains('Set name').click()cy.get('[data-testid=\"greeting\"]').should('have.text', 'Hello, Ada Lovelace!')cy.get('@promptDialog').should('have.been.calledWith', 'Enter your name:')\n```\n\nTo simulate the user cancelling the prompt (equivalent to `dialog.dismiss()` in Playwright), return `null` from the stub:\n\n```\ncy.visit('/', {  onBeforeLoad(win) {    cy.stub(win, 'prompt').returns(null)  },})\n```\n\n### beforeunload\n\nIf your tests need to verify that a `beforeunload` handler is registered and sets a `returnValue`, use the `window:before:unload` event to assert on the event object directly:\n\nBefore: Playwright\n\ntest.ts\n\n```\npage.on('dialog', async (dialog) => {  expect(dialog.type()).toBe('beforeunload')  await dialog.dismiss()})await page.close({ runBeforeUnload: true })\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\n// Assert that the beforeunload handler fired and set a returnValuecy.on('window:before:unload', (event) => {  expect(event.returnValue).to.eq('')})// Trigger navigation to fire beforeunloadcy.get('a').contains('Leave').click()\n```\n\n### print dialogs\n\nIn Cypress, stub `window.print` using `onBeforeLoad` in `cy.visit()` and assert that the stub was called.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.goto('/')await page.evaluate(  '(() => { window.waitForPrint = new Promise(f => window.print = f) })()')await page.getByRole('button', { name: 'Print invoice' }).click()await page.waitForFunction('window.waitForPrint')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.visit('/', {  onBeforeLoad(win) {    cy.stub(win, 'print').as('printDialog')  },})cy.get('button').contains('Print invoice').click()cy.get('@printDialog').should('have.been.calledOnce')\n```\n",
      "section": "app",
      "anchors": [
        "dialogs"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 608
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#alert",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "alert()",
      "heading_level": 3,
      "content_markdown": "### alert()\n\nCypress always accepts `alert()` dialogs.\n\nBefore: Playwright\n\ntest.ts\n\n```\n// Register a handler to inspect the alert text before acceptingpage.on('dialog', async (dialog) => {  expect(dialog.type()).toBe('alert')  expect(dialog.message()).toBe('Unsaved changes will be lost.')  await dialog.accept()})await page.getByRole('button', { name: 'Leave page' }).click()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\n// Cypress auto-accepts alerts. Listen to assert on the message.cy.on('window:alert', (message) => {  expect(message).to.eq('Unsaved changes will be lost.')})cy.get('button').contains('Leave page').click()\n```\n",
      "section": "app",
      "anchors": [
        "alert"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 87
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#confirm",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "confirm()",
      "heading_level": 3,
      "content_markdown": "### confirm()\n\nBefore: Playwright\n\ntest.ts\n\n```\n// Accept the confirmationpage.on('dialog', (dialog) => dialog.accept())await page.getByRole('button', { name: 'Delete account' }).click()await expect(page.getByText('Account deleted')).toBeVisible()// Dismiss the confirmationpage.on('dialog', (dialog) => dialog.dismiss())await page.getByRole('button', { name: 'Delete account' }).click()await expect(page.getByText('Account deleted')).not.toBeVisible()\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\n// Cypress auto-accepts confirm(). No handler needed to test the accepted path.cy.get('button').contains('Delete account').click()cy.contains('Account deleted').should('be.visible')// Return false to dismiss the confirmationcy.on('window:confirm', () => false)cy.get('button').contains('Delete account').click()cy.contains('Account deleted').should('not.exist')\n```\n\nIf you need to assert on the confirmation message text and control acceptance in the same test, combine both:\n\n```\ncy.on('window:confirm', (message) => {  expect(message).to.eq('Are you sure you want to delete your account?')  return false // return true or omit the return to accept})cy.get('button').contains('Delete account').click()\n```\n\nFor tests that need to verify the stub was called, use `cy.stub()` instead of `cy.on()`. This lets you use Sinon assertions to confirm the dialog was triggered the expected number of times.\n\n```\nconst confirmStub = cy.stub().as('confirmDialog').returns(false)cy.on('window:confirm', confirmStub)cy.get('button').contains('Delete account').click()cy.get('@confirmDialog').should('have.been.calledOnce')\n```\n",
      "section": "app",
      "anchors": [
        "confirm"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 203
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#prompt",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "prompt()",
      "heading_level": 3,
      "content_markdown": "### prompt()\n\nIn Cypress, you must stub `window.prompt` before the page loads using the `onBeforeLoad` callback in `cy.visit()`. This gives you full control over the return value and lets you assert that the prompt was called.\n\nBefore: Playwright\n\ntest.ts\n\n```\npage.on('dialog', async (dialog) => {  expect(dialog.type()).toBe('prompt')  expect(dialog.message()).toBe('Enter your name:')  await dialog.accept('Ada Lovelace')})await page.getByRole('button', { name: 'Set name' }).click()await expect(page.getByTestId('greeting')).toHaveText('Hello, Ada Lovelace!')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.visit('/', {  onBeforeLoad(win) {    cy.stub(win, 'prompt').as('promptDialog').returns('Ada Lovelace')  },})cy.get('button').contains('Set name').click()cy.get('[data-testid=\"greeting\"]').should('have.text', 'Hello, Ada Lovelace!')cy.get('@promptDialog').should('have.been.calledWith', 'Enter your name:')\n```\n\nTo simulate the user cancelling the prompt (equivalent to `dialog.dismiss()` in Playwright), return `null` from the stub:\n\n```\ncy.visit('/', {  onBeforeLoad(win) {    cy.stub(win, 'prompt').returns(null)  },})\n```\n",
      "section": "app",
      "anchors": [
        "prompt"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 144
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#beforeunload",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "beforeunload",
      "heading_level": 3,
      "content_markdown": "### beforeunload\n\nIf your tests need to verify that a `beforeunload` handler is registered and sets a `returnValue`, use the `window:before:unload` event to assert on the event object directly:\n\nBefore: Playwright\n\ntest.ts\n\n```\npage.on('dialog', async (dialog) => {  expect(dialog.type()).toBe('beforeunload')  await dialog.dismiss()})await page.close({ runBeforeUnload: true })\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\n// Assert that the beforeunload handler fired and set a returnValuecy.on('window:before:unload', (event) => {  expect(event.returnValue).to.eq('')})// Trigger navigation to fire beforeunloadcy.get('a').contains('Leave').click()\n```\n",
      "section": "app",
      "anchors": [
        "beforeunload"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 95
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#print-dialogs",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "print dialogs",
      "heading_level": 3,
      "content_markdown": "### print dialogs\n\nIn Cypress, stub `window.print` using `onBeforeLoad` in `cy.visit()` and assert that the stub was called.\n\nBefore: Playwright\n\ntest.ts\n\n```\nawait page.goto('/')await page.evaluate(  '(() => { window.waitForPrint = new Promise(f => window.print = f) })()')await page.getByRole('button', { name: 'Print invoice' }).click()await page.waitForFunction('window.waitForPrint')\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\ncy.visit('/', {  onBeforeLoad(win) {    cy.stub(win, 'print').as('printDialog')  },})cy.get('button').contains('Print invoice').click()cy.get('@printDialog').should('have.been.calledOnce')\n```\n",
      "section": "app",
      "anchors": [
        "print-dialogs"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 77
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#test-generation",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Test generation",
      "heading_level": 2,
      "content_markdown": "## Test generation\n\n[Cypress Studio](/llm/markdown/app/guides/cypress-studio.md) lets you generate test code by interacting with your application in the Cypress app. It records actions like clicks and form inputs and writes the corresponding Cypress commands. Cypress Studio is a replacement for Playwright's `playwright codegen`. With a [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md) account, you also get **Studio AI**, AI-powered assertion recommendations as you record.\n",
      "section": "app",
      "anchors": [
        "test-generation"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 77
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#component-testing",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Component Testing",
      "heading_level": 2,
      "content_markdown": "## Component Testing\n\nPlaywright Component Testing is **experimental** and uses a `mount` fixture (for example via `@playwright/experimental-ct-react`). Cypress Component Testing uses `cy.mount()`, typically scaffolded in your Cypress support file.\n\n### Framework and bundler support\n\n|  | Playwright Component Testing | Cypress Component Testing |\n| --- | --- | --- |\n| Frameworks | Experimental packages for React, Vue, and Svelte | Official mounting libraries for [React](/llm/markdown/app/component-testing/react/overview.md), [Vue](/llm/markdown/app/component-testing/vue/overview.md), [Angular](/llm/markdown/app/component-testing/angular/overview.md), and [Svelte](/llm/markdown/app/component-testing/svelte/overview.md) |\n| Bundlers | Vite | Vite & Webpack |\n\nSee [Component Testing](/llm/markdown/app/component-testing/get-started.md) for more details.\n\nBefore: Playwright\n\nbutton.test.ts\n\n```\nimport { test, expect } from '@playwright/experimental-ct-react'import { Button } from './Button'test('renders', async ({ mount }) => {  const component = await mount(<Button>Save</Button>)  await expect(component).toContainText('Save')})\n```\n\nAfter: Cypress\n\nbutton.cy.ts\n\n```\nimport { Button } from './Button'describe('Button', () => {  it('renders', () => {    cy.mount(<Button>Save</Button>)    cy.contains('button', 'Save').should('be.visible')  })})\n```\n\nSee [Component Testing](/llm/markdown/app/component-testing/get-started.md) and [`cy.mount()`](/llm/markdown/api/commands/mount.md).\n",
      "section": "app",
      "anchors": [
        "component-testing"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 192
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#framework-and-bundler-support",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Framework and bundler support",
      "heading_level": 3,
      "content_markdown": "### Framework and bundler support\n\n|  | Playwright Component Testing | Cypress Component Testing |\n| --- | --- | --- |\n| Frameworks | Experimental packages for React, Vue, and Svelte | Official mounting libraries for [React](/llm/markdown/app/component-testing/react/overview.md), [Vue](/llm/markdown/app/component-testing/vue/overview.md), [Angular](/llm/markdown/app/component-testing/angular/overview.md), and [Svelte](/llm/markdown/app/component-testing/svelte/overview.md) |\n| Bundlers | Vite | Vite & Webpack |\n\nSee [Component Testing](/llm/markdown/app/component-testing/get-started.md) for more details.\n\nBefore: Playwright\n\nbutton.test.ts\n\n```\nimport { test, expect } from '@playwright/experimental-ct-react'import { Button } from './Button'test('renders', async ({ mount }) => {  const component = await mount(<Button>Save</Button>)  await expect(component).toContainText('Save')})\n```\n\nAfter: Cypress\n\nbutton.cy.ts\n\n```\nimport { Button } from './Button'describe('Button', () => {  it('renders', () => {    cy.mount(<Button>Save</Button>)    cy.contains('button', 'Save').should('be.visible')  })})\n```\n\nSee [Component Testing](/llm/markdown/app/component-testing/get-started.md) and [`cy.mount()`](/llm/markdown/api/commands/mount.md).\n",
      "section": "app",
      "anchors": [
        "framework-and-bundler-support"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 153
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#reusable-code-patterns",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Reusable code patterns",
      "heading_level": 2,
      "content_markdown": "## Reusable code patterns\n\n### Page object model\n\nIf you're using Page Object Model (POM) in Playwright, the same pattern works in Cypress:\n\nBefore: Playwright\n\nloginPage.ts\n\n```\nexport class LoginPage {  constructor(private page: Page) {}  async login(username: string, password: string) {    await this.page.getByLabel('Username').fill(username)    await this.page.getByLabel('Password').fill(password)    await this.page.getByRole('button', { name: 'Login' }).click()  }}\n```\n\nAfter: Cypress\n\nsupport.cy.ts\n\n```\nexport class LoginPage {  login(username: string, password: string) {    cy.get('[data-testid=\"username\"]').type(username)    cy.get('[data-testid=\"password\"]').type(password)    cy.contains('button', 'Login').click()  }}\n```\n",
      "section": "app",
      "anchors": [
        "reusable-code-patterns"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 96
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#page-object-model",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Page object model",
      "heading_level": 3,
      "content_markdown": "### Page object model\n\nIf you're using Page Object Model (POM) in Playwright, the same pattern works in Cypress:\n\nBefore: Playwright\n\nloginPage.ts\n\n```\nexport class LoginPage {  constructor(private page: Page) {}  async login(username: string, password: string) {    await this.page.getByLabel('Username').fill(username)    await this.page.getByLabel('Password').fill(password)    await this.page.getByRole('button', { name: 'Login' }).click()  }}\n```\n\nAfter: Cypress\n\nsupport.cy.ts\n\n```\nexport class LoginPage {  login(username: string, password: string) {    cy.get('[data-testid=\"username\"]').type(username)    cy.get('[data-testid=\"password\"]').type(password)    cy.contains('button', 'Login').click()  }}\n```\n",
      "section": "app",
      "anchors": [
        "page-object-model"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 91
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#parameterized-tests",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Parameterized tests",
      "heading_level": 2,
      "content_markdown": "## Parameterized tests\n\nBoth Playwright and Cypress support generating tests from a data array using standard JavaScript. The pattern is the same in both tools: call `.forEach()` on your dataset outside of or inside a `describe` block, and generate one test per entry.\n\nBefore: Playwright\n\ntest.ts\n\n```\n;[  { name: 'Maya', expected: 'Hello, Maya!' },  { name: 'Theo', expected: 'Hello, Theo!' },  { name: 'Nora', expected: 'Hello, Nora!' },].forEach(({ name, expected }) => {  test(`testing with ${name}`, async ({ page }) => {    await page.goto(`https://example.com/greet?name=${name}`)    await expect(page.getByRole('heading')).toHaveText(expected)  })})\n```\n\nAfter: Cypress\n\ntest.cy.ts\n\n```\n;[  { name: 'Maya', expected: 'Hello, Maya!' },  { name: 'Theo', expected: 'Hello, Theo!' },  { name: 'Nora', expected: 'Hello, Nora!' },].forEach(({ name, expected }) => {  it(`testing with ${name}`, () => {    cy.visit(`/greet?name=${name}`)    cy.get('h1').should('have.text', expected)  })})\n```\n\n### Custom commands\n\nBefore: Playwright\n\nfixtures/auth.ts\n\n```\nexport const test = base.extend({  authenticatedPage: async ({ page }, use) => {    await page.goto('/login')    await page.getByLabel('Username').fill('user')    await page.getByLabel('Password').fill('pass')    await page.getByRole('button', { name: 'Login' }).click()    await use(page)  },})\n```\n\nAfter: Cypress\n\nsupport.cy.ts\n\n```\nCypress.Commands.add('login', (username: string, password: string) => {  cy.session([username, password], () => {    cy.visit('/login')    cy.get('[data-testid=\"username\"]').type(username)    cy.get('[data-testid=\"password\"]').type(password)    cy.contains('button', 'Login').click()    cy.url().should('include', '/dashboard')  })})\n```\n\nSee [Custom Commands](/llm/markdown/api/cypress-api/custom-commands.md) for more details.\n",
      "section": "app",
      "anchors": [
        "parameterized-tests"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 264
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#custom-commands",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Custom commands",
      "heading_level": 3,
      "content_markdown": "### Custom commands\n\nBefore: Playwright\n\nfixtures/auth.ts\n\n```\nexport const test = base.extend({  authenticatedPage: async ({ page }, use) => {    await page.goto('/login')    await page.getByLabel('Username').fill('user')    await page.getByLabel('Password').fill('pass')    await page.getByRole('button', { name: 'Login' }).click()    await use(page)  },})\n```\n\nAfter: Cypress\n\nsupport.cy.ts\n\n```\nCypress.Commands.add('login', (username: string, password: string) => {  cy.session([username, password], () => {    cy.visit('/login')    cy.get('[data-testid=\"username\"]').type(username)    cy.get('[data-testid=\"password\"]').type(password)    cy.contains('button', 'Login').click()    cy.url().should('include', '/dashboard')  })})\n```\n\nSee [Custom Commands](/llm/markdown/api/cypress-api/custom-commands.md) for more details.\n",
      "section": "app",
      "anchors": [
        "custom-commands"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 89
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#ci-parallelization",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "CI parallelization",
      "heading_level": 2,
      "content_markdown": "## CI parallelization\n\n### Key architectural difference\n\nPlaywright parallelizes tests locally using workers and across machines using manual sharding. You define the split strategy in config or at the command line.\n\n*   Worker count controlled via config (`workers: 4`) or CLI (`--workers=4`)\n*   Sharding splits tests across machines (`--shard=1/4`)\n*   You define the split strategy upfront\n\nBefore: Playwright\n\n```\nplaywright test --shard=2/4 # Machine 2 playwright test --shard=3/4 # Machine 3playwright test --shard=4/4 # Machine 4\n```\n\nCypress parallelizes tests across machines through [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md), which distributes specs dynamically based on historical run durations. This requires a Cypress Cloud account.\n\n*   Parallelization happens in CI through Cypress Cloud.\n*   Specs are distributed dynamically based on historical run data.\n*   No manual sharding required.\n\nAfter: Cypress Cloud\n\n```\ncypress run--record --parallel\n```\n\n### Smart Orchestration in Cypress Cloud\n\nWhen you use Cypress Cloud, you gain **Smart Orchestration** features that go beyond simple parallelization:\n\n**[Load Balancing](/llm/markdown/cloud/features/smart-orchestration/load-balancing.md)**\n\n*   Distributes specs across machines based on **previous run durations**\n*   Minimizes overall run time automatically\n*   No need to manually configure shards or splits\n\n**[Spec Prioritization](/llm/markdown/cloud/features/smart-orchestration/spec-prioritization.md)**\n\n*   Runs specs that failed in the previous run first\n*   Provides faster feedback on fixes\n*   Reduces time to detect persistent failures\n\n**[Auto Cancellation](/llm/markdown/cloud/features/smart-orchestration/run-cancellation.md)**\n\n*   Cancels remaining specs after a threshold of failures\n*   Avoids wasting CI time on runs that can't pass\n*   Configurable failure thresholds\n\nSee [Parallelization](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) for setup details.\n\n### Debugging with Test Replay\n\nTest Replay is available on all Cypress Cloud plans at no additional cost. It records a full, interactive replay of every test run, including network requests, console output, and DOM snapshots. Replays are shareable via link and do not require a local trace file.\n\nBefore: Playwright\n\n```\nplaywright test --trace on\n```\n\n```\nnpx playwright show-trace test-results/**/trace.zip\n```\n\nAfter: Cypress\n\n```\ncypress run --record --key <record_key>\n```\n\nSee [Test Replay](/llm/markdown/cloud/features/test-replay.md) for more details.\n",
      "section": "app",
      "anchors": [
        "ci-parallelization"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 416
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#key-architectural-difference",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Key architectural difference",
      "heading_level": 3,
      "content_markdown": "### Key architectural difference\n\nPlaywright parallelizes tests locally using workers and across machines using manual sharding. You define the split strategy in config or at the command line.\n\n*   Worker count controlled via config (`workers: 4`) or CLI (`--workers=4`)\n*   Sharding splits tests across machines (`--shard=1/4`)\n*   You define the split strategy upfront\n\nBefore: Playwright\n\n```\nplaywright test --shard=2/4 # Machine 2 playwright test --shard=3/4 # Machine 3playwright test --shard=4/4 # Machine 4\n```\n\nCypress parallelizes tests across machines through [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md), which distributes specs dynamically based on historical run durations. This requires a Cypress Cloud account.\n\n*   Parallelization happens in CI through Cypress Cloud.\n*   Specs are distributed dynamically based on historical run data.\n*   No manual sharding required.\n\nAfter: Cypress Cloud\n\n```\ncypress run--record --parallel\n```\n",
      "section": "app",
      "anchors": [
        "key-architectural-difference"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 171
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#smart-orchestration-in-cypress-cloud",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Smart Orchestration in Cypress Cloud",
      "heading_level": 3,
      "content_markdown": "### Smart Orchestration in Cypress Cloud\n\nWhen you use Cypress Cloud, you gain **Smart Orchestration** features that go beyond simple parallelization:\n\n**[Load Balancing](/llm/markdown/cloud/features/smart-orchestration/load-balancing.md)**\n\n*   Distributes specs across machines based on **previous run durations**\n*   Minimizes overall run time automatically\n*   No need to manually configure shards or splits\n\n**[Spec Prioritization](/llm/markdown/cloud/features/smart-orchestration/spec-prioritization.md)**\n\n*   Runs specs that failed in the previous run first\n*   Provides faster feedback on fixes\n*   Reduces time to detect persistent failures\n\n**[Auto Cancellation](/llm/markdown/cloud/features/smart-orchestration/run-cancellation.md)**\n\n*   Cancels remaining specs after a threshold of failures\n*   Avoids wasting CI time on runs that can't pass\n*   Configurable failure thresholds\n\nSee [Parallelization](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) for setup details.\n",
      "section": "app",
      "anchors": [
        "smart-orchestration-in-cypress-cloud"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 137
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#debugging-with-test-replay",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Debugging with Test Replay",
      "heading_level": 3,
      "content_markdown": "### Debugging with Test Replay\n\nTest Replay is available on all Cypress Cloud plans at no additional cost. It records a full, interactive replay of every test run, including network requests, console output, and DOM snapshots. Replays are shareable via link and do not require a local trace file.\n\nBefore: Playwright\n\n```\nplaywright test --trace on\n```\n\n```\nnpx playwright show-trace test-results/**/trace.zip\n```\n\nAfter: Cypress\n\n```\ncypress run --record --key <record_key>\n```\n\nSee [Test Replay](/llm/markdown/cloud/features/test-replay.md) for more details.\n",
      "section": "app",
      "anchors": [
        "debugging-with-test-replay"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 104
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#handling-flaky-tests",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Handling flaky tests",
      "heading_level": 2,
      "content_markdown": "## Handling flaky tests\n\nFlaky tests are a common challenge in testing. Both frameworks offer retry mechanisms, but Cypress Cloud adds intelligent flake detection and management.\n\nBefore: Playwright\n\nplaywright.config.ts\n\n```\nexport default {  retries: process.env.CI ? 2 : 0,}\n```\n\nAfter: Cypress\n\ncypress.config.ts\n\n```\nexport default {  retries: {    runMode: 2,    openMode: 0,  },}\n```\n\n### Flaky test management in Cypress Cloud\n\nBeyond basic retries, Cypress Cloud provides:\n\n**[Flaky test management](/llm/markdown/cloud/features/flaky-test-management.md)**\n\n*   Automatically identifies tests that pass after retry\n*   Tracks flake rate over time\n*   Provides analytics on which specs are most flaky\n*   Helps prioritize stability improvements\n",
      "section": "app",
      "anchors": [
        "handling-flaky-tests"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 132
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#flaky-test-management-in-cypress-cloud",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Flaky test management in Cypress Cloud",
      "heading_level": 3,
      "content_markdown": "### Flaky test management in Cypress Cloud\n\nBeyond basic retries, Cypress Cloud provides:\n\n**[Flaky test management](/llm/markdown/cloud/features/flaky-test-management.md)**\n\n*   Automatically identifies tests that pass after retry\n*   Tracks flake rate over time\n*   Provides analytics on which specs are most flaky\n*   Helps prioritize stability improvements\n",
      "section": "app",
      "anchors": [
        "flaky-test-management-in-cypress-cloud"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 59
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#accessibility-testing",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Accessibility testing",
      "heading_level": 2,
      "content_markdown": "## Accessibility testing\n\nIf you record test runs to Cypress Cloud, [Cypress Accessibility](/llm/markdown/accessibility/get-started/introduction.md) provides automated accessibility scanning across your entire test suite with no changes to your test code.\n\nRather than requiring `checkA11y()` calls in each test, Cypress Accessibility captures DOM snapshots during test execution server-side, using the same protocol as Test Replay. Every distinct state your tests interact with is scanned automatically. This means pages and components that your tests visit but do not explicitly instrument are still checked.\n\nLearn more about [Accessibility testing](/llm/markdown/accessibility/get-started/introduction.md).\n",
      "section": "app",
      "anchors": [
        "accessibility-testing"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 113
    },
    {
      "id": "app/guides/migration/playwright-to-cypress#cheat-sheet",
      "doc_id": "app/guides/migration/playwright-to-cypress",
      "heading": "Cheat sheet",
      "heading_level": 2,
      "content_markdown": "## Cheat sheet\n\nThe `await` required in Playwright is not included in the cheat sheet for readability.\n\n| Playwright | Cypress |\n| --- | --- |\n| `page.locator('css')` | `cy.get('css')` |\n| `page.getByTestId('submit')` | `cy.get('[data-testid=\"submit\"]')` |\n| `page.goto('/path')` | `cy.visit('/path')` |\n| `page.reload()` | `cy.reload()` |\n| `page.title()` | `cy.title()` |\n| `page.url()` | `cy.url()` |\n| `page.waitForResponse(...)` | `cy.intercept(...).as('x'); cy.wait('@x')` |\n| `page.setInputFiles(selector, 'file.png')` | `cy.get(selector).selectFile('file.png')` |\n| `page.locator(selector).click()` | `cy.get(selector).click()` |\n| `page.locator(selector).click({ force: true })` | `cy.get(selector).click({force: true})` |\n| `page.locator(selector).click({ button: 'right' })` | `cy.get(selector).rightclick()` |\n| `page.locator(selector).click({ modifiers: ['Shift'] })` | `cy.get(selector).click({shiftKey: true})` |\n| `page.locator(selector).dblclick()` | `cy.get(selector).dblclick()` |\n| `page.locator(selector).hover()` | `cy.get(selector).trigger('mouseover')` |\n| `page.locator(selector).fill('text')` | `cy.get(selector).clear().type('text')` |\n| `page.locator(selector).focus()` | `cy.get(selector).focus()` |\n| `page.locator(selector).press('Tab')` | `cy.get(selector).press(Cypress.Keyboard.Keys.TAB)` |\n| `page.locator(selector).selectOption('blue')` | `cy.get('[data-testid=\"color\"]').select('blue')` |\n| `page.locator(selector).scrollIntoViewIfNeeded()` | `cy.get(selector).scrollIntoView()` |\n| `page.locator(selector).screenshot()` | cy.get(selector).screenshot() |\n| `page.route('**/api', ...)` | `cy.intercept('/api', ...)` |\n| `page.screenshot()` | cy.screenshot() |\n| `use: { storageState: 'state.json' }` | `cy.session('user', login)` |\n| `page.clock.setFixedTime(date)` | `cy.clock(now)` |\n| `page.clock.install({ time })` | `cy.clock(now)` |\n| `page.clock.fastForward(duration)` | `cy.tick(ms)` |\n| `page.clock.runFor(duration)` | `cy.tick(ms)` |\n| `page.clock.setSystemTime(date)` | `clock.setSystemTime(date)` |\n| `page.on('dialog', dialog => dialog.accept())` | Auto-accepted by default |\n| `page.on('dialog', dialog => dialog.dismiss())` | `cy.on('window:confirm', () => false)` |\n| `page.on('dialog', dialog => dialog.accept('x'))` | `cy.stub(win, 'prompt').returns('x')` in `onBeforeLoad` |\n| `page.on('dialog')` with `beforeunload` | `cy.on('window:before:unload', event => ...)` |\n| `expect(page).toHaveURL(/re/)` | `cy.url().should('contain', 're')` |\n| `expect(locator).toHaveText('Yes!')` | `cy.get(selector).should('have.text', 'Yes!')` |\n| `expect(locator).toHaveAttribute('href')` | `cy.get(selector).should('have.attr', 'href')` |\n| `expect(locator).toBeChecked()` | `cy.get(selector).should('be.checked')` |\n| `expect(locator).toBeDisabled()` | `cy.get(selector).should('be.disabled')` |\n| `expect(locator).toBeVisible()` | `cy.get(selector).should('be.visible')` |\n| `expect(locator).toBeHidden()` | `cy.get(selector).should('be.hidden')` |\n| `expect(locator).toBeEnabled()` | `cy.get(selector).should('be.enabled')` |\n| `expect(locator).toBeAttached()` | `Cypress.dom.isAttached($el)` |\n| `expect(locator).toBeFocused()` | `cy.get(selector).should('have.focus')` |\n| `expect(locator).toHaveValue('123')` | `cy.get(selector).should('have.value', '123')` |\n| `expect(locator).toContainText('Yes!')` | `cy.get(selector).should('contain', 'Yes!')` |\n| `expect(locator).toContainClass('light')` | `cy.get(selector).should('have.class', 'light')` |\n| `expect(locator).toHaveCSS('color', 'red')` | `cy.get(selector).should('have.css', 'color', 'red')` |\n| `browserContext.cookies()` | `cy.getCookies()` |\n| `browserContext.clearCookies()` | `cy.clearCookies()` |\n| `browserContext.addCookies(cookie)` | `cy.setCookie(name, value)` |\n",
      "section": "app",
      "anchors": [
        "cheat-sheet"
      ],
      "path": "/llm/json/chunked/app/guides/migration/playwright-to-cypress.json",
      "token_estimate": 435
    }
  ]
}