August 14, 2026 · 7 min read · sdet.qa

Migrating Selenium and Cypress Suites to Playwright with AI (2026)

Practical recipes and system prompts for migrating Selenium and Cypress suites to idiomatic Playwright TypeScript with AI - mapping tables, review gates, pitfalls.

Migrating Selenium and Cypress Suites to Playwright with AI (2026)

Migrating a mature Selenium or Cypress suite to Playwright by hand is a slog: hundreds of files, thousands of selectors, and years of accreted waits and custom commands. An LLM changes the economics - it can do the mechanical translation in seconds per file and get you to idiomatic Playwright, not a line-by-line transliteration. But “in seconds” is a trap if you skip review, because the failure mode is not a broken test that goes red; it is a weakened test that goes green while checking less. This post gives you the recipes, system prompts, mapping tables, and review gates to migrate fast without quietly gutting your coverage.

If you are still deciding whether the migration is worth it, start with Selenium vs Playwright and WebdriverIO vs Playwright. For the bigger picture on AI-driven QA, see our pillar guide, Building a Self-Operating QA Agent with Playwright MCP.

What maps cleanly from Selenium and Cypress to Playwright?

The good news is that the two things people hate most about legacy suites - flaky waits and brittle selectors - are exactly what Playwright fixes, and both map cleanly.

Waiting collapses into auto-waiting. Selenium’s implicitlyWait, its WebDriverWait/ExpectedConditions blocks, and every Thread.sleep mostly disappear. Playwright auto-waits for elements to be actionable before it acts, so the translation is usually deletion, not conversion. Cypress already retried commands, so its implicit waits map the same way - and its explicit cy.wait(5000) sleeps should be dropped, not ported.

Brittle selectors become role locators. A By.xpath("//div[@class='btn-primary']//span") or a cy.get('.submit-btn') becomes getByRole('button', { name: 'Submit' }). This is the highest-value part of the migration, because you are not just changing syntax - you are moving to locators that survive DOM churn. For a deep dive, see Accessibility-Tree Locators for Resilient Playwright Tests.

Basic actions are one-to-one. Click, type, navigate, and assert-visible all have direct Playwright equivalents. This is where AI translation is nearly flawless.

What does not map cleanly?

The parts that do not translate are the parts that relied on a framework model Playwright does not share. These need redesign, and this is where an over-eager model produces confident nonsense.

Cypress runs inside the browser’s event loop and chains commands on a custom queue; Playwright drives the browser from the outside. So cy.intercept network stubbing maps to Playwright’s page.route, but custom commands defined on Cypress.Commands, plugin-based behavior, and anything assuming synchronous in-browser chaining need rethinking. Selenium’s world is different again: Grid configuration, WebDriver capabilities, and driver lifecycle have no Playwright equivalent because Playwright manages browser contexts itself.

Page-object base classes and shared custom waits are the other trap. A custom waitForSpinnerToDisappear often existed to paper over a race condition; blindly porting it carries the flakiness forward. The migration is a chance to delete it and let auto-waiting do the job.

The Selenium/Cypress to Playwright mapping table

Give this table to your model as reference, and keep it for human reviewers too.

Source constructPlaywright equivalentNotes
driver.findElement(By.css(...))page.locator(...)Prefer getByRole where possible
By.xpath / brittle CSSgetByRole / getByLabel / getByTextHighest-value change
implicitlyWait / WebDriverWait(removed) auto-waitingUsually just delete
Thread.sleep / cy.wait(ms)(removed) or expect(...).toBeVisible()Never port raw sleeps
cy.get('sel')page.getByRole(...)Re-derive as role locator
cy.contains(text)page.getByText(text)Direct
cy.intercept(...)await page.route(...)Runs external, not in-browser
cy.visit(url) / driver.get(url)await page.goto(url)Direct
.should('be.visible')await expect(locator).toBeVisible()Must stay an assertion
.should('have.text', t)await expect(locator).toHaveText(t)Watch for weakening
Cypress custom commandsFixtures / helper functionsRedesign, do not transliterate
Selenium Grid / capabilitiesplaywright.config.ts projectsConfig concept, not code

What is the AI-assisted migration workflow?

The workflow is a loop: feed the model one test plus a strict system prompt, get idiomatic Playwright back, run it, then gate it through human review before it replaces anything.

Here is a system prompt that produces disciplined output rather than a hopeful transliteration.

You are migrating a test to Playwright TypeScript. Follow these rules exactly:

1. Preserve every assertion. Output the same number of assertions as the
   source, or more. If you cannot map an assertion, keep it and mark it
   with // REVIEW: <reason>. Never silently drop a check.
2. Remove all explicit waits and sleeps. Rely on Playwright auto-waiting.
   Do not add manual waitForTimeout.
3. Replace CSS/XPath selectors with role-based locators (getByRole,
   getByLabel, getByText) wherever the element intent is clear. If you must
   fall back to page.locator(css), add // REVIEW: brittle selector.
4. Convert custom Cypress commands to helper functions or fixtures. Do not
   invent behavior; if semantics are unclear, emit a // REVIEW comment.
5. Use web-first assertions (expect(locator).toBeVisible()), not
   expect(await locator.isVisible()).toBe(true).
6. Output only the migrated test file. Do not summarize.

The two rules that matter most are (1) and (3): they force the model to surface its uncertainty as // REVIEW comments instead of hiding it in green tests. Those comments become your reviewer’s checklist.

Before and after: a Cypress test

Here is a representative Cypress login test with a raw wait and brittle selectors.

// login.cy.js  (BEFORE - Cypress)
describe('login', () => {
  it('logs in a valid user', () => {
    cy.visit('/login');
    cy.get('#email').type('[email protected]');
    cy.get('.pw-field > input').type('correct-horse');
    cy.get('button.submit-btn').click();
    cy.wait(3000); // wait for redirect
    cy.get('.dashboard-header').should('be.visible');
    cy.get('.welcome').should('contain', 'Welcome back');
  });
});

And the idiomatic Playwright the workflow should produce - waits gone, selectors re-derived as roles, both assertions preserved.

// login.spec.ts  (AFTER - Playwright)
import { test, expect } from '@playwright/test';

test('logs in a valid user', async ({ page }) => {
  await page.goto('/login');
  await page.getByRole('textbox', { name: 'Email' }).fill('[email protected]');
  await page.getByLabel('Password').fill('correct-horse');
  await page.getByRole('button', { name: 'Submit' }).click();

  // cy.wait(3000) removed - auto-waiting handles the redirect
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  await expect(page.getByText('Welcome back')).toBeVisible();
});

Note what a careless migration would have done: dropped the cy.wait and the second assertion, leaving a test that passes but no longer checks the welcome message. Assertion count is your tripwire - the source had two shoulds, so the output must have two expects.

What review gates do you need?

Never let a migrated file merge on a green run alone. AI translation passes the run and fails the intent often enough that you need explicit gates.

GateWhat it checksHow
Assertion parityMigrated file has >= source assertion countDiff review, count expect vs should/assert
No hidden waitsNo waitForTimeout, no raw sleeps introducedLint rule or grep in CI
Locator qualityRole locators used; // REVIEW on any fallbackHuman review of flagged lines
Behavior parityTest fails when the feature is actually brokenMutation check or deliberate break
Green in CIPasses on the same environment as the sourceRun in parallel before deleting old test

The behavior-parity gate is the one people skip and regret. A test that only ever passes is worthless; before you delete the Selenium original, deliberately break the feature and confirm the Playwright version goes red. If it stays green, the migration weakened it.

What are the common pitfalls?

Over-literal translation. The model faithfully ports a Thread.sleep(5000) into page.waitForTimeout(5000), carrying the flakiness and slowness forward. The system prompt above forbids this, but reviewers should grep for it anyway.

Dropped assertions. The single most damaging pitfall, covered above. Count assertions on every file.

Weakened assertions. Subtler than dropping: .should('have.text', 'Welcome back') becomes a mere toBeVisible(), so the exact-text check silently degrades to an existence check. Read the diff, do not just count.

Invented custom-command behavior. When the model does not understand a Cypress custom command, it sometimes guesses at the implementation. The prompt tells it to emit // REVIEW instead; enforce that in review.

Config drift. Selenium Grid capabilities and Cypress config do not map to code - they map to playwright.config.ts projects. Handle environment and browser config as a separate, deliberate task, not part of per-file translation.

The bottom line

AI makes a Playwright migration dramatically faster, but only if you treat it as a translator that must be audited, not an autopilot. Let the model do the mechanical work - collapsing waits into auto-waiting, rewriting selectors as role locators, mapping the API - and put your human effort where it counts: assertion parity, behavior parity, and the handful of constructs that need redesign rather than translation. Migrate incrementally, run both suites in parallel, and delete each old test only once its reviewed twin proves it fails when the feature really breaks.

If you would rather hand the whole re-platforming to a team that does this repeatedly, our Test Automation Framework Engineering and SDET as a Service practices run incremental Playwright migrations without dropping coverage.

Frequently Asked Questions

Can AI migrate Selenium or Cypress tests to Playwright automatically?

AI can do most of the mechanical translation and get you to idiomatic Playwright far faster than hand-porting, but it is not fully automatic. A model reliably maps cy.* commands and Selenium calls to the Playwright API, converts implicit and explicit waits to auto-waiting, and rewrites brittle selectors as role locators. What it cannot be trusted with unreviewed is preserving every assertion, custom command semantics, and complex fixtures - those need human review gates on every migrated file.

What maps cleanly from Selenium and Cypress to Playwright?

The clean wins are waiting and locators. Selenium's implicit and explicit waits and Cypress's built-in retries both collapse into Playwright's auto-waiting, so most sleep and wait code simply disappears. Brittle CSS and XPath selectors map to role-based locators like getByRole and getByLabel. Basic actions - click, type, navigate, assert visible - have direct one-to-one equivalents in the Playwright API.

What does not map cleanly to Playwright?

The hard parts are custom Cypress commands, cy.intercept network stubbing, Selenium Grid and WebDriver session config, and page-object base classes. Cypress runs in the browser event loop while Playwright drives it externally, so anything relying on that model - synchronous chaining, certain plugins - needs rethinking, not translating. Custom waits that hid real race conditions also need attention rather than a literal port.

What is the biggest risk when using AI to migrate tests?

The biggest risk is silently dropped or weakened assertions. An over-literal translation can produce Playwright that runs green while checking less than the original - a removed assertion, a soft check turned into a no-op, or a wait that masks a real failure. This is why every migrated test needs a diff-level review gate comparing assertion count and intent against the source, not just a passing run.

Should I migrate to Playwright all at once or incrementally?

Migrate incrementally. Run Playwright alongside the existing Selenium or Cypress suite, port test files in priority order, and delete each old file only once its Playwright twin is reviewed and green in CI. A big-bang rewrite risks a coverage gap during the switch and makes it hard to tell whether a new failure is a migration bug or a real regression.

Test automation, engineered.

Book a free 30-minute call. We assess your test automation gaps and show you how a modern SDET practice ships faster with fewer escapes.

Talk to an Expert