Skip to content

Locating by id

Playwright has no getById: an id is a CSS selector away, page.locator("#login"). That works until an id contains characters CSS gives a meaning to. #settings.panel means “id settings and class panel”, #form:user is a selector that does not parse, and #1st is invalid because a CSS identifier cannot start with a digit. Ids like these are common in generated markup. getById matches the id attribute itself, so any id works as written.

<section id="settings.panel">…</section>
<input id="form:user" />
<li id="items[0]">…</li>
<p id="#literal-hash">…</p>
<p id="1st">…</p>
registry.add("section@settings").getById("settings.panel");
registry.add("form.input@user").getById("form:user");
registry.add("list.item@first").getById("items[0]");
registry.add("paragraph@hash").getById("#literal-hash"); // the "#" is part of the id
registry.add("paragraph@intro").getById("1st");

Each of these finds its element, where page.locator("#settings.panel") finds nothing.

A string is compared with the id attribute verbatim and case-sensitively. There are no prefixes: getById("#login") looks for id="#login". The selector is an attribute selector, with quotes, backslashes and line breaks in the id escaped for you:

getById("settings.panel") → [id="settings.panel"]
getById('say "hi"') → [id="say \"hi\""]

A RegExp is evaluated against each element’s id with its flags, through the attribute engine Playwright’s own getByTestId(RegExp) uses. An unanchored pattern matches part of an id; anchor it with ^ and $ to match a whole one. For buttons with the ids button.submit.ab12, button.submit.CD34 and other:

getById(…) matches
/^button\.submit\.[a-z0-9]{4}$/ button.submit.ab12
/^button\.submit\.[a-z0-9]{4}$/i both button.submit ids
/submit/ both button.submit ids
/^submit/ none

The selector is internal:attr=[id=/^button\.submit\.[a-z0-9]{4}$/i]. Use a RegExp for generated ids with a stable shape, or with nth, filter, count() or all() for every element whose id matches.

Every flag goes to Playwright unchanged, as with Playwright’s locators. g changes nothing about matching. y (sticky) anchors at the start, because Playwright tests each id from its first character: /foo/y behaves as /^foo/, and ^ says so more plainly.

An empty id throws when it is registered: an id attribute must have at least one character, and browsers disagree about what [id=""] matches.

getById requires a non-empty id for "main.form@user".

On a seed the message names the factory: createReusable.getById requires a non-empty id. A RegExp is never rejected or rewritten.

getById follows Playwright’s rules like any locator: an action needs exactly one match, and nth, filter, count() and all() work as usual. It finds elements in open shadow roots, not in closed ones, and inside a frame only when its path runs through a frame segment.

A CSS id selector is case-insensitive in quirks mode (a page without a doctype), so there #SETTINGS matches id="settings". getById is case-sensitive in every document mode: getById("settings") matches id="settings", and getById("SETTINGS") does not.