test(chrome): compare the secure fact across both producers

it is the fourth fact the dump and the web runtime derive independently,
and the one that decides whether a typed value is written into the
shared record. three-valued, so the fixture guard requires all three
states rather than both polarities.
This commit is contained in:
pj committed 2026-08-18 20:14:07 +05:30
1 parent 6aecccd2d6
commit 9a920444b3
4 files changed
+67

No files matched your search

@@ -10,6 +10,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"slices" "slices"
"strconv"
"testing" "testing"
"time" "time"
@@ -51,6 +52,11 @@ type elementFacts struct {
editable bool editable bool
scrollable bool scrollable bool
hintText string hintText string
// secure is three-valued: "true", "false", or "" where the producer states
// nothing, which android states for every element. It decides whether a
// typed value may be written into the shared record, so the two producers
// disagreeing about it writes a credential into a run's trace.
secure string
// handleClickable and handleEditable are the same facts on the ax element a // handleClickable and handleEditable are the same facts on the ax element a
// spec reaches through state.ax.find, a third place they are computed and the // spec reaches through state.ax.find, a third place they are computed and the
// one that has twice been the odd one out: clickable answered a hardcoded // one that has twice been the odd one out: clickable answered a hardcoded
@@ -100,6 +106,7 @@ func TestHierarchy_DerivesTheSameFactsAsTheWebRuntime(t *testing.T) {
requireEveryElementNamed(t, "the hierarchy dump", fromDump) requireEveryElementNamed(t, "the hierarchy dump", fromDump)
requireEveryElementNamed(t, "the web runtime", fromWebRuntime) requireEveryElementNamed(t, "the web runtime", fromWebRuntime)
requireBothPolarities(t, fromWebRuntime) requireBothPolarities(t, fromWebRuntime)
requireEverySecureState(t, fromWebRuntime)
requireTheHandleAgreesWithTheEnumeration(t, fromWebRuntime) requireTheHandleAgreesWithTheEnumeration(t, fromWebRuntime)
compareEnumeratedElements(t, fromDump, fromWebRuntime) compareEnumeratedElements(t, fromDump, fromWebRuntime)
compareDerivedFacts(t, fromDump, fromWebRuntime) compareDerivedFacts(t, fromDump, fromWebRuntime)
@@ -127,6 +134,7 @@ func factsFromHierarchyDump(t *testing.T, dump string) []factRow {
editable: element.Editable, editable: element.Editable,
scrollable: element.Attributes["scrollable"] == "true", scrollable: element.Attributes["scrollable"] == "true",
hintText: element.Attributes["hintText"], hintText: element.Attributes["hintText"],
secure: secureFromDump(element),
positiveBounds: hasPositiveBounds( positiveBounds: hasPositiveBounds(
element.Bounds.Width(), element.Bounds.Width(),
element.Bounds.Height(), element.Bounds.Height(),
@@ -175,6 +183,7 @@ func factsFromWebRuntime(
HintText string `json:"hintText"` HintText string `json:"hintText"`
HandleClickable bool `json:"handleClickable"` HandleClickable bool `json:"handleClickable"`
HandleEditable bool `json:"handleEditable"` HandleEditable bool `json:"handleEditable"`
Secure *bool `json:"secure"`
Width int `json:"width"` Width int `json:"width"`
Height int `json:"height"` Height int `json:"height"`
} }
@@ -194,6 +203,7 @@ func factsFromWebRuntime(
hintText: item.HintText, hintText: item.HintText,
handleClickable: item.HandleClickable, handleClickable: item.HandleClickable,
handleEditable: item.HandleEditable, handleEditable: item.HandleEditable,
secure: secureFromWebRuntime(item.Secure),
positiveBounds: hasPositiveBounds(item.Width, item.Height), positiveBounds: hasPositiveBounds(item.Width, item.Height),
}, },
}) })
@@ -201,6 +211,24 @@ func factsFromWebRuntime(
return rows return rows
} }
// secureFromDump and secureFromWebRuntime read the same three-valued fact off
// the two producers. The dump leaves the field out entirely for an element it
// states nothing about, the web runtime reports null for it, and both mean the
// same thing: unknown, not "not a secure entry".
func secureFromDump(element *hierarchy.Element) string {
if !element.SecureReported() {
return ""
}
return strconv.FormatBool(element.Secure)
}
func secureFromWebRuntime(secure *bool) string {
if secure == nil {
return ""
}
return strconv.FormatBool(*secure)
}
// hasPositiveBounds is the positiveBounds fact of pkg/spec/src/targets.ts, // hasPositiveBounds is the positiveBounds fact of pkg/spec/src/targets.ts,
// applied to both producers so the geometry comparison cannot drift from the // applied to both producers so the geometry comparison cannot drift from the
// rule it stands in for. // rule it stands in for.
@@ -262,6 +290,27 @@ func requireBothPolarities(t *testing.T, rows []factRow) {
} }
} }
// requireEverySecureState is requireBothPolarities for the one fact that is not
// a boolean. A fixture holding no password entry would compare "false" against
// "false" over the whole page and pass while proving nothing about the state
// that decides whether a typed value may be written down.
func requireEverySecureState(t *testing.T, rows []factRow) {
t.Helper()
seen := map[string]bool{}
for _, row := range rows {
seen[row.facts.secure] = true
}
for _, state := range []string{"true", "false", ""} {
if !seen[state] {
t.Errorf(
"the fixture no longer holds an element the web runtime reports "+
"secure=%q for, so comparing that state proves nothing",
state,
)
}
}
}
// 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, and typeable, to the same degree, or a spec // enumeration must be clickable, and typeable, to the same degree, or a spec
@@ -371,6 +420,17 @@ func compareDerivedFacts(t *testing.T, fromDump, fromWebRuntime []factRow) {
web.hintText, web.hintText,
) )
} }
if dump.secure != web.secure {
t.Errorf(
"%q (<%s>): the hierarchy dump derives secure=%q, the web runtime "+
"derives secure=%q; one host would write into the record a value "+
"the other redacts",
row.id,
dump.tag,
dump.secure,
web.secure,
)
}
for _, fact := range []struct { for _, fact := range []struct {
name string name string
dump bool dump bool
@@ -26,6 +26,7 @@
<button id="shadow-save">save</button> <button id="shadow-save">save</button>
<button id="shadow-cancel" disabled>cancel</button> <button id="shadow-cancel" disabled>cancel</button>
<input id="shadow-amount" type="text" value="10" placeholder="0.00" /> <input id="shadow-amount" type="text" value="10" placeholder="0.00" />
<input id="shadow-password" type="password" placeholder="Password" />
<div id="shadow-plain">plain</div> <div id="shadow-plain">plain</div>
</div> </div>
<div id="shadow-scroller"><div id="shadow-scroller-content"></div></div>`; <div id="shadow-scroller"><div id="shadow-scroller-content"></div></div>`;
+1
View File
@@ -61,6 +61,7 @@
<input id="hint-aria" type="text" aria-label="Search" placeholder="Type here" name="q" /> <input id="hint-aria" type="text" aria-label="Search" placeholder="Type here" name="q" />
<input id="hint-placeholder" type="text" placeholder="What's this for?" name="note-field" /> <input id="hint-placeholder" type="text" placeholder="What's this for?" name="note-field" />
<input id="hint-name" type="text" name="reference-field" /> <input id="hint-name" type="text" name="reference-field" />
<input id="password" type="password" placeholder="Password" />
<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>
<!-- contenteditable is INHERITED, so #bio-word answers isContentEditable <!-- contenteditable is INHERITED, so #bio-word answers isContentEditable
+5
View File
@@ -21,6 +21,7 @@ type AxHandle = {
attrs?: Record<string, string>; attrs?: Record<string, string>;
clickable?: boolean; clickable?: boolean;
editable?: boolean; editable?: boolean;
secure?: boolean | null;
}; };
type Ax = { find(selector: unknown): AxHandle | undefined }; type Ax = { find(selector: unknown): AxHandle | undefined };
@@ -50,6 +51,10 @@ function domFacts(): unknown[] {
hintText: handle?.attrs?.hintText ?? "", hintText: handle?.attrs?.hintText ?? "",
handleClickable: handle?.clickable ?? false, handleClickable: handle?.clickable ?? false,
handleEditable: handle?.editable ?? false, handleEditable: handle?.editable ?? false,
// Three-valued, so it stays null rather than collapsing onto false: the Go
// side compares it against a dump that leaves the fact out entirely for an
// element no producer states it for.
secure: handle?.secure ?? null,
width: target.width ?? 0, width: target.width ?? 0,
height: target.height ?? 0, height: target.height ?? 0,
}; };