test(browser): tap a role-based control end to end

every control on the page is an <li role="option">, the shape the
replay ui gives its step rows, and the spec carries no action of its
own: the property firing is the evidence the default enumeration offered
a tap on one.
This commit is contained in:
pj committed 2026-08-15 12:46:13 +05:30
1 parent a460eba3db
commit f3096d195b
3 files changed
+57

No files matched your search

+12
View File
@@ -65,6 +65,18 @@ func TestBrowserShadowDOMIsReachable(t *testing.T) {
}
}
// TestBrowserAriaRoleIsTappable drives a page whose only controls are
// <li role="option"> rows, the shape the replay UI gives its step list. The
// tappable set covered role="button" and nothing else, so a spec had to
// hand-write an action to reach a row; with the standard interactive roles
// covered, the default tap enumeration finds them and the counter moves.
func TestBrowserAriaRoleIsTappable(t *testing.T) {
violations := runFixture(t, "aria-roles")
if !slices.Contains(violations, "noRowWasEverSelected") {
t.Fatalf("no role=\"option\" row was ever tapped; violations=%v", violations)
}
}
// runFixture serves the named testdata case over an in-process file server,
// drives it through headless Chrome with a fixed seed and a bounded step count,
// and returns every property name that was ever reported violated.
+29
View File
@@ -0,0 +1,29 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>aria roles</title>
</head>
<body>
<!-- Shaped like the replay UI's step list: the rows are the only controls on
the page, and they are plain <li> elements carrying role="option". A tap
target set that reads element names and role="button" alone sees nothing
here at all. -->
<ol id="steps" role="listbox" aria-label="Steps">
<li id="step-1" role="option">step 1</li>
<li id="step-2" role="option">step 2</li>
<li id="step-3" role="option">step 3</li>
</ol>
<div id="selected">0</div>
<script>
let selections = 0;
const display = document.getElementById("selected");
for (const row of document.querySelectorAll('[role="option"]')) {
row.addEventListener("click", function () {
selections += 1;
display.textContent = String(selections);
});
}
</script>
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
import { always, extract, taps } from "@sanderling/spec";
const selections = extract((s) => {
const el = s.ax.find({ id: "selected" });
return el ? parseInt(el.text, 10) || 0 : 0;
}).named("selections");
// Every control on the page is an <li role="option">, the shape the replay UI
// gives its own step rows. Nothing else can move this counter, so the spec
// carries no action of its own: this property firing IS the evidence that the
// default enumeration offered a tap on a role-based control.
const noRowWasEverSelected = always(() => selections.current === 0);
export const properties = { noRowWasEverSelected };
export const actionsRoot = taps;