Browser testing
Use this page to choose between Sel (Selenium) and Play (Playwright) and to write selectors that work on both.
Selenium or Playwright?
Section titled “Selenium or Playwright?”Sel (Selenium) | Play (Playwright) | |
|---|---|---|
| Browsers | Chrome (default), Firefox | Chromium only |
| Docker mode | Yes (agent.selenium.docker=true) | No |
| Waiting | Retries failed actions until the timeout | Waits until the element is ready, and retries every error |
| Shadow DOM | >>>, plus an automatic search of open shadow roots | >>>; css= and text= pierce open shadow roots |
| Iframes | Searched automatically | Searched automatically |
| Choose it when | You need Firefox or Docker | Chromium is enough |
Both return the same Browser object, and browser.locator(...) returns the same Locator.
Same script, two engines
Section titled “Same script, two engines”Only the first line changes.
local browser = Sel.start() -- or Play.start()browser.navigate("https://example.com/login")browser.locator("id=username").fill("tester")browser.locator("role=button[name=\"Sign in\"]").click()print(browser.locator("css=h1").text())browser.close()Pass true to start Firefox: Sel.start(true). Call browser.close() when you are done, or Sel.close() / Play.close() to close every browser the library opened.
Selectors
Section titled “Selectors”A selector is a prefix and a value, such as id=username. Without a prefix it is XPath if it starts with /, ./ or (, and CSS otherwise.
| Selector | Example | Selenium | Playwright |
|---|---|---|---|
xpath= | xpath=//button[@type='submit'] | ✓ | ✓ (does not pierce shadow DOM) |
css= | css=form .primary | ✓ | ✓ |
id= | id=username | ✓ | ✓ |
name= | name=email | ✓ | ✓ |
class= | class=btn | ✓ one class | ✓ several classes |
tag= | tag=h1 | ✓ | ✓ |
linkText= | linkText=Sign in | ✓ exact text | ✓ exact text |
text= | text=Save | ✓ | ✓ |
role= | role=button[name="Save"] | ✓ | ✓ |
label= | label=Email | ✓ | ✓ |
placeholder= | placeholder=Email | ✓ | ✓ |
alt= | alt=Logo | ✓ | ✓ |
title= | title=Help | ✓ | ✓ |
testid= | testid=save (or testId=) | ✓ | ✓ |
Chain >> | css=form >> css=input | ✓ | ✓ |
Shadow DOM >>> | css=my-app >>> css=button | ✓ | ✓ |
| Iframe | no prefix needed | ✓ | ✓ |
- Text matching: unquoted text is a case-insensitive substring. Quoted text (
text="Log in") is an exact match. - Chains: each step is a selector of its own and is searched inside the elements the previous step found.
>>>is short for>> shadow >>. - Shadow DOM: only open shadow roots can be entered. On Selenium, elements inside a shadow root are clicked with JavaScript, which skips the visibility and overlay checks.
- Iframes: you never switch frames. A selector not found in the main page is searched in every iframe, and the first match wins.
Waits, timeouts and retries
Section titled “Waits, timeouts and retries”Every action is retried every 100 ms until it succeeds or the timeout passes. The timeout comes from the project settings (10000 ms for new projects). Change it for one browser with browser.setTimeOut(ms).
When an action still fails, the script stops with an error and saves a screenshot and the page source. Call browser.stopOnFailure(false) to continue instead. Use locator.wait() or locator.exists() to wait for a condition, not sleep.
Name selectors in locators.yaml
Section titled “Name selectors in locators.yaml”Start a selector with ? (for example browser.locator("?Email")) to look it up in the project’s locators.yaml. The file maps the page and the action to a list of candidate selectors, and the first one that exists wins. See Projects.