mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(spec): a secure selector names the password field on web
secure is derived from the field type, not written by the markup, so matching it as a raw attribute reached nothing: the key is accepted, no unknown-key error fires, and find answered undefined on web for the field it answers with on ios. false is every editable field that is not a password entry, since an element that is no field reports null and answers to neither value.
This commit is contained in:
1 parent
8414953135
commit
b0895ef179
3 files changed
+49
-1
No files matched your search
@@ -119,8 +119,25 @@ const KNOWN_KEY_TO_CSS: Record<string, (value: string) => string> = {
|
|||||||
placeholder: (v) => `[placeholder="${cssEscape(v)}"]`,
|
placeholder: (v) => `[placeholder="${cssEscape(v)}"]`,
|
||||||
placeholderValue: (v) => `[placeholder="${cssEscape(v)}"]`,
|
placeholderValue: (v) => `[placeholder="${cssEscape(v)}"]`,
|
||||||
hintText: (v) => `[placeholder="${cssEscape(v)}"]`,
|
hintText: (v) => `[placeholder="${cssEscape(v)}"]`,
|
||||||
|
secure: secureSelector,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// secure is derived from the field's type rather than written by the markup, so
|
||||||
|
// matching it as a raw attribute reaches nothing at all. Both producers of the
|
||||||
|
// fact, elementHandle below and the hierarchy dump in
|
||||||
|
// internal/driver/chrome/driver.go, read `type === "password"` off a field they
|
||||||
|
// call editable, so false is every editable field that is NOT a password entry
|
||||||
|
// rather than everything that is not one: an element that is no field reports
|
||||||
|
// null, as android reports null for everything, and answers to neither value.
|
||||||
|
function secureSelector(value: string): string {
|
||||||
|
if (value === "true") return `input[type="password"]`;
|
||||||
|
if (value !== "false") return ":not(*)";
|
||||||
|
const textInput = ["password", ...NON_TEXT_INPUT_TYPES]
|
||||||
|
.map((type) => `:not([type="${type}"])`)
|
||||||
|
.join("");
|
||||||
|
return `:is(input${textInput}, textarea, [contenteditable]:not([contenteditable="false"]))`;
|
||||||
|
}
|
||||||
|
|
||||||
// cssEscape delegates to the platform CSS.escape (per CSSOM spec). It produces
|
// cssEscape delegates to the platform CSS.escape (per CSSOM spec). It produces
|
||||||
// output safe for both identifier and string contexts, since CSS string
|
// output safe for both identifier and string contexts, since CSS string
|
||||||
// literals accept the same `\HEX ` and `\X` escape sequences as identifiers.
|
// literals accept the same `\HEX ` and `\X` escape sequences as identifiers.
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export function fakeElement(spec: FakeElementSpec): FakeElement {
|
|||||||
const element: FakeElement = {
|
const element: FakeElement = {
|
||||||
...spec,
|
...spec,
|
||||||
tagName: spec.tag.toUpperCase(),
|
tagName: spec.tag.toUpperCase(),
|
||||||
type: spec.tag === "input" ? "text" : "",
|
type: spec.tag === "input" ? (attributes.type ?? "text") : "",
|
||||||
isContentEditable: editable && spec.tag !== "input" && spec.tag !== "textarea",
|
isContentEditable: editable && spec.tag !== "input" && spec.tag !== "textarea",
|
||||||
id: spec.id ?? "",
|
id: spec.id ?? "",
|
||||||
className: attributes.class ?? "",
|
className: attributes.class ?? "",
|
||||||
|
|||||||
@@ -735,6 +735,37 @@ test("secure states the field type either way, and nothing off a field", () => {
|
|||||||
assert.equal(handleOf(heading).secure, null);
|
assert.equal(handleOf(heading).secure, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The fact above and the selector below have to name the same field. `secure` is
|
||||||
|
// derived from the field's type, not written by the markup, so matching it as a
|
||||||
|
// raw attribute reaches nothing: `secure` is an accepted key, no unknown-key
|
||||||
|
// error fires, and find answers undefined here for the field it answers with on
|
||||||
|
// ios. A spec that names the password entry that way types the password into
|
||||||
|
// nothing and every property over it passes vacuously.
|
||||||
|
test("secure selects the field this host reports secure", () => {
|
||||||
|
const password = fakeElement({
|
||||||
|
tag: "input", x: 0, y: 0, width: 100, height: 20,
|
||||||
|
id: "login_password", editable: true, attrs: { type: "password" },
|
||||||
|
});
|
||||||
|
const email = fakeElement({
|
||||||
|
tag: "input", x: 0, y: 20, width: 100, height: 20,
|
||||||
|
id: "login_email", editable: true, attrs: { type: "email" },
|
||||||
|
});
|
||||||
|
const heading = fakeElement({ tag: "h1", x: 0, y: 40, width: 100, height: 20, id: "title" });
|
||||||
|
withFakeDocument([password, email, heading], () => {
|
||||||
|
const ax = __testing__.buildAx() as { findAll(selector: unknown): Record<string, unknown>[] };
|
||||||
|
assert.deepEqual(
|
||||||
|
ax.findAll({ secure: true }).map((field) => [field.id, field.secure]),
|
||||||
|
[["login_password", true]],
|
||||||
|
);
|
||||||
|
// Not the heading: an element that is no field at all reports null, the way
|
||||||
|
// android reports null for every element, and answers to neither value.
|
||||||
|
assert.deepEqual(
|
||||||
|
ax.findAll({ secure: false }).map((field) => [field.id, field.secure]),
|
||||||
|
[["login_email", false]],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("attrs carries every other attribute alongside tag and aria-label", () => {
|
test("attrs carries every other attribute alongside tag and aria-label", () => {
|
||||||
const attrs = attrsOf(
|
const attrs = attrsOf(
|
||||||
domElement({ tag: "input", attributes: { id: "txn-note", placeholder: "What's this for?" } }),
|
domElement({ tag: "input", attributes: { id: "txn-note", placeholder: "What's this for?" } }),
|
||||||
|
|||||||
Reference in new issue
Block a user