mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
feat(hierarchy): an element reports whether it masks what is typed into it
ios reads it off SecureTextField, which the companion already sent and nothing read; web reads input[type=password]. Android cannot: the native tree mapper drops the password attribute before the sidecar sees it, so the fact is three-valued and null there rather than a false that would read as "not secure".
This commit is contained in:
1 parent
5f1f50c2fb
commit
b1e95739ad
11 files changed
+186
-5
No files matched your search
@@ -42,6 +42,7 @@ export interface KnownAttrSelectors {
|
||||
checked?: boolean;
|
||||
selected?: boolean;
|
||||
editable?: boolean;
|
||||
secure?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,6 +89,8 @@ export interface AccessibilityElement {
|
||||
focused?: boolean;
|
||||
selected?: boolean;
|
||||
editable?: boolean;
|
||||
/** Field masks what is typed into it; null where the platform does not report it. */
|
||||
secure?: boolean | null;
|
||||
bounds?: { left: number; top: number; right: number; bottom: number };
|
||||
x?: number;
|
||||
y?: number;
|
||||
|
||||
@@ -176,6 +176,7 @@ const SELECTOR_KEYS: readonly string[] = [
|
||||
"placeholderValue",
|
||||
"resource-id",
|
||||
"scrollable",
|
||||
"secure",
|
||||
"selected",
|
||||
"tag",
|
||||
"testID",
|
||||
@@ -552,6 +553,8 @@ function elementHandle(
|
||||
const y = Math.round(rect.top + rect.height / 2);
|
||||
const ariaLabel = element.getAttribute("aria-label") ?? "";
|
||||
const text = (element.textContent ?? "").trim().slice(0, 200);
|
||||
const editable =
|
||||
element.matches(EDITABLE_SELECTOR) && isEditableElement(element as HTMLElement);
|
||||
const datasetCopy: Record<string, string> = {};
|
||||
const dataset = (element as HTMLElement).dataset ?? {};
|
||||
for (const key of Object.keys(dataset)) {
|
||||
@@ -579,7 +582,7 @@ function elementHandle(
|
||||
// a contenteditable container typeable here while collectTargets and the
|
||||
// hierarchy dump, which both require the element ITSELF to match
|
||||
// EDITABLE_SELECTOR, called the same span inert.
|
||||
editable: element.matches(EDITABLE_SELECTOR) && isEditableElement(element as HTMLElement),
|
||||
editable,
|
||||
focused: focusedElement === element,
|
||||
// Checkbox and option state lives in the DOM PROPERTY: the markup attribute
|
||||
// records only what the page started with, so a handle reading it reports a
|
||||
@@ -588,6 +591,12 @@ function elementHandle(
|
||||
// dump the goja host gets.
|
||||
checked: state.checked === true,
|
||||
selected: state.selected === true,
|
||||
// Three-valued, unlike the other state flags: null on anything that is not
|
||||
// a field, matching the hierarchy dump in internal/driver/chrome/driver.go.
|
||||
// A consumer deciding what a typed value may be written into a record has
|
||||
// to tell "not a password field" apart from "nobody said", and Android says
|
||||
// nothing.
|
||||
secure: editable ? state.type === "password" : null,
|
||||
x,
|
||||
y,
|
||||
bounds: {
|
||||
|
||||
+2
-1
@@ -29,6 +29,7 @@
|
||||
"placeholderValue",
|
||||
"resource-id",
|
||||
"scrollable",
|
||||
"secure",
|
||||
"selected",
|
||||
"tag",
|
||||
"testID",
|
||||
@@ -40,5 +41,5 @@
|
||||
"unknownKeyExample": [
|
||||
"descripton"
|
||||
],
|
||||
"unknownKeyMessage": "selector key \"descripton\" cannot match: no element carries that attribute, and it is not one of the accepted keys: accessibilityIdentifier, accessibilityLabel, accessibilityText, aria-label, ariaLabel, bounds, checked, class, className, clickable, content-desc, contentDescription, data-testid, desc, descPrefix, editable, elementType, enabled, focused, hintText, id, idPrefix, identifier, label, package, placeholder, placeholderValue, resource-id, scrollable, selected, tag, testID, testTag, text, title, value"
|
||||
"unknownKeyMessage": "selector key \"descripton\" cannot match: no element carries that attribute, and it is not one of the accepted keys: accessibilityIdentifier, accessibilityLabel, accessibilityText, aria-label, ariaLabel, bounds, checked, class, className, clickable, content-desc, contentDescription, data-testid, desc, descPrefix, editable, elementType, enabled, focused, hintText, id, idPrefix, identifier, label, package, placeholder, placeholderValue, resource-id, scrollable, secure, selected, tag, testID, testTag, text, title, value"
|
||||
}
|
||||
@@ -628,11 +628,12 @@ function domElement(spec: {
|
||||
text?: string;
|
||||
checked?: boolean;
|
||||
contentEditable?: boolean;
|
||||
type?: string;
|
||||
}): unknown {
|
||||
const attributes = spec.attributes ?? {};
|
||||
return {
|
||||
tagName: spec.tag.toUpperCase(),
|
||||
type: spec.tag === "input" ? "text" : "",
|
||||
type: spec.type ?? (spec.tag === "input" ? "text" : ""),
|
||||
isContentEditable: spec.contentEditable ?? false,
|
||||
checked: spec.checked,
|
||||
id: attributes.id ?? "",
|
||||
@@ -719,6 +720,21 @@ test("checked reads the live property, not the markup attribute", () => {
|
||||
assert.equal(handleOf(cleared).checked, false);
|
||||
});
|
||||
|
||||
// `secure` answers three ways. A consumer deciding what a typed value may be
|
||||
// written into a record has to tell "not a password field" apart from "no
|
||||
// platform said", and android says nothing: a field answering false only when
|
||||
// asked about a password would make every web field look like an android one.
|
||||
test("secure states the field type either way, and nothing off a field", () => {
|
||||
const password = domElement({ tag: "input", attributes: { id: "pwd" }, type: "password" });
|
||||
assert.equal(handleOf(password).secure, true);
|
||||
|
||||
const email = domElement({ tag: "input", attributes: { id: "email" }, type: "email" });
|
||||
assert.equal(handleOf(email).secure, false);
|
||||
|
||||
const heading = domElement({ tag: "h1", attributes: { id: "title" } });
|
||||
assert.equal(handleOf(heading).secure, null);
|
||||
});
|
||||
|
||||
test("attrs carries every other attribute alongside tag and aria-label", () => {
|
||||
const attrs = attrsOf(
|
||||
domElement({ tag: "input", attributes: { id: "txn-note", placeholder: "What's this for?" } }),
|
||||
|
||||
Reference in new issue
Block a user