diff --git a/test/browser/browser_test.go b/test/browser/browser_test.go
index efc62d9..3cae9cd 100644
--- a/test/browser/browser_test.go
+++ b/test/browser/browser_test.go
@@ -65,6 +65,18 @@ func TestBrowserShadowDOMIsReachable(t *testing.T) {
}
}
+// TestBrowserAriaRoleIsTappable drives a page whose only controls are
+//
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.
diff --git a/test/browser/testdata/aria-roles/index.html b/test/browser/testdata/aria-roles/index.html
new file mode 100644
index 0000000..40d9696
--- /dev/null
+++ b/test/browser/testdata/aria-roles/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+ aria roles
+
+
+
+
+ - step 1
+ - step 2
+ - step 3
+
+ 0
+
+
+
diff --git a/test/browser/testdata/aria-roles/spec.ts b/test/browser/testdata/aria-roles/spec.ts
new file mode 100644
index 0000000..066edd6
--- /dev/null
+++ b/test/browser/testdata/aria-roles/spec.ts
@@ -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 , 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;