Skip to content

[py] Publish a machine-readable API reference and ship guidance in the wheel - #17906

Open
AutomatedTester wants to merge 4 commits into
trunkfrom
copse/review-the-documentation-creation-in-this-f927d8
Open

AutomatedTester wants to merge 4 commits into
trunkfrom
copse/review-the-documentation-creation-in-this-f927d8

Conversation

@AutomatedTester

@AutomatedTester AutomatedTester commented Aug 12, 2026

Copy link
Copy Markdown
Member

🔗 Related Issues

None yet. The review that motivated this is committed alongside it as docs/plans/llm-facing-documentation.md; This will be removed before merging

💥 What does this PR do?

A growing share of Selenium's documentation is read by machines rather than people — during training, during retrieval, and live over HTTP when a coding agent is asked to "write a Selenium test". When that reading goes badly the model falls back on stale priors and emits DesiredCapabilities, an executable_path, a third-party driver manager, and a sleep where a wait belongs. Users then attribute that code to Selenium.

This implements the Python tracks of the attached plan. The plan itself covers all five bindings; nothing here changes any other binding.

Canonical URL. Three copies of the Python API docs compete for recall — the release build on selenium.dev, the per-commit Read the Docs preview, and unofficial mirrors that outrank both — and none of them declared which was authoritative. Sets html_baseurl, so every page (including the Read the Docs build) carries rel=canonical pointing at the released reference. Rewrites the index wording to name that build as canonical and Read the Docs as a preview of the next release.

Machine-readable output, all at stable URLs under /selenium/docs/api/py/:

File Source
llms.txt new Sphinx extension, py/docs/source/_ext/llm_assets.py
sitemap.xml same extension
objects.inv already emitted by Sphinx; now documented rather than discarded
deprecations.json new py/generate_deprecations.py

deprecations.json currently publishes 16 deprecated APIs, each with the replacement to use instead, parsed straight out of the warnings.warn messages. That mapping previously existed only as prose inside function bodies, where nothing but a running program could read it.

In-package guidance. selenium/llms.txt now ships inside the wheel, so a tool with the package on disk finds the project's own guidance instead of inferring it. The published llms.txt reads its rules back out of that same file, so there is one copy rather than two to drift apart.

🔧 Implementation Notes

deprecations.json is committed, not just generated. It lives in py/docs/source/_extra/ and is published verbatim via html_extra_path. Committing it means a PR that deprecates something shows the deprecation in its diff, and it means bazel build //py:docs works on a clean checkout. A unit test fails if it stops matching the source. This needed narrow negations in .gitignore, which otherwise ignores everything under py/docs/source/ to keep the autosummary stubs out.

The dataset deliberately records no line numbers. CI evaluates a pull request as its merge with trunk, so a published line number goes stale when trunk moves, not only when the branch changes — any PR inserting a line above a warnings.warn would fail the sync test. api already addresses each entry. A regression test pins this.

viewcode is replaced by linkcode — the two conflict, so this is a choice rather than an addition. viewcode rendered ~140 modules of source into the site as a second, non-canonical copy of what is already on GitHub, which is the same duplicate-content problem this PR is otherwise trying to fix. linkcode_resolve links each object to GitHub at the release tag with a line anchor. This does delete published pages (_modules/**) — nothing in the repo references them, but it is the one change here that removes rather than adds, and it is easy to revert on its own if maintainers disagree.

intersphinx adds a network fetch to the docs build. Inventories for the stdlib, trio and urllib3 are fetched at build time. Timeout is 5s and a failure degrades to plain literals rather than erroring, but it is a new network dependency for bazel build //py:docs and worth a maintainer's opinion.

The guidance rules themselves are deliberately versioned — each says which release the old form stopped being correct in (executable_path 4.10, find_element_by_* 4.3, options.headless 4.9), because a reader that has absorbed a decade of blog posts needs the version to know which memory to discard.

🤖 AI assistance

  • No substantial AI assistance used
  • AI assisted (complete below)
    • Tool(s): Claude Code (Opus 5)
    • What was generated: the documentation review in docs/plans/llm-facing-documentation.md, and a first pass at all of the Python changes — the Sphinx extension, the deprecation extractor, the conf.py changes, the unit tests, and the prose in py/selenium/llms.txt.
    • I reviewed all AI output and can explain the change

💡 Additional Considerations

Verification. Rebased onto trunk and verified locally with Bazel:

  • bazel build //py:docsbuild succeeded. The extension reports wrote llms.txt and sitemap.xml (138 pages); llms.txt, sitemap.xml, objects.inv and deprecations.json are all present in the HTML root, the published deprecations.json is byte-identical to the committed one, rel=canonical is emitted, _modules/ is correctly absent, and spot-checked linkcode anchors land on the right lines. No new Sphinx warnings come from any of the added code.
  • bazel test //py:unit --test_size_filters=small — 34 tests pass, including both new files.
  • bazel run //py:ruff-check -- --no-fix and //py:ruff-format — clean.

An earlier revision of this PR could not run any of the above locally (a TLS failure fetching mocha), so the description previously said the Sphinx build was unverified. That no longer holds — it has now been built and its output inspected.

Follow-up work, from the plan:

  • The other four bindings need the same treatment (Track C), and their deprecations.json files need merging into one cross-binding dataset (D1).
  • docs/api/llms.txt and a real landing page at /selenium/docs/api/ (A1, A2) need all five bindings first.
  • Linking the API reference from the site llms.txt, and a Sitemap: reference for /selenium/docs/api/, are changes in SeleniumHQ/seleniumhq.github.io (B2, A4).
  • Docs only regenerate at release, so this is invisible for a full cycle until nightly publishing exists (F1).

🔄 Types of changes

  • New feature (non-breaking change which adds functionality and tests!)

@selenium-ci selenium-ci added C-py Python Bindings B-build Includes scripting, bazel and CI integrations labels Aug 12, 2026
…achines

A growing share of Selenium's documentation is read by models rather than
people: during training, during retrieval, and live over HTTP when a coding
agent is asked to write a Selenium test. When that reading goes badly the
model falls back on stale priors and emits DesiredCapabilities, an
executable_path, a third-party driver manager, and a sleep where a wait
belongs — and users attribute that code to Selenium.

Reviews what this repository publishes today across all five bindings and
sets out six tracks of work to fix it, with the waves that can run in
parallel. Findings include: the API reference has no machine-readable entry
point and is unlinked from the site llms.txt; objects.inv, element-list and
xrefmap.yml are generated and then discarded; the Python docs have three
competing copies and no canonical URL; deprecation markers exist in every
binding but the "gone, use this instead" mapping exists nowhere consumable;
and nothing LLM-facing ships inside the wheel, gem, jar, npm package or
nupkg.
…e wheel

The Python API reference is increasingly read by machines — crawlers,
retrieval pipelines, and coding agents fetching it live — and it offered them
no entry point, no canonical URL, and no way to tell a current API from one
removed several releases ago. Three copies of the Python docs compete for
recall, none of them declaring which is authoritative.

Canonical URL: sets html_baseurl so every page, including the per-commit
Read the Docs preview, carries rel=canonical pointing at the released
reference. Rewrites the index wording to name that build as canonical and
Read the Docs as a preview of the next release.

Machine-readable output: a new Sphinx extension writes llms.txt and
sitemap.xml into the HTML root; objects.inv is documented at its stable URL;
and generate_deprecations.py parses the sources with ast to emit
deprecations.json, publishing 16 deprecated APIs with the replacement to use
instead. Swaps viewcode for linkcode so source links point at GitHub at the
release tag rather than at a second, non-canonical copy of the source
rendered into the site.

In-package guidance: selenium/llms.txt ships inside the wheel, so a tool with
the package on disk finds the project's own guidance instead of inferring it.
The published llms.txt reads its rules back out of that file, leaving one
copy rather than two to drift apart.

Implements the Python tracks of docs/plans/llm-facing-documentation.md.
The dataset is committed, so every field in it has to survive edits that
have nothing to do with deprecations. A line number does not: inserting a
line anywhere above a `warnings.warn` call invalidates it.

CI made that concrete. Pull requests are evaluated as their merge with
trunk, so the sync test compared a dataset generated on this branch against
sources that included trunk's later commits, and failed on three entries in
remote/webdriver.py whose warnings had shifted by one line. Nothing about
the deprecations had changed. The same failure would hit any contributor
whose change shifted a line in a file declaring a deprecation, and would
hit this dataset again on the next such commit to trunk.

`api` already addresses an entry, and does not go stale. Line numbers now
only order the result, which is what they were useful for.
Rebasing onto trunk picked up a newer ruff pin that flags the
equivalent list comprehension as C416. Also refreshes the version
stamp in the published dataset; the deprecation set is unchanged.
@AutomatedTester
AutomatedTester force-pushed the copse/review-the-documentation-creation-in-this-f927d8 branch from 428e8c5 to a5c1828 Compare September 16, 2026 16:03
@diemol

diemol commented Sep 16, 2026

Copy link
Copy Markdown
Member

I don't understand, is this an ARD or something just for Python?

@AutomatedTester

Copy link
Copy Markdown
Member Author

This is something for python for now but happy to turn this into an ADR. At the moment it was more of a test to see how things would happen. Let me know your preference.

@diemol

diemol commented Sep 17, 2026

Copy link
Copy Markdown
Member

I don't think we need an ADR for this. As this one is about Python, it would be good to have an issue that tracks implementation for all of the languages. This makes sense to implement across languages.

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

Labels

B-build Includes scripting, bazel and CI integrations C-py Python Bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants