Frameworks

Selenium 4 vs Playwright: Locator Models Compared

By.xpath and RelativeLocator against Locator, getByRole and strict mode. How the two most-used frameworks find elements, what each gives XPath users, and what to expect when migrating.

seleniumplaywrightmigrationcomparison

Two models

Selenium returns elements. findElement runs the locator immediately, gives you a WebElement handle, and that handle points at a specific DOM node. If the page re-renders, the handle goes stale.

Playwright returns locators. page.locator(...) is a description. Nothing runs until you act, and each action re-runs the description against the live DOM, auto-waiting for the element to be actionable.

This difference shapes everything else.

Locator types

NeedSelenium 4Playwright
Test idBy.cssSelector("[data-testid=save]")getByTestId('save')
Role and nameXPath: //button[normalize-space()='Save']getByRole('button', { name: 'Save' })
LabelXPath: label patternsgetByLabel('Email')
TextXPath: contains(., 'x')getByText('x')
CSSBy.cssSelectorlocator('css=...') or plain string
XPathBy.xpathlocator('xpath=...') or leading //
SpatialRelativeLocator.with(...).below(...)none (use filter or structure)
Inside elementelement.findElement(By.xpath(".//x"))locator.locator('...')
FrameswitchTo().frame()frameLocator()
Shadow DOMgetShadowRoot() then CSSCSS pierces automatically

What Selenium 4 added

  • Relative locators: above, below, toLeftOf, toRightOf, near. They use rendered bounding boxes, so they express “the input under the Email label” without knowing the DOM structure. They can be surprising when layouts wrap responsively; XPath axes are deterministic where relative locators are geometric.
  • getShadowRoot() on WebElement, giving a search context inside open shadow roots (CSS only).
  • Selenium Manager for driver management, and BiDi for events and network. Neither changes locators.

What Playwright gives XPath users

  • The same XPath 1.0 engine; any locator from this site works with the xpath= prefix.
  • Strict mode: an action on a locator that matches two elements fails loudly. In Selenium findElement silently picks the first.
  • Chaining across engines: XPath to a container, CSS into its shadow root.
  • Auto-waiting built in, so no WebDriverWait boilerplate around every XPath.
  • filter({ hasText }): case-insensitive, whitespace-tolerant text filtering without normalize-space() and translate() gymnastics.
  • Tooling: codegen, trace viewer and the inspector’s locator picker.

Migrating XPath from Selenium to Playwright

Most locators port with zero changes:

driver.findElement(By.xpath("//tr[td[normalize-space()='USB-C Hub']]//button[@aria-label='Remove']")).click();
await page.locator("//tr[td[normalize-space()='USB-C Hub']]//button[@aria-label='Remove']").click();

Things that need attention:

  1. Multiple matches. Selenium clicked the first; Playwright throws. Decide whether the duplicate was a bug (anchor the locator) or intended (.first()).
  2. Expressions starting with (. Add the xpath= prefix; auto-detection only triggers on // and ...
  3. Hidden duplicates. Selenium threw ElementNotInteractable when the first match was hidden; Playwright waits for visibility and may pick up the same problem as a strict-mode violation or a timeout. Anchor to the visible container.
  4. Frame switching. Replace switchTo().frame(...) and defaultContent() pairs with frameLocator(...) chains. XPath inside the frame is unchanged.
  5. Waits. Delete explicit waits around locators; Playwright auto-waits. Keep waits for non-element conditions.
  6. Shadow DOM. Any Selenium getShadowRoot() code simplifies to a CSS locator that pierces.

Then, gradually, replace XPath for visible controls with getByRole and getByLabel where it improves readability. Keep XPath for tables, rows and structural queries.

Migrating the other way

Playwright suites moving to Selenium (rare, but it happens for Java shops or grid requirements) lose role locators. XPath becomes the replacement for getByRole, getByLabel and getByText:

PlaywrightSelenium XPath
getByRole('button', { name: 'Save' })//button[normalize-space()='Save'] | //*[@role='button'][normalize-space()='Save']
getByLabel('Email')//input[@id=//label[normalize-space()='Email']/@for]
getByText('Welcome')//*[text()[contains(normalize-space(), 'Welcome')]]
getByPlaceholder('Search')//input[@placeholder='Search']
getByTestId('x')//*[@data-testid='x']

Which should a new project pick?

Not a locator question. Pick Playwright for a new JavaScript, Python or .NET project unless you need Selenium Grid infrastructure, a language Playwright does not support, or Appium alignment. In both, XPath skills carry over unchanged.