Problem
#1603 shipped 17 matchers. Eight more from Playwright's set have no equivalent,
and most of them already have their answer sitting on Locator:
Object.keys(matchers) // no toHaveCSS, toHaveId, toHaveJSProperty, toHaveRole,
// toHaveAccessibleName, toHaveAccessibleDescription,
// toHaveValues, toBeInViewport| matcher | what it needs | already present? |
|---|---|---|
toHaveRole | the computed role | locator.ariaRole() |
toHaveAccessibleName | the accessible name | locator.accessibleName() |
toHaveAccessibleDescription | aria-describedby resolution | partly, in src/aria/roles.ts |
toHaveCSS | a resolved property | getComputedStyle, cascade-aware since #1597 |
toHaveId | the id attribute | trivial |
toHaveJSProperty | a property off the element | trivial |
toHaveValues | a multi-select's selected values | needs selectOption (#1608) to be worth having |
toBeInViewport | a bounding box against the viewport | not answerable — no layout |
Why it matters
toHaveRole and toHaveAccessibleName are the two that pull their weight. They
assert the thing getByRole queries on, which is what makes a failing role query
diagnosable: getByRole('button', { name: 'Save' }) finding nothing tells you
nothing about why, and the usual next question — "what role does this element
actually have, and what name does it compute?" — currently has to be asked through
locator.ariaRole() and a plain expect, losing the retry.
toHaveCSS is the one people reach for after #1597 and #1600 made the cascade
real. Asserting a colour or a display through getComputedStyle plus a bare
expect samples once, so it has the flakiness #1603 exists to remove.
toHaveId and toHaveJSProperty are conveniences over toHaveAttribute and a
read; cheap, and their absence is noticed because Playwright examples use them.
Proposed scope
toHaveRole(role),toHaveAccessibleName(name, { exact }),toHaveAccessibleDescription(description, { exact })— over the existingLocatorreaders, with the same string-or-RegExp comparison the other text matchers use, so "contains" keeps one definition.toHaveCSS(name, value)— resolve throughgetComputedStyle. Values must be compared as the engine reports them, so a test asserting'red'against a computed'rgb(255, 0, 0)'has to fail rather than be normalised into passing; say so in the failure message, since that is the mistake everyone makes first.toHaveId(id),toHaveJSProperty(name, value).toHaveValues(values)— deferred untilselectOption(#1608) lands, since there is no way to set a multi-select's state until then.toBeInViewportshould not be added. There is no layout, so the box has no position —xandyare always the origin, asserted intest/box-metrics-cascade.test.ts. It could only ever answer "yes", which is the silent-pass failure #1602 was about. Better absent.
Acceptance
- Each new matcher retries, proven by one test per matcher where the value only becomes correct on a later tick.
.notwaits for the opposite, as the existing ones do.toHaveCSS('color', 'red')fails against a computedrgb(255, 0, 0), and the message shows both, so the mismatch is obvious rather than mysterious.toHaveRoleagrees withgetByRoleon the same element — asserted together, so a query and an assertion about the same role cannot disagree.toBeInViewportis absent, and a test records why, so it is not added later by someone working from Playwright's list.
Follows #1603.