)]}'
{
  "log": [
    {
      "commit": "bf19cbff49b9736a46946cc8acbec734f2f86b1d",
      "tree": "c0efe873c3110df3a41a0b5fa31a5ab4e69db7fb",
      "parents": [
        "577555deb3948de4e4dc24ed07d2c22875109c8d"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Thu Nov 12 21:04:24 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Nov 12 21:24:22 2026"
      },
      "message": "[py] Snapshot accessibility in Classic sessions too (#18307)\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: a393610d6d42bbbf28c30b6bc4212682a58bbb6c\n"
    },
    {
      "commit": "577555deb3948de4e4dc24ed07d2c22875109c8d",
      "tree": "9f26ca656949e743d3e70a69354b2bf8470d0b26",
      "parents": [
        "de70b67ef51c83f173caa3edb802e93b9fde8741"
      ],
      "author": {
        "name": "Tingri Lohar Mohammed Mohsin",
        "email": "96524936+Mohsintl@users.noreply.github.com",
        "time": "Thu Nov 12 10:01:36 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Nov 12 10:24:29 2026"
      },
      "message": "[py] Fix Select escaping of trailing double quotes (#18292)\n\nWhen text contained both quote types, _escape_string split on \u0027\"\u0027 and\nthen also appended a closing quote if the text ended with one. Python\u0027s\nstr.split keeps the trailing empty part, so the quote was emitted twice\nand the generated XPath matched the text plus an extra \u0027\"\u0027. Options like\nit\u0027s \"quoted\" could never be selected by visible text.\n\nBuild the concat() from the actual pieces instead, so every quote in the\ntext appears exactly once.\n\nCo-authored-by: Tingri Lohar Mohammed Mohsin \u003cMohsintl@users.noreply.github.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: f3ed8fb99473a010e1f239d612c870315d047ee3\n"
    },
    {
      "commit": "de70b67ef51c83f173caa3edb802e93b9fde8741",
      "tree": "736bef15e6dfdb59781a73289965485dcd5e8cd1",
      "parents": [
        "b53421e2edd204fe9db19b9a60b5282071e7662a"
      ],
      "author": {
        "name": "Saiyam Jain",
        "email": "62669344+saiyam-jain-dev@users.noreply.github.com",
        "time": "Tue Nov 10 17:02:13 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Nov 10 17:24:52 2026"
      },
      "message": "[py] narrow pytest.raises(Exception) in bidi tests (#18286)\n\nCo-authored-by: Diego Molina \u003cdiemol@gmail.com\u003e\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: a6e7c3eb7fbe16b2c23abaf30832f7ef485a82e5\n"
    },
    {
      "commit": "b53421e2edd204fe9db19b9a60b5282071e7662a",
      "tree": "cb48f9d3efcf9d8b61cb91b2f7eeb0d9dbc5e13f",
      "parents": [
        "41fbb39ecb050b055a866bc1a0cf71ed3e77a0a8"
      ],
      "author": {
        "name": "Diego Molina",
        "email": "diemol@users.noreply.github.com",
        "time": "Tue Nov 10 11:57:28 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Nov 10 12:24:56 2026"
      },
      "message": "[py][rb] Fix tests failing after the browser version update (#18294)\n\n- rb: remove the setTextLayoutModeOverride pending guards for Chrome and\n  Edge, as both now support it\n- py: mark the user context proxy tests as xfail on Edge, as Edge now\n  upgrades HTTP to HTTPS in untrusted networks, like Chrome\n\nCo-Authored-By: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 7b987231e1d39e0696a07eab2d02d4362b00791f\n"
    },
    {
      "commit": "41fbb39ecb050b055a866bc1a0cf71ed3e77a0a8",
      "tree": "bdd990d919099a183692d1438605dcc28a032113",
      "parents": [
        "155e653ff9581565dda84b7f941d88e0cbb3bd75"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Tue Nov 10 09:47:39 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Nov 10 10:24:47 2026"
      },
      "message": "[build] Automated Browser Version Update (major) with CDP (#18278)\n\n* [build] Automated Browser Version Update (major) with CDP\n\n* [build] Automated Browser Version Update (major) with CDP\n\n* [build] Automated Browser Version Update (major) with CDP\n\n* [build] Automated Browser Version Update (major) with CDP\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: bdb4fc7a3515831a8e85820db468e75d5f4f108f\n"
    },
    {
      "commit": "155e653ff9581565dda84b7f941d88e0cbb3bd75",
      "tree": "013751a842f356917509f74fac18edde9b7bacc2",
      "parents": [
        "66dd4f5367fae258a87452a538874b4c90db561d"
      ],
      "author": {
        "name": "Diego Molina",
        "email": "diemol@users.noreply.github.com",
        "time": "Mon Nov 09 11:56:31 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Nov 09 12:25:02 2026"
      },
      "message": "[py][rb][js] Fix tests failing in the nightly CI run (#18289)\n\n* [py][rb][js] Fix tests failing in the nightly CI run\n\n- py: navigate to the result page with click() instead of submit(); after a\n  scripted submit, safaridriver\u0027s Forward command does not navigate\n- rb: wait for the result page title after submit, since Safari updates the\n  URL before the title\n- rb: only keep the Safari pending guard on the input specs that still fail,\n  as Safari Technology Preview now handles the key action ones\n- rb: mark the accessibility snapshot specs pending on Safari Technology Preview\n- rb: limit the setTextLayoutModeOverride pending guard to Chrome, as Edge\n  supports it now\n- js: run the MCP upload tests from a temp directory with real fixture copies;\n  Bazel runfiles on Windows are symlinks whose real path is outside the working\n  directory\n\nCo-Authored-By: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\n\n* [js] Remove the temp fixture directory after the MCP upload tests\n\nCo-Authored-By: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\n\n* [rb] Keep the setTextLayoutModeOverride guard for the pinned Edge\n\nCo-Authored-By: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: b74be3ebbd33f0a732f5e146a54c4294ab93fb6e\n"
    },
    {
      "commit": "66dd4f5367fae258a87452a538874b4c90db561d",
      "tree": "4f09f9850bda8ee310845e8a6569a7d19a6ade73",
      "parents": [
        "89810d0204c50b8b3f73c5648f15f394f96604d1"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Mon Nov 09 10:17:26 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Nov 09 10:24:46 2026"
      },
      "message": "[build] Automated Dependency Update (#18288)\n\n* [build] Automated Dependency Update\n\n* [py] Combine nested if in Proxy.to_capabilities for ruff SIM102\n\nCo-Authored-By: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@gmail.com\u003e\nCo-authored-by: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: fa74ddfb758b44094d88cd66e980c1a404a27332\n"
    },
    {
      "commit": "89810d0204c50b8b3f73c5648f15f394f96604d1",
      "tree": "4a991692335f169e87b5ffb5a5636d56edbb005d",
      "parents": [
        "bf772e8689e46e61483796c9db5bcbdcf86834bd"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Thu Nov 05 09:41:08 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Nov 05 10:24:21 2026"
      },
      "message": "[js][py][rb][java][dotnet] add the accessibility snapshot atom and the binding methods (#18232)\n\n* [js] add the accessibility-snapshot atom scaffold\n\nFirst slice of the in-page accessibility snapshot proposed in ADR 18205\n(docs/decisions/18205-accessibility-tree-snapshot.md): the atom exists,\nships through @selenium/atoms, and pins the root contract, so the\nremaining slices (roles, hidden, tree walk, names, states, composed DOM,\ncorpus) and the binding PRs can build against a stable subpath.\n\n- registry entry `accessibility-snapshot` in typescript/defs.bzl,\n  exported as `accessibilitySnapshot` and published under\n  `@selenium/atoms/accessibility-snapshot`; package.json regenerated\n  with typescript/generate-exports.cjs\n- entry point plus core/accessibility/{types,snapshot}.ts; the\n  ATOM_SRCS and tsconfig globs now reach subdirectories under core/\n- fragment `accessibility-snapshot-typescript` with the same visibility\n  as the other TypeScript atoms the bindings embed\n- QUnit page accessibility_tree_typescript_test.html, written first:\n  the whole page is rooted at the html element as role `document`\n  named from the title, Document and html roots are equivalent, every\n  node carries the full schema with empty description/states, and a\n  non-element or detached root throws rather than returning a partial\n  tree\n\nBelow the document root every element is still reported as an unnamed\n`generic` node; roles, names, states and children arrive in the next\nslices, so README.npm.md is not updated yet.\n\n* [js] compute roles for the accessibility snapshot\n\nSecond slice of ADR 18205: every node now carries the role its explicit\n`role` attribute names, or the html-aam implicit role.\n\n- explicit roles: the first token naming a WAI-ARIA 1.2 role authors\n  may use (abstract roles and unknown tokens fall through), matched\n  ASCII case-insensitively; none/presentation are ignored on focusable\n  elements and on elements with a global ARIA attribute (WAI-ARIA 1.2\n  section 9.1)\n- implicit roles: the html-aam table, including the contextual rows\n  (header/footer outside sectioning content, aside and section only\n  when labelled, li only inside ul/ol/menu, select by multiple/size,\n  input by type and list, td as gridcell inside a grid, th by scope,\n  img alt\u003d\"\")\n- elements html-aam gives no role report `generic`, so the pruning rule\n  in ADR 18205 treats them like a div; frames report the ADR\u0027s `iframe`\n  token\n- two deliberate divergences towards what engines expose, pinned by the\n  test page: input type\u003dpassword is a textbox and summary is a button\n\naccessibility_roles_typescript_test.html was written first and failed\n28 of 32 tests against the previous slice; it now passes 125 assertions\nin Chromium, with the tree page unchanged.\n\n* [js] compute hidden-ness for the snapshot atom\n\nThird slice of ADR 18205: core/accessibility/hidden.ts decides whether\nan element is hidden in the accname sense, which the tree walk in the\nnext slice uses to leave nodes out.\n\n- isHidden: aria-hidden\u003dtrue on the element or an ancestor (aria-hidden\n  false cannot undo it), display none, visibility hidden or collapse on\n  the element\u0027s own computed style, the hidden attribute through the UA\n  stylesheet (so author CSS can show it again), closed details content\n  other than the first summary, noscript, template content, detached\n  elements\n- ancestry follows the flat tree: a slotted element follows its slot, a\n  shadow root\u0027s child its host; an unslotted child of a shadow host and\n  the fallback content of a filled slot are not rendered\n- hiddenState reports what an element does on its own (rendered, self,\n  subtree) so the walk can prune a subtree once and still descend into\n  a visibility-hidden element whose children may be visible\n- this is deliberately not bot.dom.isShown: opacity, off-screen position\n  and the clip pattern stay rendered, which the page pins\n\nThe module is reached through a test-only bundle, typescript/testing/\naccessibility.ts, registered in TEST_BUNDLES and never published, since\nthe public atom only exposes hidden-ness through the tree walk.\n\naccessibility_hidden_typescript_test.html was written first and failed\n15 of 21 tests against a stub; it now passes all 55 assertions in\nChromium, with the roles and tree pages unchanged.\n\n* [js] walk the tree for the accessibility snapshot\n\nFourth slice of ADR 18205: a snapshot now has children. The walk visits\neach element once, top-down, threading ancestor state instead of asking\nisHidden per node.\n\n- core/accessibility/tree.ts: an element produces a node when its role\n  is not generic, none or presentation (or, once names land, when it\n  has a name); pruned elements hoist their children in place, so\n  document order is kept; aria-hidden and display none drop the\n  subtree; a visibility hidden element drops its own node and text but\n  descends, since a child may set itself visible again; closed details\n  content is skipped; text runs become `text` leaves with collapsed\n  whitespace and no element; iframe and roles with presentational\n  children (button, img, checkbox, option, ...) are leaves\n- the root always produces a node, even a generic or hidden one, so a\n  subtree snapshot of a plain div is deterministic; a root hidden as a\n  subtree has no children\n- core/accessibility/context.ts: per-call SnapshotContext with a role\n  cache and the set of placed elements. It lives exactly as long as the\n  call because the snapshot is a point-in-time copy; a module-level\n  cache would return stale results after the page changes an\n  attribute. Style and name caches join it when the name slice needs\n  them.\n\nThe walk uses childNodes; the composed-DOM slice swaps in the flat tree\nand aria-owns. Names below the document root are still empty, so text\nleaves are not yet suppressed under nodes named from content.\n\naccessibility_tree_typescript_test.html gained the walk fixtures first\nand failed 6 of 17 tests against the previous slice; it now passes all\n44 assertions in Chromium, with the roles and hidden pages unchanged.\n\n* [js] compute names for the accessibility snapshot\n\nFifth slice of ADR 18205: every node now carries its accessible name and\ndescription, following the accname 1.2 text alternative computation with\nthe html-aam rules for native elements.\n\n- core/accessibility/accname.ts: aria-labelledby (space-joined, hidden\n  references included, not followed from a referenced element, a self\n  reference contributes the control value), aria-label, native sources\n  (labels with the control itself left out, button input values with\n  the Submit and Reset defaults, img and area alt, legend, figcaption,\n  caption, option and optgroup labels, svg title, the document title),\n  embedded control values, name from content for the roles that allow\n  it and for every descendant during recursion, with ::before and\n  ::after content included and block-level children set off by spaces,\n  then title, then placeholder for text inputs\n- descriptions: aria-describedby, aria-description, then title when the\n  name did not already use it\n- core/accessibility/value.ts: the values accname step 2E reads from\n  textboxes, selects and range controls; the states slice reuses it\n- core/accessibility/context.ts gains the computed-style and name\n  caches, which is where repeated work first appears: labelledby\n  targets are read once per snapshot and descendants keep the style\n  the walk already resolved\n- the walk applies the full pruning rule, so a generic element with a\n  name now produces a node, and text an ancestor\u0027s name was computed\n  from is not repeated as leaves below it, while the element structure\n  there still is\n\nTwo choices the record leaves open are pinned by the pages: title beats\nplaceholder on text inputs, as html-aam orders them, and inline children\nconcatenate without an added space (\"Save\u003cb\u003enow\u003c/b\u003e\" is \"Savenow\").\n\naccessibility_name_typescript_test.html (29 tests) and\naccessibility_description_typescript_test.html (7 tests) were written\nfirst and failed 27 and 6 of them against the previous slice; the tree\npage gained the named-generic and consumed-text cases and failed 6 of\n19. All five pages now pass in Chromium: 74, 12, 47, 125 and 55\nassertions.\n\n* [js] address review findings on the snapshot atom\n\nFixes for the automated review of the first five slices of ADR 18205,\neach with a regression case added to its QUnit page first.\n\nHidden-ness\n- inert content and its descendants are hidden\n- content-visibility: hidden is a new state, contents: the element\u0027s own\n  box is rendered but nothing below it; hidden\u003d\"until-found\" uses it\n- area elements are as hidden as the image whose usemap points at their\n  map, instead of always hidden through the UA stylesheet\u0027s display none\n- isHiddenByAncestor is exported for the walk\n\nRoles\n- focusable, for the presentational conflict rule, now means able to\n  take focus (tabindex, href on anchors and areas, enabled form\n  controls, contenteditable) instead of WebDriver\u0027s isFocusable, which\n  counts every anchor and label\n- the four globals WAI-ARIA 1.2 deprecates but still lists stop\n  none/presentation too\n- required owned elements of a presentational table or list (rowgroup,\n  row, cell, header, listitem) are generic\n- a section or aside counts as named by its title, and an\n  aria-labelledby only when it resolves to something with text\n\nTree walk\n- text directly inside a closed details is collapsed content\n- a root inside a hidden ancestor has no children\n- frames, textareas, video and audio are leaves by element, so an\n  iframe with another role, a textarea\u0027s default text and media\n  fallback content are not walked\n- graphics-symbol has presentational children\n- a contents-hidden element keeps its node and drops its children\n\nNames\n- a present alt attribute is the final answer for img and area, so a\n  decorative image does not pick up its title\n- an embedded control reached while naming something else contributes\n  its value ahead of its own label, which also ends the recursion\n  between two labels that wrap each other\u0027s control\n- an ARIA listbox contributes its selected options, a contenteditable\n  textbox its rendered text\n- an invisible child contributes neither its text, generated content\n  nor attribute-based alternative, only visible descendants\n- generated content is tokenised, so a slash inside a string is text\n  and only a bare slash introduces the alternative text\n\nTwo findings are not changed: text below a node named from content is\nnot repeated even when a descendant\u0027s aria-label replaced it, which is\nwhat assistive technology hears and a choice the PR lists for\nconfirmation; and children of a closed shadow host read as rendered,\nsince script cannot see a closed root, a limitation the PR records.\n\nThe four affected pages failed 27 tests against the previous commit and\nnow pass: 138, 65, 59 and 83 assertions, with the description page\nunchanged at 12.\n\n* [js] report states and values in the snapshot atom\n\nSixth slice of ADR 18205: every node carries its `states` map and, for\ntextbox, searchbox, combobox and range roles, its `value`.\n\ncore/accessibility/states.ts decides which keys appear:\n\n- checked whenever the role supports it (checkbox, radio, switch,\n  menuitemcheckbox, menuitemradio), from the input or aria-checked, with\n  \"mixed\" only where the role allows it\n- pressed when the element sets aria-pressed; a button without it is not\n  a toggle button, so pressed\u003dfalse would mislead (pinned)\n- expanded when the element sets aria-expanded, and on a summary from\n  its details\u0027 open state\n- selected always for option, tab and treeitem, where selection is part\n  of the role; for gridcell, row and headers only when the element sets\n  aria-selected, since most grids are not selectable (pinned)\n- level always for headings (tag, a valid aria-level, or ARIA\u0027s default\n  of 2); elsewhere when the element sets aria-level\n- disabled true only when disabled, natively (reusing core/enabled.ts,\n  so fieldset inheritance applies) or through aria-disabled on the\n  element or an ancestor\n- focused true only on the active element, never the body\n\nvalue reuses core/accessibility/value.ts: the current text, the selected\noption of a select, the typed text of a list-backed input, and\naria-valuetext, aria-valuenow or the native value for range roles. A\nrange role with no source has no value key.\n\nREADME.npm.md documents the atom now that it returns more than roles.\n\naccessibility_states_typescript_test.html (18 tests) and\naccessibility_value_typescript_test.html (6 tests) were written first\nand failed 18 and 4 of them; the tree page gained the state keys its\nfixtures now carry and failed 7 of 30. All seven pages pass in\nChromium: 61, 24, 60, 138, 65, 83 and 12 assertions.\n\n* [js] walk the flat tree and honour aria-owns\n\nSeventh slice of ADR 18205: the walk and name-from-content follow the\nflat tree instead of childNodes, and aria-owns re-parents.\n\n- hidden.ts gains flatTreeChildren: a shadow host\u0027s children are its\n  shadow root\u0027s children, a slot\u0027s children are the nodes assigned to it\n  (or its fallback while empty, nested slots flattened away), otherwise\n  the DOM children. Light children no slot takes never appear; a slot\n  inside a hidden part of the shadow tree hides what is assigned to it.\n  A closed shadow root cannot be seen, so its host\u0027s light children are\n  walked as a known limitation, which the shadow page pins.\n- tree.ts records every aria-owns relation whose owner is in the walked\n  subtree up front, skips the owned elements at their DOM position and\n  places them after the owner\u0027s own children, so accessibility\n  parentage wins (ADR §3) while the element reference still identifies\n  the element. An element cannot own itself or an ancestor; a missing id\n  is ignored; an owned descendant is placed once in its normal position;\n  a pruned owner hoists what it owns where it would have been; a hidden\n  owner takes it along; an owner outside the root does not claim an\n  element inside it. Owners inside shadow trees are not found by\n  querySelectorAll and leave their targets in place.\n- the per-element logic moves into visitElement so children and owned\n  elements go through the same path; the per-call context carries the\n  ownership map and the claimed set\n\naccessibility_shadow_typescript_test.html (10 tests) and\naccessibility_owns_typescript_test.html (9 tests) were written first\nand failed 8 and 9 of them against the childNodes walk; all nine pages\nnow pass in Chromium: 10, 9, 60, 138, 65, 83, 12, 61 and 24 assertions.\n\n* [js] add the accessibility snapshot corpus\n\nEighth slice of ADR 18205: the shared corpus the record makes the\ncross-binding contract, with the reference text renderer and the checks\nthat keep both honest.\n\ncommon/src/web/accessibility/\n- 16 plain pages covering headings and landmarks, inline text, links\n  and buttons, form controls, lists, tables, images, hidden content,\n  names and descriptions, generic and presentational wrappers, details\n  and dialog, shadow DOM, aria-owns, frames, ARIA widgets, whitespace\n  and generated content; every element that produces a node has an id\n- for each page the whole-document snapshot as .expected.json, with\n  element references written as ids, and as .expected.txt in the text\n  form of ADR §1; index.json lists the pages; README.md gives the\n  conventions, how to record, and where engine divergences go\n\njavascript/atoms/\n- typescript/testing/accessibility-text.ts: the reference toText,\n  `- role \"name\" [state] [state\u003dvalue]: value` with children indented\n  two spaces and `- text: content` leaves, in the test-only bundle so it\n  is never published; the bindings port it and the corpus .txt files\n  hold the ports to one source\n- typescript/accessibility-node.schema.json: the ADR §2 shape as JSON\n  Schema, with the states table spelled out (disabled and focused only\n  ever true, level an integer from 1)\n- test/accessibility_corpus_typescript_test.html loads each page in an\n  iframe and compares tree and text with the expectation, preferring an\n  engine-specific file when one is recorded; with ?record\u003d1 it shows the\n  expectations to record instead\n- two Node tests with no browser: accessibility-unit-tests covers the\n  renderer, accessibility-corpus-consistent checks that every page has\n  both expectation files, every expectation validates against the\n  schema, every .txt is the renderer\u0027s output over its .json, ids are\n  unique and the root is the html element; it fails on a drifted text\n  file and on a false `disabled`, which is what turns the ADR\u0027s\n  compatibility rule into a failing test\n- closure-test gets the corpus as data and //common/src/web:web gains\n  visibility to //javascript/atoms; README.md points at all of it\n\nThe renderer tests were written first (6 failing, then passing); the\ncorpus runner passes 49 assertions over the 16 pages in Chromium and the\nconsistency check passes over every expectation.\n\n* [js] harden the accessibility snapshot atom\n\nNinth slice of ADR 18205: scale, timing and odd input, and a ceiling on\nthe bundle the bindings inject.\n\n- accessibility_robustness_typescript_test.html: a 5000-node subtree, a\n  1000-level deep tree hoisted through without overflowing, 500 cells\n  labelled by one header (the name cache makes that one resolution),\n  an owner of 500 elements, references to nothing, role tokens wrapped\n  in whitespace, an empty role attribute, duplicate ids, non-ASCII and\n  right-to-left text, a huge and a negative aria-level, and two\n  snapshots of the same tree being identical. The bounds are generous\n  enough for a loaded CI runner and exist to catch quadratic behaviour;\n  the measured durations are in the assertion messages, as the record\n  asks the first implementation to measure large trees.\n- accessibility-unit-tests also holds the atom bundle\n  (accessibility-snapshot.generated.js) under 64 KiB and prints its size\n  on every run, so a dependency that balloons the atom fails before it\n  ships. It is 41 KB today.\n\nInterference from page scripts is deliberately not tested: in the same\nrealm the page\u0027s scripts run before the atom and can replace any\nprototype it relies on, so no in-page defence is complete. The bindings\ninstall the atom in a BiDi sandbox realm, whose prototypes the page\ncannot reach, which is the design\u0027s answer (ADR §4). The page says so.\n\nThe page passes 25 assertions in Chromium. Measured there: 5002 nodes\nin 66 ms, the 1000-level tree in 8 ms, 500 labelled cells in 37 ms, 500\nowned elements in 5 ms. The unit test passes 7.\n\n* [js] add a benchmark page for the snapshot atom\n\ntest/accessibility_bench.html times the accessibility-snapshot atom on\nfixed tree shapes (lists, tables, headings, a document-like page, a\nlabelledby fan-in, deep nesting) and counts the DOM calls the walk makes\nby wrapping getComputedStyle, getAttribute, closest, querySelectorAll,\ngetElementById and innerText. It is not a test: the suite only runs\n*_test.html, and timing assertions tight enough to mean anything would\nbe flaky in CI. It is the before/after for the performance work that\nfollows and for the bindings, opened from the debug server.\n\nBaseline, headless Chromium 141, atom at 961f924: 8 to 28 us per\nelement depending on shape, linear in tree size; 1.8 to 8.5\ncomputed-style reads and 11 to 25 attribute reads per element, the high\nend on tables, headings and labelledby fan-in. README.md records the\ntable and how to run it.\n\n* [js] cache hidden state in the snapshot context\n\nPerformance step 1 of the review: hiddenState, isHidden and\nisHiddenByAncestor take the per-call context, cache the state per\nelement, and read computed style through the context\u0027s style cache. The\nwalk, name-from-content and reference resolution all ask about the same\nelements, and each used to pay its own getComputedStyle. The testing\nbundle hands the hidden page a fresh context per call.\n\nNothing in the output changes: all ten unit pages, the corpus and the\nNode tests pass unchanged.\n\nBenchmark (test/accessibility_bench.html, headless Chromium 141),\ngetComputedStyle reads per element and us per element, before -\u003e after:\n\n  list 8000           1.8 -\u003e 1.5    8 -\u003e 7\n  table 1000x10       6.7 -\u003e 3.0   27 -\u003e 12\n  headings 4000       5.7 -\u003e 3.0   17 -\u003e 11\n  document-like 1000  2.5 -\u003e 1.7   15 -\u003e 10\n  fan-in 2000         8.5 -\u003e 2.0   28 -\u003e 14\n\n* [js] skip generated-content reads where none exist\n\nPerformance step 2 of the review. The ::before and ::after text of an\nelement is read once per snapshot and kept in the context, instead of\ntwice per name-from-content traversal that reaches the element. Before\nreading, the atom checks once per tree scope (document or shadow root)\nwhether any of its stylesheets, adopted sheets included and grouping\nrules and imports followed, has a rule that can set generated content;\nwhen none does, the pseudo-element style reads are skipped. A sheet\nwhose rules cannot be read counts as defining them. An element\u0027s own\nshadow root (:host::before) and the tree of the slot it is assigned to\n(::slotted()::before) are consulted as well as its own tree.\n\nThe UA stylesheet is not consulted: its only generated content is the\nopen-quote and close-quote keywords on q, which are not strings and were\nnever text the atom could read; the names page now pins that.\n\nNothing in the output changes: all eleven pages, the corpus and the Node\ntests pass unchanged. The names page gains guards for generated content\nfrom a nested @media rule, from a shadow sheet\u0027s :host::before, and from\nan adopted stylesheet.\n\nBenchmark (headless Chromium 141), getComputedStyle reads per element\nand us per element, after step 1 -\u003e after step 2:\n\n  list 8000           1.5 -\u003e 1.0    7 -\u003e 7\n  table 1000x10       3.0 -\u003e 1.0   12 -\u003e 13\n  headings 4000       3.0 -\u003e 1.0   11 -\u003e 11\n  document-like 1000  1.7 -\u003e 1.0   10 -\u003e 9\n  fan-in 2000         2.0 -\u003e 1.0   14 -\u003e 11\n\nThe bench pages define no generated content, so this measures the gate;\npages that do pay the per-element cache instead.\n\n* [js] cache descendant text alternatives\n\nPerformance step 3 of the review. Two per-call caches in the name\ncomputation, both for the standard traversal (a name, not following a\nreference, hidden content excluded, no control skipped):\n\n- the text alternative of an element reached as a descendant of\n  something being named, so a link inside a cell inside a row is\n  computed once rather than once per content-named ancestor\n- the name-from-content text of an element, so an element whose own\n  name is computed after an ancestor consumed its content does not walk\n  its children again\n\nThe top-level result (computeName) differs in steps 2C, 2E and 2F and\nkeeps its own cache. Other traversals compute afresh.\n\nThe benchmark page gains a shape with three nested content-named levels\n(table with links 500x5), the case these caches exist for. On shapes\nwith one content-named level the attribute reads do not change: the\ndescendant and top-level computations are different modes and cannot\nshare a result.\n\nNothing in the output changes: all eleven pages, the corpus and the Node\ntests pass unchanged.\n\nBenchmark (headless Chromium 141), us per element and getAttribute reads\nper element, step 2 -\u003e step 3, both rebuilt. Times vary by about 20%\nbetween runs; the read counts are the reliable column.\n\n  list 8000                 7 -\u003e 7      10.5 -\u003e 10.5\n  table 1000x10            13 -\u003e 10     19.7 -\u003e 19.7\n  table with links 500x5   16 -\u003e 9      20.2 -\u003e 19.3\n  headings 4000            14 -\u003e 10     15.7 -\u003e 15.7\n  document-like 1000        8 -\u003e 9      13.8 -\u003e 13.8\n  fan-in 2000              12 -\u003e 11     18.5 -\u003e 18.5\n\n* [js] cache reference text alternatives per target\n\nPerformance step 4 of the review. The text alternative of an\naria-labelledby or aria-describedby target is computed once per\nsnapshot and kept in the context. It is the same whoever references the\ntarget and whether for a name or a description, since 2B is not applied\ninside a reference and 2C is; a header that labels every cell of a\ncolumn was computed once per cell.\n\nThe robustness page\u0027s fan-in test claimed the header was \"resolved once\"\nwhen it was not: the name cache covered each cell\u0027s own name, not the\ntarget. The header now holds a probe whose aria-label is read every time\nits text alternative is computed, and the test asserts the count, which\nis what the claim needs.\n\nNothing in the output changes: all eleven pages, the corpus and the Node\ntests pass unchanged.\n\nBenchmark (headless Chromium 141), fan-in 2000 (2,000 cells labelled by\none header), step 3 -\u003e step 4, us per element and getAttribute reads per\nelement:\n\n  fan-in 2000              11 -\u003e 12     18.5 -\u003e 17.5\n\nThe header in the bench is one span of text, so the saving per referrer\nis small there; the probe test shows the count.\n\n* [js] thread aria-disabled down the snapshot walk\n\nPerformance step 5 of the review. The walk already carried two facts\nabout an element\u0027s ancestors down to it (whether their visibility hides\ntext, whether an ancestor\u0027s name consumed the text); it now carries a\nthird, whether an ancestor sets aria-disabled, in one Inherited record.\ncomputeStates takes that flag instead of climbing every ancestor of\nevery node, which was the one O(n * depth) path left. An owned element\nand the root, whose ancestors the walk has not seen, still climb once.\ncore/enabled.ts is asked only about the six element types it can report\nas disabled; for anything else it answered enabled after six tag checks.\n\nNothing in the output changes: all eleven pages, the corpus and the Node\ntests pass unchanged.\n\nBenchmark (headless Chromium 141), us per element and getAttribute reads\nper element, step 4 -\u003e step 5:\n\n  list 8000                 9 -\u003e 8      10.5 -\u003e  9.3\n  table 1000x10            10 -\u003e 13     19.7 -\u003e 14.8\n  table with links 500x5    9 -\u003e 11     19.3 -\u003e 13.9\n  headings 4000            11 -\u003e 12     15.7 -\u003e 13.0\n  document-like 1000       10 -\u003e 12     13.8 -\u003e 11.2\n  fan-in 2000              12 -\u003e 8      17.5 -\u003e 13.0\n\n* [js] drop aria-owns relations that would loop\n\nTwo siblings that own each other were both claimed, so neither was\nvisited at its DOM position and both subtrees vanished from the\nsnapshot. The same happened to the target of an owner whose children\nare presentational (a button owning a menu), since the walk never\ncollects children for it, and the recorded test cases only covered an\nelement owning itself or an ancestor.\n\nAn element is now owned by the first owner in document order that can\ntake it. A relation is dropped when the target is, or contains, the\nowner or any of its accessibility ancestors (ownership recorded so far,\nDOM parentage otherwise), or when the owner has presentational children.\n\nAn owned element inside a hidden container was also placed as rendered,\nbecause only its own hidden state was checked: its DOM ancestors are\nnever reached on the way to it. They are checked now.\n\n* [js] read only rendered text as generated content\n\nChromium serialises the computed content of an icon pseudo-element as\nurl(\"https://host/icon.svg\"), with the URL quoted, and the reader of\nquoted strings took it for text: a button with such an icon was named\nby the image URL followed by its label. Functions in the value are now\nread as one token, so a string beside an image still counts and the\nimage does not.\n\nA pseudo-element with display: none or visibility: hidden has no box\nand no text assistive technology exposes; its content is left out.\n\nGenerated content set through ::part()::before comes from the host\u0027s\nscope, which the gate that skips pseudo-element reads did not consult;\nfor an element with a part name every enclosing tree is checked now.\n\n* [js] stop one reference from silencing the next\n\nThe set of referenced elements already visited was shared by a whole\nname computation, so when two descendants of the element being named\npointed aria-labelledby at the same target, the second reference was\nskipped and that descendant fell through to its content; a row of cells\nlabelled by one header was named \"Qty 2\" instead of \"Qty Qty\", and the\nwrong text was cached for the descendant.\n\nNothing below a reference follows references again, so the set guarded\nagainst no cycle. It is now a per-attribute set that only stops an id\nlisted twice from counting twice, which is how Chromium reads it.\n\n* [js] report focus inside shadow roots\n\nThe focused state compared the element with the document\u0027s active\nelement, which for an element inside a shadow tree is the shadow host:\nthe focused element was never reported and, when the host produced a\nnode, the host was. The check now asks the element\u0027s own root, and a\nhost whose open shadow root holds the focus is not itself focused.\n\n* [js] name button inputs from their labels first\n\nhtml-aam ranks the label elements of a submit, reset or button input\nahead of its value attribute, and Chromium exposes them that way. The\natom skipped the labels for those three types only, so a labelled\nsubmit button was named \"Submit\".\n\nA number input takes a placeholder too; it is now a placeholder source.\n\n* [js] scope banner and contentinfo by ancestor role\n\nA header or footer is a landmark only outside article, aside, main, nav\nand section elements and outside elements with the article,\ncomplementary, main, navigation or region role (html-aam). Only the\nelement names were checked, so a header inside a div with role\u003dmain was\nreported as a banner.\n\n* [js] keep a snapshot value on one line of text\n\nThe text form writes a node\u0027s value after the colon as is, so a\ntextarea whose value holds a line break produced a node spanning two\nlines, which no port of the renderer can read back and which would make\na recorded expectation ambiguous. The renderer now escapes line breaks\nand backslashes in the value (\\n, \\r, \\\\); names already go through\nwhitespace collapsing and never needed it.\n\naria-valuetext was also returned untrimmed where aria-valuenow was\ntrimmed; both are trimmed now.\n\n* [build] check out the corpus expectations with LF\n\nThe corpus consistency test compares each .expected.txt byte for byte\nwith the renderer\u0027s output over its .expected.json. The repository\nforces LF for html, js and css but these files only had text\u003dauto, so a\ncheckout with core.autocrlf on turned every comparison red although\nnothing was wrong.\n\n* [js] look up owner roles through the context\n\nThe role of a cell, header, row or row group depends on its table\u0027s,\nand an li\u0027s on its list\u0027s, but those were recomputed from the attribute\nfor every dependent element instead of going through the per-call role\ncache: the table of a 1000x10 grid had its role attribute read 2202\ntimes in one snapshot. The role functions now take a lookup for the\nowner\u0027s role, which the context routes through its cache, and a cell\nreads its table\u0027s role once rather than computing it and then reading\nthe attribute again.\n\nAttribute reads per element, bench page in headless Chromium:\ntable 1000x10 14.8 to 12.9, table 100x100 15.0 to 13.0, table with\nlinks 13.9 to 12.9, fan-in 13.0 to 11.5, list 9.3 to 9.0; computed\nstyle reads unchanged at 1.0.\n\n* [build] give test bundles every test-only module\n\nThe accessibility test bundle imports its text renderer from a sibling\nfile under typescript/testing/, but each test bundle listed only the\natom sources and its own entry as srcs. esbuild under Bazel resolves\nimports among the sources it is given, so the bundle failed to build on\nCI and took the two Node tests and the three closure-test targets with\nit from the corpus commit on; the local stand-in used for development\nresolved from disk and did not show it.\n\nEvery test-only module is now a source of every test bundle. Only what\nan entry imports ends up in its bundle.\n\nThe same file writes //common/src/web in the short form buildifier\nwants; the format check on CI failed on the long one.\n\n* [build] copy the corpus into the Node test\u0027s bin\n\nThe corpus consistency test took //common/src/web as data, a filegroup\nof source files in another package, and rules_js refuses to copy those\ninto a js_test\u0027s runfiles (\"Expected to find source file BUILD.bazel in\n//javascript/atoms, but instead it is in //common/src/web\"), so the\ntarget failed analysis on CI while the three browser suites passed. The\ncorpus now comes through a js_library in its own package, which copies\nit to the output tree the test runs in.\n\n* [js] find aria-owns owners through the flat tree\n\nOwners were found with querySelectorAll on the root, which never looks\ninside shadow roots and does find light-DOM children no slot renders.\nAn owner inside an open shadow root therefore left its targets at their\nDOM position, and an owner that is never rendered claimed a rendered\ntarget, which was then skipped at its position and never placed: it\nvanished from the snapshot.\n\nOwners are now collected along the flat tree the walk itself visits.\n\n* [js] report states as WAI-ARIA scopes them\n\naria-disabled on an ancestor reaches the descendants that can take\nfocus, not the headings and paragraphs between them (WAI-ARIA 1.2); the\natom marked everything below as disabled. The ancestor check for a root\nor owned element also stopped at a shadow root, so a control inside a\nhost with aria-disabled lost the state when snapshotted on its own.\n\naria-pressed, aria-expanded and, on grid parts, aria-selected set to\n\"undefined\" or empty now count as absent, which is what those values\nmean: not a toggle, not expandable, not selectable. An option element\ngiven another role no longer reports the native selection.\n\n* [js] mask password values in the snapshot\n\nA password field\u0027s value was reported as typed. Snapshots end up in\nlogs, reports and failure artifacts, and the browser\u0027s own tree exposes\none bullet per character, so the atom does the same, for the value and\nfor what the field contributes to an enclosing name. Corpus page 004 is\nre-recorded for it.\n\n* [js] treat only HTML slot elements as slots\n\nThe flat tree asked any element named slot for its assigned nodes; an\nelement with that name in another namespace has no such method and\naborted the snapshot.\n\n* [js] pin generated names on shadow hosts\n\nA :host::before rule in a shadow sheet names the host through its\ngenerated content. For a closed root the gate that skips pseudo-element\nreads cannot see the sheet, so that text is found only while some sheet\nin scope defines generated content; the test records the working case\nand that limit.\n\n* [py] add accessibility_snapshot() to the binding\n\nFirst binding slice of ADR 18205. Both methods return an\nAccessibilityNode (role, name, description, value, states, element,\nchildren) with to_text(), the text form of the record.\n\nselenium/webdriver/remote/accessibility_snapshot.py runs the\naccessibility-snapshot atom over BiDi: script.addPreloadScript installs\nit into the \"selenium\" sandbox realm of every new document, and a\ndocument that predates the preload installs it on first use in one\ncallFunction. Results come back with maxDomDepth 0, so every element is\na node remote value whose sharedId becomes a WebElement. Each iframe\nleaf is filled in with the tree of its browsing context, found through\nthe frame\u0027s contentWindow, so cross-origin frames stitch too; a frame\nthat cannot be reached stays a leaf. A subtree root may live in any\nframe of the window, so its shared id is tried against each browsing\ncontext of the window until one owns it; when none does the root is\nstale and StaleElementReferenceException is raised, as it is for a root\nthe atom reports detached. A session without BiDi raises\nWebDriverException naming BiDi, per the record. The atom is copied into\nthe package like the other atoms and is git-ignored like them.\n\nTests, written first:\n- unit: to_text() over every page of the shared corpus must equal the\n  recorded expected.txt, which holds this port to the reference\n  renderer; the line grammar\u0027s escapes; the decoder over a hand-written\n  callFunction result\n- bidi: every corpus page\u0027s whole-page snapshot equals the recorded\n  JSON and text, with element references mapped to ids and an engine-\n  specific expectation preferred where one exists; the document root;\n  references usable for further commands; a subtree root always\n  producing a node; frames stitched in, including a snapshot taken\n  while the driver is switched into a frame and a subtree snapshot of\n  an element inside one; stale and detached roots; a snapshot being a\n  copy\n- classic: both methods raise and name BiDi\n\nVerified locally with the generated bidi package, pytest and\nChromeDriver 141 against headless Chromium 141: 19 unit, 25 BiDi and 2\nclassic tests pass; ruff and mypy are clean. CI runs the Bazel targets\n//py:unit and //py:test-{chrome,firefox,edge}-bidi.\n\n* [js] add getAccessibilitySnapshot() to the binding\n\nSecond binding slice of ADR 18205, after Python. Both methods return an\nAccessibilityNode (role, name, description, value, states, element,\nchildren) with toText(), the text form of the record; the class is\nexported from the package.\n\nlib/accessibility_snapshot.js runs the accessibility-snapshot atom over\nBiDi: script.addPreloadScript installs it into the \"selenium\" sandbox\nrealm of every new document, and a document that predates the preload\ninstalls it on first use in one callFunction. The atom module built by\nlib/atoms/make-atoms-module.js is a function whose source is the\nfunction declaration the call takes, so nothing is stringified by hand.\nResults come back with maxDomDepth 0, so every element is a node remote\nvalue whose sharedId becomes a WebElement. Each iframe leaf is filled in\nwith the tree of its browsing context, found through the frame\u0027s\ncontentWindow, so cross-origin frames stitch too; a frame that cannot be\nreached stays a leaf. A subtree root may live in any frame of the\nwindow, so its shared id is tried against each browsing context until\none owns it; when none does the root is stale and\nStaleElementReferenceError is thrown, as it is for a root the atom\nreports detached. A session without BiDi throws\nUnsupportedOperationError naming BiDi, per the record.\n\nThe three commands go over the driver\u0027s BiDi connection directly rather\nthan through the generated domain modules: those validate every node\nremote value\u0027s attribute map against the schema and warned once per\nelement, which a snapshot of a few hundred elements turned into a wall\nof console noise.\n\nTests, written first:\n- small: toText() over every page of the shared corpus must equal the\n  recorded expected.txt, which holds this port to the reference\n  renderer; the line grammar\u0027s escapes; the decoder over a hand-written\n  callFunction result; the BiDi guard\n- large, on every BiDi browser: every corpus page\u0027s whole-page snapshot\n  equals the recorded JSON and text, with element references mapped to\n  ids and an engine-specific expectation preferred where one exists;\n  the document root; references usable for further commands; a subtree\n  root always producing a node; frames stitched in, including a snapshot\n  taken while the driver is switched into a frame and a subtree snapshot\n  of an element inside one; stale and detached roots; a snapshot being a\n  copy\n\nVerified locally with the generated BiDi modules compiled from the\npinned CDDL, mocha and ChromeDriver 141 against headless Chromium 141:\n21 small and 25 large tests pass; eslint and prettier are clean. CI runs\n//javascript/selenium-webdriver:small-tests and the per-browser\naccessibility_snapshot targets.\n\n* [py] fix accessibility snapshot frame roots\n\nFirefox resolves a shared id in any document of the browsing context\ngroup, not only the one that owns the node, so a subtree snapshot\nrooted in a child frame ran against the top document. The called\nscript now checks ownership and answers null for a root it does not\nown, which the client treats like an unknown node and moves on to the\nnext context.\n\nAn iframe taken as the root now carries its document, the same as an\niframe found below the root.\n\nFrame stitching and context discovery swallow only errors that say a\nframe or node is gone; anything else propagates. The local_dev rake\ntask copies the atom alongside the other remote atoms.\n\n* [js] fix accessibility snapshot frame roots\n\nFirefox resolves a shared id in any document of the browsing context\ngroup, not only the one that owns the node, so a subtree snapshot\nrooted in a child frame ran against the top document. The called\nscript now checks ownership and answers null for a root it does not\nown, which the client treats like an unknown node and moves on to the\nnext context.\n\nAn iframe taken as the root now carries its document, the same as an\niframe found below the root.\n\nFrame stitching and context discovery swallow only errors that say a\nframe or node is gone; anything else propagates.\n\n* [js] frames always report the iframe token\n\nAn iframe with an explicit role was reported under that role, and the\nbindings find the frames whose documents they stitch in by the ADR\u0027s\niframe token, so such a frame never got its document. ARIA in HTML\nallows application, document, img, none and presentation on a frame,\nand the embedded document is exposed whichever one the frame carries,\nso the token now wins over the explicit role; the roles and tree pages\npin it.\n\n* [js] run the snapshot suite on BiDi browsers only\n\nThe snapshot needs BiDi, which the test environment enables for\nChrome, Edge and Firefox only; on macOS the harness also runs Safari,\nwhere every case would hit the unsupported-session guard. The suite\nnames the three browsers, which are all of the Bazel browsers, so the\nfile needs no entry in test_browsers.bzl.\n\n* [js] let eslint resolve the snapshot atom module\n\nThe eslint test under Bazel checks that every static require resolves\nin its runfiles. lib/accessibility_snapshot.js requires the built atom\nmodule directly, where the other sources load their atoms through a\npath built at run time that the rule cannot follow, so the module is\nnow in the lint target\u0027s data, as it already is in the package.\n\n* [js] leave the generated atom module out of eslint\n\nThe lint target now carries lib/atoms/accessibility-snapshot.js so the\nrequire in lib/accessibility_snapshot.js resolves, and eslint then\nlinted the generated bundle itself, which prettier rejects line by\nline. It joins the generated BiDi modules in the ignore list.\n\n* [rb] add accessibility_snapshot to the binding\n\nDriver#accessibility_snapshot and Element#accessibility_snapshot return\nan AccessibilityNode (role, name, description, value, states, element,\nchildren) with #to_text, the Ruby adoption of ADR 18205. The atom is\ncopied into the gem like the other atoms and run over BiDi: a preload\nscript installs it into the \"selenium\" sandbox realm of every new\ndocument, a document that predates the preload installs it on first\nuse, and results come back with maxDomDepth 0 so each element is a\nnode remote value whose sharedId becomes an Element. Frames are\nstitched through contentWindow, a subtree root is looked for in each\nbrowsing context of the window with the script checking ownership, a\nstale or detached root raises StaleElementReferenceError, and a\nsession without BiDi raises UnsupportedOperationError naming\nweb_socket_url.\n\nThe unit spec holds #to_text to every corpus page\u0027s recorded text, the\nline grammar\u0027s escapes and the wire decoder; the BiDi spec snapshots\nevery corpus page whole and compares tree and text, then covers the\ndocument root, element references, subtree roots, frames, stale and\ndetached roots, a snapshot being a copy, and the classic guard.\nSignatures cover the new class and methods.\n\n* [rb] load the BiDi error classes the snapshot uses\n\nError::NoSuchNodeError, which the frame search raises and rescues, is\nregistered by bidi/error.rb from the generated error codes. The BiDi\nbridge always loads that file, and only a BiDi session reaches the\nsnapshotter, but the dependency was implicit; the module now requires\nit itself. A unit spec runs the search over a stand-in connection: the\ntop-level document refuses the root, the child document owns it, and\nno owner at all is the stale element error.\n\n* [rb] keep the snapshot\u0027s error class in common\n\n07d6cbf made common/accessibility_snapshot.rb require bidi/error for\nError::NoSuchNodeError, which every spec target that depends on the\ncommon library alone cannot load (its runfiles hold no bidi file), and\nwhich loads the BiDi class ahead of its autoloads in the unit target.\nThe search over the window\u0027s contexts now raises and rescues its own\nNotOwnedError, a WebDriverError subclass, for the BiDi \"no such node\"\nerror and the script\u0027s null answer, so common depends on nothing from\nthe BiDi library at load time.\n\n* [js] fix form and disabled control roles\n\nhtml-aam gives a form element the form landmark only when it has an\naccessible name; the atom gave it to every form. An unnamed form is\nnow generic, so it is pruned and its controls take its place, which\nre-records corpus pages 001 and 004. The roles page pins a named, a\ntitled and an unnamed form.\n\nA disabled control with a tabindex cannot take focus, but the focus\ncheck read the tabindex first and so refused the presentational role\non such a button; native controls are now checked for being disabled\nbefore the tabindex is read. Pinned on the roles page.\n\n* [js] landmark names from a label\u0027s alternatives\n\nWhether a form, section or nested aside has an accessible name decides\nits role, and the check read only the text and aria-label of an\naria-labelledby target. A label whose text comes from an image\u0027s alt,\na tooltip or a control\u0027s value, as accname reads it, now counts too,\nso such a form keeps its landmark. Pinned on the roles page for a form\nand a section labelled by an element holding only an image.\n\n* [py] guard and preload fixes from review\n\nThe BiDi guard moves out of the snapshotter into the accessor both\ndriver.accessibility_snapshot() and element.accessibility_snapshot()\ngo through, where the session is checked before anything is built.\n\nThe preload flag was set before script.addPreloadScript was sent, so\na failed preload looked installed and was never tried again; it is now\nset only after the call succeeds, and a unit test runs the preload\nagainst a stand-in that fails until told otherwise.\n\n* [js] set the preload flag only after it succeeds\n\nThe flag was set before script.addPreloadScript was sent, so a failed\npreload looked installed and was never tried again; it now follows the\nsuccessful call, and a small test runs the preload against a stand-in\nconnection that fails until told otherwise.\n\n* [rb] set the preload flag only after it succeeds\n\nThe flag was set before script.addPreloadScript was sent, so a failed\npreload looked installed and was never tried again; it now follows the\nsuccessful call, and the unit spec\u0027s stand-in connection can refuse\nthe preload to show the next snapshot tries it again.\n\n* [js] share one preload across snapshots\n\nWith the flag set only after script.addPreloadScript succeeds, two\nsnapshots started before the first preload returned each registered a\nscript. The registration is now a shared promise: overlapping\nsnapshots await the same one, and a failed one is dropped so the next\nsnapshot tries again. A small test starts two preloads and releases\none response.\n\n* [java] add getAccessibilitySnapshot() to the binding\n\nWebDriver and WebElement gain a default getAccessibilitySnapshot()\nthat throws UnsupportedOperationException, as getAriaRole does, so\nthird-party implementations keep compiling. RemoteWebDriver and\nRemoteWebElement implement it over BiDi: the atom is preloaded into\nthe \"selenium\" sandbox realm and installed on demand for documents\nthat predate the preload, child frames are stitched in through their\ncontentWindow, and a subtree root is looked up in every context of\nthe window with an ownership check, since Firefox resolves shared ids\nacross the browsing context group. A root that no document owns\nraises StaleElementReferenceException; a classic-only session raises\nUnsupportedCommandException naming BiDi before any command is sent.\n\nAccessibilityNode is a plain immutable value with the ADR\u0027s fields\nand toText() rendering the shared line grammar. The small test holds\ntoText() to every recorded corpus page; the bidi test snapshots each\ncorpus page whole and compares the serialized tree and text, plus\nframes, subtrees, stale and detached roots.\n\n* [js] read attr() in generated content, cache image maps\n\nChromium resolves an attr() in a pseudo-element\u0027s computed content to\nits string before serialising it; Firefox keeps the function, so a\nbutton named by `content: attr(data-label)` lost that label there.\nThe parser now reads the named attribute from the originating element\nfor any attr() token, and still treats url() and the other functions\nas not text. The names page pins a present and a missing attribute.\n\nAn area is as hidden as the image that uses its map, found with a\ndocument-wide query that every area of the map repeated. The context\nnow caches the answer per map name, so a map of 200 areas queries the\ndocument once; the robustness page counts the queries.\n\n* [rb] build snapshot elements with the bridge\u0027s class\n\nElement references in a snapshot were plain Element instances, so a\nclient that configured Remote::Bridge.element_class got elements\nwithout its methods from a snapshot while every other lookup honoured\nthe class. The snapshotter now goes through the same configured\nclass, and the unit spec snapshots with a custom one. The signature\nfor the bridge\u0027s class-level accessor is added for steep.\n\n* [build] sort the core small tests\u0027 dependencies\n\nbuildifier orders deps lexically; the InProject dependency added for\nthe accessibility corpus sat above the source packages, which failed\nthe Format check.\n\n* [dotnet] add GetAccessibilitySnapshotAsync()\n\nWebDriver and WebElement gain GetAccessibilitySnapshotAsync(), as\nvirtual methods on the classes like ComputedAccessibleLabel rather\nthan interface members, so IWebDriver and IWebElement implementations\nkeep compiling. The driver opens one BiDi connection on the first\nsnapshot and closes it with the session: the atom is preloaded into\nthe \"selenium\" sandbox realm and installed on demand for documents\nthat predate the preload, child frames are stitched in through their\ncontentWindow, and a subtree root is looked up in every context of\nthe window with an ownership check, since Firefox resolves shared ids\nacross the browsing context group. A root that no document owns\nraises StaleElementReferenceException; a session without a\nwebSocketUrl raises UnsupportedOperationException naming\nUseWebSocketUrl before any command is sent.\n\nAccessibilityNode is an immutable class with the ADR\u0027s fields and\nToText() rendering the shared line grammar. The unit tests pin the\ngrammar and the guard over a recording executor; the browser test\nsnapshots each corpus page whole and compares the serialized tree and\ntext, plus frames, subtrees, stale and detached roots.\n\n* [build] let the core small tests read the web fixtures\n\n//java/test/org/openqa/selenium:SmallTests takes //common/src/web as\ndata for the accessibility corpus, but the filegroup was visible only\nto the environment package, so RBE failed analysis.\n\n* [dotnet] close the snapshot connection safely\n\nDisposal now takes the connection lock, so a first snapshot still\nconnecting when the driver is disposed cannot leave its BiDi\nconnection open, and a snapshot started after disposal fails with\nObjectDisposedException instead of reconnecting. A failure while\nclosing the connection is logged rather than thrown, so the driver\u0027s\nown session quit and executor disposal still run.\n\n* [dotnet] drop an unused using from the unit test\n\nRemoteSessionSettings lives in OpenQA.Selenium, so the Remote using\nwas unused and dotnet format style removed it in the Format check.\n\n* [js] add the wire shape of the accessibility atom\n\nEvery binding decoded the snapshot from a nested BiDi remote value,\nasked for each frame\u0027s contentWindow in a second call, and read\n\"detached\" out of an exception\u0027s text. accessibilitySnapshotWire\nreturns the same snapshot as the bindings want it: the tree as one\nJSON string with element references replaced by indices into an\nelements array and frame nodes given an index into a windows array of\ntheir content windows, null for a root another document owns, and\n{error: \"detached\"} for a root no longer in its document. A binding\nparses one string and maps two arrays.\n\nThe encoder is pure and unit tested in Node; the wire page covers the\ndefault function over a document, a frame, a foreign root and a\ndetached root; the corpus page decodes every recorded page through the\nencoder. The fragment is registered beside the atom with the same\nvisibility, exported from the package, and held to the same 64 KiB\nceiling.\n\n* [py] fetch the snapshot in the atom\u0027s wire shape\n\nThe binding now embeds the atom\u0027s wire entry and sends it inline with\none script.callFunction per document: the result is the tree as JSON\ntext beside the element references and the frames\u0027 content windows,\nso the nested remote-value decoder, the second call per frame and the\n\"detached\" text match go away, and so does the preload with its flag,\nits retry rule and its on-demand install, a round of review bugs in\nitself. A root another document owns comes back as null and a\ndetached root as an error value, both read as data.\n\nThe unit tests cover the wire decoder and keep the text form held to\nthe corpus; the BiDi suite is unchanged and passes on Chrome.\n\n* [js] fetch the snapshot in the atom\u0027s wire shape\n\nThe atom module is now built from the wire entry and sent inline with\none script.callFunction per document: the result is the tree as JSON\ntext beside the element references and the frames\u0027 content windows,\nso the remote-value decoder, the second call per frame, the \"detached\"\ntext match and the preload with its shared promise, retry rule and\non-demand install all go away. A root another document owns comes\nback as null and a detached root as an error value.\n\nThe small tests cover the wire decoder, the two data answers and the\nstitching of a frame through its window index; the browser suite is\nunchanged and passes on Chrome.\n\n* [rb] fetch the snapshot in the atom\u0027s wire shape\n\nThe gem now carries the atom\u0027s wire entry and sends it inline with one\nscript.callFunction per document: the result is the tree as JSON text\nbeside the element references and the frames\u0027 content windows, so the\nremote-value decoder, the second call per frame, the \"detached\" text\nmatch and the preload with its flag and on-demand install go away. A\nroot another document owns comes back as nil and a detached root as\nan error value, both read as data.\n\nThe unit spec covers the wire decoder and the two data answers over a\nstand-in connection; the BiDi spec is unchanged and passes on Chrome.\nThe signatures follow.\n\n* [java] fetch the snapshot in the atom\u0027s wire shape\n\nThe jar now carries the atom\u0027s wire entry and sends it inline with one\nscript.callFunction per document: the result is the tree as JSON text\nbeside the element references and the frames\u0027 content windows, so the\nremote-value decoder, the second call per frame, the \"detached\" text\nmatch and the preload with its flag and on-demand install go away. A\nroot another document owns comes back as null and a detached root as\nan error value, both read as data.\n\nThe unit test covers the wire decoder; the BiDi test is unchanged and\npasses on Chrome.\n\n* [dotnet] fetch the snapshot in the atom\u0027s wire shape\n\nThe assembly now carries the atom\u0027s wire entry and sends it inline with\none script.callFunction per document: the result is the tree as JSON\ntext beside the element references and the frames\u0027 content windows, so\nthe remote-value decoder, the second call per frame, the \"detached\"\ntext match and the preload with its flag and on-demand install go\naway. A root another document owns comes back as null and a detached\nroot as an error value, both read as data.\n\nThe BiDi connection stays owned and disposed as before; the browser\ntests are unchanged and pass on Chrome.\n\n* [dotnet] run the snapshot tests on the shared driver\n\nThe browser fixture derives from DriverTestFixture and asks for the\nWebSocket URL in ConfigureDriverOptions, so one browser serves every\ntest instead of one per test. The corpus expectations are fetched from\nthe test web server, as the pages themselves are, so the test no\nlonger locates common/src/web on disk.\n\n* [dotnet] put the System using first in the snapshotter\n\nThe Format job\u0027s dotnet format style sorts System namespaces ahead of\nthe project\u0027s. The text renderer also says why it joins lines with LF\non every platform.\n\n* [dotnet] let the driver own the BiDi connection\n\nWebDriver now holds one IBiDi, opened on first use through the internal\nBiDiAsync accessor and closed with the driver, so every component that\nworks over BiDi shares it. The snapshotter takes that connection in its\nconstructor and keeps no state of its own: the connection lock, the\ndisposal and the ObjectDisposedException move to the driver, and the\ndriver and element entry points create a snapshotter per call.\n\nThe browser tests are unchanged and pass on Chrome.\n\n* [dotnet] close the BiDi connection once and keep its lock\n\nOnly the first disposal closes the driver\u0027s BiDi connection, and the\nlock that serialises the connection attempt is released rather than\ndisposed, so a caller queued behind the close wakes to find the driver\ngone instead of waiting forever.\n\nThe test app server\u0027s second start now returns the running listener\u0027s\nURLs instead of leaving the first one behind, and the remote path of\nthe driver factory applies the fixture\u0027s options callback, so the\nsnapshot tests get their WebSocket URL on a remote run too.\n\n* [dotnet] read the corpus with a client per request\n\nThe NUnit analyzer refuses a disposable field without a one-time\ntear-down (NUnit1032), and the case list is read at discovery time\nbefore any tear-down could pair with it, so each read opens its own\nHttpClient. The fetch helper also drops the nullable annotation the\ntest project does not enable.\n\n* [js] answer null for a null root in the wire atom\n\nFirefox hands a shared id of another document\u0027s node to the called\nfunction as null, and the wire entry read null as \"no root\" and\nsnapshotted the whole document, so on Firefox a subtree snapshot rooted\ninside a frame came back as the top document in every binding. A null\nroot now answers null, the same as a root another document owns, so the\nbindings move on to the next browsing context; only an absent root\nmeans the whole document. The wire page pins both answers.\n\n* [dotnet] open the BiDi connection as the session starts\n\nA session whose capabilities carry a WebSocket URL now connects to it\nin the constructor, right after the session is created, so every\ncomponent that works over BiDi shares one connection for the driver\u0027s\nlife and nothing is opened lazily: the connection lock and the close\nflag go, the accessor is a plain property, and disposal closes the one\nconnection. A connection that cannot be made fails the constructor,\nwhich quits the session it just started; the unit test pins that with\na WebSocket URL nothing listens on.\n\n* [dotnet] answer an unowned root with null, not a throw\n\nThe BiDi \"no such node\" error meant the document of the context did\nnot own the root, and travelled as an internal exception the context\nsearch and the frame stitching caught. The call now answers null for\nit, the same answer the atom gives for a root another document owns,\nso the search reads one value and the exception type goes.\n\n* [dotnet] name the snapshot helpers with verbs\n\nThe private helpers of the snapshotter, the node\u0027s text renderer and\nthe tests read as nouns (Items, Required, ContextOf, Line, Roles);\neach now says what it does (ReadItems, GetRequired, ReadContext,\nFormatLine, ListRoles). The \"no such node\" answer is read as null only\nfor a call that sent a shared id, so a failed whole-page call keeps\nits error.\n\n* [dotnet] give the snapshotter no reference to the driver\n\nThe snapshotter used the driver for two things: the handle of the\nwindow to snapshot and turning a node\u0027s shared id into an element. It\nnow takes the BiDi connection, that handle and an element factory in\nits constructor, so it depends on nothing above it; the driver\u0027s\naccessor supplies the three.\n\n* [dotnet] keep one snapshotter per driver\n\nThe snapshotter is stateless, so the driver creates it once, on first\nuse and thread-safely through Lazy\u003cT\u003e, instead of once per call. It\nnow takes an action for the current window handle rather than the\nvalue, so the one instance snapshots whichever window is current, and\nthe driver drops it with the connection on disposal.\n\n* [dotnet] leave the test app server as it was\n\nThe idempotent StartAsync was out of this change\u0027s scope: the double\nstart it guarded against predates the snapshot tests and does not\naffect them, since the server the case list is read from is running\neither way. The file is back to trunk.\n\n* [dotnet] mock the executor in the snapshot unit tests\n\nThe unit tests built a recording ICommandExecutor and a driver subclass\nby hand and asserted on the commands the driver sent. The maintainer\nasked that unit tests depend on ICommandExecutor as little as possible,\nsince it is going away, and mock with Moq rather than subclass. The\nexecutor is now a Moq mock answering every command with success, the\ndriver a Moq mock of WebDriver calling the base, and the tests assert\nonly what is thrown: the snapshot methods raise without BiDi, and a\nsession whose WebSocket URL cannot be reached fails fast.\n\n* [dotnet] name the snapshot unit tests by what they check\n\nThe maintainer asked that the unit tests not enable parallelization\nexplicitly, which the project will switch on in one place later, and\nthat they be named in the method, scenario and expected result form the\nother .NET unit tests use. Both fixtures drop the Parallelizable\nattribute, and each test now names the member it exercises, the input\nand the outcome; the test that held four line grammar cases is split\ninto one test per case, and the ToString check into its own test.\n\n* [dotnet] say when a snapshot cannot be taken\n\nSince the driver connects to BiDi in its constructor, a session that\nasked for BiDi and could not connect never reaches the snapshot\nmethods: it fails while the driver is constructed. The documentation of\nGetAccessibilitySnapshotAsync on WebDriver and WebElement still said\nthe UnsupportedOperationException is thrown when \"the session has no\nBiDi connection\", which now means only a session started without\nUseWebSocketUrl. Both say so, and point to the constructor for the\nother case; the constructor documents that it opens the connection,\nshares it, and quits the session it started when the connection cannot\nbe made. The internal accessor\u0027s comments follow the same wording.\n\n* [dotnet] await the corpus reads in the snapshot tests\n\nThe browser fixture fetched the corpus expectations from the test web\nserver by blocking on the HttpClient tasks with GetAwaiter().GetResult().\nThe maintainer asked for an async FetchAsync instead. The expectation\nreaders are now async and awaited from the test, which is async already.\nThe one read that cannot await is the case list, since NUnit discovers\ntest cases synchronously; it uses the client\u0027s synchronous Send and\nparses the index from the response stream, so no task is blocked on.\n\n* [dotnet] document the snapshot members with XML docs\n\nThe maintainer asked that members be described with the language\u0027s\ndocumentation comments rather than line comments, private ones too.\nEvery line comment that sat above a member of the snapshotter, the node,\nthe driver\u0027s BiDi members and the browser fixture is now a \u003csummary\u003e,\nand the private helpers that had no comment get a one-line summary.\nNo code changes.\n\n* [dotnet] move the BiDi connection test to its fixture\n\nThe test that a driver fails to construct when its WebSocket URL cannot\nbe reached sat in the accessibility snapshot unit tests, where the\nmaintainer pointed out it does not belong: it checks the driver\u0027s BiDi\nconnection, not snapshots. It moves to WebDriverConnectionTests in the\nBiDi unit test folder, and the snapshot fixture keeps only the two\ntests about snapshots without BiDi, with a helper that no longer takes\na WebSocket URL.\n\n* [dotnet] assert the connection test\u0027s exception type\n\nThe test that a driver fails to construct when its WebSocket URL cannot\nbe reached accepted any exception, so an unrelated constructor failure\nwould have passed it. It now asserts the WebSocketException the refused\nconnection raises, as the inner exception of the invocation exception\nthe mocked driver\u0027s proxy wraps it in, and its name says so.\n\n* [dotnet] use the newer member style in the new classes\n\nThe maintainer asked that classes new in this PR follow the newer .NET\nconventions rather than the older ones the existing classes still mix:\nno `this.` when accessing members, private instance fields named\n`_camelCase` and private static fields `s_camelCase`. The snapshotter,\nthe node and the browser fixture drop every `this.` qualifier, the\nsnapshotter\u0027s three fields and logger and the node\u0027s state order are\nrenamed, and the classes that are not new keep their existing style.\nNo behaviour changes.\n\n* [dotnet] name the snapshotter field after its class\n\nThe maintainer asked that the driver\u0027s field holding the lazily created\nAccessibilitySnapshotter be named after that class rather than\n\"accessibility\". No behaviour change.\n\n* [dotnet] fix the doc comment on CreateElementFromReference\n\nThe helper\u0027s XML comment was copied from GetElementFromResponse and\nstill described a \"response\" parameter the method does not have, which\nthe compiler reported as CS1572 on every .NET build. The comment now\ndescribes the method and its elementId parameter. The other warning the\nbuild reported, a nullable annotation in the snapshot unit tests, went\naway when that test moved in 7e06a36. No code changes.\n\n* [dotnet] bridge the BiDi connection via Task.Run\n\nThe constructor is synchronous, so the connection it opens was\nawaited inline with GetAwaiter().GetResult(). Make ConnectBiDiAsync\ntruly async, still through AsBiDiAsync(), and have the constructor\nbridge to it on the thread pool with Task.Run, the way Execute()\nbridges to ExecuteAsync(). A caller constructing a driver under a\nsynchronization context no longer deadlocks on the connection.\nDispose bridges to CloseBiDiAsync the same way.\n\n* [dotnet] name the node constructor args in tests\n\nThe AccessibilityNode constructor takes seven positional values, and\nin the unit tests several of them are empty strings or nulls, so a\nreader could not tell which was the description and which the value.\nName every argument at each construction site.\n\n* [dotnet] await the snapshot in the public methods\n\nThe two public GetAccessibilitySnapshotAsync methods returned the\nsnapshotter\u0027s task directly, so the no-BiDi UnsupportedOperationException\nwas thrown at the call rather than at the await, and the public frame\nwas missing from stack traces. Make them async and await the snapshotter\nwith ConfigureAwait(false); the internal snapshotter is unchanged.\n\n* [dotnet] return the snapshot task directly again\n\nThis reverts b3252f5. The .NET maintainer approved returning the\nsnapshotter\u0027s task from the two public GetAccessibilitySnapshotAsync\nmethods without an async state machine: the snapshotter is internal\nand the single entry point, so the frames the public method would add\nto a stack trace hold nothing a user can act on, and the direct return\nis the cheaper call.\n\n* [java] name the atom call helper callAtom\n\nThe private helper that runs the atom\u0027s wire entry in a browsing\ncontext\u0027s sandbox was named call, which said nothing about what is\ncalled. Name it callAtom, as the Java reviewer asked.\n\n* [java] name the ownership error NotOwnedException\n\nThe private exception the snapshotter raises for a shared id the\ntargeted document does not own was named NotOwned. Java exception\nclasses end in Exception; name it NotOwnedException, as the Java\nreviewer asked.\n\n* [java] wrap the line the rename made too long\n\nThe NotOwnedException rename pushed one return statement past the\n100 columns google-java-format allows; wrap it the way the formatter\ndoes.\n\n* [java] name the BiDi error codes the snapshotter reads\n\nThe snapshotter matched the BiDi \"no such node\" and \"no such frame\"\nerrors on string literals repeated at each use. Hold them in two\nconstants, as the Java reviewer asked.\n\n* [js] add url and interestingOnly to the atom\n\nTwo additions the ADR gained in review. A node of role link carries\nthe resolved href of its a or area element as url, as CDP\u0027s tree\nexposes it; an anchor without href, or an element made a link by role\nalone, has none, and the text form does not render it. Both entries\ntake an options object: with interestingOnly false the pruning rule is\noff, every rendered element produces a node and generic, none and\npresentation are reported as the roles they are, after Puppeteer\u0027s\naccessibility.snapshot({ interestingOnly: false }); hidden-ness,\nnames, states and references are computed the same way in both modes.\n\nThe schema gains url; the wire form carries it and passes the options\nthrough. The tree page pins the url rules and both modes on new\nfixtures, the wire page the option and the url in the JSON text, the\nNode tests the url in the wire node. The corpus records both forms of\nevery page (NNN.expected.full.json/.txt), which the corpus page and\nthe consistency test cover; every rendered corpus element now has an\nid, since every one appears in the unpruned form, and the corpus hrefs\nare absolute so the recorded url does not depend on the origin the\ncorpus is served from. The READMEs describe both.\n\n* [py] add url and interesting_only to the snapshot\n\nAccessibilityNode gains url, the resolved href of a link node, read\nfrom the wire form and never rendered. Both accessibility_snapshot()\nmethods take interesting_only, True by default; with False the atom\nkeeps every rendered element as a node. The flag travels as the first\nargument of the call, ahead of the optional root, so the whole-page\ncall needs no placeholder for a root it does not send.\n\nThe unit tests hold to_text() to both corpus forms, decode a link\u0027s\nurl, and run the snapshotter over a stand-in script module to pin the\nargument order; the BiDi suite compares the unpruned snapshot of every\ncorpus page with its .full expectation, reads a link\u0027s url back\nthrough the element, and takes an unpruned subtree.\n\n* [js] add url and interestingOnly to the snapshot\n\nAccessibilityNode gains url, the resolved href of a link node, read\nfrom the wire form and never rendered. Both getAccessibilitySnapshot()\nmethods take an options object; with interestingOnly false the atom\nkeeps every rendered element as a node. The flag travels as the first\nargument of the call, ahead of the optional root.\n\nThe small tests hold toText() to both corpus forms, decode a link\u0027s\nurl, and run the snapshotter over a recording send to pin the argument\norder; the browser suite compares the unpruned snapshot of every\ncorpus page with its .full expectation, reads a link\u0027s url back\nthrough the element, and takes an unpruned subtree.\n\n* [rb] add url and interesting_only to the snapshot\n\nAccessibilityNode gains #url, the resolved href of a link node, read\nfrom the wire form and never rendered. Driver#accessibility_snapshot,\nElement#accessibility_snapshot and the bridge take interesting_only:,\ntrue by default; with false the atom keeps every rendered element as\na node. The flag travels as the first argument of the call, ahead of\nthe optional root. The signatures follow.\n\nThe unit spec holds #to_text to both corpus forms, decodes a link\u0027s\nurl, and pins the argument order over the stand-in connection; the\nintegration spec compares the unpruned snapshot of every corpus page\nwith its .full expectation, reads a link\u0027s url back through the\nelement, and takes an unpruned subtree.\n\n* [java] Add link urls and AccessibilitySnapshotOptions\n\nAccessibilityNode gains getUrl(), the resolved href of a link whose\nelement has one, read from the atom\u0027s url field and not rendered by\ntoText(). AccessibilitySnapshotOptions is an immutable options type\nwith one setting so far, interestingOnly (default true); with false\nevery rendered element produces a node, as Puppeteer\u0027s\naccessibility.snapshot({ interestingOnly: false }) does. WebDriver and\nWebElement gain a getAccessibilitySnapshot(options) overload, with the\nno-arg method delegating to the defaults. The snapshotter passes the\nflag to the atom as the first argument, so a whole-page call sends no\nplaceholder for the root.\n\nTests cover both recorded corpus forms, the url field through fromWire\nand the BiDi test, the options type, and an unpruned subtree snapshot.\n\n* [dotnet] Add link urls and interestingOnly to snapshots\n\nAccessibilityNode gains Url, the resolved href of a link whose element\nhas one, read from the atom\u0027s url field and not rendered by ToText().\nGetAccessibilitySnapshotAsync on WebDriver and WebElement takes an\noptional interestingOnly parameter (default true); with false every\nrendered element produces a node, with the role it has, as Puppeteer\u0027s\naccessibility.snapshot({ interestingOnly: false }) does. The\nsnapshotter passes the flag to the atom as the first argument, so a\nwhole-page call sends no placeholder for the root.\n\nTests cover both recorded corpus forms, the url field, and an unpruned\nsubtree snapshot.\n\n* [java] Lay out two lambdas as google-java-format 2.0.0 does\n\nTrunk moved to google-java-format 2.0.0, which keeps a lambda\u0027s\nparameter list on the line of the call that takes it. The Format job\nrewrote the two lambdas in the snapshotter and its unit test that way;\nthis is that diff.\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 7c6628ef4fe95daa190db74d21dbdb16810ac7ae\n"
    },
    {
      "commit": "bf772e8689e46e61483796c9db5bcbdcf86834bd",
      "tree": "9b4928d9cd2bcbbd5852d9375d660293c4d8ea53",
      "parents": [
        "07c3d0845c26f6ebb13e7dbd15f80519f1f383d7"
      ],
      "author": {
        "name": "Jyotheeswara Reddy Gottam",
        "email": "gjreddy1@gmail.com",
        "time": "Wed Nov 04 18:33:00 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Nov 04 19:24:35 2026"
      },
      "message": "[py] Support relative locators from WebElement and ShadowRoot (#18241)\n\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 06fc4d47f2473caf5ec25ab493c1326260423b76\n"
    },
    {
      "commit": "07c3d0845c26f6ebb13e7dbd15f80519f1f383d7",
      "tree": "48fd02b1585d284d4d8c1db0277015f72141f6aa",
      "parents": [
        "024c77f244b93ed17bd8a55f95b7fef5247fad10"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Mon Nov 02 09:04:01 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Nov 02 09:23:42 2026"
      },
      "message": "[build] Automated Dependency Update (#18242)\n\n* [build] Automated Dependency Update\n\n* [java] Format with google-java-format 2.0.0\n\nCo-Authored-By: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@gmail.com\u003e\nCo-authored-by: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: e4c3ad5e86accc0c6b1056260cc5654dfe995e19\n"
    },
    {
      "commit": "024c77f244b93ed17bd8a55f95b7fef5247fad10",
      "tree": "612e75cdfdbb8248a69820fa3e9ea7b11de0c750",
      "parents": [
        "0fd3407461ecfd4479906550408c315938613072"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Fri Oct 30 16:51:13 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 30 19:24:34 2026"
      },
      "message": "[py] make python rake tasks hermetic and support Windows with a Bazel-provided uv\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: b2485e69f07f736a235ba660e5da375eaeca2b44\n"
    },
    {
      "commit": "0fd3407461ecfd4479906550408c315938613072",
      "tree": "2a2702920032c1d708f1b8a6fe5f0543f26d73a5",
      "parents": [
        "6a3674e790cff6d7cc80a608c99690ead2374798"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Fri Oct 30 14:13:38 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 30 14:24:22 2026"
      },
      "message": "[py] Return a protocol-neutral driver.script (#18264)\n\n* [py] Return a protocol-neutral driver.script\n\ndriver.script now returns selenium.webdriver.remote.script.Script, the\nsupported API from ADR 17670, instead of the BiDi script module.\n\n- execute() converts arguments and results the way execute_script\n  does (elements become WebElement) and raises JavascriptException.\n- PinnedScript and the handler payload types move to the neutral\n  module; common.bidi.script still re-exports them.\n  PinnedScript.realm is deprecated.\n- Protocol commands such as evaluate, call_function,\n  add_preload_script, get_realms and disown still work through\n  driver.script but raise a DeprecationWarning naming the\n  replacement.\n\nPart of #17814\n\n* [py] Keep script keyword names, return ShadowRoot\n\nKeep the parameter names the BiDi script module used (pin\u0027s\nfunction_declaration, unpin\u0027s script_id, execute\u0027s\nfunction_declaration) so keyword callers keep working, and return\nshadow roots from execute() as ShadowRoot, as execute_script does.\n\n* [py] Name the wrapped script module _script\n\nMatch Java (RemoteScript.script), JS (#script) and Ruby (@network),\nwhich name the wrapped BiDi module after the module rather than a\ngeneric _impl.\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 5edd416550f657837c6676d071859ef25e9182eb\n"
    },
    {
      "commit": "6a3674e790cff6d7cc80a608c99690ead2374798",
      "tree": "d5a9ef3688196a6eb6d97c2faac5ab08b0115c05",
      "parents": [
        "094b17ce76c9bde479687dfe3d3a644e90f54756"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Fri Oct 30 13:39:23 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 30 14:24:01 2026"
      },
      "message": "[py] Deprecate driver.webextension (#18265)\n\ndriver.webextension returns the generated BiDi webextension module,\nwhich ADR 17670 keeps out of the supported API. Warn when it is used\nand point to driver.install_web_extension() and\ndriver.uninstall_web_extension(), which cover the same commands and\nfollow ADR 17817.\n\nThe existing module tests stay until the property is removed, with the\nwarning filtered, and a new test checks that the warning is raised.\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: f6f732445c83ebb8b52d289f0032262b9d781117\n"
    },
    {
      "commit": "094b17ce76c9bde479687dfe3d3a644e90f54756",
      "tree": "bfe122c828d378651768d95f374d5ac986e9fdb9",
      "parents": [
        "b6f0d50f5e1ae02361984e48193a8a47358a7143"
      ],
      "author": {
        "name": "Adam Dangoor",
        "email": "adamdangoor@gmail.com",
        "time": "Thu Oct 29 22:24:37 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Oct 29 23:24:03 2026"
      },
      "message": "[py] Add WebDriver cookie type annotations (#17537)\n\n* [py] Add WebDriver cookie type annotations\n\nCo-authored-by: Cursor \u003ccursoragent@cursor.com\u003e\n\n* [py] Address cookie typing review feedback\n\n* Complete get_cookies return type\n\n* [py] Use Any for get_cookies dict values\n\nKeeps cookie values typed as Any so existing typed callers do not need changes.\n\nCo-Authored-By: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\n\n* [py] Add cookie module to the api_request_context Bazel target\n\nCo-Authored-By: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Cursor \u003ccursoragent@cursor.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@users.noreply.github.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@gmail.com\u003e\nCo-authored-by: Claude Opus 5.5 \u003cnoreply@anthropic.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: f80fa577e2f1da93cdbdd3253cadd70c19cc9b56\n"
    },
    {
      "commit": "b6f0d50f5e1ae02361984e48193a8a47358a7143",
      "tree": "7791757a4b03fb125dad9eca99950adcc9b98a7f",
      "parents": [
        "c85bc7161e17d37105948b3ef1a5de50b9586966"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Thu Oct 29 13:07:25 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Oct 29 13:28:16 2026"
      },
      "message": "Release selenium-4.52.0 (#18259)\n\n* bump versions\n\n* changelogs updated\n\n* rust changelogs updated\n\n* update selenium manager versions\n\n* update authors file\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 5b37fd4d98c08d6a43a0790417f91b8f6f32fb5c\n"
    },
    {
      "commit": "c85bc7161e17d37105948b3ef1a5de50b9586966",
      "tree": "0acd5895e29068cee9d211c07b13e55650fdd314",
      "parents": [
        "5187fc5533238876147f74e4016ccb148b448706"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Thu Oct 29 12:19:11 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Oct 29 13:27:31 2026"
      },
      "message": "[py] fix race condition in test for browsing context handler removal\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 7fda528509da5ff5bd4d930d6eea9601fa9ca4a7\n"
    },
    {
      "commit": "5187fc5533238876147f74e4016ccb148b448706",
      "tree": "bdfe90e77c479f8401cd320a0ee5bfb275b35928",
      "parents": [
        "4e34f013694000f134dc47708b272d559446d31d"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Thu Oct 29 12:21:28 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Oct 29 12:24:39 2026"
      },
      "message": "[py] unit test the BiDi protocol generator (#18052)\n\n* [py] unit test the BiDi protocol generator\n\nRuby covers its BiDi generator in bidi_generate_spec.rb; the Python port it was\nported from had none, so every naming helper, literal helper and emitter was\nunverified.\n\nThe schema the generator reads is already gated upstream: the JavaScript\nprojector diffs it against cddl2ts as an independent oracle, and checkSchema\nand checkCompleteness verify referential integrity against the CDDL AST. What\nnone of that reaches is the step after it, turning a correct schema into\ncorrect Python. A required field emitted optional, a lost _EXTENSIBLE, a\ndiscriminator baked when it should stay settable -- each is invisible to the\nschema gate, and to anything that reads the generated tree, since that tree is\nwhat is under suspicion.\n\nSo the expectations are written by hand from the intended output rather than\nderived from the generator, which is what makes them an oracle. Verified by\nmutating the generator: emitting required fields as optional, dropping the\n_EXTENSIBLE flag, and dropping required\u003dTrue from the wire metadata each turn\nthe suite red.\n\npy:bidi_protocol_generator exposes the generator as a testonly library so the\nunit suite can import it; the two binaries keep listing the source directly.\n\nRefs #18020\n\n* [py] keep the trailing comma on a single-arm presence union\n\nA lone `_PRESENCE` entry was emitted as `((\"x.A\", (\"width\", \"height\")))`, which\nPython reads as the 2-tuple itself, so `for variant, keys in cls._PRESENCE` would\nraise ValueError. No current schema type has a single presence arm; the generator\ntest pinned the broken form. Assert on the parsed value rather than only the text.\n\n* [py] unit test the generator\u0027s vendor overlay folding\n\nSchema._apply_vendor and vendor_field_name landed on trunk with the\nFirefox vendor CDDL and had no coverage. Pin the namespaced Python name,\nthe fold into the extended record with the wire key kept, and the three\nrejections: an unknown target, a non-record target, and a field that\nredeclares a shared one.\n\n* [py] escape regex metacharacters in generator test match patterns\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: fb1ac3d715e731a26e5e47eee7b1bafd346ed3d4\n"
    },
    {
      "commit": "4e34f013694000f134dc47708b272d559446d31d",
      "tree": "086613383b6bc0a8a32c3a7617a039d639414e2b",
      "parents": [
        "73d7335710fe8a5b8913f17623573e92c756ec1c"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Tue Oct 27 23:29:56 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 28 22:24:48 2026"
      },
      "message": "[py] load blank.html after bidi network tests that fail a navigation\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: c3201faba24f9a07032f2133c4b175e3271930eb\n"
    },
    {
      "commit": "73d7335710fe8a5b8913f17623573e92c756ec1c",
      "tree": "38974221dd1df56ecd383a61c25357b056216aed",
      "parents": [
        "b0c347df2cd766baedbe8d70237af9200e25d5ad"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Wed Oct 28 14:07:21 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 28 14:25:30 2026"
      },
      "message": "[py][js] Initial Creation of a external shippable selenium manager (#17542)\n\n* [py][js] publish Selenium Manager as its own package\n\nAdds PyPI and npm packages for Selenium Manager, so it can be run with\nuvx or npx without installing a binding. A meta package picks the\nplatform package that carries the right binary for the machine.\n\nRefs #17326\n\n* [py][js] make the manager wheels installable\n\nThe Python wheels carried no binary at all: py_wheel packages each dep\u0027s\nDefaultInfo files, and py_library keeps data in runfiles, so the manager\nwas silently dropped from all four. Name the binary as a wheel dep.\n\nRetag the wheels so pip can actually match them. PyPI rejects the bare\nlinux_x86_64 tag, and the binary is static musl that runs on glibc too,\nso ship it under manylinux and musllinux. The Windows binary is i686 but\npip matches on the tag, not the binary, so a win32-only wheel was\ninvisible to the 64-bit interpreters nearly everyone runs; ship win32,\nwin_amd64 and win_arm64.\n\nAdd Linux arm64 to both ecosystems. Trunk has shipped that binary in\nevery binding since #17999, but the umbrella packages had nowhere to\nsend those users: the Python marker was gated on x86_64 and the npm map\nwas keyed on platform alone.\n\n* [py][js] use one manager entrypoint, fix metadata\n\nVersioning: SM_VERSION now lives in py/selenium-manager/version.bzl and\njavascript/selenium-manager/version.bzl, following the java/version.bzl\nand dotnet/version.bzl precedent. The meta package\u0027s four platform pins\nare derived from it rather than restated, so they cannot drift on a\nbump. Versions move from a stale 4.45.0 to the repo\u0027s current 4.50.0\nline, and the rake tasks now rewrite only the manifests the packaging\nbackends parse.\n\nMetadata: all five Python packages declared description_content_type\nwith no long description, and named LICENSE/NOTICE files that were not\npresent in any of the package directories. Added READMEs, wired\ndescription_file, and copied the licences into *.dist-info/ the way\npy/BUILD.bazel already does. The npm meta package gained a README,\nmain/exports, and the shebang npm needs to make its bin executable on\nPOSIX.\n\nEntrypoint: every distribution registered a `selenium-manager` console\nscript, so the meta package and whichever platform package installed\nlast contended for the same name. Platform packages now expose only\nbinary_path(); selenium_manager is the single place that launches a\nprocess, and it honours SE_MANAGER_PATH, which the Python side was\nmissing. binary_path() is public so a binding can resolve the manager\nwithout spawning it. index.js mirrors the shape for Node.\n\nThe dispatch test was named test/test_platform_dispatch.py, which\npy_test_suite classifies by filename rather than path, so the suite\ngenerated no tests at all; renamed to platform_dispatch_tests.py and\nconverted to pytest to match the rest of py/.\n\n* [py][js] name Windows manager package by its arch\n\nThe Windows platform package carried no architecture in its name, so a\nfuture native x64 or arm64 build would have had to either displace the\ni686 binary inside `selenium-manager-windows` or rename a published\ndistribution. Neither is something we can do once the name is on PyPI\nand npm.\n\nThe suffix names the binary the package carries, not the machine it is\ndispatched to, which is why one `-x86` package still answers every\nWindows environment marker: Windows runs i686 on x64 via WOW64 and on\narm64 via emulation. macOS keeps no suffix because its binary is\nuniversal and there is no single architecture to name.\n\nA native build for either 64-bit Windows target now arrives as its own\npackage and narrows the markers, rather than changing what an existing\npackage contains.\n\n  selenium-manager-windows  -\u003e selenium-manager-windows-x86\n  @selenium/manager-win32   -\u003e @selenium/manager-win32-ia32\n\nThe npm package also declares `cpu` explicitly now, matching the Linux\npackages, so narrowing it later is a visible edit rather than the\nremoval of an omission.\n\n* [js] publish manager packages via publish_npm_package\n\nRe-running a release now skips versions already on npm, as node:release\ndoes since trunk dropped pre-release verification, and accepts rbe.\n\n* [py][js] keep manager versions in step with bindings\n\nThe manager packages were still at 4.50.0 while the bindings they ship\nbeside are at 4.52.0, breaking the lockstep the version files promise.\n\nThe execute-bit repair only ran in main(), so library callers of\nbinary_path() missed it, while an SE_MANAGER_PATH binary was chmodded\ndespite the comment saying it is left alone. Repair only the packaged\nbinary, inside binary_path(), and tolerate a read-only site-packages.\n\n* [py][js] make manager usage stats opt-in\n\nThe standalone launchers now set SE_AVOID_STATS\u003dtrue unless the user has\nchosen (an empty value counts as unset), so installing the package is\nnot consent to usage stats; SE_AVOID_STATS\u003dfalse or se-config.toml opts\nin. The binary is unchanged, so the bindings keep their existing\nbehaviour.\n\nThe manager npm packages are only ever published as @selenium/manager,\nso node:release_manager skips nightly runs rather than publishing them\nunder the @seleniumhq scope GitHub Packages requires.\n\nA temp directory containing a space broke the stand-in binary\u0027s\nredirect.\n\n* [py] let bindings catch manager lookup failures\n\nbinary_path() raised SystemExit for an unsupported platform or a missing\nplatform package, so a binding catching Exception to fall back to its\nvendored manager had its process end instead. It now raises\nRuntimeError, matching binaryPath() in the npm package; the\nselenium-manager command still exits with the same message.\n\npy:release_manager stamped its own nightly timestamp, which could differ\nby a minute from the selenium wheel released beside it. It now reuses\nthe binding\u0027s version.\n\n* [py] stamp a nightly binding before manager nightly\n\npy:release_manager nightly copied whatever version the binding had, so\non a stable checkout it built manager wheels with the stable version. It\nnow stamps the binding as a nightly first when it is not one already.\n\nThe library-caller test swallowed any exception, so it passed even if\nbinary_path() stopped raising; it now asserts a RuntimeError.\n\n* [py] upload manager wheels in py:release_manager\n\npy:release_manager built the manager wheels but never uploaded them, so\na release left nothing on PyPI. It now runs twine through\n//py:selenium-manager-release, or the testpypi variant for nightlies, in\nthe same way py:release ships the selenium wheel.\n\ndry-run builds the wheels without needing credentials or uploading,\nmatching node:release_manager. rbe selects the rbe_release config.\n\n* [py][js] ship manager notices and SBOM in packages\n\nThe platform packages carried the Selenium Manager binary without the\nthird-party notices for its statically linked dependencies or its\nCycloneDX SBOM, which every binding ships beside the same binary. Copy\nboth next to the binary in each npm package and wheel, as the\nbindings do.\n\n* [js] unit test manager platform dispatch\n\nbinaryPath() had no tests, so a change to the platform map or the\npackage layout could break npx and library callers unnoticed. Each\ncase runs binaryPath() in a child process with its own platform,\nenvironment and module path, against packages laid out the way npm\ninstalls them.\n\n* [js] inherit the environment in manager dispatch tests\n\nUnder Bazel, process.execPath is the rules_js launcher, which needs the\nJS_BINARY__* variables to find node, so a child started with a bare\nenvironment failed before running any case.\n\n* [py] match manager wheels\u0027 Python floor to the binding\n\nThe manager wheels declared requires-python \u003e\u003d3.9 while the selenium\nbinding requires \u003e\u003d3.10. Align them, and list the same Python\nclassifiers as the binding (3.10-3.15).\n\n* [py][js] take manager versions from the root version.bzl\n\nTrunk now keeps each binding\u0027s version in //:version.bzl. The manager\npackages kept their own SM_VERSION, which rake copied over with\nfind-and-replace on every bump. Read VERSIONS[\"python\"] and\nVERSIONS[\"javascript\"] instead, so a manager package can only ever carry\nthe version of the binding it ships beside.\n\nThe npm manifests no longer hold a version; jq stamps it, and the meta\npackage\u0027s platform pins, at build time as selenium-webdriver does. The\nPython packages\u0027 pyproject.toml files are dropped: nothing built from\nthem, since the wheels take all their metadata from py_wheel, and they\nwere the last place a version was written by hand.\n\nThe py:version_manager and node:version_manager tasks go with them. The\nnightly manager release now stamps the binding the same way py:release\ndoes, reusing a timestamp py:release already wrote.\n\n* [build] keep a leading shebang first in update_copyright\n\nupdate_copyright prepended the notice above any file whose first line\nwas not a comment, so the npm manager launcher ended up with its\n#!/usr/bin/env node on line 19, where Node rejects it as a syntax\nerror. Treat a leading #! line as outside the notice and write the\nnotice after it, then move the launcher\u0027s shebang back to line 1.\n\n* [js] report when the manager launcher cannot start the binary\n\nA manager that never ran (ENOENT, EACCES, ENOEXEC) has no exit status,\nso the launcher exited 1 without saying why. Print the reason, keep\npassing through real exit codes, and cover the launcher in the unit\ntests.\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 25d340b2cf2c5064131b22a61c419d547851a863\n"
    },
    {
      "commit": "b0c347df2cd766baedbe8d70237af9200e25d5ad",
      "tree": "979cc503f691e4c338947e60cddce3427a6f9987",
      "parents": [
        "17e1a714a9c32a767632e60001021ebc8dc71215"
      ],
      "author": {
        "name": "Saiyam Jain",
        "email": "62669344+saiyam-jain-dev@users.noreply.github.com",
        "time": "Wed Oct 28 01:22:37 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 28 01:27:23 2026"
      },
      "message": "[py] Reject null list elements in BiDi serialization inbound and outbound (#18240)\n\nReject null list elements in both directions, raising BiDiSerializationError when encountering None in lists of primitives, records, or enums.\n\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 8a6aefc10e01a6d88bf2e42d318034918086b6e4\n"
    },
    {
      "commit": "17e1a714a9c32a767632e60001021ebc8dc71215",
      "tree": "138e7f2188d929e4cdddbe7925fd9eaa39fc3e6f",
      "parents": [
        "54f6aa611c17f489e17535a6b5722339d9a99e19"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Wed Oct 28 00:04:10 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 28 00:23:59 2026"
      },
      "message": "[py] deprecate websocket_interval now that websocket responses are not polled (#18252)\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: a9be7b1857d479dd40c5f134e7f24bae825d512b\n"
    },
    {
      "commit": "54f6aa611c17f489e17535a6b5722339d9a99e19",
      "tree": "5b7e792234da9f675074534eda3984a4ba8f6153",
      "parents": [
        "53d9a5fc2b39f3be3eb3ccb60f0750a6b996f445"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Tue Oct 27 21:58:29 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Oct 27 22:26:23 2026"
      },
      "message": "[py] settle page before stubbed response test (#18251)\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: b8b4e0b97be5a95bbb5d43f604c5c277ca494efa\n"
    },
    {
      "commit": "53d9a5fc2b39f3be3eb3ccb60f0750a6b996f445",
      "tree": "09560175f11c9f9e73d6387590f3854cf434fd17",
      "parents": [
        "83db8998f2ff123a9f8fcfd6b2df6093a9ecf2cd"
      ],
      "author": {
        "name": "Saiyam Jain",
        "email": "62669344+saiyam-jain-dev@users.noreply.github.com",
        "time": "Tue Oct 27 20:23:11 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Oct 27 22:26:01 2026"
      },
      "message": "[py] Implement __hash__ and explicit ScriptKey equality comparison (#18243)\n\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: ee6af75226f4d4180c7e50b879eb2dcf3b68aaf8\n"
    },
    {
      "commit": "83db8998f2ff123a9f8fcfd6b2df6093a9ecf2cd",
      "tree": "a071b3b2b415ea767be68f1598f5f661dcb6e260",
      "parents": [
        "b2efaab1c667bb9d4e64ea7963589e0051ca518b"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Tue Oct 27 20:07:00 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Oct 27 20:25:37 2026"
      },
      "message": "[py][rb][bidi] Eliminate sleep-based polling for websocket command response (#18231)\n\n* [py][rb][bidi] Eliminate sleep-based polling for websocket command response\n\n* [py][rb][bidi] Address review feedback on event-based websocket waits\n\nPython:\n- clean up per-command events/replies on every exit path (send failure,\n  serialization error, timeout)\n- drop replies that arrive after the command timed out instead of\n  keeping them forever\n- wake pending commands when the socket closes so they fail fast with a\n  \"connection closed\" error instead of running out the timeout\n- close the socket when the connection fails to start\n- remove the unused sleep import\n\nRuby:\n- wake pending commands when the socket is closed (including when the\n  listener dies) so they raise IOError instead of timing out\n- drop replies that no command is waiting for\n- always clear pending state in an ensure block\n- keep timeout error message consistent with the previous Wait-based one\n- update RBS signatures and exclude the file from ClassLength\n\n* [rb][bidi] Release pending websocket commands however the listener stops\n\nAn unexpected error in the listener (e.g. a malformed frame failing to\nparse) ended the thread without waking waiting commands, so they ran\nout the full response timeout.\n\n* [py][bidi] Wait for wheel scroll to settle in input tests\n\nThese tests read pageYOffset straight after perform_actions and only\npassed because the old ~100ms response polling gave the browser time to\nfinish scrolling. Wait for the offset instead, as\ntest_wheel_scroll_with_duration already does.\n\n---------\n\nCo-authored-by: Your Name \u003crajatjain.ix@gmail.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 951cb66969efe5364b2ab79cd595972604bcd24e\n"
    },
    {
      "commit": "b2efaab1c667bb9d4e64ea7963589e0051ca518b",
      "tree": "771eeaacab2eee1d7d2b6679f29569087cbbc423",
      "parents": [
        "950982054da065e524f7ede99f57290c6381d80b"
      ],
      "author": {
        "name": "Saiyam Jain",
        "email": "62669344+saiyam-jain-dev@users.noreply.github.com",
        "time": "Tue Oct 27 19:51:29 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Oct 27 20:25:14 2026"
      },
      "message": "[py] Use ssl_proxy descriptor when initializing Proxy from raw dict (#18237)\n\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 10141c5af27c362b21f45add74b7052430704f30\n"
    },
    {
      "commit": "950982054da065e524f7ede99f57290c6381d80b",
      "tree": "10a1821611ef4b666feeb153526217ec06ba24e7",
      "parents": [
        "387c1dd874ac9d26875c1a8d29fdea4f277f28da"
      ],
      "author": {
        "name": "Corey Goldberg",
        "email": "1113081+cgoldberg@users.noreply.github.com",
        "time": "Tue Oct 27 17:48:20 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Oct 27 18:24:36 2026"
      },
      "message": "[py] Bump minimum version of certifi so upgrades don\u0027t keep revoked certs (#18250)\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: b99ecdf48c6911b90b7bc422dac97231dff2d5c0\n"
    },
    {
      "commit": "387c1dd874ac9d26875c1a8d29fdea4f277f28da",
      "tree": "592554ac018c2f8da613d5c8777b86d852287769",
      "parents": [
        "3bd7bd94c28fc3646905fb9a86b7e9b8675057b0"
      ],
      "author": {
        "name": "Saiyam Jain",
        "email": "62669344+saiyam-jain-dev@users.noreply.github.com",
        "time": "Tue Oct 27 17:32:05 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Oct 27 18:24:20 2026"
      },
      "message": "[py] Add explicit stacklevel\u003d2 to remaining warnings.warn calls (#18244)\n\n---------\n\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 618cb92fe50d8257ab1cb3cecaf0273a0ba3fc78\n"
    },
    {
      "commit": "3bd7bd94c28fc3646905fb9a86b7e9b8675057b0",
      "tree": "41778f806d9faf670b66ca4494ee399c8b70b499",
      "parents": [
        "8dcb8ab19f89549cde8d2e1bb1271f0ee22435ad"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Mon Oct 26 18:54:16 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Oct 26 19:24:45 2026"
      },
      "message": "[py] generate the Python version from version.bzl so tests do not read it (#18158)\n\n* [py] generate _version.py from the root version.bzl so tests do not read the version\n\n* [py] spell nightly versions as PEP 440 dev releases\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 23092e92fb29f640d7d43b92f424439442665752\n"
    },
    {
      "commit": "8dcb8ab19f89549cde8d2e1bb1271f0ee22435ad",
      "tree": "338737dcb342cbb39274b982f0644d9440ecc4df",
      "parents": [
        "3c430e9a2b35ded5bb8e34aabc6aa11deaf490cf"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Mon Oct 26 16:56:11 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Oct 26 17:27:36 2026"
      },
      "message": "[build] Automated Browser Version Update (major) with CDP (#18214)\n\n* [build] Automated Browser Version Update (major) with CDP\n\n* [rb] update test guard for setTextLayoutModeOverride since Chrome and Edge 156 now recognizes it\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nCo-authored-by: Titus Fortner \u003ctitus.fortner@gmail.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 081cbad43f4a69d2740bbc0226cccbddadad15cd\n"
    },
    {
      "commit": "3c430e9a2b35ded5bb8e34aabc6aa11deaf490cf",
      "tree": "c94f3a0549a0145f84fdc83c557224a26e75da0e",
      "parents": [
        "9baf070f18698c741d87dc7717b1a467b946ac97"
      ],
      "author": {
        "name": "Saiyam Jain",
        "email": "62669344+saiyam-jain-dev@users.noreply.github.com",
        "time": "Sun Oct 25 18:04:48 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sun Oct 25 18:23:53 2026"
      },
      "message": "[py] Align _cookie_matches domain and path matching with RFC 6265 (#18238)\n\n---------\n\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: e21558f2c7547ce42ed5db2c9ab15fbca8de2b84\n"
    },
    {
      "commit": "9baf070f18698c741d87dc7717b1a467b946ac97",
      "tree": "09d0d82cb946b4da5b083147e4cbb1e29e4b7ff6",
      "parents": [
        "fc92fb2e9da19c8072c6bd19ced347877d043443"
      ],
      "author": {
        "name": "Saiyam Jain",
        "email": "62669344+saiyam-jain-dev@users.noreply.github.com",
        "time": "Sun Oct 25 15:38:15 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sun Oct 25 15:54:42 2026"
      },
      "message": "[py] Prevent TypeError and KeyError in ErrorHandler when value is None or missing (#18239)\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 652351e284b3ebaf691e8e86a91a57549ab30ca1\n"
    },
    {
      "commit": "fc92fb2e9da19c8072c6bd19ced347877d043443",
      "tree": "9ab633ae93d890d5926573d68e46b9b93ae34ec7",
      "parents": [
        "d596a652f8fe32e15182142002db3d49053e96ec"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Fri Oct 23 06:39:39 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 23 07:23:48 2026"
      },
      "message": "[py] hold generated BiDi numbers to their declared ranges (#18230)\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: fdaebd89197c36b2fe50a8845d7e05ac8c35119f\n"
    },
    {
      "commit": "d596a652f8fe32e15182142002db3d49053e96ec",
      "tree": "7dfcfa57492822eeab3c56d0f7f0d3c405761172",
      "parents": [
        "aae8f002041285b74a0455655d6f48b39e2b882e"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Wed Oct 21 15:56:41 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 21 20:25:12 2026"
      },
      "message": "[build] use Bazel\u0027s size-based test timeouts, capping large tests at 600s on RBE\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: e1c9c89f4be5ed5fcdf239bab6b2d4817d994860\n"
    },
    {
      "commit": "aae8f002041285b74a0455655d6f48b39e2b882e",
      "tree": "601b4b427cb00eff4f921fc717d2e52fef8caefb",
      "parents": [
        "1310c6877405ea485328179e468d5181b8bf3e10"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Wed Oct 21 18:45:55 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 21 19:26:05 2026"
      },
      "message": "[build] Automated Dependency Update (#18201)\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: ea5689472e0946e86193ddf1f0434d7f1dbb581d\n"
    },
    {
      "commit": "1310c6877405ea485328179e468d5181b8bf3e10",
      "tree": "fca7aaad57b7176cf4ccc5c3a0d4bfe6ebc1159f",
      "parents": [
        "de57dc60cce151dbdf2cb0bb76ee3641bf469816"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Wed Oct 21 16:15:35 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 21 16:30:24 2026"
      },
      "message": "[build] bump certifi and webpki trust roots for eMudhra CA distrust (#18218)\n\nEight eMudhra intermediate CA keys were found to be trivially recoverable\nand have been distrusted. Pick up the patched trust stores:\n\n- certifi 2026.7.22 -\u003e 2026.10.19\n- rustls-webpki 0.103.15 -\u003e 0.103.16\n- webpki-roots / webpki-root-certs 1.0.9 -\u003e 1.0.10\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: c0acd0833f417d57cfbaeb45f0a45180dd6b2a0c\n"
    },
    {
      "commit": "de57dc60cce151dbdf2cb0bb76ee3641bf469816",
      "tree": "33934fcbeac073d571558e8665242183d63a9f72",
      "parents": [
        "d4ee125066352f260f5cc9a495b2fefce9a9f095"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Wed Oct 21 13:36:01 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 21 13:43:53 2026"
      },
      "message": "[py] bound Service.start() by time so Windows fails fast (#18213)\n\n* [py] bound Service.start() by a 30 second timeout instead of an attempt count\n\n* [py] treat a driver process that exits with status 0 during Service.start() as a failure\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 8c5bf9aeaeb96330a618ed86a34dfda9dabae4f5\n"
    },
    {
      "commit": "d4ee125066352f260f5cc9a495b2fefce9a9f095",
      "tree": "07bfdfd5f0adef08bcd0211b4a342e3a47e34eac",
      "parents": [
        "274d879863f772479b2b66773a8d7d76f761d464"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Tue Oct 20 13:44:41 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Oct 20 14:24:19 2026"
      },
      "message": "[py] ignore NoSuchWindowException while waiting for the title after a link click (#18187)\n\nSafari can report \"Callback was not called before the unload event\" as\nNoSuchWindowException while the navigation is in progress, matching the\nexisting handling in click_scrolling_tests.\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 545edd1b8b5af2245ce21773719102667ff3b533\n"
    },
    {
      "commit": "274d879863f772479b2b66773a8d7d76f761d464",
      "tree": "9d084fb558f4b45e14d5ab774d15831b43fd37cd",
      "parents": [
        "ae373b8bf02045cd9de269e4fd74f49fe752cd19"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Sun Oct 18 19:53:54 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sun Oct 18 20:25:02 2026"
      },
      "message": "[build] remove no-sandbox tags that are redundant or no longer needed\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 010382bdb9f7b83a6cd72093e025739351fc9e0f\n"
    },
    {
      "commit": "ae373b8bf02045cd9de269e4fd74f49fe752cd19",
      "tree": "c3fae0990dffdda0cbcd882255dff95cc7c87006",
      "parents": [
        "ac2589301a3e68112f87d4f2eb1b9c4e348cdf68"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Sat Oct 17 15:02:06 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sat Oct 17 18:24:22 2026"
      },
      "message": "[py] keep parallel Windows test sessions from sharing the test web server port and hanging at exit\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 37a9f3b0482aeb07147fa12f006c966f94b25e19\n"
    },
    {
      "commit": "ac2589301a3e68112f87d4f2eb1b9c4e348cdf68",
      "tree": "564d7779702255a458d88620281deb56508afa49",
      "parents": [
        "833dc37e7431c3554336a933fad8f25f17120215"
      ],
      "author": {
        "name": "Alex Popov",
        "email": "iampopovich@gmail.com",
        "time": "Wed Oct 14 17:06:45 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 14 17:25:12 2026"
      },
      "message": "[py] cleanup for ruff rule sim102 . part of the tranche 3 stage (#18034)\n\n* refactor: streamline traceback frame extraction and simplify BiDi driver checks\n\n* refactor: optimize namespace extraction logic in firefox_profile.py\n\n* refactor: simplify module filtering logic in generate_api_module_listing.py\n\n* refactor: simplify version setter validation in server.py\n\n* refactor: add SIM102 to the list of supported identifiers in pyproject.toml\n\n---------\n\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 6fc1c2b932c6e115d9b83481e6f2bbc3b340d65d\n"
    },
    {
      "commit": "833dc37e7431c3554336a933fad8f25f17120215",
      "tree": "13076cb2fcc6ae424828beeccf5a0b96f2a4d917",
      "parents": [
        "2675987956bfc3ac6a6f23637449ceca0ecacad3"
      ],
      "author": {
        "name": "Daniel",
        "email": "mathmtit@gmail.com",
        "time": "Wed Oct 14 15:09:34 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 14 15:25:35 2026"
      },
      "message": "[py] Adopt ruff PYI041 and G201 from the new default rule set (#18154)\n\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 9896eb855db6849aa04e170acf46a3c8612ce292\n"
    },
    {
      "commit": "2675987956bfc3ac6a6f23637449ceca0ecacad3",
      "tree": "9603fbdfcd252073c1b3947fe850dfaf2b7334bb",
      "parents": [
        "15902d385cb66a29f50efc5445f1f874c82cce18"
      ],
      "author": {
        "name": "Nagendra Bhat",
        "email": "50840189+goldpanth3r@users.noreply.github.com",
        "time": "Wed Oct 14 14:40:37 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 14 15:25:15 2026"
      },
      "message": "[py] Adopt all Ruff FURB rules (#18168)\n\nAdopt all Ruff FURB rules by adding FURB110 and FURB171\n\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: b47faabc941587640fd9a9039c7b277819a5604f\n"
    },
    {
      "commit": "15902d385cb66a29f50efc5445f1f874c82cce18",
      "tree": "362f5b0daa92bb3b7a7ca0466b40cf40e0511b73",
      "parents": [
        "baa4b3a2c67dbb6945b7d772eaaf2331c08a8858"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Mon Oct 12 12:43:58 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Oct 12 13:24:59 2026"
      },
      "message": "[py] Install web extensions from the driver (#17970)\n\n* [py] Install web extensions from the driver\n\nAdd install_web_extension and uninstall_web_extension to the driver,\nimplementing the Python binding for ADR 17817. Install accepts an\nunpacked directory, a packed archive, or base64 bytes, and returns a\nWebExtension wrapping the id the browser assigned. Uninstall takes\nthat object back rather than a raw id.\n\nA directory only resolves on the machine running the browser, so a\nremote session uploads it first and installs from the path the remote\nend hands back. The upload keeps the directory as the archive\u0027s single\ntop-level entry, which is what the Grid resolves the returned path\nfrom.\n\nFirefox falls back to the classic moz/addon endpoints when BiDi is not\nenabled. Those endpoints are now registered on demand, because only a\nwebdriver.Firefox session has them in its command registry; a Grid\nsession driven through webdriver.Remote previously failed with a bare\n\"AssertionError: Unrecognised command INSTALL_ADDON\".\n\nChromium without BiDi raises instead of silently doing less, and the\nFirefox-only install_addon and uninstall_addon are deprecated in\nfavour of the new methods.\n\n* [py] Match the web extension tests to Firefox\u0027s install rules\n\nFirefox rejects moz:permanent for an unpacked extension directory with\n\"Permanent installation of unpacked extensions is not supported\", so\ncombining permanent\u003dTrue with a directory could never pass. A permanent\ninstall is also signature-checked, because it goes through\nAddonManager.getInstallForFile rather than installTemporaryAddon.\n\nSplit the combined test into the three cases Firefox supports:\npermanent\u003dTrue against the signed .xpi, permanent\u003dFalse against the\nunpacked directory, and allow_private_browsing on its own.\n\nFirefox\u0027s BiDi webExtension.install does not read\nmoz:allowPrivateBrowsing yet -- it passes allowPrivateBrowsing\u003dfalse to\nAddon.installWithPath unconditionally -- so that test asserts only that\nthe option is accepted. The classic endpoint does honour it. Document\nboth constraints on install_web_extension and in the BiDi manifest.\n\n* [py] Run the web extension tests against the Grid\n\ninstall_web_extension uploads an unpacked extension to the remote end\nand installs it from the path handed back, so the Grid path was the one\npart of the ADR that only unit tests covered.\n\nOpt the file into REMOTE_BIDI_TESTS so test-chrome-remote-bidi and\ntest-firefox-remote-bidi pick it up.\n\nThe Chromium tests build their own driver because they need\nenable_webextensions, so that fixture has to honour the server fixture\nas well; without it the tests quietly start a local browser and the\nremote target proves nothing.\n\n* [py] Install web extensions through the generated _bidi protocol\n\ncommon/bidi is being retired, so install_web_extension and\nuninstall_web_extension now go through common/_bidi instead. This is the\nfirst production caller of the generated protocol layer.\n\nThe vendor fields the Firefox options map onto were being written into\nthe old generator\u0027s enhancement manifest by hand. The shared schema\nalready declares them, under a `vendor` section the projector keeps\nseparate so the neutral schema stays neutral, so teach the Python\ngenerator to fold a vendor overlay back into the type it extends. A\nvendor field is then an ordinary optional field: it lands in its record,\nin its command\u0027s signature, and in the serializer\u0027s type checks with no\nspecial casing, and it tracks the schema rather than a hand-written\nlist. The wire key stays fully qualified; only the Python name is\nnamespaced, because `moz:permanent` is not an identifier.\n\nThe unit tests now record the websocket frames rather than standing in\nfor the webExtension module, so they assert the wire payload itself.\n\n* [py] Give every driver consumer the generated protocol layer\n\nwebdriver.py now imports selenium.webdriver.common._bidi, but only the\ntargets that explicitly test BiDi listed :bidi_protocol, so everything\nelse failed to import the driver at all.\n\n:remote cannot carry the dependency, because the protocol generator\ndepends on :remote to read errorhandler\u0027s error-code tables, and that\nwould cycle. :common already supplies the old bidi package to the same\nconsumers, so the new one rides along beside it.\n\n* [py] Assert moz:allowPrivateBrowsing instead of assuming it is ignored\n\nThe private browsing test only checked that Firefox accepted the option,\non the strength of a reading of webExtension.sys.mjs that turned out to be\na year out of date. Current mozilla-central does read\nmoz:allowPrivateBrowsing and passes it through to Addon.installWithPath.\n\nReplace that test with the pair the Ruby specs use: install into a Firefox\nlaunched with browser.privatebrowsing.autostart and assert the extension\ninjects into a private window when the option is passed, and does not when\nit is omitted. The second test is what makes the first meaningful -- if the\nflag were ignored, the first would fail.\n\nThe pref has to be set before launch, so these need a driver of their own.\nA Grid node runs one Firefox at a time, so the fixture releases the shared\nsession first; otherwise the second session queues until the test times out.\n\nAlso document that Android requires moz:permanent alongside\nmoz:allowPrivateBrowsing.\n\n* [py] Document the permanent default and bump deprecation version\n\nLeaving `permanent` unset installs temporarily over BiDi but permanently\nthrough the WebDriver-Classic fallback, so say so on\ninstall_web_extension. Bump the install_addon / uninstall_addon\ndeprecation markers to 4.52, the release this will ship in.\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: fd2fe0e8ac5f7db4f4ab53720679daeb8c30839e\n"
    },
    {
      "commit": "baa4b3a2c67dbb6945b7d772eaaf2331c08a8858",
      "tree": "c7a8e71a28059a59307dc4853d44873e0092d581",
      "parents": [
        "69ed9b5984d7c64ba868e6249b7931b40d679465"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Sat Oct 10 16:58:37 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sat Oct 10 19:24:46 2026"
      },
      "message": "[build] run the Python and JavaScript unit tests inside the smoke jobs like Ruby\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 1151eadf5f34ff6b34537fb8ff918667ac406e31\n"
    },
    {
      "commit": "69ed9b5984d7c64ba868e6249b7931b40d679465",
      "tree": "87a778f96477cd796f127d112d689b55a87b7b60",
      "parents": [
        "0be942fdf734294014568515a6f36868398afbdc"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Sat Oct 10 16:41:39 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sat Oct 10 17:25:01 2026"
      },
      "message": "[build] Automated CDDL Spec Update (#18144)\n\n* update cddl spec files\n\n* [build] follow the script.EvaluateResult to EvaluationResult rename in tests and spotbugs excludes\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nCo-authored-by: Titus Fortner \u003ctitus.fortner@gmail.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: da9f2ac9dbed11c9aba339ae5405402f927ddd3e\n"
    },
    {
      "commit": "0be942fdf734294014568515a6f36868398afbdc",
      "tree": "f7790821ba671a1ae2a28bef41b8e8b5d3b50e1b",
      "parents": [
        "98e02a90c6e38e817dea727277bbd7d31ccbe3dc"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Sat Oct 10 13:45:46 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sat Oct 10 15:24:45 2026"
      },
      "message": "[build] update TESTING.md files to describe pinned browsers and the RBE and GitHub Actions split\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: d7a506d0605ff9ac56edce92690ca81a16e3285e\n"
    },
    {
      "commit": "98e02a90c6e38e817dea727277bbd7d31ccbe3dc",
      "tree": "885d422f3da9704f02dbca26191e8dede90ff607",
      "parents": [
        "5160424f44944906fddbc9a18da35d0d43518d3f"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Sat Oct 10 13:23:59 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sat Oct 10 14:24:10 2026"
      },
      "message": "[build] scope RBE internet access to the tests that exercise Selenium Manager (#18147)\n\n* [build] deny RBE network access by default and grant it to the Rust suite per target\n\n* [build] run the se-manager targets on RBE by letting them ignore the browser pin\n\n* [build] remove se-manager tests from Linux x86_64 GHA runners for Python, Ruby \u0026 JS\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 7cebd3a155551693bf50ff22834421f4dd553279\n"
    },
    {
      "commit": "5160424f44944906fddbc9a18da35d0d43518d3f",
      "tree": "7b7ec0f050185c5170f6ce7286cabe622cc08ed6",
      "parents": [
        "21c4aa9cd21485881a2d34bc4d2cc595367cb625"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Fri Oct 09 21:27:40 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 09 22:25:57 2026"
      },
      "message": "[py] pass --no-sandbox to Chromium on Linux whether or not the browser is pinned\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: cefd4e56ae384504a5bc954732ebad8d77ba4950\n"
    },
    {
      "commit": "21c4aa9cd21485881a2d34bc4d2cc595367cb625",
      "tree": "a266a069868485237f2d6cd7c30c66bed6b89b12",
      "parents": [
        "a5e40a05c0e056f83a81bcafc1a2315ecb434cc8"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Fri Oct 09 20:52:15 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 09 21:25:56 2026"
      },
      "message": "[py] use local self-signed certificate for tests (#18148)\n\n* [py] serve the test pages over HTTPS with a checked-in certificate and drop badssl.com from the tests\n\n* [py] let the secure test listener follow the http host so remote nodes can reach it\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 2e38f225ccd0d2ceb2a201ca0a0c60bb2861c122\n"
    },
    {
      "commit": "a5e40a05c0e056f83a81bcafc1a2315ecb434cc8",
      "tree": "9efd1d13f9c0ba547ffab5d68caf5b2eb427a652",
      "parents": [
        "7c750193bd2fb8936addcb239b011ee17ca32f2d"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Fri Oct 09 20:26:15 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 09 21:25:13 2026"
      },
      "message": "[build] drop pre-release verification since all pushes skip if version exists\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 9bfad07d50046ffcb2a8511ba9f76eec4c3e31f4\n"
    },
    {
      "commit": "7c750193bd2fb8936addcb239b011ee17ca32f2d",
      "tree": "4b3b7899afce060e07d812ba86bd4be3b3a00a8b",
      "parents": [
        "bf7d175d712f8e20241151a7b9ccc9f117e471d5"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "selenium-ci@users.noreply.github.com",
        "time": "Fri Oct 09 18:59:27 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 09 19:23:48 2026"
      },
      "message": "[build] Reset versions to nightly after selenium-4.51.0 release\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 82c2aa25177501f4c417c0ad13a19aa8ba90a15c\n"
    },
    {
      "commit": "bf7d175d712f8e20241151a7b9ccc9f117e471d5",
      "tree": "35fb718bbedef3d8e5908d9540eaa259e31bb18e",
      "parents": [
        "ac76959fd3c42f4dd9e83f547c0de41e3e5978b1"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Fri Oct 09 18:35:38 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 09 18:44:49 2026"
      },
      "message": "[build] Prepare for release of selenium-4.51.0 (#18149)\n\n* bump versions\n\n* changelogs updated\n\n* rust changelogs updated\n\n* update selenium manager versions\n\n* update authors file\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 35c5fab4a58fa5878edb255eb7747b04ca51a015\n"
    },
    {
      "commit": "ac76959fd3c42f4dd9e83f547c0de41e3e5978b1",
      "tree": "685a4e8dda930d9d5f7b5fc7e3fd77704ef60f9b",
      "parents": [
        "135a3ab45639e4ae3e78a848d90fb30d607efc0a"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Fri Oct 09 17:39:36 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 09 18:24:31 2026"
      },
      "message": "[build] Automated Browser Version Update (major) with CDP (#18131)\n\nUpdate pinned browser versions\n\nCo-authored-by: Titus Fortner \u003ctitusfortner@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 8f7503a553aac68a0f0f960ce152b4a984f2978b\n"
    },
    {
      "commit": "135a3ab45639e4ae3e78a848d90fb30d607efc0a",
      "tree": "2e95446175dae87714d7575b8d76d1759df993a6",
      "parents": [
        "416e9b55b934dca8606c98f5f40d400f73213890"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Fri Oct 09 01:03:11 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Oct 09 01:26:13 2026"
      },
      "message": "[build] pin the driver location once per test process so services built in tests skip Selenium Manager (#18143)\n\n* [build] pin the driver location once per test process so services built in tests skip Selenium Manager\n\n* [build] pass the pinned browser to the Firefox tests that build their own driver\n\n* [py] restore the driver env var after the service env-var tests instead of deleting it\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 92807c94d687bedc75e428c96de7a01c132c65c2\n"
    },
    {
      "commit": "416e9b55b934dca8606c98f5f40d400f73213890",
      "tree": "922426f04db995c8dd34106fc68f226eb02df766",
      "parents": [
        "24a5f0a24c052839367e5c292ba28c9ae50cfa26"
      ],
      "author": {
        "name": "Corey Goldberg",
        "email": "1113081+cgoldberg@users.noreply.github.com",
        "time": "Thu Oct 08 02:08:03 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Oct 08 02:25:06 2026"
      },
      "message": "[py] Support Python 3.15 (#18065)\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 3d4a8c1162ceedd38fce9b117aa5fc4531aecd95\n"
    },
    {
      "commit": "24a5f0a24c052839367e5c292ba28c9ae50cfa26",
      "tree": "57ce0b0b3b91708e8e852abadfc16a1af3b6eb49",
      "parents": [
        "6845a0329408f149fafdbe901f480e2bd47be3fb"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Wed Oct 07 15:56:31 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Oct 07 16:26:38 2026"
      },
      "message": "[py] Suffix generated CDP names that collide with Python keywords\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: d22e8d51a4c4a74f95ca3e872684aedcf2be0d62\n"
    },
    {
      "commit": "6845a0329408f149fafdbe901f480e2bd47be3fb",
      "tree": "c016b63a8f6f107985fd6217d9b4ec6b48a469fd",
      "parents": [
        "6691add08fa1334c8da9b161fcff6c706e61b4a6"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Tue Oct 06 18:16:03 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Oct 06 18:24:26 2026"
      },
      "message": "[build] clean up IntelliJ project files and align .editorconfig with formatters (#18119)\n\n* [build] remove stale IntelliJ project files and dangling library references\n\n* [java] match .editorconfig to google-java-format and remove IntelliJ code style scheme\n\n* [java] prompt IntelliJ users to install and enable the google-java-format plugin\n\n* [build] ignore everything in .idea except an allowlist of shared settings\n\n* [py] stop tracking IntelliJ python.iml module\n\n* [build] match .editorconfig indentation and line length to each language formatter\n\n* [js] match IntelliJ semicolon and quote style to Prettier for selenium-webdriver\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 5026a72395c89aa5fcc24fafe97803322388967a\n"
    },
    {
      "commit": "6691add08fa1334c8da9b161fcff6c706e61b4a6",
      "tree": "d2c078897b9486b974ee607b7feab30a45721b09",
      "parents": [
        "b303b382b8ff3a382c7cbdbb1463eeca156e8588"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Mon Oct 05 08:10:03 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Oct 05 08:24:57 2026"
      },
      "message": "[build] Automated Dependency Update (#18124)\n\nupdate dependencies and pinned binaries\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 8ba36d40044cd1253430e2752417d0f5ec7023de\n"
    },
    {
      "commit": "b303b382b8ff3a382c7cbdbb1463eeca156e8588",
      "tree": "eb8fc2ebd0a9b69fa52d146a25fabb6bcb6835a1",
      "parents": [
        "d0a3b481212178b0c3cee2718503d449925d6785"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Thu Oct 01 17:23:47 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Oct 01 18:24:46 2026"
      },
      "message": "[build] pin Firefox BiDi vendor CDDL from the Firefox repository (#18094)\n\n* [build] pin Firefox BiDi vendor CDDL from release branch on mozilla-firefox/firefox instead of local copies\n\n* [build] run update_cddl to repin CDDL sources\n\n* [py] point at the renamed bluetooth CDDL\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: bdb7ccc33f7be5f3c86853d9efb4d5d3143610bc\n"
    },
    {
      "commit": "d0a3b481212178b0c3cee2718503d449925d6785",
      "tree": "ae944e56e010144664108054b1a67f55b3a6d456",
      "parents": [
        "cc9807977462850272f147aeb24dd59bc778cc89"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Thu Oct 01 09:37:13 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Oct 01 09:59:21 2026"
      },
      "message": "[py][bidi] Send setClientWindowState rect members as sibling params (#18100)\n\nbrowser.SetClientWindowStateParameters is a CDDL group choice, so the\nchosen variant contributes its members to the enclosing params map. The\nhigh-level wrapper nested the whole rect under \"state\" instead, which a\nconformant browser rejects with invalid argument -- leaving no working\nway to set a client window rect from Python.\n\nSplice the variant\u0027s members in beside clientWindow, and read a\ndataclass through its field names rather than __dict__ so the baked\ninit\u003dFalse discriminator is picked up. A dict state is now flattened the\nsame way, and a state that is neither a named string, a rect, nor a dict\nis rejected up front rather than sent malformed.\n\nThe only coverage was an assertion that the named-state constants held\ntheir own values, which could not fail for any wiring reason. Replace it\nwith a rect round-trip against a real browser, and add unit tests that\nassert the exact outbound frame through a stand-in connection -- five of\nthose eight fail against the previous serialization.\n\nFixes #18085\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: e086574f977c623b03846f8c83fd848267fdfc73\n"
    },
    {
      "commit": "cc9807977462850272f147aeb24dd59bc778cc89",
      "tree": "f5deb2d3117d19b276486885b15742c27829f345",
      "parents": [
        "cff7c08c69be0b812f1e33532185bf13ecce508f"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Thu Oct 01 03:34:21 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Oct 01 03:58:43 2026"
      },
      "message": "[build] Reset versions to nightly after selenium-4.50.0 release\n\nnormally done via CI bot but release issues put it on the wrong branch\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 2d4e08da3bec4a8283b206c38de70faf185016bf\n"
    },
    {
      "commit": "cff7c08c69be0b812f1e33532185bf13ecce508f",
      "tree": "b7a3036a9412207bb2292bd7927878aa01cd38c7",
      "parents": [
        "245a5f7544768fc1b3cf677e6fba9dac35003635"
      ],
      "author": {
        "name": "cui fliter",
        "email": "imcusg@gmail.com",
        "time": "Thu Oct 01 02:27:46 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Thu Oct 01 03:25:56 2026"
      },
      "message": "[py] Validate WebSocket response interval correctly (#17984)\n\n---------\n\nSigned-off-by: cuishuang \u003cimcusg@gmail.com\u003e\nCo-authored-by: Corey Goldberg \u003c1113081+cgoldberg@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 4bac0b9aab02d75b90096cd52c6610bb815cb8db\n"
    },
    {
      "commit": "245a5f7544768fc1b3cf677e6fba9dac35003635",
      "tree": "db8584221f7259f581e0793fd6e6537ac3034d29",
      "parents": [
        "ca118a093cec00d672bf7e39ea2b41eea357882f"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Wed Sep 30 19:46:27 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 30 20:26:18 2026"
      },
      "message": "[build] Prepare for release of selenium-4.50.0 (#18105)\n\n* bump versions\n\n* changelogs updated\n\n* rust changelogs updated\n\n* update selenium manager versions\n\n* update authors file\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: ca2a96694916c818db1c388b7a1c14cf3380c098\n"
    },
    {
      "commit": "ca118a093cec00d672bf7e39ea2b41eea357882f",
      "tree": "456c0d9f181275ea81270fad28bab072a2f4f713",
      "parents": [
        "2e264a71df7bddc79eefd976ec3b18069e943ca2"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Tue Sep 29 20:11:34 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Sep 29 20:51:46 2026"
      },
      "message": "[py] fix tests to use pages we control instead of example.com\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 57183304adb05d36d724da998691d1d086b82bd4\n"
    },
    {
      "commit": "2e264a71df7bddc79eefd976ec3b18069e943ca2",
      "tree": "ba067e4fbe832ff0c5a3c292b34827d6da5f62b2",
      "parents": [
        "94da2ab6b19cee72be2d3a71eab964729cdcf38f"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Mon Sep 28 08:04:37 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Sep 28 08:24:49 2026"
      },
      "message": "[build] Automated Dependency Update (#18082)\n\nupdate dependencies and pinned binaries\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 76febdc715a93c9256eb2ac35624c406bc2159b7\n"
    },
    {
      "commit": "94da2ab6b19cee72be2d3a71eab964729cdcf38f",
      "tree": "76145e8b474bb319f66f5915e7519941270e60f1",
      "parents": [
        "d412c30e04ff6b7754dc47fd9d08b886c674c2c3"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Fri Sep 25 19:22:09 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Sep 25 19:24:52 2026"
      },
      "message": "[build] Automated CDDL Spec Update (#18075)\n\n* update cddl spec files\n\n* [py] Expand CDDL group references when generating BiDi types\n\n* [build] Make hasPlannedNavigation optional until browsers send it\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@gmail.com\u003e\nCo-authored-by: Titus Fortner \u003ctitus.fortner@gmail.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 6f841aa1f8f3b73f10af195b9f2b8734dd7b81f7\n"
    },
    {
      "commit": "d412c30e04ff6b7754dc47fd9d08b886c674c2c3",
      "tree": "dd75cedce26b1327faecb166d604c00fbe5ca1d5",
      "parents": [
        "615e3a6a4fb8a66800ef6ecbe4e92f93c1c1da28"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Fri Sep 25 12:31:53 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Sep 25 13:24:09 2026"
      },
      "message": "[build] Automated Browser Version Update (major) with CDP (#18067)\n\nUpdate pinned browser versions\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 52a288e07fe4e5540bbf67c7d42cbaf66a1c61de\n"
    },
    {
      "commit": "615e3a6a4fb8a66800ef6ecbe4e92f93c1c1da28",
      "tree": "72c1a970e16655a87c26c9fd6611cc250e86f2bf",
      "parents": [
        "b2b13d37e8c2ce9cb20e0192992ba657523d0440"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Wed Sep 23 17:11:40 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 23 17:25:44 2026"
      },
      "message": "[py] [dotnet] [java] declare Firefox default preferences in each binding (#18070)\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 1d7affd714878cf162d39e89611dc8385df0124f\n"
    },
    {
      "commit": "b2b13d37e8c2ce9cb20e0192992ba657523d0440",
      "tree": "65360ceee1f6d5b5f7bece05164b54a029e3d33c",
      "parents": [
        "8c153d86cbe927687535a2fcae73a1fe9966ade8"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Wed Sep 23 10:42:48 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 23 11:03:38 2026"
      },
      "message": "[build] Fix the two CDDL repin fallouts breaking every Python target (#18068)\n\n* [build] Point py at the renamed bluetooth scanning CDDL\n\nThe webref repin renamed the Web Bluetooth extract to\nbluetooth-scanning, and MODULE.bazel followed, but py/BUILD.bazel\nkept the old repository name. Every Python target that reaches\n//py:create-bidi-src now fails analysis with \"No repository visible\nas \u0027@web_bluetooth_all_cddl\u0027\".\n\n* [build] Check out the CDDL overlays with LF line endings\n\n.gitattributes normalizes by content, so a Windows checkout gives the\noverlays in common/bidi CRLF endings. The CDDL parser keeps the\ncarriage return in the production name, so every type those files\ndefine becomes unresolvable and BiDi schema generation fails with\n\"unresolved type mozDebugging.GetScriptSourceParameters\" and the\nlike. Converting the overlays to CRLF reproduces the same failure on\na POSIX host.\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 0de8ca5dacd378bf55a873013a834c5ec5f14bbc\n"
    },
    {
      "commit": "8c153d86cbe927687535a2fcae73a1fe9966ade8",
      "tree": "82046c2fbef4cff154d4d4d556d7ae881de2685c",
      "parents": [
        "89fa6a0551f9d23bba4bb13ab7027d17be5fc244"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Tue Sep 22 22:06:43 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Sep 22 22:24:06 2026"
      },
      "message": "[dotnet][java][js][py][rb] remove deprecated Firefox profile extension and cert methods (#18066)\n\n* [dotnet][java][js][py][rb] remove deprecated Firefox profile extension and cert methods\n\n* [rb] drop the now-unused rexml runtime dependency\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 952251725d5379f791cab4845e882a0c476438ca\n"
    },
    {
      "commit": "89fa6a0551f9d23bba4bb13ab7027d17be5fc244",
      "tree": "ab6967e5305e70a873c73b15d2f218af379b446f",
      "parents": [
        "9b3ec7ed33913776372d3506fac3cea959d42b8e"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Mon Sep 21 09:22:13 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Sep 21 09:24:57 2026"
      },
      "message": "[build] Automated Dependency Update (#18061)\n\nupdate dependencies and pinned binaries\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 4ceb7526d651165d7d86ccb16acbc0d91f25288c\n"
    },
    {
      "commit": "9b3ec7ed33913776372d3506fac3cea959d42b8e",
      "tree": "a23baba1555d906b27482aa5b621688418f203c8",
      "parents": [
        "dfdf3185679012ea84e1e708381348a67466077f"
      ],
      "author": {
        "name": "Adam Dangoor",
        "email": "adamdangoor@gmail.com",
        "time": "Mon Sep 21 08:33:31 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Sep 21 08:49:43 2026"
      },
      "message": "[py] Type WebElement.get_attribute name (#18028)\n\n* Type WebElement.get_attribute name\n\n* [py] Type get_property and get_dom_attribute name parameters\n\nExtends this PR\u0027s get_attribute annotation to the two sibling\nmethods that share the same untyped name parameter, as requested\nin review.\n\nCo-Authored-By: Claude Sonnet 5 \u003cnoreply@anthropic.com\u003e\n\n---------\n\nCo-authored-by: Diego Molina \u003cdiemol@users.noreply.github.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@gmail.com\u003e\nCo-authored-by: Claude Sonnet 5 \u003cnoreply@anthropic.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: d1a2905b74c768435cb3df66f47348eb5b84ce2a\n"
    },
    {
      "commit": "dfdf3185679012ea84e1e708381348a67466077f",
      "tree": "ec85dd9b5726f354baa4491a07c1fdfc3a31cc6e",
      "parents": [
        "ba28059e783dcaead03420ac32df27fd944bc462"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Sun Sep 20 18:20:15 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sun Sep 20 18:25:18 2026"
      },
      "message": "[build] replace the BiDi KNOWN_INCOMPLETE allowlist with spec-shaped CDDL overlays (#18057)\n\n* [build] replace the BiDi KNOWN_INCOMPLETE allowlist with spec-shaped CDDL overlays\n\n* [build] update cddl spec files\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: e93424fe4b3199a77954936d8e5055bcc1cf9511\n"
    },
    {
      "commit": "ba28059e783dcaead03420ac32df27fd944bc462",
      "tree": "a617e4b8ec46d5588568b11cebefc389c628f592",
      "parents": [
        "33860340dd42ced83ff31011f9bd0a0aff690ad8"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Sat Sep 19 18:11:10 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sat Sep 19 18:24:19 2026"
      },
      "message": "[py] run grid specific tests on windows during scheduled tests (#18054)\n\n* [py] split the Grid-specific tests into test-\u003cbrowser\u003e-grid suites and run them on the Windows nightly\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 3e8b086a1d8701a7e4bd0fb5360122c66c8f9866\n"
    },
    {
      "commit": "33860340dd42ced83ff31011f9bd0a0aff690ad8",
      "tree": "db2035fc4d057132337e8a0560f8f7896051e869",
      "parents": [
        "6eaf5d2b0eb7f91011d7942cb51cb0e127d3ddbf"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Fri Sep 18 22:35:50 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Sep 18 23:24:22 2026"
      },
      "message": "[py] skip clean_driver fixture under --remote since it always builds a local driver\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: a1882401b61518440c900c1f15857a341b2de4da\n"
    },
    {
      "commit": "6eaf5d2b0eb7f91011d7942cb51cb0e127d3ddbf",
      "tree": "c4b8d142d7097c0bfb44581163e42496fe50ce9f",
      "parents": [
        "4e01f53649f30f02d0806021a9b2ecc04f746745"
      ],
      "author": {
        "name": "Adam Dangoor",
        "email": "adamdangoor@gmail.com",
        "time": "Wed Sep 16 22:02:09 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 16 22:25:08 2026"
      },
      "message": "[py] Type BaseOptions.set_capability parameters (#18027)\n\nType BaseOptions.set_capability parameters\n\nCo-authored-by: Diego Molina \u003cdiemol@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 527d509fb50000e189448aa4b54df9286f8ab8c8\n"
    },
    {
      "commit": "4e01f53649f30f02d0806021a9b2ecc04f746745",
      "tree": "b7065240be77eea3b695e37a5c37754207aad71a",
      "parents": [
        "e34ad073f72cea05af4a2744307cdad7389f1cdd"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Wed Sep 16 21:56:17 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 16 22:24:51 2026"
      },
      "message": "[py] move complete GitHub Actions test suite to nightly and run only high value smoke tests for PRs (#18040)\n\n* [py] split CI into per-OS smoke and scheduled full tiers\n\n* [py] fix tests to scroll the input into view before sending keys through actions\n\n* [py] run os-sensitive smoke targets only where RBE cannot, not on ubuntu\n\n* [py] drop the GHA remote job; RBE runs the Grid suite\n\n* [py] make the full tier filter the exact complement of the smoke filter\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 4fd11e186332d1f46484572db0cc0b4ac2835cc3\n"
    },
    {
      "commit": "e34ad073f72cea05af4a2744307cdad7389f1cdd",
      "tree": "8c0c46f17144d07b9b54a9f73bb9f1e24c744b11",
      "parents": [
        "e228dedd4411d6ee3337aa477ad3756f8dd2fee8"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Wed Sep 16 17:55:41 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 16 18:23:54 2026"
      },
      "message": "[py] run the requirements lock check on RBE with a platform-agnostic lock (#18039)\n\n* [py] allow the requirements lock check to run on RBE since it can use the host network\n\n* [py] make requirements lock check platform-agnostic\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 603fe2936308dd1452744a9da7ec808d6e506b46\n"
    },
    {
      "commit": "e228dedd4411d6ee3337aa477ad3756f8dd2fee8",
      "tree": "5c9a59995d0f697a2f08e2e562a1728cd385d62f",
      "parents": [
        "9992984dbea7abb709873c528bcc59f72bb06864"
      ],
      "author": {
        "name": "Navin Chandra",
        "email": "navinchandra772@gmail.com",
        "time": "Tue Sep 15 13:56:06 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Sep 15 14:26:30 2026"
      },
      "message": "[py]: Apply ClientConfig `user_agent` and `extra_headers` per connection (#17944)\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 8d1433af8923288c0db0596a47fd53c56a44619b\n"
    },
    {
      "commit": "9992984dbea7abb709873c528bcc59f72bb06864",
      "tree": "8b6762734eaa2324a7af1584d14aec237aea6a21",
      "parents": [
        "085725de89c89b2186b9b2ec2e1ec1f807479074"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Mon Sep 14 08:39:31 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Sep 14 09:24:13 2026"
      },
      "message": "[build] Automated Dependency Update (#18032)\n\nupdate dependencies and pinned binaries\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: b39d1c270006fa8ac0d1df240e3f47cec9bef40c\n"
    },
    {
      "commit": "085725de89c89b2186b9b2ec2e1ec1f807479074",
      "tree": "9dccf7be56c56516f00824c11521fb26687171ff",
      "parents": [
        "79afada6e2161b5086e655420750f9c53bb634c5"
      ],
      "author": {
        "name": "Alex Popov",
        "email": "iampopovich@gmail.com",
        "time": "Sun Sep 13 23:47:35 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Sep 14 00:25:09 2026"
      },
      "message": "[py] cleanup for ruff b904 raise from  (#18030)\n\n* [py] chain the exception context in remote webdriver raises\n\n`_get_cdp_details` re-raised as WebDriverException without a cause, hiding\nwhich capabilities key (`goog:chromeOptions` or `ms:edgeOptions`) returned\nNone, so chain the AttributeError.\n\n`execute_script` suppresses the KeyError instead -- it carried nothing the\nmessage did not already say -- but the script id it did carry is now part of\nthe message, so a caller with several pinned scripts can tell which one is\nmissing.\n\n* [py] chain the OSError context when the service path is None\n\nThe sibling raise a few lines down already passed `from err`; this branch is\nreached from the same EACCES handler and should report the same cause.\n\n* [py] make the --driver fixture errors self-explanatory\n\n`clean_driver` collapsed two unrelated failures into one message: no --driver\npassed (TypeError from subscripting None) and a --driver that SupportedDrivers\ndoes not know (AttributeError from getattr). The second case reported \"This\ntest requires a --driver to be specified\", which is untrue.\n\nargparse `choices\u003ddrivers` currently makes the second case unreachable, but\nonly for as long as `drivers` and SupportedDrivers stay in sync, and the\nmerged handler gave no hint when they drift. Split the two, and name the\noffending driver in the message.\n\nThe other two fixtures do not call getattr, so their message is accurate;\nthey just suppress the context.\n\n* [py] chain the exception context in cdp, utils, firefox profile and server\n\nCauses that carry information are chained with `from err`. Two are suppressed\ninstead: the LookupError from a ContextVar carries nothing beyond \"unset\", and\nthe ConnectionRefusedError in Server.start is the success path -- it means the\nport was free -- so it would only mislead in a startup-timeout traceback.\n\n* [py] enforce ruff B904 (raise ... from) in the Python bindings\n\nPart of #17989. All 17 findings are addressed by the preceding commits, so\nthe carve-out can go and the rule keeps new ones from landing.\n\n* [py] drop the hand-rolled traceback argument from AddonFormatError\n\n`AddonFormatError` has no `__init__`, so the second positional argument never\nbecame `__traceback__` -- it just landed in `args`, and with two args\n`Exception.__str__` prints the whole tuple. Every add-on parsing failure ended\nits traceback with a memory address:\n\n    AddonFormatError: (\"[Errno 2] ... install.rdf\", \u003ctraceback object at 0x7f..\u003e)\n\nThe pattern dates to 983d5b2e62 (2014), a port of Python 2\u0027s three-argument\n`raise`, which was the only way to keep the original traceback back then. The\npreceding commit added `from e`, so `__cause__` now carries the cause properly\nand the argument is redundant as well as noisy. It also kept every frame of\nthe caught exception alive for as long as the AddonFormatError was referenced.\n\nThis changes `str(exc)` and `exc.args` for the two raises that passed it. The\nclass is not re-exported from any package `__init__`, and the third raise in\nthe same file already passed a single argument.\n\n`import sys` goes with them; nothing else in the file used it.\n\n* fix: use a variable for script ID in JavascriptException message\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 413306e545029a3ee830db4580de40c5f754d654\n"
    },
    {
      "commit": "79afada6e2161b5086e655420750f9c53bb634c5",
      "tree": "5f7ee1e8352411f07aa4ed62a83f2b1b072726e4",
      "parents": [
        "09f1b0eb5949e5fb919cd8be17d13c0ee128583d"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Fri Sep 11 20:25:05 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Sep 11 21:24:18 2026"
      },
      "message": "[py] replace the blanket remote BiDi test suite with an opt-in list (#18023)\n\n* [py] remove the remote BiDi test targets and their CI job\n\n* [py] add an opt-in list for BiDi tests that also run through Grid\n\n* [py] source the opt-in remote BiDi suite only from REMOTE_BIDI_TESTS\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 2aa90e832940d5f4d1c8fc3eacb8ce0a0a57b23f\n"
    },
    {
      "commit": "09f1b0eb5949e5fb919cd8be17d13c0ee128583d",
      "tree": "46c36926b036f96fc65fda722e6bf413fbdcd98c",
      "parents": [
        "5ab1259c7f74ffdb8d9777486ed04d1b6aad5a36"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Fri Sep 11 18:54:07 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Fri Sep 11 19:24:03 2026"
      },
      "message": "[py] remove the webkitgtk and wpewebkit test targets\n\nthese tests require browsers that are not accessible to our build system\nwebkit team runs our tests with pytest instead of with bazel\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: ca4a42ab98ddde53d904692db8e1621b8026bee2\n"
    },
    {
      "commit": "5ab1259c7f74ffdb8d9777486ed04d1b6aad5a36",
      "tree": "4f6aea7d5da7e89e11c1cb7e3d1478dd2a536229",
      "parents": [
        "65230af47f59589e8caf82ee8d739a6050305444"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "selenium-ci@users.noreply.github.com",
        "time": "Wed Sep 09 15:37:36 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 09 16:26:32 2026"
      },
      "message": "[build] Reset versions to nightly after selenium-4.49.0 release\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 0a5e0e73a688360ac3cdfe4af8be7a5c91b23878\n"
    },
    {
      "commit": "65230af47f59589e8caf82ee8d739a6050305444",
      "tree": "432428e2f4f7fce83abdcb0495d75bf355e09e82",
      "parents": [
        "26273c1a8a3ef507620088d2451f641cb8c06d48"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Wed Sep 09 15:27:34 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 09 16:25:54 2026"
      },
      "message": "[build] Prepare for release of selenium-4.49.0 (#18012)\n\n* bump versions\n\n* changelogs updated\n\n* rust changelogs updated\n\n* update selenium manager versions\n\n* update authors file\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: bedb24f6f703876e7e4c5d506f0d697eb358c720\n"
    },
    {
      "commit": "26273c1a8a3ef507620088d2451f641cb8c06d48",
      "tree": "eb3119ad8761757e534e5baa0fd89ea38dc4eac2",
      "parents": [
        "fed0abfcfd6b4ecfd42f076ea1d5bf8d401d8e60"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Wed Sep 09 13:41:50 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 09 14:24:54 2026"
      },
      "message": "[py] adopt the worthwhile ruff 0.16 rule families (tranches 1 and 2) (#17992)\n\n* [py] enforce the mechanical ruff rule families from ruff 0.16 (#17989)\n\nTranche 1 of #17989. Adds the rule families whose findings are pure local\nrewrites, and fixes the 20 findings so //py:ruff-check stays green.\n\nAdopted as whole families (verified clean at full-family scope): C4, FLY, W.\nAdopted as individual rules, because the rest of their family is far noisier\nthan the subset ruff 0.16 enables by default: FURB122, FURB167, FURB188,\nFURB192, PERF102. For scale, whole-family PLR is 332 findings (251 of them\nPLR2004) against 4 in the default set, and whole-family FURB/PERF are 29/19\nagainst 10/2 -- so select entries have to be rule codes there, not family\nnames.\n\nFixes:\n- FURB188 x4 generate_bidi.py: removeprefix/removesuffix\n- FURB122 x2 generate.py, firefox_profile.py: writelines\n- FURB167 x2 update_cddl.py: re.S -\u003e re.DOTALL\n- FURB192 x2 pinned_browsers.py, update_cdp.py: sorted(...)[-1] -\u003e max(...)\n- C400 x2, C408 x2, C417, C419, C420: comprehension rewrites\n- PERF102 x2 cdp.py: .items() -\u003e .values()\n- FLY002 remote_server_tests.py: static join -\u003e literal\n\nNote on FURB192: sorted(...)[-1] and max(...) disagree when two entries tie on\nthe sort key -- a stable sort returns the last tied element, max the first.\nBoth call sites pick the newest chrome-for-testing build within a milestone,\nwhere version strings are unique, so this is inert in practice. Calling it out\nbecause it is the only behavioural change in the commit.\n\nThe comprehension rewrites in webdriver.py touch _wrap_value/_unwrap_value and\nthe one in _script_handlers.py feeds a BiDi preload script, so those were\nchecked for equivalence against the pre-change code over a generated input\ncorpus before being applied. bazel test --test_size_filters\u003dsmall //py:unit\npasses 32/32; //py:ruff-check and //py:ruff-format --check are clean.\n\nDeliberately not adopted here: FURB110 (18, if-exp -\u003e or, a readability\nregression as often as not) and the rest of PERF (PERF401/403 comprehension\nrewrites, PERF203 try-except-in-loop), which are loop-shape changes rather\nthan mechanical ones.\n\n* [py] settle shebang/exec-bit consistency and enforce ruff EXE (#17989)\n\nTranche 1 of #17989, split out from the mechanical-rewrite commit because it is\nthe only part that changes git file modes.\n\nSix files disagreed with themselves about whether they are directly\nexecutable. All six are py_binary srcs, invoked through Bazel\n(./go update_cddl -\u003e bazel run //scripts:update_cddl) or with an explicit\ninterpreter (py/tox.ini uses {envpython} ./generate_api_module_listing.py), so\nnone of them ever relies on its shebang. Resolved all six the same way -- not\ndirectly executable:\n\n- chmod -x (100755 -\u003e 100644), which clears EXE002:\n  py/generate_api_module_listing.py, py/generate_bidi.py, py/run_mypy.py,\n  py/run_sphinx_autogen.py\n- drop the now-unused shebang, which clears EXE001:\n  scripts/update_cddl.py, scripts/update_docfx.py\n\n//scripts:update_cddl, //scripts:update_docfx, //py:generate-api-listing,\n//py:generate_bidi, //py:mypy and //py:sphinx-autogen all still build.\n\n* [py] replace the assert 1 \u003d\u003d 0 idiom with pytest.raises and enforce PLR0133 (#17989)\n\nTranche 1 of #17989. All four PLR0133 (comparison-of-constant) findings are the\nsame shape in visibility_tests.py:\n\n    try:\n        element.click()\n        assert 1 \u003d\u003d 0, \"should have thrown an exception\"\n    except (ElementNotVisibleException, ElementNotInteractableException):\n        pass\n\nwhich is pytest.raises spelled the long way round, and reports \"assert 1 \u003d\u003d 0\"\nrather than \"DID NOT RAISE\" when the expectation is not met. Converted all four\nto a with pytest.raises(...) block. PT011 and PT012 are already in\nextend-ignore, so the tuple form and the trailing assert in the send_keys test\nneed no further exemption.\n\nAdds PLR0133 as an individual rule rather than the PLR family: whole-family PLR\nis 332 findings (251 of them PLR2004 magic-value-comparison), and it also\ncarries PLR0913/PLR0917 (too-many-arguments / -positional-arguments, 45 hits)\nwhich land on public constructor signatures the API-compatibility invariant\nprotects.\n\n//py:test/selenium/webdriver/common/visibility_tests-chrome passes 15/15, with\nall four converted tests confirmed as run rather than skipped.\n\n* [py] enforce ruff PIE and PLE (#17989)\n\nTranche 1 of #17989. Both families are small and clean at whole-family scope,\nso they go in as bare family names.\n\nPIE790 x5 -- dropped the unnecessary `pass` after a docstring in cdp.py,\nwebdriver.py (start_client/stop_client), webelement.py (BaseWebElement) and\nthe test webserver. Safe autofix.\n\nThe remaining four findings are false positives, suppressed with a `noqa` and\na reason rather than \"fixed\":\n\n- PIE796 x3, generate_bidi.py CddlType: the duplicate Enum values are\n  deliberate aliases (TEXT-\u003eTSTR, INT/NINT-\u003eUINT), and get_annotation already\n  resolves them through `__members__`, which includes aliases. The bug this\n  rule would have caught was fixed in 9d0ae9e68f; what is left is the intended\n  pattern, so the comment now says so explicitly.\n- PLE0605 x1, webdriver/__init__.py: `__all__ \u003d sorted(_LAZY_IMPORTS.keys())`.\n  sorted() returns a list, so this is correct at runtime; the rule only accepts\n  a literal. The usual argument for a literal is static readability, but\n  __init__.pyi already re-exports every name explicitly with `as` aliases, so\n  type checkers never read this line. Deriving __all__ from _LAZY_IMPORTS is\n  what stops the two from drifting at runtime, so it stays.\n\nNote for reviewers: nothing currently asserts that __init__.pyi\u0027s re-export\nlist and _LAZY_IMPORTS agree. That gap is worth its own change; it is not\nintroduced here.\n\nVerified: regenerating the BiDi sources with this generator produces output\nbyte-identical to trunk\u0027s across all 22 files, apart from the one\n_script_handlers.py line changed deliberately in the mechanical commit.\n//py:unit passes 32/32, including BaseWebElement\u0027s ABC subclass/register\nbehaviour and the start_client/stop_client no-op hooks whose `pass` was\nremoved.\n\n* [py] enforce ruff bugbear (B) with carve-outs (#17989)\n\nTranche 2 of #17989. Adopts B as a family rather than a list of rule codes, so\nthat new bugbear rules in a future ruff release surface as one review decision\ninstead of arriving silently. Whole-family B is 59 findings against the 33 ruff\n0.16 enables by default, so four rules are listed in extend-ignore.\n\nFixed (7):\n- B009 x5 / B010 x1 -- constant getattr/setattr in print_page_options.py,\n  proxy.py and timeouts.py. Safe autofix. proxy.py\u0027s is inside a descriptor\u0027s\n  __set__; proxyType is a plain class attribute rather than another descriptor,\n  so direct assignment is equivalent, and the dynamic setattr(obj, self.name, ...)\n  on the next line is left alone.\n- B006 x1 -- javascript/private/gen_file.py `def main(argv\u003d[])`. The default is\n  unreachable in practice (the only caller is `main(sys.argv)`) and would\n  IndexError on argv[1] if it were ever used, so it is dropped rather than\n  replaced with a None sentinel.\n- B018 x1 -- click_scrolling_tests.py accessed `.size` for its side effect to\n  prove that reading it does not scroll; now `_ \u003d ...` so the intent is explicit.\n\nCarved out via per-file-ignores (18):\nB018 under py/test/** and in py/conftest.py. 17 of the 19 findings are a\nproperty access inside `with pytest.raises(...)`, which is the idiomatic way to\nassert that reading a property raises; the 19th is the driver-liveness probe in\nconftest.py. Note that conftest.py is not under py/test/**, so the pattern\nsuggested on the issue would have missed it. Both globs resolve against the\nworking directory rather than this file, which is why they are spelled\n\"py/...\" -- there is now a comment saying so, because they silently stop\nmatching if ruff is invoked from inside py/.\n\nDeferred via extend-ignore:\n- B904 (17) raise-without-from-inside-except. This is the highest-value rule in\n  the whole 184-finding set for a library -- it materially improves user\n  tracebacks -- but it is 16 findings in py/selenium and deserves its own change\n  rather than being bundled into a lint-config commit.\n- B017 (7) pytest.raises(Exception). Narrowing each site needs the actual\n  exception type confirmed against a live browser (three are BiDi tests, where\n  8cbf4bfa2d\u0027s typed errors make narrowing most valuable), so it is deferred\n  rather than guessed.\n- B028 (4), B007 (3), B024 (2).\n\n//py:unit passes 32/32. //javascript/chrome-driver:header, :source and\n//javascript/ie-driver:header build clean, covering the gen_file.py change.\n\n* [py] narrow pytest.raises(Exception) and enforce B017 (#17989)\n\nTranche 2 of #17989, completing the bugbear adoption by dropping B017 from\nextend-ignore. Each exception type was confirmed by running the Bazel test\ntarget with a deliberately wrong assertion and reading the type back out of the\nfailure, rather than by guessing:\n\n- proxy_tests.py x2 -\u003e ValueError, from Proxy._verify_proxy_type_compatibility.\n- executing_javascript_tests.py -\u003e TypeError. Passing the driver as a script\n  argument is rejected by json.dumps client-side, before any command is sent.\n- _bidi/browsing_context_tests.py x2 -\u003e NoSuchFrameException, for get_tree on a\n  closed context.\n- _bidi/script_tests.py -\u003e NoSuchHandleException, for call_function on a\n  disowned handle.\n\nThe two BiDi files are where this is most worth having: 8cbf4bfa2d added typed\nerrors for BiDi wire error codes, so these assertions now pin the exact code\nthe spec requires instead of accepting anything at all.\n\nquit_tests.py needed a tuple. Which error surfaces after driver.quit() depends\non what is left listening -- against a local driver the process is gone and\nurllib3 raises MaxRetryError, while against a Grid the server is still up and\nthe session lookup fails with InvalidSessionIdException. Both were confirmed by\nrunning quit_tests-chrome and quit_tests-chrome-remote. Worth noting for a\nseparate change: MaxRetryError is a raw urllib3 error escaping the public API,\nand arguably ought to be wrapped in a WebDriverException.\n\nVerified: proxy_tests-chrome, quit_tests-chrome, quit_tests-chrome-remote and\nexecuting_javascript_tests-chrome all pass. In the BiDi suites the four\nnarrowed tests passed 6/6 across repeat runs; the one failure in those files is\ntest_activate_browsing_context, which fails roughly one run in two both here\nand on trunk (asynchronous focus handover) and is unrelated to this change.\n//py:ruff-check clean, //py:unit 32/32.\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 2cd4ec2aae79f3aeaf6947c63182a3a022f0e9f4\n"
    },
    {
      "commit": "fed0abfcfd6b4ecfd42f076ea1d5bf8d401d8e60",
      "tree": "bddfb3e789d83497006c020aa0083561ce5f6d29",
      "parents": [
        "8be696f29dc6f09c62816c09cfdca1956dd0e651"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Wed Sep 09 12:41:06 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 09 13:26:26 2026"
      },
      "message": "[build] Automated Browser Version Update (major) with CDP (#18005)\n\n* Update pinned browser versions\n\n* [java][js] ignore strict file interactability upload tests on Chrome 153+ which raises a focus error instead of ElementNotInteractable\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nCo-authored-by: Titus Fortner \u003ctitusfortner@users.noreply.github.com\u003e\nCo-authored-by: Titus Fortner \u003ctitus.fortner@gmail.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 0536451d5b340478614e5a2fdba898dec3e59e3a\n"
    },
    {
      "commit": "8be696f29dc6f09c62816c09cfdca1956dd0e651",
      "tree": "99e7a04aef39477604c6d728e36fd3a0c2b8539a",
      "parents": [
        "61fb52962b1e6dc8034e85c0ab17f5c7260dae56"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Wed Sep 09 10:03:02 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Wed Sep 09 10:24:07 2026"
      },
      "message": "[py] add BiDi upload tests that verify files actually reach the server (#18007)\n\nThe existing `input.setFiles` tests stop at the file input\u0027s `value`\nproperty, which only shows the filename string was accepted. Classic\n`upload_tests.py` submits the form and reads what the endpoint echoed\nback, so it proves the bytes were transmitted; the BiDi tests did not.\n\nAdd three tests to the `_bidi` input suite:\n\n- `test_set_files_posts_file_to_server` and its two-file variant mirror\n  the classic upload tests, asserting the endpoint echoed back both the\n  filename and the file content.\n- `test_type_and_set_files_in_same_form` covers filling a form the way a\n  user does, typing into a text field and attaching a file in one flow.\n  It also asserts `#fileResults` changed, so a real `change` event has\n  to fire rather than just a value write.\n\nCopse-Models: acp:claude-agent-acp#opus[1m]\n\nCo-authored-by: Copse \u003cnoreply@copse.dev\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 92394dcf7b0effa4d0a2791c9f5885eb0827bb19\n"
    },
    {
      "commit": "61fb52962b1e6dc8034e85c0ab17f5c7260dae56",
      "tree": "bb1f179a1a6bda36db69fb7afa3c9fa6c5c0d457",
      "parents": [
        "6005ad1a80afafb5371a50712e21996ae238f836"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Tue Sep 08 19:22:58 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Sep 08 20:29:32 2026"
      },
      "message": "[build] ship prebuilt Selenium Manager Linux arm64 binary in all bindings (#17999)\n\n* [build] ship prebuilt Selenium Manager Linux arm64 binary in all bindings\n\n* [build] run the Ruby Selenium Manager smoke tests on a Linux arm64 runner\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 0aaee1e6a990a8383ae04dfb607924f2cd1bc19f\n"
    },
    {
      "commit": "6005ad1a80afafb5371a50712e21996ae238f836",
      "tree": "dd4efc9997dafdc9c4e93867809d9bd40198a252",
      "parents": [
        "e1ea8c562940a3b23866db1cb66992f932842e58"
      ],
      "author": {
        "name": "aayushisabharwal",
        "email": "aayushi.sabharwal@gmail.com",
        "time": "Tue Sep 08 15:00:21 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Sep 08 15:26:27 2026"
      },
      "message": "[py] remove stale xfail markers from python tests (#18002)\n\n[py] remove stale xfail markers from window size/position tests\n\nThe xfail_edge markers on the window size/position tests were added in\n#15638 without a reason or linked issue, and the xfail_chrome,\nxfail_firefox and xfail_remote markers next to them cite a geckodriver\nwindow-positioning bug that no longer reproduces on CI.\n\nWith all of these markers removed, window_tests.py and\napi_example_tests.py pass on chrome, firefox and edge on Windows and\non chrome and firefox through a Grid on Linux, across several runs.\nThe Edge tests also pass locally on macOS.\n\nThe xfail_safari markers are untouched, as are the markers on the two\nlogging tests, which still raise AttributeError outside Chromium.\n\nCo-authored-by: aayushisabharwal \u003c174711043+aayushisabharwal@users.noreply.github.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 6147f47ca1bcc222f74722fb9f15d1cd5411ca7c\n"
    },
    {
      "commit": "e1ea8c562940a3b23866db1cb66992f932842e58",
      "tree": "5225d5feba29711702001cad8cb388941254758b",
      "parents": [
        "d4a1a631d7749a695282bd590dda83e1fe3f4898"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Tue Sep 08 09:28:06 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Tue Sep 08 10:26:15 2026"
      },
      "message": "[build] Automated Dependency Update (#17991)\n\nupdate dependencies and pinned binaries\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nCo-authored-by: Diego Molina \u003cdiemol@users.noreply.github.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: f4721fddc0158a20d0e31397e89ca1c1bbe72991\n"
    },
    {
      "commit": "d4a1a631d7749a695282bd590dda83e1fe3f4898",
      "tree": "85959d680fd3cebe830d01b0b249e06dfca2ce78",
      "parents": [
        "8ec024458e54c11073e57cf3ac6e94f8b48ed4a8"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Mon Sep 07 19:20:16 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Sep 07 19:24:10 2026"
      },
      "message": "[py] fix linting issue\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 58ddb0356569f15c459feb17c37e6864ac13b4cb\n"
    },
    {
      "commit": "8ec024458e54c11073e57cf3ac6e94f8b48ed4a8",
      "tree": "c3e21a86c529ae52c4f142f2389b3b6eb5941fca",
      "parents": [
        "b3252fc10d2390e5d79a11812861dec79b8ff37b"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Mon Sep 07 17:04:18 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Sep 07 18:25:47 2026"
      },
      "message": "[py] tolerate Safari\u0027s mid-navigation window errors in click and window tests\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 4704bc5ef277e56b541f18a71d136830b55d64f0\n"
    },
    {
      "commit": "b3252fc10d2390e5d79a11812861dec79b8ff37b",
      "tree": "bbb7f1aba711125e889671227e4ee83c4d47b8b5",
      "parents": [
        "92a08810d99874b89c6f9ffc920785be0cad24c8"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titusfortner@users.noreply.github.com",
        "time": "Mon Sep 07 09:13:19 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Sep 07 09:26:58 2026"
      },
      "message": "[py] raise typed WebDriver errors for BiDi wire error codes (#17952)\n\n* [py] raise typed WebDriver errors for BiDi from a generated error-code map\n\n* [py] generate BiDi error classes instead of minting them at import\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 8cbf4bfa2dc9bab3d11ec78c85a93bc3605ecb83\n"
    },
    {
      "commit": "92a08810d99874b89c6f9ffc920785be0cad24c8",
      "tree": "64ebfe1be670b7b7031434b71b21116ab875c5ed",
      "parents": [
        "3f0f0997f6bc65141d0412ae4aaeb004c9cc245a"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Mon Sep 07 09:11:17 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Sep 07 09:26:27 2026"
      },
      "message": "[py] fix flaky BiDi input tests that raced the page\u0027s autofocus (#17981)\n\nsingle_text_input.html focuses #textInput via autofocus. Firefox may apply that after the load event, so keys dispatched immediately after pages.load() can land on \u003cbody\u003e and the leading characters get dropped. That is why the failure rotated between the keyboard tests in the file instead of pinning to one assertion.\n\ntest_combined_input_actions was affected too: the pointer and key sources advance tick-for-tick, so keyDown(\"t\") fires alongside the pointer move, before the click that would have focused the input.\n\nRequest focus explicitly and wait for document.activeElement before performing actions, in both the generated _bidi mirror and the driver.input facade tests.\n\nFixes #17979\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 396099a1d8ccae70d374418f25f75926c9717d7e\n"
    },
    {
      "commit": "3f0f0997f6bc65141d0412ae4aaeb004c9cc245a",
      "tree": "acc209b350a388f37dde1e9c58731c521b76ba0c",
      "parents": [
        "9bf90b7e6cfd717b1f35293f3d696489a66d96d9"
      ],
      "author": {
        "name": "David Burns",
        "email": "david.burns@theautomatedtester.co.uk",
        "time": "Mon Sep 07 09:10:16 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Mon Sep 07 09:26:07 2026"
      },
      "message": "[py] run the BiDi tests against the Grid server (#17980)\n\n* [py] run BiDi tests against the Grid server\n\nAdd test-\u003cbrowser\u003e-remote-bidi targets for chrome and firefox, combining the\nBiDi test sources with the grid data/env wiring the other remote suites use.\nBIDI_TESTS was excluded from every remote suite glob, so the Python BiDi tests\nhad never run through Grid\u0027s websocket proxy.\n\nNo client change was needed: --remote and --bidi are independent code paths,\nLocalNode already rewrites webSocketUrl to /session/\u003cid\u003e/se/bidi, and\nProxyNodeWebsockets tunnels it to the node.\n\nKept out of the test-\u003cbrowser\u003e-remote aggregate for now, since this is new\ncoverage. No xfail_remote markers were added: the suite passes except for two\ntests that fail identically without --remote (a headless-Chrome\ndocument.hasFocus() artifact, and a pre-existing firefox wheel-scroll timing\nflake that fails 3/3 runs locally).\n\n* [py] wire the remote BiDi suite into CI and document it\n\nAdd a test-remote-bidi aggregate test_suite alongside test-remote, and a\nseparate remote-bidi-tests job in ci-python.yml that queries it. Keeping it as\nits own job means a red BiDi run cannot redden the established remote job while\nthis coverage is new.\n\nDocument the remote targets and the --remote / --bidi --remote pytest flags in\npy/TESTING.md, including the Grid jar prerequisite.\n\n* [py] set target_compatible_with on the remote test suites\n\nThe bidi and per-browser suites pass BROWSERS[browser][\"target_compatible_with\"]\nthrough; the remote ones did not. It is a no-op today because the value is empty\nfor chrome and firefox, but the divergence would silently drop the platform\nconstraint the first time a remote suite is added for a browser that has one\n(ie, safari, webkitgtk).\n\n* [py] collapse the duplicated xfail_remote and remote-directory guards\n\nThe \"skip unless --remote\" guard was written out three times (driver,\nfirefox_options, chromium_options) and the xfail_\u003cdriver\u003e/xfail_remote marker\ndance twice (driver, clean_driver). Extract _skip_unless_remote and\n_apply_xfail_markers.\n\nThe two marker blocks had drifted apart, so unifying them fixes two latent bugs\nin the clean_driver path:\n\n- it did not support the condition kwarg, and passed it straight to\n  pytest.xfail(), which does not accept it (TypeError)\n- neither block popped run before calling pytest.xfail(), so an explicit\n  run\u003dTrue raised TypeError as well\n\nThe `yield`/`return` after pytest.skip() in both blocks was dead code;\npytest.skip() raises.\n\nVerified against alerts_tests, which exercises all three marker paths\n(condition, run\u003dFalse, plain xfail): 20 passed, 1 skipped, 2 xfailed both\nbefore and after.\n\n* [py] give the remote test suites the COMMON_TAGS their siblings carry\n\nThe remote suites set tags \u003d [\"no-sandbox\", \"remote\", \"\u003cbrowser\u003e-remote\"],\nomitting BROWSERS[browser][\"tags\"] (COMMON_TAGS + the bare browser name) that\nevery non-remote suite carries. Compose from the sibling expression instead and\nkeep the two remote markers on top.\n\nbrowser-test and requires-network are inert here - nothing in .bazelrc*,\n.github/, scripts/ or the Rakefile filters on either, and requires-network is\nmoot anyway because no-sandbox already disables the sandbox. The tag that\nmatters is the bare browser name: py/TESTING.md documents\n\n    bazel test //py/... --test_tag_filters\u003dchrome\n\nand that silently matched no remote target. It now matches all 26 per-browser\nremote-bidi targets. Ruby already does this and says why in a comment\n(rb/spec/tests.bzl), so this aligns Python with the reference implementation.\n\nThe omission was incidental rather than deliberate: the per-browser remote\ntargets in b1904f8a89 (#16851) inherited the minimal tag set of the single\ntest-remote target they replaced, and no rationale for the tag choice is\nrecorded.\n\n* [py] make the focus and wheel-scroll bidi tests work headed and headless\n\nBoth tests read an asynchronous result exactly once, so they raced it.\n\ntest_activate_browsing_context asserted that creating a second window takes\nfocus off the first. Focus is handed over asynchronously, so poll for it. That\nalone is not enough: headless Chromium has no window manager and never hands\nfocus over at all, confirmed by a 5s wait timing out 5/5 runs. So skip in that\ncase only - gated on the new `headless` fixture, and only after the wait has\nactually expired. Headless Firefox does hand focus over and keeps the full\nassertions; headed runs still fail loudly if the handover regresses, because the\nskip cannot trigger outside headless.\n\ntest_wheel_scroll_with_duration spreads a 100px scroll over 500ms and then\nasserted pageYOffset \u003d\u003d 100 immediately; Firefox was reliably one pixel short.\nWait for the scroll to settle before asserting, keeping the exact assertion\nrather than widening it to a tolerance.\n\nVerified per browser and per mode:\n- test_activate: chrome headless skips (was failing), chrome headed passes with\n  full assertions, firefox headless passes with full assertions and 0 skips\n- test_wheel_scroll_with_duration: failed 3/3 runs at HEAD, 0/3 after\n\nBoth tests exist twice (bidi/ and _bidi/); both copies are updated.\n\nNote: py/test/selenium/webdriver/common/_bidi/input_tests.py has further\npre-existing firefox flakiness not addressed here - test_key_input_with_value_attribute,\ntest_key_input_special_characters and test_combined_input_actions each failed in\nsome runs, a different one each time, both before and after this change.\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 3952c6bd017f2d6b0b5fd963766b49cc46c53022\n"
    },
    {
      "commit": "9bf90b7e6cfd717b1f35293f3d696489a66d96d9",
      "tree": "04f6715390b2a07d270dc11677c60b8ba0f8bd51",
      "parents": [
        "09ed8a5bd12bde46de5158403eb08ff0ee616277"
      ],
      "author": {
        "name": "Selenium CI Bot",
        "email": "diemol+selenium-ci@gmail.com",
        "time": "Sun Sep 06 21:58:35 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sun Sep 06 22:24:09 2026"
      },
      "message": "[build] Automated Dependency Update (#17937)\n\n* update dependencies and pinned binaries\n\n* [py] override new ruff rule selection to keep existing checks\n\n---------\n\nCo-authored-by: Selenium CI Bot \u003cselenium-ci@users.noreply.github.com\u003e\nCo-authored-by: Titus Fortner \u003ctitus.fortner@gmail.com\u003e\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 2e989902563b799b567e7e3e938a6a959a823dd4\n"
    },
    {
      "commit": "09ed8a5bd12bde46de5158403eb08ff0ee616277",
      "tree": "218980892dd7eae76b05c72cf64a25a62b96d6e9",
      "parents": [
        "4abe1a8d9eb42a1ca00a43b9b9b17233a6b43d94"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Sun Sep 06 18:40:07 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sun Sep 06 19:26:07 2026"
      },
      "message": "[py] remove no-op port self-assignment from the test web server\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 802accd24d0872faebdc7624b85c8820bcc0e5a9\n"
    },
    {
      "commit": "4abe1a8d9eb42a1ca00a43b9b9b17233a6b43d94",
      "tree": "2e4263e9163ec0ebd38b3abc25330b8fd79424c5",
      "parents": [
        "708876b8f6f9bb7c59bc2a4faa72261fe4748d40"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Sun Sep 06 18:40:07 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sun Sep 06 19:25:43 2026"
      },
      "message": "[py] add missing assert in device serial options test\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 50cc58f73d68555ac8cb953df596c3ded2b63d33\n"
    },
    {
      "commit": "708876b8f6f9bb7c59bc2a4faa72261fe4748d40",
      "tree": "adbb5db2d57597fc91b596a43243129e9804ee73",
      "parents": [
        "cd6a40c830a86e60e5fe340c1f7f90dce7962e14"
      ],
      "author": {
        "name": "Titus Fortner",
        "email": "titus.fortner@gmail.com",
        "time": "Sun Sep 06 18:40:07 2026"
      },
      "committer": {
        "name": "Copybara-Service",
        "email": "copybara-worker@google.com",
        "time": "Sun Sep 06 19:25:18 2026"
      },
      "message": "[py] fix swapped assert arguments in async script array tests\n\nNOKEYCHECK\u003dTrue\nGitOrigin-RevId: 23c8a890e8505362f537c129e1ef13a61e8a0b82\n"
    }
  ],
  "next": "cd6a40c830a86e60e5fe340c1f7f90dce7962e14"
}
