# Migrate Cypress Tests to Playwright in a Vue App

Yes. An agent ports your Vue app's Cypress specs to Playwright, runs both suites headless against the same server and opens a pull request that removes Cypress.

[Run this on Rush](https://cloud.getrush.ai/run/migrate-cypress-to-playwright/vue/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and inventories everything Cypress touches before it writes a Playwright line. In a Vue 3 starter like antfu-collective/vitesse, that is more than the specs: `cypress.config.ts` (base URL `http://localhost:3333`, `specPattern: 'cypress/e2e/**/*.spec.*'`, `supportFile: false`, and `cypress-vite` as the file preprocessor), `cypress/tsconfig.json`, the `test:e2e` script that runs `cypress open`, the `test-e2e` job in `.github/workflows/ci.yml` that uses `cypress-io/github-action@v6`, `cypress` in the `onlyBuiltDependencies` list of `pnpm-workspace.yaml`, and the `cypress`, `cypress-vite` and `eslint-plugin-cypress` entries in the dev dependency catalog. Each one has to change or go, or the removal is half done.

Then it ports the specs one assertion at a time. Vitesse has one spec, `cypress/e2e/basic.spec.ts`, with two tests. `basic nav` visits `/`, checks for the text `[Home Layout]`, types `Vitesse` and Enter into `#input`, expects the URL `http://localhost:3333/hi/Vitesse` and the text `[Default Layout]`, then clicks the `[btn]` attribute selector and expects to be back at `/`. `markdown` clicks `[data-test-id="about"]` in `TheFooter.vue`, expects `/about`, checks that a `.shiki` code block rendered, and checks that every link to the project's GitHub page in `.prose` has `target="_blank"` and a `rel` containing `noopener`. The Playwright version keeps those selectors and those checks, with `expect(page).toHaveURL` and locator assertions in place of `cy.url().should` and `cy.get().should`.

It writes `playwright.config.ts` with a `webServer` block that starts the app and waits for port 3333. The `dev` script is `vite --port 3333 --open`, and `--open` tries to launch a browser on a headless machine, so the config calls `vite --port 3333` directly; the existing Cypress job runs against `vite preview --port 3333` after `pnpm run build`, and the agent notes which of the two it chose and why. New specs go under `e2e/` with a `.spec.ts` suffix, outside `test/`, because `vite.config.ts` points Vitest at `test/**/*.test.ts` and the two runners must not pick up each other's files.

Before deleting anything, it proves the two suites agree. It installs dependencies with `pnpm install --frozen-lockfile` (the repository pins `pnpm@10.30.2` in `packageManager`), installs Chromium for Playwright, and runs `cypress run` and `playwright test` headless against the same server in the same sandbox. Only when both pass does it remove Cypress: the config, the folder, the catalog entries, the CI job (replaced with a Playwright job), and the lockfile changes that follow.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Migrate this Vue app's end-to-end tests from Cypress to Playwright. First list every place Cypress appears: the config, the specs, any support files, package.json scripts and dependencies, workspace or catalog files, ESLint config, tsconfig files and CI workflows. Port each Cypress spec to a Playwright spec that keeps the same selectors and the same assertions, put the specs where the unit test runner will not pick them up, and add a playwright.config.ts whose webServer starts the app on the port the Cypress config uses, without opening a browser. Install dependencies with the lockfile, install Chromium for Playwright, and run both the Cypress suite and the Playwright suite headless against the same server; report each run's output. Only when both pass, remove Cypress everywhere you listed it and replace the Cypress CI job with a Playwright job. Open a pull request that maps each Cypress test to its Playwright test.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request on its own branch. In an app like Vitesse the diff adds `playwright.config.ts` and `e2e/basic.spec.ts`, replaces the `test:e2e` script, swaps the Cypress job in `.github/workflows/ci.yml` for one that installs Chromium and runs `playwright test`, and deletes `cypress.config.ts`, the `cypress/` folder and the Cypress entries in `package.json`, `pnpm-workspace.yaml` and `pnpm-lock.yaml`. The description maps each old test to its new one, `basic nav` to `basic nav` and `markdown` to `markdown`, so a reviewer can read them side by side.

The proof that nothing was lost is in the session transcript: the `cypress run` output and the `playwright test` output from the same sandbox, against the same server, before Cypress was removed. The pull request's checks then run the new Playwright job alongside the existing lint, typecheck and Vitest jobs on GitHub's runners.

Before merging, a reviewer checks that every Cypress assertion has a Playwright twin rather than a weaker one, that the web server command matches how the team wants to test (the dev server or the built preview), and that no Cypress dependency is left in the lockfile or the workspace config.

## Run it on every Vue repository still on Cypress

A team with several front ends can send the same request once per repository: one `POST /o/{org}/sessions` per repository URL in `resources`, each in its own sandbox on its own branch. A key runs at most its `max_concurrent` sessions at once (4 by default), so a script sends the next when one finishes.

Set a `webhook_url` on the key and Rush posts `run.completed` or `run.failed` when each run's turn ends. If a reviewer wants a change, such as running against `vite preview` instead of the dev server, a follow-up `POST /o/{org}/sessions/{id}/messages` wakes the same sandbox with the work still on disk.

## Why run it on Rush?

The point of a migration like this is the side-by-side run, and that needs both test runners, a Chromium for each and a running Vite server on one machine at the same time. In a Rush sandbox the agent installs all of it from the lockfile on a clean machine, so the comparison is not affected by a Cypress binary cached on someone's laptop or a browser version that differs from CI's.

Every command is kept with its output, so the pull request is not asking anyone to trust that the new suite covers what the old one did: the transcript shows the Cypress run and the Playwright run passing against the same build, and the pull request's checks show the Playwright job passing on GitHub.

## Frequently asked questions

### Will the Playwright tests use the same selectors as my Cypress tests?

Yes, the prompt asks it to keep them. In Vitesse that means `#input`, the `[btn]` attribute, `[data-test-id="about"]` and the `.shiki` and `.prose` classes. If a selector does not translate cleanly, the agent says so in the pull request instead of quietly picking a different element.

### Does it keep Cypress custom commands or support files?

It ports what exists. Vitesse sets `supportFile: false`, so there is nothing to port there. In a repository with `cypress/support/commands.ts`, the agent turns each custom command into a Playwright fixture or helper function and lists the mapping in the pull request.

### Which browsers does the Playwright config run?

Chromium by default, which matches what the Cypress job ran in CI. Add Firefox or WebKit projects in your `input` if you want them; the agent installs those browsers in the sandbox and runs each one.

### What if the two suites disagree?

The prompt says to remove Cypress only when both pass. If the Playwright port fails where Cypress passed, the agent fixes the port; if both fail on the same page, it reports that as a bug in the app rather than editing the assertion away.
