dom-to-locator
v1.0.0
Published
Generate Playwright locators from live DOM elements
Readme
dom-to-locator
Generate Playwright locator strings from live DOM elements.
What it does
Given any DOM element, produces the best Playwright locator string (e.g. getByRole('button', { name: 'Submit' })) using the same scoring and uniqueness algorithm that powers Playwright's Inspector, Codegen, and recorder tools.
Strategies are tried in priority order: test ID, ARIA role + name, placeholder, label, alt text, text content, title, CSS ID, tag name — falling back to nth-index or full CSS path when nothing else is unique.
Install
pnpm add dom-to-locatorUsage
import { generateLocator, generateLocators, asLocator } from "dom-to-locator";
// Best locator for an element
generateLocator(element);
// => "getByRole('button', { name: 'Submit' })"
// Multiple ranked variants
generateLocators(element);
// => ["getByTestId('submit-btn')", "getByRole('button', { name: 'Submit' })"]
// Custom test ID attribute
generateLocator(element, { testIdAttributeName: "data-cy" });
// Python / Java / C#
generateLocator(element, { language: "python" });
// => 'get_by_role("button", name="Submit")'
// Scoped to a subtree
generateLocator(element, { root: containerElement });
// Format an internal selector string (no DOM needed)
asLocator("javascript", 'internal:role=button[name="Submit"i]');
// => "getByRole('button', { name: 'Submit' })"API
generateLocator(element, options?): string
Returns the single best Playwright locator for the given element.
generateLocators(element, options?): string[]
Returns multiple locator variants, ranked from best to worst.
generateInternalSelector(element, options?): string
Returns the raw internal selector string before language-specific formatting.
asLocator(language, selector): string
Converts an internal selector string to a language-specific locator. No DOM required.
asLocators(language, selector): string[]
Converts an internal selector string to multiple language-specific locator variants.
Options
| Option | Type | Default | Description |
| --------------------- | --------------------- | --------------- | ----------------------------------------------------------------- |
| testIdAttributeName | string | 'data-testid' | HTML attribute used for test IDs |
| language | Language | 'javascript' | Target language: 'javascript', 'python', 'java', 'csharp' |
| root | Element \| Document | document | Scope locator generation to a subtree |
How it works
The pipeline has two stages:
1. DOM element to internal selector (browser-side, requires live DOM)
The selectorGenerator inspects the target element and builds candidate selectors using every available strategy (ARIA role, test ID, text, CSS, etc.). Each candidate is scored — lower is better — and tested for uniqueness by querying the DOM. If a selector matches exactly one element (the target), it wins. When no single selector is unique, the generator composes nested selectors (parent >> child) or appends nth= as a last resort.
2. Internal selector to locator string (isomorphic, no DOM needed)
The locatorGenerators module parses the internal selector syntax (internal:role=button[name="Submit"i]) and routes it through a language-specific factory to emit idiomatic code (getByRole('button', { name: 'Submit' }) for JavaScript, get_by_role("button", name="Submit") for Python, etc.).
Scoring priority
| Score | Strategy | Locator |
| ---------- | ----------------------- | -------------------------------------- |
| 1 | Test ID | getByTestId(...) |
| 2 | Other data-test* attrs | locator('[data-test=...]') |
| 100 | ARIA role + name | getByRole('button', { name: '...' }) |
| 120 | Placeholder | getByPlaceholder(...) |
| 140 | Label | getByLabel(...) |
| 160 | Alt text | getByAltText(...) |
| 180 | Text content | getByText(...) |
| 200 | Title | getByTitle(...) |
| 500 | CSS ID | locator('#id') |
| 510 | ARIA role (no name) | getByRole('button') |
| 10,000 | nth-index | .nth(N) |
| 10,000,000 | CSS fallback | locator('div > span.class') |
Provenance
This package extracts and bundles the locator generation subsystem from Playwright (Apache-2.0). The original code lives across two layers:
packages/injected/src/selectorGenerator.ts+ supporting modules (roleUtils.ts,domUtils.ts,selectorUtils.ts,selectorEvaluator.ts,roleSelectorEngine.ts) — the DOM-side candidate generation and uniqueness testingpackages/playwright-core/src/utils/isomorphic/(locatorGenerators.ts,selectorParser.ts,cssParser.ts,cssTokenizer.ts,stringUtils.ts) — the isomorphic selector parsing and locator formatting
The main adaptation is host.ts, a 220-line shim that replaces Playwright's 1,800+ line InjectedScript class, registering only the selector engines that locator generation actually exercises.
License
BSD-3-Clause. Derived from Playwright (Apache-2.0).
