Summary
BrowserPage exposes selector-string actions (click, type, hover, focus, waitForSelector) but has no locator layer. None of Playwright's locator entry points exist, and neither do the older element-handle helpers, so there is no way to build a reusable, re-queried reference to an element.
Locators are the primary way modern Playwright tests are written, so their absence is the largest single gap between this Browser API and Playwright.
Reproduction
import { Browser } from 'very-happy-dom'
const page = new Browser().newPage()
console.log(typeof page.locator) // undefined
console.log(typeof page.getByRole) // undefined
console.log(typeof page.getByText) // undefined
console.log(typeof page.getByLabel) // undefined
console.log(typeof page.getByPlaceholder) // undefined
console.log(typeof page.getByAltText) // undefined
console.log(typeof page.getByTitle) // undefined
console.log(typeof page.getByTestId) // undefined
console.log(typeof page.frameLocator) // undefined
console.log(typeof page.$) // undefined
console.log(typeof page.$$) // undefined
console.log(typeof page.$eval) // undefined
console.log(typeof page.$$eval) // undefinedConfirmed against main at 5fae54d4 with Bun 1.4.3.
Current implementation
BrowserPage resolves a selector to an element inline on each call, via the frame's document.querySelector, and acts on it immediately. There is no object representing "the element this selector will match", so nothing can be stored, chained, filtered or re-queried.
Expected behavior
A Locator that resolves lazily on each use, rather than capturing an element at creation:
const saveButton = page.getByRole('button', { name: 'Save' })
await saveButton.click()Entry points:
| Method | Resolves by |
|---|---|
page.locator(selector) | CSS (and ideally Playwright's text= / >> forms) |
page.getByRole(role, options) | ARIA role, with name, exact, checked, disabled, selected |
page.getByText(text, options) | text content, string or RegExp, with exact |
page.getByLabel(text, options) | associated <label>, including aria-label and aria-labelledby |
page.getByPlaceholder(text) | placeholder attribute |
page.getByAltText(text) | alt attribute |
page.getByTitle(text) | title attribute |
page.getByTestId(id) | the configured test-id attribute, data-testid by default |
Locator methods worth covering first: click, hover, textContent, innerText, inputValue, getAttribute, count, first, last, nth, all, and the isVisible/isChecked/isEnabled predicates. Chaining (parent.locator('child')) and filter({ hasText }) are what make locators composable, so they matter more than breadth of individual methods.
getByRole needs role resolution and accessible-name computation, which is the substantial piece — implicit roles from tag names plus explicit role attributes would already cover most test usage.
Note that locator actions in Playwright auto-wait for actionability. Matching that fully is a larger change; resolving at call time and throwing a clear error when nothing matches would be a reasonable first step.
Related: #1590 (frame windows are stubs), which affects anything a locator needs to evaluate in page context. The element-handle helpers $, $$, $eval and $$eval are listed above for completeness, but locators supersede them and are the better target.