Skip to content

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.

Where you are A driver, a wait strategy, four libraries and a flaky suite
Where this ends One tool, auto-waiting, traces on failure, green CI

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.

Explicit waits before every interaction
Actionability checks are built in — delete the waits
XPath and CSS chains tied to the DOM
Role, label and text — locate what the user sees
A driver you create, manage and quit
Fixtures hand you a clean browser context per test
Runner, assertions, reporting, waits — four libraries
One dependency, one config file
Debugging by re-running with more logging
Open the trace and step through the failed run
The single biggest mistake Porting your Selenium framework one-to-one. Custom wait helpers, a driver factory, a BasePage with 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.

0%
0 of 60 steps done · ~52 h of focused work left

Get started

~3 h · foundation

The goal of day one is not understanding — it is a green test running in three browsers so you have something to modify.

Selenium — setup
WebDriverManager.chromedriver().setup();
WebDriver driver = new ChromeDriver();
driver.manage().timeouts()
  .implicitlyWait(ofSeconds(10));
driver.get("https://example.com");
// ... test ...
driver.quit();
Playwright — setup
test('opens the site', async ({ page }) => {
  await page.goto('https://example.com');
  // no driver, no waits, no teardown
});
Checkpoint You can run one test across three browsers, open the HTML report, and explain why nothing in your test creates or closes a browser.
Trap Codegen output is a starting point, not a style guide. It leans on brittle selectors when the page has no accessible names. Treat it as a way to discover the API, then write the real test yourself.

Core concepts

~4 h · the mental model

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.

Checkpoint You can explain, without looking it up, what a browser context is and why it makes Playwright's parallelism cheap compared with spinning up drivers.
Trap A locator does nothing until you act or assert on it. Storing one in a variable is fine and idiomatic — it is not a stale-element risk, because it is re-resolved on every use. That is the opposite of a Selenium WebElement, and the habit takes a week to shed.

Locators

~6 h · the one that matters

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.

Selenium — brittle by default
driver.findElement(By.xpath(
  "//div[@class='form']//button[2]"
)).click();
Playwright — user-visible
await page
  .getByRole('button', { name: 'Save' })
  .click();
Checkpoint You can rewrite ten XPath selectors from your existing suite as role or label locators, and name the two where you legitimately could not.
Trap Reaching for 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

~4 h · interacting

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.

Checkpoint You have opened a trace of a genuinely failed test and found the cause from the DOM snapshot, without adding a single log line.
Trap 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

~3 h · where flakiness dies

Web-first assertions retry until they pass or time out. Used properly, they replace essentially every explicit wait you have ever written.

Selenium — wait, then assert
new WebDriverWait(driver, ofSeconds(10))
  .until(ExpectedConditions
    .visibilityOfElementLocated(By.id("msg")));
assertEquals("Saved",
  driver.findElement(By.id("msg")).getText());
Playwright — the assertion waits
await expect(page.getByTestId('msg'))
  .toHaveText('Saved');
Checkpoint Your test files contain zero calls to waitForTimeout(), and you can justify any explicit wait that survived.
Trap 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

~8 h · where apps fight back

Everything above works on a demo site. This stage is the difference between a tutorial and a suite that runs against your actual product.

Selenium — new window
String parent = driver.getWindowHandle();
for (String h : driver.getWindowHandles()) {
  if (!h.equals(parent)) driver.switchTo().window(h);
}
// ... and remember to switch back
Playwright — new window
const [popup] = await Promise.all([
  page.waitForEvent('popup'),
  page.getByRole('link', { name: 'Docs' }).click(),
]);
await expect(popup).toHaveTitle(/Docs/);
Checkpoint You can force an error state your UI almost never shows — a 500 on save, a slow endpoint — by intercepting the request, and assert the app handles it.
Trap Stubbing every network call because it makes tests fast. You end up testing your stubs. Intercept for error paths and edge cases; keep the critical journeys hitting a real backend.

A framework worth maintaining

~10 h · architecture

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.

A fixture replaces the base class
// 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');
});
Checkpoint A new engineer can clone the repo, run one command, and have tests passing locally in under ten minutes.
Trap Page objects that wrap assertions and waits — 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

~8 h · breadth

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.

Checkpoint Your longest journey test sets up its preconditions through the API and only exercises the UI for the thing it is actually testing.
Trap Turning on visual comparison across the whole suite in week one. Font rendering differs between your laptop and the CI container, and you will spend more time approving diffs than finding bugs. Start with two or three stable, high-value screens and pin the rendering environment.

CI/CD

~6 h · where it earns its keep

A suite that only runs on your machine is a personal hobby. This stage turns it into something the team relies on.

Checkpoint A developer whose PR failed can find the cause from the pipeline alone — trace attached, no local reproduction, no message to you.
Trap 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

ongoing · the part with no end

The suite you finish this quarter is the suite you maintain for three years. These are the habits that decide whether that is pleasant.

Checkpoint You can answer "is the build red because of the product or because of the tests?" in under a minute, every time.

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 wantSeleniumPlaywright
Open a pagedriver.get(url)await page.goto(url)
Find somethingdriver.findElement(By.id("x"))page.getByRole('button', { name })
Wait for itWebDriverWait + ExpectedConditions— built in, delete this
PauseThread.sleep(2000)— never; assert instead
Type textel.clear(); el.sendKeys("hi")await loc.fill('hi')
Dropdownnew Select(el).selectByValue("2")await loc.selectOption('2')
Hover / dragnew Actions(driver).moveToElement(el)await loc.hover() / loc.dragTo(t)
Iframedriver.switchTo().frame(...)page.frameLocator('#f').getByRole(...)
Alertdriver.switchTo().alert().accept()page.on('dialog', d => d.accept())
New windowgetWindowHandles() looppage.waitForEvent('popup')
Run JS((JavascriptExecutor) driver).executeScriptawait page.evaluate(fn)
Screenshot((TakesScreenshot) driver).getScreenshotAsawait page.screenshot() / automatic
Assert textassertEquals(expected, el.getText())await expect(loc).toHaveText(exp)
Fresh sessionnew ChromeDriver() … driver.quit()— fixtures, per test, automatically
Scale outSelenium Gridworkers + CI sharding
Debug a failurelogs, screenshots, re-runnpx 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.

SituationStay with SeleniumMove to Playwright
Native mobile appsYes — Appium builds on the WebDriver protocolEmulation only; not a device solution
Real Safari on real macOS hardwareYes — drives actual SafariWebKit is the engine, not the shipped browser
Legacy or locked-down enterprise browsersYes — broadest reachChromium, Firefox, WebKit
A large, healthy, fast Selenium suiteYes — rewrites are rarely freeWrite new tests here; migrate on touch
Team lives in Java or C#Full first-class ecosystemSupported, but TypeScript gets features first
New web project, CI-heavy, flakiness painYes, clearly
About "big bang" migrations The pattern that works is boring: keep the Selenium suite running, write every new test in Playwright, and port an old test only when you were going to touch it anyway. Teams that stop the world to rewrite everything usually end up maintaining two half-suites and shipping neither.

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.

Week 1

Stages 1–2

Install, first tests, config, fixtures. Port three existing tests from your own suite — real ones, not tutorial ones.

Week 2

Stage 3

Locators only. Rewrite twenty selectors from your Selenium suite and argue with yourself about each one.

Week 3

Stages 4–5

Actions, assertions, trace viewer. Ban waitForTimeout from your vocabulary.

Week 4

Stage 6

The hard one. Iframes, tabs, uploads, network interception, auth reuse. Expect to be slower than you want.

Week 5

Stage 7

Build the framework — small. Page objects, fixtures, data, reporting. Resist rebuilding your old one.

Week 6

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