Skip to content

DOM.getNodeForLocation returns the wrapping <label> instead of the <input> over most of a normal-flow labeled input box #385

Description

@brianynwu

Summary

For a normal-flow <label>text <input></label> (label text followed by an input, the common form pattern), DOM.getNodeForLocation returns the INPUT only in a thin strip at the top of the input's box and returns the wrapping LABEL for the entire center and bottom of the input — even though the input is the topmost, visible, hit-testable element across its whole box (and Chromium/Playwright's Chromium return the INPUT at every one of those points).

This makes CDP-driven click targeting / hit verification unreliable for form controls: an automation that computes an element's clickable point from DOM.getContentQuads (the box center) and confirms it with DOM.getNodeForLocation sees a miss (the label, an ancestor, not the input or a descendant), so it refuses or mis-targets the click.

moli v1.1.2 (x86_64-unknown-linux-gnu), moli serve --host <ip> --port 9222 --layout, connected via playwright-core@1.55.1 connectOverCDP.

Minimal reproduction

Page (a data: URL, no external deps):

<!doctype html><meta charset=utf-8>
<body><form><p><label>Customer name: <input name=custname></label></p></form>

CDP sequence (Node + playwright-core, connected over CDP to a running moli serve --layout):

import { chromium } from "playwright-core";
const b = await chromium.connectOverCDP("http://<moli-ip>:9222");
const ctx = await b.newContext();
const p = await ctx.newPage();
const cdp = await ctx.newCDPSession(p);

const html = "<!doctype html><meta charset=utf-8><body><form><p><label>Customer name: <input name=custname></label></p></form>";
await p.goto("data:text/html," + encodeURIComponent(html), { waitUntil: "load" });

const { result } = await cdp.send("Runtime.evaluate", { expression: 'document.querySelector("input")' });
const desc = await cdp.send("DOM.describeNode", { objectId: result.objectId });
const inputBackend = desc.node.backendNodeId;

const { quads } = await cdp.send("DOM.getContentQuads", { objectId: result.objectId });
const q = quads[0];                 // [x1,y1, x2,y2, x3,y3, x4,y4]
const cx = Math.round((q[0] + q[4]) / 2);

for (const y of [q[1] + 1, Math.round((q[1] + q[5]) / 2), q[5] - 1]) {   // top, center, bottom of the input box
  const nl = await cdp.send("DOM.getNodeForLocation", { x: cx, y: Math.round(y) });
  const r = await cdp.send("DOM.resolveNode", { backendNodeId: nl.backendNodeId });
  const t = await cdp.send("Runtime.callFunctionOn",
    { objectId: r.object.objectId, functionDeclaration: "function(){return this.tagName}", returnByValue: true });
  console.log(`(${cx},${Math.round(y)}) -> ${t.result.value} ${nl.backendNodeId === inputBackend ? "== INPUT" : "!= INPUT  <-- BUG"}`);
}

Observed (moli v1.1.2)

INPUT content quad: [156.03,19, 315.97,19, 315.97,35, 156.03,35]   (box y ≈ 19..35)
(236,20) -> INPUT   == INPUT      # top edge: correct
(236,27) -> LABEL   != INPUT  <-- BUG   # center: returns the wrapping <label>
(236,34) -> LABEL   != INPUT  <-- BUG   # bottom: returns the wrapping <label>

The input's getContentQuads box is y ≈ 19..35, so all three points are inside the input, yet only the top ~1px strip hit-tests to the INPUT; the rest returns the ancestor LABEL.

Expected

DOM.getNodeForLocation at any point inside the input's content box should return the INPUT (the topmost hit-testable element there), matching Chromium — as getContentQuads already reports that box as the input's.

Notes / narrowing

  • The trigger is the normal-flow label-wraps-text-then-input layout (the input sits on the text baseline within the label's line box). An input positioned so it is not baseline-aligned inside a text-carrying line box (e.g. position:absolute) hit-tests correctly at every point — so this looks like a vertical hit-test / line-box (baseline-alignment) issue rather than a general control problem.
  • A related symptom appears for below-the-fold controls after a scroll (Page.getFrameTree/scroll then getNodeForLocation) — likely the same hit-test path; happy to open a separate issue with a repro if useful.
  • Impact is limited to hit-test verification (getNodeForLocation); getFullAXTree, describeNode, and getContentQuads all report the input correctly.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions