Name a form's controls by the label cells of rows

Merged
YO
10d ago
+498-14

Summary

Forms on older sites are laid out in tables that label a field with the cell before it, not with a <label>:

<tr><th>工種又は業種</th><td><select name="KoujiSyubetu">…</select></td></tr>

Such a control has no accessible name. On a public bid-information site's search form, the session outline showed every select and field as bare [6-697] select = ; options: …, and an element operation refused them:

{"select": {"in": "6-697", "option": "土木一式工事"}}
-> element 6-697 has no name a step could find it by again; act on it with an instruction that describes it

So every field of such a form needed a model call, and the outline did not say which select was which.

Fix

A new pageSnapshot.roots() parses the tree and names each form control the tree leaves unnamed (fields, selects/combo boxes/list boxes, checkboxes/radios/switches, file inputs):

  1. its own accessible name, when it has one;
  2. else the label cell of its table row: the nearest cell before the one holding it that has text and holds no control or button (a cell holding a select is named after the select's value, and one holding a button after the button, so both are skipped). Data tables (row, cell, rowheader, columnheader) and layout tables (LayoutTableRow/LayoutTableCell) both count, and when the nearest row has no label cell, as for a select in a table nested in a cell, the enclosing row is tried;
  3. else its name attribute, or its id. Snapshot reads these for the unnamed controls only, in one evaluation that follows each control's deep XPath into frames of the same origin and into open shadow roots (the runtime writes a shadow root's host as host[1]//).

Controls of one role that one label cell names are numbered in order after it: the two fields of a date range in one cell become textbox "入札予定日範囲 1" and textbox "入札予定日範囲 2", so each has its own name and the act a step writes says which one it means. A control of another role beside them keeps the label alone: an and/or select next to a search field stays select "文字列検索", since its role already tells it apart from textbox "文字列検索".

The outline, element operations (click, type, select by ID), and replay identification (targetOf, used to record and check an action's target) all read the tree through roots(), so they agree on the names. A target recorded with no name, as earlier versions recorded such controls, is compared among the names the tree itself gives, and an element nothing names is recorded the same way, so those recordings still replay; an empty name still does not match a control with a name of its own. The raw tree (describe --tree, and what the model is sent) is unchanged. The CLI reference says how such fields are named.

On the live search form every control is now named: select "年度", select "調達機関", select "工種又は業種", textbox "文字列検索", select "表示件数", select "並び順". select/type by ID on them succeed and are recorded as Select "土木一式工事" in the "工種又は業種" dropdown and so on.

Tests

  • TestOutlineNamesControlsByTheirRow: a tree recorded from Chrome (testdata/outline/rowform.tree) of a frameset holding a data-table form, a layout table, and a select inside a nested table. It covers <th> and <td> labels, two selects in one row, a cell holding a control or a button skipped, two fields of one cell numbered while a select beside a field is not, a checkbox keeping its own label, and a field with no row label named by its name attribute or by its id outside the table. Without attributes, that field stays unnamed.
  • TestSessionActsOnARowNamedElement: select, type, and click by ID on those controls succeed through a session, with the act a step writes naming each (Type 2026/04/30 into the "入札予定日範囲 2" field), and no model request.
  • TestReplayTargetRecordedUnnamed: a select recorded with no name still targets the select now named by its row; a field nothing names is recorded and replayed with no name; an empty name does not match a checkbox with its own name.
  • TestStagehandNamesControlsByTheirRow (real Chrome): on a frameset whose form is in a frame, the outline names the controls by row and attribute, a field after a button's cell by its row, and a field in a shadow root by its own name rather than that of a light-DOM field under its host. Element operations by ID pick the option, type into the fields (the second field of a date range only), and check the checkbox.

Mutation checks: skipping the naming fails all three; dropping the row label fails all three; not reading attributes fails the real-browser test; dropping the numbering fails all three, and numbering across roles fails the outline and real-browser tests; letting a button's cell label fails the outline and real-browser tests; splitting the XPath on / again fails the real-browser test.


Summary by cubic

Names form controls on sites that lay out forms in tables, using the label cell before each field. Such controls previously had no accessible name, so the outline showed bare selects and textboxes, and element operations refused them. They are now named by their row's label cell — skipping cells that hold a control or button — or by their name/id attribute as a fallback, read through frames and shadow roots. Controls sharing one label cell are numbered in order. The outline, element operations, and replay checks all use these names; controls with their own accessible name keep it, and the raw tree is unchanged.

Actions recorded before rows named controls still replay: a target recorded with no name is compared by the names the tree itself gives, so such a recording finds a control its row now names. An empty name does not match a control that has a name of its own.

Summary by CodeRabbit

  • New Features
    • Browser session outlines now use preceding table-row labels to name form controls, falling back to each control’s name or ID when no row label applies.
    • When multiple controls of the same kind share a row label, their names are numbered in order. These controls can be selected, filled, and clicked through browser sessions.
    • Controls without a row label or name/ID remain unnamed. Recorded actions can still match controls whose labels are inferred from their table rows.
  • Documentation
    • Updated browser session guidance to explain control naming and numbering.