diff --git a/internal/driver/chrome/selector_parity_test.go b/internal/driver/chrome/selector_parity_test.go index 76b3729..d31c9e8 100644 --- a/internal/driver/chrome/selector_parity_test.go +++ b/internal/driver/chrome/selector_parity_test.go @@ -149,6 +149,22 @@ func TestSelectors_ResolveTheSameElementsAsTheWebRuntime(t *testing.T) { object: objectSelector("secure", "false"), want: []string{"login_email", "login_note", "login_terms"}, }, + { + // The head subtree renders nothing, so the hierarchy dump drops it + // (buildTree in driver.go) and so does the enumeration the picker + // walks (targetElements in web-runtime.ts). A selector resolving + // into it names an element the goja host cannot see at all. + name: "tag naming the head element", + selector: "tag:head", + object: objectSelector("tag", "head"), + want: nil, + }, + { + name: "tag naming an element inside the head", + selector: "tag:title", + object: objectSelector("tag", "title"), + want: nil, + }, { // The root element answers a selector like any other: the string // form scans from the root down, and the object form used to start diff --git a/pkg/spec/src/web-runtime.ts b/pkg/spec/src/web-runtime.ts index 3966a90..0d99d5b 100644 --- a/pkg/spec/src/web-runtime.ts +++ b/pkg/spec/src/web-runtime.ts @@ -426,12 +426,22 @@ function deepQueryAll(selector: string, root: ParentNode): Element[] { // matchedElements resolves one compiled selector: the document query first, then // the states it named, which no query can express. +// +// The head subtree is dropped here the way targetElements drops it from the +// enumeration and buildTree (internal/driver/chrome/driver.go) drops it from the +// dump: it renders nothing, so a selector reaching into it names an element the +// goja host cannot see at all. document.head is absent only from the small fake +// documents the unit tests install. function matchedElements(root: ParentNode, compiled: CompiledSelector): Element[] { const { css, xpath, match } = compiled; let found: Element[] = []; if (css) found = deepQueryAll(css, root); else if (xpath) found = evaluateXPathAll(xpath, root as Node); - return match === undefined ? found : found.filter(match); + const head: Element | undefined = document.head; + return found.filter( + (element) => + !(head !== undefined && head.contains(element)) && (match === undefined || match(element)), + ); } function queryElement(