Dev Tools
Client-sidefiles never upload

CSS / XPath selector tester

Paste HTML and a selector, see which elements match and preview each outerHTML.

Matches
Selector syntax error: DOMParser not available
β€ΊHow to use
  1. Paste an HTML snippet on the left, type a selector in the middle, the right side shows matched elements with outerHTML and a textContent preview.
  2. Toggle CSS / XPath mode β€” CSS uses querySelectorAll, XPath uses document.evaluate.
  3. Syntax errors surface as a red message (the browser's native error).
Tips
  • Trying to test Playwright's getByText? That's Playwright-specific, not standard CSS.
  • iframe contents are not parsed β€” only the top-level HTML.

Selector in depth: CSS vs XPath, stable selector patterns, why Playwright's getByRole wins

The painful part of E2E automation isn't writing steps β€” it's selectors that break every other day. Here are the patterns that worked across years of writing, debugging, and migrating Selenium / Cypress / Playwright suites, plus why Playwright pushes role-based locators.

CSS vs XPath: choose by use case

CSS wins when: - The element has clean class / id / attribute hooks - You want speed (30–50% faster than XPath in every browser engine) - Teammates already read CSS XPath wins when: - You need to match by text content: //button[text()="Submit"] (CSS only got similar power with :has(), Safari 16+) - You need to walk to a parent: //input/../label (CSS literally cannot) - You have nested conditions: "div that has class A but no descendant with class B" My rule of thumb: default to CSS, save XPath for the two parent-or-text cases.

Three principles for selectors that survive 6 months

1. Always prefer data-testid: decoupled from product visuals, immune to UI redesigns. Cypress, Playwright, Selenium all recognize them. ``html ` Selector: [data-testid="submit-payment"] β€” survives even a swap to an icon button. 2. Never nth-child or nth-of-type: change the list order and the selector dies; even backend re-sorting breaks it. 3. Prefer semantic classes over styling classes: .btn-primary-rounded-lg-hover (Tailwind-style) changes constantly; .submit-button (semantic) is stable. Or just always go data-testid. 4. Don't trust IDs blindly: frameworks generate id="react-aria-:r1:"`-style IDs that change on hydration. Use them when stable, fallback otherwise.

Why Playwright's getByRole is a game-changer

Traditional selectors say: "find the DOM at this position" β€” structure changes, selector breaks. Playwright's getByRole says: "find an accessibility-tree node with role=button and an accessible name containing 'Submit'". ``ts page.getByRole('button', { name: 'Submit' }) ` Benefits: - Independent of visual reordering: move the button left to right β€” the test still passes - Aligned with a11y: a broken test means a broken accessibility tree, so you catch UX bugs free - Cross-framework: roles are consistent across React / Vue / Angular Nice side effect: it forces you to use

Timing in dynamic content

Worst antipattern: waitForTimeout(2000) β€” flaky on slow networks, wasteful on fast ones. Correct: - Playwright: await expect(page.getByText('Success')).toBeVisible() β€” built-in auto-wait, polls for 5s by default - Selenium: WebDriverWait(driver, 10).until(EC.visibility_of_element_located(...)) - Robot Framework: Wait Until Element Is Visible selector timeout=10s Advanced β€” wait for the actual network response: ``ts await page.waitForResponse(resp => resp.url().includes('/api/payment') && resp.status() === 200 ); await expect(page.getByText('Payment success')).toBeVisible(); `` More precise than waiting for an element β€” sometimes the element renders from cache before the real API call completes.

Cross-browser gotchas

- :has() selector: Safari 16+, Chrome 105+, Firefox 121+ only. Works in your dev env, breaks for an old Firefox user - text= Playwright pseudo-locator: Playwright-specific syntax β€” paste into a browser DevTools console and it won't work - /text()="..." in XPath: Safari implements some XPath functions incompletely β€” verify with the [selector tester tool](/en/tools/selector) before shipping - Shadow DOM: regular CSS selectors cannot pierce shadow boundaries; Playwright's locators do automatically, but the same string in DevTools won't

Before writing a test, paste your HTML and selector into the [selector tester](/en/tools/selector) and verify it matches exactly what you expect. Toggle CSS / XPath to see which gives a cleaner expression.

Related tools