cy2play
v0.1.0
Published
A CLI tool that converts Cypress tests to Playwright using AST parsing and optional AI assistance.
Maintainers
Readme
🎭 Cy2Play
Intelligently convert your Cypress tests to Playwright — using AST parsing + optional AI assistance.
Why Cy2Play?
Migrating from Cypress to Playwright is tedious:
- Syntax differences — Cypress uses chainable sync-like APIs; Playwright uses
async/await. - Scale — Large test suites can have thousands of files. Manual rewriting is slow and error-prone.
- Edge cases —
cy.intercept, custom commands, andthiscontext don't map 1:1.
Cy2Play automates 80–95% of that work with a hybrid engine: fast deterministic AST rules for standard commands, with optional LLM assistance for complex edge cases.
Features
- ⚡ Three conversion modes:
strict(rules only),hybrid(default — AST + AI),pure-ai(full LLM rewrite) - 🔒 Local LLM support — Use Ollama or LM Studio so your code never leaves your machine
- 🧠 AST-powered — Not regex find-and-replace; real syntax tree transformations
- 📊 Migration reports — See what changed, what needs manual review, and estimated AI cost
- 🎨 Auto-formatted output — Prettier integration for clean generated code
Installation
npm install -g cy2playOr use directly with npx:
npx cy2play convert ./cypress/e2eQuick Start
1. Convert a file or directory (hybrid mode — default)
npx cy2play convert ./cypress/e2e/login.cy.ts2. Strict mode (no AI, instant, free)
npx cy2play convert ./cypress/e2e --mode strict3. Use a local LLM (Ollama)
npx cy2play convert ./cypress/e2e \
--mode hybrid \
--provider local \
--local-url http://localhost:11434 \
--model codellama4. Dry run (preview without writing files)
npx cy2play convert ./cypress/e2e --dry-runConfiguration
Create a cy2play.config.json in your project root:
{
"mode": "hybrid",
"targetDir": "./playwright-tests",
"llm": {
"provider": "openai",
"model": "gpt-4o",
"apiKey": "env:OPENAI_API_KEY",
"temperature": 0.2
},
"localLlm": {
"enabled": false,
"baseUrl": "http://localhost:11434/v1",
"model": "llama3"
},
"customMappings": {
"cy.dataCy": "page.getByTestId"
}
}| Option | Type | Default | Description |
|:---|:---|:---|:---|
| mode | "strict" \| "hybrid" \| "pure-ai" | "hybrid" | Conversion strategy |
| targetDir | string | "./playwright-tests" | Output directory |
| llm.provider | "openai" \| "anthropic" \| "local" | "openai" | LLM backend |
| customMappings | object | {} | Map custom Cypress commands to Playwright equivalents |
How It Works
Input (.cy.ts) → AST Parser → Transformation Rules → [LLM for unknowns] → Code Generator → Prettier → Output (.spec.ts)- Strict mode — Deterministic AST transformation for known commands (
cy.get,cy.visit,.click(),.should()). - Hybrid mode — AST handles ~85% of conversions; unknown/complex blocks are sent as snippets to an LLM.
- Pure AI mode — The entire file is sent to an LLM for rewriting.
See PRD.md for detailed architecture and specifications.
Example Conversion
Input (login.cy.ts):
describe('Login', () => {
beforeEach(() => {
cy.visit('/login');
});
it('should log in successfully', () => {
cy.get('[data-cy=email]').type('[email protected]');
cy.get('[data-cy=password]').type('password123');
cy.get('[data-cy=submit]').click();
cy.url().should('include', '/dashboard');
});
});Output (login.spec.ts):
import { test, expect } from '@playwright/test';
test.describe('Login', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('should log in successfully', async ({ page }) => {
await page.locator('[data-cy=email]').fill('[email protected]');
await page.locator('[data-cy=password]').fill('password123');
await page.locator('[data-cy=submit]').click();
await expect(page).toHaveURL(/dashboard/);
});
});Development
# Clone the repo
git clone https://github.com/your-username/cy2play.git
cd cy2play
# Install dependencies
npm install
# Run in dev mode
npm run dev -- convert ./path/to/cypress/tests
# Build
npm run build
# Run tests
npm testRoadmap
See PROGRESS.md for the detailed engineering roadmap and task tracking.
Contributing
Contributions are welcome! Please open an issue to discuss your idea before submitting a PR.
- Fork the repo
- Create a feature branch (
git checkout -b feat/my-feature) - Commit your changes
- Push and open a PR
