Free QA resource

XPath vs CSS Selectors

The quick reference every automation engineer keeps open — every common locator in CSS and XPath, side by side. Works for Selenium and Playwright.

🧭 Rule of thumb: CSS for speed & readability · XPath when you need text or to walk up the DOM.

Get the free PDF 📄

Enter your email and the printable cheat sheet is yours instantly — no spam, unsubscribe anytime.

Basics & attributes

GoalCSSXPath
Any element*//*
By taginput//input
By id#username//*[@id='username']
By class (in list).btn//*[contains(@class,'btn')]
Multiple classes.btn.primary//*[contains(@class,'btn') and contains(@class,'primary')]
By attribute[type='text']//*[@type='text']
Tag + attributeinput[name='email']//input[@name='email']
data-testid (preferred hook)[data-testid='submit']//*[@data-testid='submit']
Attribute starts-with[id^='user']//*[starts-with(@id,'user')]
Attribute contains[class*='card']//*[contains(@class,'card')]
Attribute ends-with[id$='_btn']no ends-with in XPath 1.0

Hierarchy & relationships

GoalCSSXPath
Descendant (any depth)div input//div//input
Direct childul > li//ul/li
Next sibling (adjacent)label + input//label/following-sibling::input[1]
Any following siblinglabel ~ input//label/following-sibling::input
Parentnot supported//span/parent::div · //span/..
Ancestornot supported//td/ancestor::table
Group / ORh1, .title//h1 | //*[contains(@class,'title')]

Text & position

GoalCSSXPath
Exact textnot supported//button[text()='Submit']
Contains textnot supported//a[contains(text(),'Login')]
Normalized text (trims spaces)not supported//h1[normalize-space()='Title']
First matchli:first-child(//input)[1] · //ul/li[1]
Nth elementli:nth-child(2)//li[2]
Last elementli:last-child//li[last()]
Negationinput:not([disabled])//input[not(@disabled)]

Pro tips

  • Prefer CSS for speed and readability. Reach for XPath only when you need text() matching or to traverse upward (parent / ancestor) — CSS can't do either.
  • Lock onto stable hooks: id, name, and especially data-testid. Avoid absolute paths like /html/body/div[2]/… — they shatter on any layout change.
  • CSS .btn matches an element whose class list contains 'btn'; XPath [@class='btn'] is an exact match. Use contains(@class,'btn') in XPath to mimic CSS.
  • Selenium uses XPath 1.0 — there's no ends-with(). Use starts-with() or contains() instead.
  • On modern Playwright / Testing-Library, prefer getByRole / getByText / getByTestId over raw selectors — more resilient and accessible by default.

Get the free PDF 📄

Enter your email and the printable cheat sheet is yours instantly — no spam, unsubscribe anytime.

Going for an automation interview?

Selectors are just the start. Get the full Selenium, Playwright & Python kits — Q&A, coding, and résumé templates.

🔥 See the SDET Kits