
Migrate
- 1.6k installs
- 23.5k repo stars
- Updated July 17, 2026
- alirezarezvani/claude-skills
migrate converts Cypress or Selenium test suites to Playwright with detection and mapping guides.
About
The migrate skill interactively converts Cypress or Selenium tests to Playwright. It auto-detects cypress directories, webdriver dependencies, or Python selenium imports, counts files, estimates effort, and chooses sequential, batched, or parallel strategies by suite size. Conversion references cypress-mapping.md for cy.visit, cy.get, intercept, fixture, and assertion translations to page.goto, locators, route, and expect matchers. Selenium mappings cover WebDriver calls to Playwright equivalents. Custom commands become fixtures or helpers. Agents run pw:init when Playwright is missing and preserve beforeEach hooks as test hooks. Use when users mention Cypress, Selenium, migrate tests, or switch to Playwright.
- Detects Cypress, Selenium JS, or Selenium Python source frameworks.
- Sizes migration and picks sequential, batch, or parallel conversion.
- Maps Cypress cy.* APIs to Playwright page and expect patterns.
- Converts custom commands to fixtures or helper functions.
- Runs Playwright init when the target project lacks configuration.
Migrate by the numbers
- 1,573 all-time installs (skills.sh)
- +1 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #465 of 2,159 Testing & QA skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
migrate capabilities & compatibility
- Capabilities
- framework detection · api mapping conversion · suite sizing
- Use cases
- testing · refactoring
What migrate says it does
Interactive migration from Cypress or Selenium to Playwright with file-by-file conversion.
npx skills add https://github.com/alirezarezvani/claude-skills --skill migrateAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.6k |
|---|---|
| repo stars | ★ 23.5k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 17, 2026 |
| Repository | alirezarezvani/claude-skills ↗ |
How do I move our Cypress or Selenium tests to Playwright?
Migrate Cypress or Selenium test suites to Playwright with framework detection, scope assessment, and file-by-file conversion mappings.
Who is it for?
Teams replacing Cypress or Selenium with Playwright incrementally.
Skip if: Greenfield test authoring without an existing suite to convert.
When should I use this skill?
User mentions migrate tests, Cypress to Playwright, or Selenium conversion.
What you get
Converted Playwright tests with preserved hooks, fixtures, and assessed migration scope.
- Playwright test files
- Migrated selector and assertion patterns
Files
Migrate to Playwright
Interactive migration from Cypress or Selenium to Playwright with file-by-file conversion.
Input
$ARGUMENTS can be:
"from cypress"— migrate Cypress test suite"from selenium"— migrate Selenium/WebDriver tests- A file path: convert a specific test file
- Empty: auto-detect source framework
Steps
1. Detect Source Framework
Use Explore subagent to scan:
cypress/directory orcypress.config.ts→ Cypressselenium,webdriverinpackage.jsondeps → Selenium.pytest files withseleniumimports → Selenium (Python)
2. Assess Migration Scope
Count files and categorize:
Migration Assessment:
- Total test files: X
- Cypress custom commands: Y
- Cypress fixtures: Z
- Estimated effort: [small|medium|large]| Size | Files | Approach |
|---|---|---|
| Small (1-10) | Convert sequentially | Direct conversion |
| Medium (11-30) | Batch in groups of 5 | Use sub-agents |
| Large (31+) | Use /batch | Parallel conversion with /batch |
3. Set Up Playwright (If Not Present)
Run /pw:init first if Playwright isn't configured.
4. Convert Files
For each file, apply the appropriate mapping:
Cypress → Playwright
Load cypress-mapping.md for complete reference.
Key translations:
cy.visit(url) → page.goto(url)
cy.get(selector) → page.locator(selector) or page.getByRole(...)
cy.contains(text) → page.getByText(text)
cy.find(selector) → locator.locator(selector)
cy.click() → locator.click()
cy.type(text) → locator.fill(text)
cy.should('be.visible') → expect(locator).toBeVisible()
cy.should('have.text') → expect(locator).toHaveText(text)
cy.intercept() → page.route()
cy.wait('@alias') → page.waitForResponse()
cy.fixture() → JSON import or test data fileCypress custom commands → Playwright fixtures or helper functions Cypress plugins → Playwright config or fixtures `before`/`beforeEach` → test.beforeAll() / test.beforeEach()
Selenium → Playwright
Load selenium-mapping.md for complete reference.
Key translations:
driver.get(url) → page.goto(url)
driver.findElement(By.id('x')) → page.locator('#x') or page.getByTestId('x')
driver.findElement(By.css('.x')) → page.locator('.x') or page.getByRole(...)
element.click() → locator.click()
element.sendKeys(text) → locator.fill(text)
element.getText() → locator.textContent()
WebDriverWait + ExpectedConditions → expect(locator).toBeVisible()
driver.switchTo().frame() → page.frameLocator()
Actions → locator.hover(), locator.dragTo()5. Upgrade Locators
During conversion, upgrade selectors to Playwright best practices:
#id→getByTestId()orgetByRole().class→getByRole()orgetByText()[data-testid]→getByTestId()- XPath → role-based locators
6. Convert Custom Commands / Utilities
- Cypress custom commands → Playwright custom fixtures via
test.extend() - Selenium page objects → Playwright page objects (keep structure, update API)
- Shared helpers → TypeScript utility functions
7. Verify Each Converted File
After converting each file:
npx playwright test <converted-file> --reporter=listFix any compilation or runtime errors before moving to the next file.
8. Clean Up
After all files are converted:
- Remove Cypress/Selenium dependencies from
package.json - Remove old config files (
cypress.config.ts, etc.) - Update CI workflow to use Playwright
- Update README with new test commands
Ask user before deleting anything.
Output
- Conversion summary: files converted, total tests migrated
- Any tests that couldn't be auto-converted (manual intervention needed)
- Updated CI config
- Before/after comparison of test run results
Cypress → Playwright Mapping
Commands
| Cypress | Playwright | Notes |
|---|---|---|
cy.visit('/page') | await page.goto('/page') | Use baseURL in config |
cy.get('.selector') | page.locator('.selector') | Prefer getByRole() |
cy.get('[data-cy=x]') | page.getByTestId('x') | |
cy.contains('text') | page.getByText('text') | |
cy.find('.child') | parent.locator('.child') | Chain from parent locator |
cy.first() | locator.first() | |
cy.last() | locator.last() | |
cy.eq(n) | locator.nth(n) | |
cy.parent() | locator.locator('..') | Or restructure with better locators |
cy.children() | locator.locator('> *') | |
cy.siblings() | Not direct — restructure test | Use parent + filter |
Actions
| Cypress | Playwright | Notes |
|---|---|---|
.click() | await locator.click() | Always await |
.dblclick() | await locator.dblclick() | |
.rightclick() | await locator.click({ button: 'right' }) | |
.type('text') | await locator.fill('text') | fill() clears first |
.type('text', { delay: 50 }) | await locator.pressSequentially('text', { delay: 50 }) | Simulates typing |
.clear() | await locator.clear() | |
.check() | await locator.check() | |
.uncheck() | await locator.uncheck() | |
.select('value') | await locator.selectOption('value') | |
.scrollTo() | await locator.scrollIntoViewIfNeeded() | |
.trigger('event') | await locator.dispatchEvent('event') | |
.focus() | await locator.focus() | |
.blur() | await locator.blur() |
Assertions
| Cypress | Playwright | Notes |
|---|---|---|
.should('be.visible') | await expect(locator).toBeVisible() | Web-first, auto-retry |
.should('not.exist') | await expect(locator).not.toBeVisible() | Or .toHaveCount(0) |
.should('have.text', 'x') | await expect(locator).toHaveText('x') | |
.should('contain', 'x') | await expect(locator).toContainText('x') | |
.should('have.value', 'x') | await expect(locator).toHaveValue('x') | |
.should('have.attr', 'x', 'y') | await expect(locator).toHaveAttribute('x', 'y') | |
.should('have.class', 'x') | await expect(locator).toHaveClass(/x/) | |
.should('be.disabled') | await expect(locator).toBeDisabled() | |
.should('be.checked') | await expect(locator).toBeChecked() | |
.should('have.length', n) | await expect(locator).toHaveCount(n) | |
cy.url().should('include', '/x') | await expect(page).toHaveURL(/\/x/) | |
cy.title().should('eq', 'x') | await expect(page).toHaveTitle('x') |
Network
| Cypress | Playwright |
|---|---|
cy.intercept('GET', '/api/*', { body: data }) | await page.route('**/api/*', route => route.fulfill({ body: JSON.stringify(data) })) |
cy.intercept('POST', '/api/*').as('save') | const savePromise = page.waitForResponse('**/api/*') |
cy.wait('@save') | await savePromise |
Fixtures & Custom Commands
| Cypress | Playwright |
|---|---|
cy.fixture('data.json') | import data from './test-data/data.json' |
Cypress.Commands.add('login', ...) | test.extend({ authenticatedPage: ... }) |
beforeEach(() => { ... }) | test.beforeEach(async ({ page }) => { ... }) |
before(() => { ... }) | test.beforeAll(async () => { ... }) |
Config
| Cypress | Playwright |
|---|---|
baseUrl in cypress.config.ts | use.baseURL in playwright.config.ts |
defaultCommandTimeout | expect.timeout or use.actionTimeout |
video: true | use.video: 'on' |
screenshotOnRunFailure | use.screenshot: 'only-on-failure' |
retries: { runMode: 2 } | retries: 2 |
Selenium → Playwright Mapping
Driver Setup
| Selenium (JS) | Playwright |
|---|---|
new Builder().forBrowser('chrome').build() | Handled by config — no driver setup |
driver.quit() | Automatic — Playwright manages browser lifecycle |
driver.manage().setTimeouts(...) | Config: timeout, expect.timeout |
Navigation
| Selenium | Playwright | Notes |
|---|---|---|
driver.get(url) | await page.goto(url) | Use baseURL |
driver.navigate().back() | await page.goBack() | |
driver.navigate().forward() | await page.goForward() | |
driver.navigate().refresh() | await page.reload() | |
driver.getCurrentUrl() | page.url() | |
driver.getTitle() | await page.title() |
Element Location
| Selenium | Playwright | Preferred |
|---|---|---|
By.id('x') | page.locator('#x') | page.getByTestId('x') |
By.css('.x') | page.locator('.x') | page.getByRole(...) |
By.xpath('//div') | page.locator('xpath=//div') | Avoid — use role-based |
By.name('x') | page.locator('[name=x]') | page.getByLabel(...) |
By.linkText('x') | page.getByRole('link', { name: 'x' }) | ✅ Best practice |
By.partialLinkText('x') | page.getByRole('link', { name: /x/ }) | ✅ Best practice |
By.tagName('button') | page.getByRole('button') | ✅ Best practice |
By.className('x') | page.locator('.x') | page.getByRole(...) |
findElement() | Returns first match | locator.first() |
findElements() | page.locator(selector) | Use .count() or .all() |
Actions
| Selenium | Playwright |
|---|---|
element.click() | await locator.click() |
element.sendKeys('text') | await locator.fill('text') |
element.sendKeys(Key.ENTER) | await locator.press('Enter') |
element.clear() | await locator.clear() |
element.submit() | await locator.press('Enter') or click submit button |
element.getText() | await locator.textContent() |
element.getAttribute('x') | await locator.getAttribute('x') |
element.isDisplayed() | await locator.isVisible() |
element.isEnabled() | await locator.isEnabled() |
element.isSelected() | await locator.isChecked() |
Waits
| Selenium | Playwright | Notes |
|---|---|---|
WebDriverWait(driver, 10).until(EC.visibilityOf(el)) | await expect(locator).toBeVisible() | Auto-retries |
WebDriverWait(driver, 10).until(EC.elementToBeClickable(el)) | await locator.click() | Auto-waits for clickable |
WebDriverWait(driver, 10).until(EC.presenceOf(el)) | await expect(locator).toBeAttached() | |
WebDriverWait(driver, 10).until(EC.textToBe(el, 'x')) | await expect(locator).toHaveText('x') | |
Thread.sleep(3000) | ❌ Never use | Use assertions instead |
driver.manage().setTimeouts({ implicit: 10000 }) | Not needed | Playwright auto-waits |
Advanced
| Selenium | Playwright |
|---|---|
Actions(driver).moveToElement(el).perform() | await locator.hover() |
Actions(driver).dragAndDrop(src, tgt).perform() | await src.dragTo(tgt) |
Actions(driver).doubleClick(el).perform() | await locator.dblclick() |
Actions(driver).contextClick(el).perform() | await locator.click({ button: 'right' }) |
driver.switchTo().frame(el) | page.frameLocator('#frame') |
driver.switchTo().defaultContent() | Not needed — use page directly |
driver.switchTo().alert() | page.on('dialog', d => d.accept()) |
driver.switchTo().window(handle) | const popup = await page.waitForEvent('popup') |
driver.executeScript(js) | await page.evaluate(js) |
driver.takeScreenshot() | await page.screenshot({ path: 'x.png' }) |
Test Structure
| Selenium (Jest/Mocha) | Playwright |
|---|---|
describe('Suite', () => { ... }) | test.describe('Suite', () => { ... }) |
it('should...', () => { ... }) | test('should...', async ({ page }) => { ... }) |
beforeAll(() => { ... }) | test.beforeAll(async () => { ... }) |
beforeEach(() => { ... }) | test.beforeEach(async ({ page }) => { ... }) |
afterEach(() => { ... }) | test.afterEach(async ({ page }) => { ... }) |
Key Differences
1. No implicit waits — Playwright auto-waits for actionability 2. No driver management — Playwright handles browser lifecycle 3. Built-in assertions — expect(locator) with auto-retry 4. Parallel by default — tests run in parallel, must be isolated 5. Traces instead of screenshots — richer debugging artifacts
Related skills
How it compares
Use migrate for mechanical Cypress/Selenium-to-Playwright conversion; use Playwright authoring skills when writing new tests from scratch.
FAQ
How is source framework detected?
Scan for cypress folders, webdriver deps, or Python selenium imports.
What about Cypress custom commands?
Translate them into Playwright fixtures or shared helper functions.
When is Playwright init required?
Run pw:init first if the project has no Playwright configuration yet.
Is Migrate safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.