Agentic assistants can convert a Selenium/Java suite to Playwright/TypeScript at a pace no human team matches — but a translated test that passes is not proof it tests the same thing. Behavior-parity runs, not green checkmarks, are the verification spine of this migration.
Tier: Advanced · Workflow area: Automation engineering
This playbook migrates a legacy Selenium suite file-by-file with an AI coding agent, holding every converted spec to a parity gate: the new test must fail when the old one fails and catch the same seeded bugs, before the Java original is allowed to die.
Thread.sleep, PageFactory classes nobody touches.npm init playwright@latest) with your team's config conventions committed first.Inventory before you convert. Have the agent map the old suite, not translate it yet:
Read src/test/java/** in this repo. Produce a migration inventory table:
test class | scenarios covered | page objects used | selectors (CSS/XPath/id counts) |
waits (explicit/implicit/Thread.sleep) | test data source | flake markers (@Retry, ignored).
Flag any test that is disabled, duplicated, or asserts nothing meaningful.
Do not write any TypeScript yet.
Kill the dead tests now. Migrating a disabled test is effort spent embalming a corpse.
Convert one vertical slice first — page object plus its tests. Scope the prompt tightly:
Convert LoginPage.java and LoginTests.java to Playwright/TypeScript in tests/login.spec.ts.
Rules:
- Use role/label-based locators (getByRole, getByLabel); no XPath. If the old XPath
encodes intent you cannot recover from the DOM, add a TODO with the original selector.
- Replace every explicit wait and Thread.sleep with Playwright auto-waiting or
expect(...).toPass — flag any sleep you cannot remove.
- Preserve assertion INTENT exactly. Do not strengthen or weaken checks.
- Match conventions in tests/example.spec.ts and playwright.config.ts.
Run the parity gate on the slice. Parity has three legs:
# Leg 1: both green on healthy app
mvn test -Dtest=LoginTests && npx playwright test tests/login.spec.ts
# Leg 2: both red on the seeded-bug build
git checkout seeded-bugs && mvn test -Dtest=LoginTests; npx playwright test tests/login.spec.ts
Sample parity log entry you should be producing per file:
login.spec.ts | old: 6 pass | new: 6 pass | seeded bug LGN-3: old FAIL / new FAIL ✔ parity
cart.spec.ts | old: 9 pass | new: 9 pass | seeded bug CRT-1: old FAIL / new PASS ✘ ESCAPE
Leg 3 is assertion review: diff old vs new assertions side by side. A converted test that passes on the broken build (an "escape", like cart.spec.ts above) means the agent dropped or weakened an assertion — the most common and most dangerous conversion defect.
Feed escapes back, scoped. Paste the escaping test pair and the seeded bug description: "the Java test catches this bug, your TypeScript version does not — find the dropped assertion and restore it, change nothing else."
Batch the long tail only after the pattern holds. Once 3–4 slices pass parity clean, let the agent convert in batches of 5–10 files per PR, each PR carrying its parity log. Never one 400-file PR — reviewability is the gate's whole value.
Delete the Java original per file, only after parity. The old test is the oracle; deleting it early deletes your evidence. Track a burn-down: converted / parity-proven / deleted.
Green-on-green proves nothing — any empty test passes. A migration is verified only when the new test fails everywhere the old one failed. Escapes on the seeded-bug run are your real defect count for the migration itself.
Before any batch PR merges:
grep -rn "xpath\|Thread.sleep\|waitForTimeout" tests/ returns only justified, commented survivors.npx playwright test --repeat-each=5); anything under 100% goes back before merge.waitForTimeout. A converted sleep is still a sleep. Grep for it; treat each as a defect.Client-owned suites at services companies: confirm the client's AI-tool policy before an agent reads their repo. If public assistants are barred, run a local coder model (Ollama, 14B+ coder class) for the conversion prompts — conversion is pattern-heavy and local models handle it acceptably — and keep the parity harness identical, since it is pure test execution. Strip client hostnames, credentials, and business-identifying test data from any prompt context; parity logs shared in reports should reference test IDs, not client URLs.
waitForTimeout without a justifying comment in the new suite.Keep the seeded-bug branch alive after the migration ends. It is now a standing calibration rig — rerun it whenever an agent, a healer, or a new hire touches the suite, and you will always know whether your tests still catch what they were born to catch.