test(chrome): the handle and the enumeration agree on editable too

The helper compared clickable alone, so the inherited-contenteditable bug
was caught by unit test only and never in a real browser.
This commit is contained in:
pj committed 2026-08-18 00:15:55 +05:30
1 parent 93580e076d
commit 04327e63b6
3 files changed
+42 -16

No files matched your search

+32 -14
View File
@@ -51,10 +51,14 @@ type elementFacts struct {
editable bool editable bool
scrollable bool scrollable bool
hintText string hintText string
// handleClickable is the clickability of the ax element a spec reaches // handleClickable and handleEditable are the same facts on the ax element a
// through state.ax.find, a third place the fact is computed and the one that // spec reaches through state.ax.find, a third place they are computed and the
// answered a hardcoded true while the other two resolved a selector. // one that has twice been the odd one out: clickable answered a hardcoded
// true while the other two resolved a selector, and editable read the
// INHERITED isContentEditable, which makes every span inside a contenteditable
// container typeable.
handleClickable bool handleClickable bool
handleEditable bool
positiveBounds bool positiveBounds bool
} }
@@ -170,6 +174,7 @@ func factsFromWebRuntime(
Scrollable bool `json:"scrollable"` Scrollable bool `json:"scrollable"`
HintText string `json:"hintText"` HintText string `json:"hintText"`
HandleClickable bool `json:"handleClickable"` HandleClickable bool `json:"handleClickable"`
HandleEditable bool `json:"handleEditable"`
Width int `json:"width"` Width int `json:"width"`
Height int `json:"height"` Height int `json:"height"`
} }
@@ -188,6 +193,7 @@ func factsFromWebRuntime(
scrollable: item.Scrollable, scrollable: item.Scrollable,
hintText: item.HintText, hintText: item.HintText,
handleClickable: item.HandleClickable, handleClickable: item.HandleClickable,
handleEditable: item.HandleEditable,
positiveBounds: hasPositiveBounds(item.Width, item.Height), positiveBounds: hasPositiveBounds(item.Width, item.Height),
}, },
}) })
@@ -258,26 +264,38 @@ func requireBothPolarities(t *testing.T, rows []factRow) {
// requireTheHandleAgreesWithTheEnumeration compares the V8 host against itself. // requireTheHandleAgreesWithTheEnumeration compares the V8 host against itself.
// An element a spec reaches through state.ax and the same element in the // An element a spec reaches through state.ax and the same element in the
// enumeration must be clickable to the same degree, or a spec taps a container // enumeration must be clickable, and typeable, to the same degree, or a spec
// the picker calls inert. The handle resolved the fact by element.matches over // taps a container the picker calls inert and types into a box the picker calls
// the tappable selector while the enumeration resolved it by membership of the // read-only. The handle resolves each fact by element.matches over a selector
// set that selector queried, and this is where those two answers are held // while the enumeration resolves it by membership of the set that selector
// together over a real page: an [onclick] attribute, an onclick property that is // queried, and this is where the two answers are held together over a real page:
// not one, elements inside a shadow root. // an [onclick] attribute, an onclick property that is not one, a <span> whose
// contenteditable is inherited from its container, elements inside a shadow root.
func requireTheHandleAgreesWithTheEnumeration(t *testing.T, rows []factRow) { func requireTheHandleAgreesWithTheEnumeration(t *testing.T, rows []factRow) {
t.Helper() t.Helper()
for _, row := range rows { for _, row := range rows {
if row.facts.handleClickable != row.facts.clickable { for _, fact := range []struct {
name string
handle bool
enumeration bool
}{
{"clickable", row.facts.handleClickable, row.facts.clickable},
{"editable", row.facts.handleEditable, row.facts.editable},
} {
if fact.handle != fact.enumeration {
t.Errorf( t.Errorf(
"%q (<%s>): the ax handle reports clickable=%v, the enumeration "+ "%q (<%s>): the ax handle reports %s=%v, the enumeration reports "+
"reports clickable=%v", "%s=%v",
row.id, row.id,
row.facts.tag, row.facts.tag,
row.facts.handleClickable, fact.name,
row.facts.clickable, fact.handle,
fact.name,
fact.enumeration,
) )
} }
} }
}
} }
// compareEnumeratedElements is the check that the two producers walk the same // compareEnumeratedElements is the check that the two producers walk the same
+4 -1
View File
@@ -63,7 +63,10 @@
<input id="hint-name" type="text" name="reference-field" /> <input id="hint-name" type="text" name="reference-field" />
<input id="agree" type="checkbox" placeholder="not a hint" /> <input id="agree" type="checkbox" placeholder="not a hint" />
<textarea id="notes"></textarea> <textarea id="notes"></textarea>
<div id="bio" contenteditable="true">bio</div> <!-- contenteditable is INHERITED, so #bio-word answers isContentEditable
true while matching neither producer's editable selector itself. It is
the element the ax handle and the enumeration disagreed on. -->
<div id="bio" contenteditable="true">bio <span id="bio-word">word</span></div>
<div id="menu" role="button">menu</div> <div id="menu" role="button">menu</div>
<!-- One element per ARIA role both producers resolve as clickable. A role <!-- One element per ARIA role both producers resolve as clickable. A role
covered on one side only makes that control reachable for one host, covered on one side only makes that control reachable for one host,
+6 -1
View File
@@ -17,7 +17,11 @@ const { collectTargets, targetElements, buildAx } = __testing__;
// reaches them and the way internal/verifier/llm.go handleLabel reads them, so // reaches them and the way internal/verifier/llm.go handleLabel reads them, so
// the comparison runs over what the model is actually shown rather than over a // the comparison runs over what the model is actually shown rather than over a
// recomputation of it. // recomputation of it.
type AxHandle = { attrs?: Record<string, string>; clickable?: boolean }; type AxHandle = {
attrs?: Record<string, string>;
clickable?: boolean;
editable?: boolean;
};
type Ax = { find(selector: unknown): AxHandle | undefined }; type Ax = { find(selector: unknown): AxHandle | undefined };
function handleOf(ax: Ax, id: string): AxHandle | undefined { function handleOf(ax: Ax, id: string): AxHandle | undefined {
@@ -45,6 +49,7 @@ function domFacts(): unknown[] {
scrollable: target.scrollable, scrollable: target.scrollable,
hintText: handle?.attrs?.hintText ?? "", hintText: handle?.attrs?.hintText ?? "",
handleClickable: handle?.clickable ?? false, handleClickable: handle?.clickable ?? false,
handleEditable: handle?.editable ?? false,
width: target.width ?? 0, width: target.width ?? 0,
height: target.height ?? 0, height: target.height ?? 0,
}; };