feat(spec): reject an unknown object-selector key in the web runtime

Same rule and the same message as the native side: a key no element can
carry throws instead of matching nothing. The accepted list is one list,
committed as a fixture both suites assert, so a spec cannot be accepted
by one runtime and rejected by the other.
This commit is contained in:
pj committed 2026-08-13 00:38:40 +05:30
1 parent 1332471b72
commit 269574706d
5 files changed
+239 -10

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
[
"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"
]
+21
View File
@@ -0,0 +1,21 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { test } from "node:test";
import { __testing__ } from "../src/web-runtime.ts";
const { SELECTOR_KEYS } = __testing__;
const goldenPath = fileURLToPath(new URL("./fixtures/selector-keys.json", import.meta.url));
const golden: string[] = JSON.parse(readFileSync(goldenPath, "utf8"));
// Both runtimes reject an object-selector key they do not know, so the two key
// lists have to be one list. Were they to drift, a spec would be accepted by
// the runtime that lists the key and fail the run on the one that does not, and
// the difference would only show on the platform nobody ran first.
// internal/hierarchy/selector_keys_test.go asserts the SAME file from the
// native side.
test("the web key list is the cross-runtime list", () => {
assert.deepEqual([...SELECTOR_KEYS], golden);
});
+41 -3
View File
@@ -357,12 +357,50 @@ test("selectorFromObject composes idPrefix with a second key", () => {
});
});
test("selectorFromObject falls back to a literal attribute for unknown keys", () => {
assert.deepEqual(selectorFromObject({ "data-foo": "bar" }), {
css: `[data-foo="bar"]`,
// A key nothing can carry yields no match, which reads exactly like a screen
// with no such element: the generator declines to act, the runner waits out the
// step, and the run ends clean having explored nothing.
test("selectorFromObject rejects a key no element can carry", () => {
assert.throws(
() => selectorFromObject({ descripton: "Supplier" }),
(error: Error) =>
error.message.includes('"descripton"') && error.message.includes("accepted keys"),
);
});
// Raw attributes the key list does not enumerate stay reachable when the page
// actually carries them.
test("selectorFromObject accepts a raw attribute the page carries", () => {
withDocumentCarrying(["data-foo"], () => {
assert.deepEqual(selectorFromObject({ "data-foo": "bar" }), {
css: `[data-foo="bar"]`,
});
});
});
// The string form's kind space stays open on both sides: "<attr>:<value>" is
// the documented way to reach a raw driver attribute, and internal/hierarchy
// resolves an unknown kind to an empty result rather than an error.
test("selectorFromString accepts a kind the object form would reject", () => {
assert.deepEqual(selectorFromString("descripton:Supplier"), {
css: `[descripton="Supplier"]`,
});
});
function withDocumentCarrying(attributes: string[], run: () => void): void {
const global = globalThis as Record<string, unknown>;
const original = global.document;
global.document = {
querySelector: (selector: string) =>
attributes.some((name) => selector === `[${name}]`) ? {} : null,
};
try {
run();
} finally {
global.document = original;
}
}
test("selectorFromObject text-only selector becomes an XPath", () => {
assert.deepEqual(selectorFromObject({ text: "Go" }), {
xpath: `//*[normalize-space(text())="Go"]`,