Guides · Test automation · Updated 22 Jul 2026
Selenium to Playwright: the SDET roadmap
Learning Playwright is not the hard part — the syntax takes a weekend. The hard part is unlearning ten years of Selenium reflexes that quietly make your Playwright tests worse. This roadmap walks the ten stages in order, shows the old code beside the new, and marks the traps that only catch people who already know Selenium.
The short version. Ten stages, roughly 45–60 focused hours if you already write Selenium tests. Stages 1–5 make you productive; stage 6 is where real applications start to hurt; stages 7–9 are what separates someone who uses Playwright from someone who owns a framework. Stage 10 never ends.
Tick items off as you go — this page remembers your progress on this device, so you can close it and come back to the exact spot.
Before you startFive reflexes to unlearn
Every one of these is correct Selenium practice and wrong in Playwright. If you skip this section you will write Selenium-in-Playwright, get flaky tests anyway, and conclude the tool was oversold.
waitForElement(), a screenshot utility — Playwright already does all of it. Ex-Selenium teams routinely rebuild 2,000 lines of infrastructure they no longer need, then wonder why the migration did not pay off. Start from the Playwright defaults and only add what you can prove is missing.
The roadmapTen stages, in order
Order matters here. Stage 3 (locators) is the one that decides whether your suite is stable a year from now, and stage 6 is the one people skip and then rediscover under deadline. Each stage ends with a checkpoint — a thing you can actually do, not a topic you have read about.
Get started
The goal of day one is not understanding — it is a green test running in three browsers so you have something to modify.
WebDriverManager.chromedriver().setup();
WebDriver driver = new ChromeDriver();
driver.manage().timeouts()
.implicitlyWait(ofSeconds(10));
driver.get("https://example.com");
// ... test ...
driver.quit();
test('opens the site', async ({ page }) => {
await page.goto('https://example.com');
// no driver, no waits, no teardown
});
Core concepts
Three ideas carry the whole tool: a test is a function that receives fixtures, a locator is a lazy query, and an assertion retries. Everything else is detail.
WebElement, and the habit takes a week to shed.Locators
If you take one stage seriously, make it this one. Locator strategy is the single largest predictor of how much time you will spend maintaining the suite in year two.
driver.findElement(By.xpath( "//div[@class='form']//button[2]" )).click();
await page
.getByRole('button', { name: 'Save' })
.click();
getByTestId() immediately because it feels like the old By.id. Test IDs are a fine last resort, but a role locator also asserts that the control is reachable by a screen reader — you get an accessibility check for free. Spend the test IDs where semantics genuinely do not exist.Actions
Every action performs actionability checks first: the element must be visible, stable, enabled and able to receive events. This is the feature that deletes most of your wait code.
force: true is not a fix. It disables the actionability checks that were telling you the truth: the element is covered by an overlay, or the page is still animating. Every forced click is a bug you agreed to ship.Assertions
Web-first assertions retry until they pass or time out. Used properly, they replace essentially every explicit wait you have ever written.
new WebDriverWait(driver, ofSeconds(10))
.until(ExpectedConditions
.visibilityOfElementLocated(By.id("msg")));
assertEquals("Saved",
driver.findElement(By.id("msg")).getText());
await expect(page.getByTestId('msg'))
.toHaveText('Saved');
waitForTimeout(), and you can justify any explicit wait that survived.expect(await locator.textContent()).toBe('Saved') looks equivalent and is not. Awaiting the value first takes a single snapshot with no retry — you have rebuilt Selenium's race condition inside Playwright. Keep the locator inside expect().Real-world scenarios
Everything above works on a demo site. This stage is the difference between a tutorial and a suite that runs against your actual product.
String parent = driver.getWindowHandle();
for (String h : driver.getWindowHandles()) {
if (!h.equals(parent)) driver.switchTo().window(h);
}
// ... and remember to switch back
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.getByRole('link', { name: 'Docs' }).click(),
]);
await expect(popup).toHaveTitle(/Docs/);
A framework worth maintaining
This is where Selenium experience becomes an asset again — design instincts transfer even when the APIs do not. Just start smaller than you think you need to.
// fixtures.ts export const test = base.extend<{ checkout: CheckoutPage }>({ checkout: async ({ page }, use) => { const checkout = new CheckoutPage(page); await checkout.gotoWithItems(2); await use(checkout); // test runs here }, }); // the test asks for what it needs — nothing to inherit test('applies a discount', async ({ checkout }) => { await checkout.applyCode('SAVE10'); await expect(checkout.total).toHaveText('$45.00'); });
loginPage.loginAndVerifySuccess(). When it fails you get "expected true, got false" and no trace of what the page looked like. Keep page objects about locating and acting; keep assertions in the test where the failure message is readable.Advanced ground
None of these are mandatory. Each one is worth a day the moment your project needs it — and knowing they exist is what stops you building a worse version by hand.
CI/CD
A suite that only runs on your machine is a personal hobby. This stage turns it into something the team relies on.
retries: 2 everywhere. Retries hide flakiness rather than fixing it, and a suite that only passes on the second attempt is a suite nobody trusts. Retry on CI only, and treat every test that needed a retry as a bug report against itself.Keep improving
The suite you finish this quarter is the suite you maintain for three years. These are the habits that decide whether that is pleasant.
Progress is stored in this browser only — nothing is sent anywhere, and clearing site data resets it.
ReferenceThe translation table
Keep this open for your first week. Almost every "how do I do X in Playwright" question is one of these rows, and roughly a third of them resolve to "you no longer do that".
| What you want | Selenium | Playwright |
|---|---|---|
| Open a page | driver.get(url) | await page.goto(url) |
| Find something | driver.findElement(By.id("x")) | page.getByRole('button', { name }) |
| Wait for it | WebDriverWait + ExpectedConditions | — built in, delete this |
| Pause | Thread.sleep(2000) | — never; assert instead |
| Type text | el.clear(); el.sendKeys("hi") | await loc.fill('hi') |
| Dropdown | new Select(el).selectByValue("2") | await loc.selectOption('2') |
| Hover / drag | new Actions(driver).moveToElement(el) | await loc.hover() / loc.dragTo(t) |
| Iframe | driver.switchTo().frame(...) | page.frameLocator('#f').getByRole(...) |
| Alert | driver.switchTo().alert().accept() | page.on('dialog', d => d.accept()) |
| New window | getWindowHandles() loop | page.waitForEvent('popup') |
| Run JS | ((JavascriptExecutor) driver).executeScript | await page.evaluate(fn) |
| Screenshot | ((TakesScreenshot) driver).getScreenshotAs | await page.screenshot() / automatic |
| Assert text | assertEquals(expected, el.getText()) | await expect(loc).toHaveText(exp) |
| Fresh session | new ChromeDriver() … driver.quit() | — fixtures, per test, automatically |
| Scale out | Selenium Grid | workers + CI sharding |
| Debug a failure | logs, screenshots, re-run | npx playwright show-trace |
Honest sectionWhen Selenium is still the right answer
Roadmaps like this one tend to read as a sales pitch. Playwright is the better default for new web test automation in 2026 — faster, more stable, far better tooling — but there are real cases where switching costs you something.
| Situation | Stay with Selenium | Move to Playwright |
|---|---|---|
| Native mobile apps | Yes — Appium builds on the WebDriver protocol | Emulation only; not a device solution |
| Real Safari on real macOS hardware | Yes — drives actual Safari | WebKit is the engine, not the shipped browser |
| Legacy or locked-down enterprise browsers | Yes — broadest reach | Chromium, Firefox, WebKit |
| A large, healthy, fast Selenium suite | Yes — rewrites are rarely free | Write new tests here; migrate on touch |
| Team lives in Java or C# | Full first-class ecosystem | Supported, but TypeScript gets features first |
| New web project, CI-heavy, flakiness pain | — | Yes, clearly |
SchedulingSix weeks, an hour a day
The most common failure is not difficulty — it is stopping at stage 5, when tests already pass on the demo site and the real work starts. Pace it deliberately.
Stages 1–2
Install, first tests, config, fixtures. Port three existing tests from your own suite — real ones, not tutorial ones.
Stage 3
Locators only. Rewrite twenty selectors from your Selenium suite and argue with yourself about each one.
Stages 4–5
Actions, assertions, trace viewer. Ban waitForTimeout from your vocabulary.
Stage 6
The hard one. Iframes, tabs, uploads, network interception, auth reuse. Expect to be slower than you want.
Stage 7
Build the framework — small. Page objects, fixtures, data, reporting. Resist rebuilding your old one.
Stages 8–9
Pick two advanced topics your project needs, then get it running in CI with artifacts on failure.
QuestionsFAQ
How long does the switch actually take for an experienced Selenium engineer?
Writing working tests: two or three days. Writing tests that are not secretly Selenium: a few weeks, because that part is about habits rather than syntax. Owning a framework in CI, with data management and reporting: a couple of months alongside normal work. The estimates on each stage above assume you already understand the testing concepts and are only learning the tool.
Do I have to learn TypeScript?
Playwright has official Java, Python and .NET bindings, and they are perfectly usable. That said, TypeScript is the primary target: new features land there first, the documentation and community examples assume it, and things like component testing and custom fixtures are most natural there. If you are choosing fresh, choose TypeScript — the JavaScript you need for tests is a small subset.
Is the Page Object Model dead in Playwright?
No, but the version you know is. Keep the idea — one place per screen that knows how to find and operate its controls. Drop the machinery: no PageFactory, no @FindBy, no base class with wait helpers, no assertions inside the object. In practice many teams end up with thin page objects plus custom fixtures, and the fixtures do the work inheritance used to.
Can I run Playwright and Selenium side by side?
Yes, and it is usually the right migration path. They are separate processes with separate reports; the only shared surface is your CI configuration and, if you are careless, your test data. Run them as two pipeline jobs and let the Selenium job shrink over time.
My Playwright tests are still flaky. What did I get wrong?
In order of likelihood: assertions that read a value before asserting rather than passing the locator into expect(); force: true hiding a genuine overlay problem; tests sharing data or an account, so parallel workers collide; and app-side race conditions that Selenium's implicit waits were accidentally papering over. That last one is common and worth saying out loud — sometimes the new tool did not introduce flakiness, it exposed it.
Where do AI tools fit into this?
They are good at the mechanical parts: translating a Selenium test, drafting a page object from a DOM snapshot, explaining a trace. They are unreliable at locator strategy, because they cannot see which attributes your developers consider stable. Playwright MCP is the more interesting direction — it gives an assistant a real browser to explore in, which suits exploratory testing better than test generation. Learn the fundamentals first; a generated suite you cannot debug is worse than no suite.
Verify it yourselfSources
- Playwright official documentation — API reference, actionability checks, locator and assertion guides: playwright.dev
- Playwright release notes — the API changes often enough that a year-old blog post can be wrong
- Selenium documentation — waits, window handling and the WebDriver protocol, for the comparisons above
- Appium project — the reason Selenium remains the answer for native mobile
NextRelated on AQA Pro
Last updated 22 July 2026 · AQA Pro