8000 WebDriver: frames, shadow roots, alerts, windows, uploads and print by adeelahmad · Pull Request #12 · adeelahmad/Tether · GitHub
[go: up one dir, main page]

Skip to content

WebDriver: frames, shadow roots, alerts, windows, uploads and print - #12

Merged
adeelahmad merged 11 commits into
mainfrom
claude/wd-gaps-5fdx2w
Oct 11, 2026
Merged

adeelahmad merged 11 commits into
mainfrom
claude/wd-gaps-5fdx2w

Conversation

@adeelahmad
Copy link
Copy Markdown
Owner

Requested by Adeel · project thread

What

Before: WebDriver sessions were one tab with no frames, shadow DOM, dialogs, extra windows, window control, file uploads or PDF. Those commands returned unknown command or unsupported operation.

After: Selenium scripts that use these features work against the real browser:

  • Frames: Switch To Frame (null, index or element) and Parent Frame, for same-site and cross-site (out-of-process) frames at any depth. Find, click, type, scripts, source, cookies and element screenshots all run in the current frame.
  • Shadow DOM: Get Element Shadow Root (open and closed roots), Find Element(s) From Shadow Root, and shadow roots passed to and returned from scripts.
  • Alerts, confirms, prompts: get/send text, accept, dismiss, plus unhandledPromptBehavior (all five values). A click or navigation that opens a dialog returns normally; a script that opens one returns null.
  • Windows: New Window (tab or window), popups (window.open, target=_blank) join the session automatically, switching, closing. The session ends with its last window, and deleting it closes the windows it owns.
  • Window rect / maximize / minimize / fullscreen: these are real now, but only for windows that hold nothing but Tether tabs (window/new {"type":"window"} or tether:options.newWindow). Windows shared with the person's tabs are left alone, as before.
  • File uploads: Send Keys on <input type=file> sets files through DOM.setFileInputFiles, and Selenium's /se/file remote upload is supported. Uploaded files are deleted when the session ends.
  • Print Page: all W3C options, validated.

Why

These were the gaps listed in docs/WEBDRIVER.md, and they are needed for the first release. The Actions API is being done in a separate PR.

How

  • cdp.Tab gained InFrame(path), ShadowRoot, SetFiles, PrintPDF, Dialog/HandleDialog. cdp.Browser gained NewWindow, Window/SetWindow, and TabInfo.OpenerID.
  • A frame view resolves its path on every call. A same-process frame is reached through its content document's main world, so the Runtime domain stays off. A cross-site frame is reached through its auto-attached iframe target session.
  • Dialog events are tracked per tab. Script, input and load waits give up with ErrDialogOpen instead of hanging on a blocked page.
  • Behaviour fix found while testing: in a background tab, any page with a cross-site iframe took about 5 seconds per click, and clicks inside the iframe never arrived. Attached tabs now turn on Emulation.setFocusEmulationEnabled, which fixes this without taking focus.
  • The WebDriver session now tracks its window handles and the current window/frame. Commands are serialised per session.

How tested

  • make check equivalents: gofmt, go vet (with and without integration, and on Go 1.23), golangci-lint v2.5.0 (0 issues), unit tests with -race
  • make test-integration: four new end-to-end tests against real headless Chromium (frames incl. an out-of-process localhost frame inside a 127.0.0.1 page, open and closed shadow roots, all dialog types and prompt behaviours, popups, new tabs and windows, resizing, local and remote uploads, PDF). All pass, along with the existing tests.
  • Docs updated (docs/WEBDRIVER.md, docs/ARCHITECTURE.md, README table, SECURITY threat model, CHANGELOG)

Security

  • No new way to reach the browser without auth, and nothing new is logged that could hold a cookie or token. The /se/file endpoint sits behind the same auth. Archive entry names are reduced to a base name (no zip-slip), and unpacked size is capped at 256 MiB.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AhaxD4mv92X8w5zV8j34MD


Generated by Claude Code

Extend the single global --allow-domains list into layered policies:

- internal/policy: domain rules (allow/deny, *.sub, *), principals and a
  JSON --policy-file defining named tokens with their own rules, an admin
  flag and a scripts permission. --deny-domains adds a global denylist.
- Auth attaches the token's principal to the request context; --token is
  the admin "default" principal.
- Tab ownership: non-admin tokens only see their own tabs, get their own
  per-site default tabs, and cannot adopt existing tabs.
- core.RulesPolicy checks global ∩ requester ∩ owner ∩ session rules,
  re-checks the final URL, and guards restricted tabs in the browser via
  the DevTools Fetch domain so redirects, clicks and page script obey the
  same rules.
- API: session rules on tab create, GET/PUT /tabs/{id}/policy, /whoami.
  WebDriver: tether:options.policy, ownership on every command, and
  Execute Script gated by the scripts permission.
- Docs: docs/POLICIES.md, SECURITY.md, ARCHITECTURE.md, README, example
  policy file. Integration test drives a real Chrome as a restricted token.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UfsJJgC4Jp8VqckdcChH4W
…rint

Close the remaining WebDriver gaps on top of new cdp.Tab primitives:

- Switch To Frame / Parent Frame for same- and cross-site (out-of-process)
  frames at any depth, via cdp.Tab.InFrame and iframe auto-attach.
- Get Element Shadow Root and Find From Shadow Root, including closed roots;
  shadow roots round-trip through scripts.
- Alert commands and unhandledPromptBehavior; calls blocked by a dialog
  fail fast instead of hanging.
- Multiple windows: New Window, popups joined automatically, switching,
  closing; the session ends with its last window.
- Window rect, maximise, minimise and fullscreen, applied only to windows
  that hold nothing but Tether tabs.
- File inputs via Element Send Keys plus Selenium's /se/file upload.
- Print Page.
- Focus emulation on attached tabs so input reaches background tabs with
  cross-site iframes promptly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhaxD4mv92X8w5zV8j34MD
@adeelahmad adeelahmad self-assigned this Oct 11, 2026
Comment thread internal/webdriver/upload.go Fixed
POST/DELETE /session/{id}/actions dispatch key, mouse, pen, touch and
wheel input as real CDP events, tick by tick, with element/pointer/viewport
origins, animated moves and scrolls, click counting, US-layout Shift, and
input state that persists until Release Actions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EnJnokVdV3oFBMpLc45zcJ
…t click

Chrome routes input into frames asynchronously, and on some versions a
real click into a cross-site frame of a background tab never arrives.
Arm a one-shot listener before clicking inside a frame, wait briefly for
it, and click from script if the real click did not land.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhaxD4mv92X8w5zV8j34MD
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhaxD4mv92X8w5zV8j34MD
Attaching to a tab that is closing can fail after Target.getTargetInfo
succeeds (Page.enable: session not found). Map that to ErrNoSuchTab so
faces answer 410/no such window instead of 500.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhaxD4mv92X8w5zV8j34MD
Resolves conflicts with #7 (console logs): Tab gets both Guard and Logs,
the API keeps the policy routes and adds /tabs/{id}/logs, and docs list
both features. Also makes TestConsoleLogs tolerate a late favicon 404
between the two WebDriver log reads (it asserted the second read was
empty; it should assert no entry repeats).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9CEjRcHsJmgWdtz6ZTKsk
Tab gains Mouse/Touch alongside Guard and Logs; the WebDriver session
keeps the log cursor and the Actions input state; docs list everything.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9CEjRcHsJmgWdtz6ZTKsk
# Conflicts:
#	README.md
#	internal/cdp/cdptest/cdptest.go
Conflict resolution plus one fix the combination needs: popups and new
windows now belong to the session that opened them. A popup is attached
as the session's own tab (same owner, same session rules, guarded), so a
non-admin token can switch to its popups, and a restricted session can
no longer leave its allowlist through window.open or New Window. A popup
that loaded a forbidden page before Tether attached is closed and the
switch fails.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9CEjRcHsJmgWdtz6ZTKsk
# Conflicts:
#	CHANGELOG.md
#	docs/WEBDRIVER.md
#	internal/cdp/cdptest/cdptest.go
#	internal/webdriver/errors.go
#	internal/webdriver/webdriver.go
#	test/integration/integration_test.go
@adeelahmad
adeelahmad merged commit 7eda11c into main Oct 11, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

0