Skip to content

Python: take no screenshot for a read in trace mode - #568

Open
vishnuv688 wants to merge 9 commits into
mainfrom
perf/562-python-page-changing-capture
Open

vishnuv688 wants to merge 9 commits into
mainfrom
perf/562-python-page-changing-capture

Conversation

@vishnuv688

@vishnuv688 vishnuv688 commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

What & why

Closes #562. Part of the #516 follow-ups.

The JS adapters take a per-action capture only for commands that can change the page; the Python adapter already applied that to its element-tree read, but still took a screenshot for every command. In #516 that was 42 screenshots for an Android flow the JS adapters capture 8 times, 44% of the run's wall time.

In trace mode the Python adapter now takes the screenshot (and action snapshot) only when core's rule says the command captures: page-changing commands, and each test's first mapped command even when it reads. Live mode is unchanged (every row still gets a screenshot).

The filmstrip keeps its frames. On a native app or a browser without a CDP screencast, Python's filmstrip is built from these screenshots, so a command that takes no capture still adds a filmstrip-only frame (no row screenshot, no snapshot) when it is a WebDriverWait poll that returns, or a read at least 200 ms after the last frame (the JS recorder's poll interval, generated from shared SCREENCAST_DEFAULTS). The end-of-test capture is the filmstrip's last frame, now on native apps too. With a CDP screencast live, nothing extra is taken. A shared captureGate mirror-vector section (40 cases) runs against core's TestCapture.queue and Python's gate, so the two cannot drift. Appium's element and session reads (isElementDisplayed, getContexts, getCurrentActivity, ...) are added to shared READ_ONLY_COMMANDS.

Type of change

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Polish (an improvement to an existing feature)
  • Breaking change (existing behavior changes for users)
  • Documentation
  • Internal (build, CI, dependencies, tooling)

Packages touched

  • shared (types and contracts)
  • core (framework-agnostic capture/reporting)
  • elements (published element/snapshot API — @wdio/elements)
  • service (WebdriverIO adapter)
  • nightwatch-devtools (Nightwatch adapter)
  • selenium-devtools (Selenium adapter)
  • selenium-devtools-py (Selenium Python adapter)
  • backend (server)
  • app (UI)
  • script (page-injected runtime)
  • trace (Trace mode)

Capability matrix

  • packages/conformance/src/features.ts updated, and tables regenerated with pnpm docs:capabilities, if this adds or changes user-facing behaviour

The Python filmstrip cell and its "Turn off per-command screenshots" reason now say what trace mode takes; tables regenerated. The docs-site tables follow in a webdriverio PR.

Notes for reviewers

  • Service changeset (patch): the added Appium reads no longer count as screen changes, so the service names its end-of-test capture after the last real action and stamps the next capture at that action's end.
  • Python fragment: patch.
  • Accepted trade-off: reads outside a wait add at most one frame per 200 ms, where every read used to add one. A wait poll that raises adds none, as before.
  • Two independent reviews; every finding is addressed.

Testing

  • pnpm build, pnpm typecheck, pnpm test (4104 passed, 1 skipped), pnpm lint, pnpm docs:capabilities --check are green; Python 931 passed (the two test_instrumentation.py failures also fail on main).
  • tests/test_capture_gate.py drives the real patched execute and WebDriverWait with a fake recorder: wait-poll frames, the 200 ms read frame and the final frame fail without the fix.

Needs checking: DEVTOOLS_MODE=trace DEVTOOLS_CAPTURE_TIMING=1 pnpm demo:python:pytest and pnpm demo:python:mobile (with PYTHONPATH set to the worktree's src): the capture count drops from 42 toward the JS adapters' 8-15, read rows show the previous capture, the filmstrip still shows the page loading during waits, and its last frame is the final screen.

Screenshots / recordings

None.

🤖 Generated with Claude Code

vishnuv688 and others added 3 commits November 10, 2026 17:04
The Python adapter reaches Appium's is_displayed() as the isElementDisplayed
wire command, and the context, activity, keyboard, lock and app-state queries
under their own names. None of them is in READ_ONLY_COMMANDS, so the deny-list
gate captured after each one as if it could have changed the screen. They join
the list, and the wire schema and Python contract are regenerated from it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the element-tree read followed the JS adapters' page-changing gate; the
screenshot was still taken after every command, so reads, waits and lookups
were most of a Python trace's capture cost (42 captures on the Android Clock
flow where WebdriverIO takes 8, 44% of wall time). Trace mode now takes the
screenshot only when the command takes the capture, and a read's row shows
the capture before it, as in the JS adapters. Live mode keeps a screenshot per
row, the dashboard's only picture of a native app.

A native app's capture is now recorded as a screenshot-only action snapshot,
so the end-of-test capture can supersede a stale one there too; before, the
reads after a click carried their own screenshots and hid that gap.

The gate is mirrored by a new captureGate section in mirror-vectors.json, run
against core's TestCapture.queue and shared's skipsCapture on one side and the
Python hook's classification, atom scripts included, on the other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dited

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vishnuv688 vishnuv688 self-assigned this Nov 10, 2026
@vercel

vercel Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
devtools Ready Ready Preview Nov 10, 2026 12:46pm UTC

Request Review

vishnuv688 and others added 6 commits November 10, 2026 17:32
…wait polls

Without a pushed CDP screencast the per-command screenshots are the filmstrip,
so skipping a read's screenshot also dropped the frames that showed a page
loading under a WebDriverWait, which the default trace used to have. A command
that takes no capture now still takes a filmstrip-only screenshot when a
non-push recorder is live: every poll a wait issues, and any other read once
the JS adapters' 200 ms poll interval (shared SCREENCAST_DEFAULTS, generated
into _contract.py) has passed since the last frame. Those shots go to the
recorder alone, with no row screenshot, action snapshot or timing record. The
end-of-test capture also reaches the recorder, so the filmstrip ends on the
state the test ended on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Python trace-mode note on "Screenshot on every command row" described a
difference that is not one: every adapter's live rows carry a screenshot, and
none of the JS adapters' trace captures follow reads either, so the cells go
back to their earlier, uniform wording and the tables are regenerated.

The Appium reads added to READ_ONLY_COMMANDS change which command the WDIO
service treats as the last screen change, so @wdio/devtools-service gets a
patch changeset saying so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ait polls still take

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… them

The Python filmstrip cell said browsers without CDP take one frame per
command, and the screenshot-switch cell that every row takes a screenshot.
Neither holds in trace mode any more: frames come from page-changing commands
plus wait polls and paced reads, and read rows carry no screenshot. Python
gets its own wording for both, true in live and trace mode alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m new docstrings

A poll that raises takes no frame, as before, so the docs now say a wait's
commands that return. The changes fragment also states the end-of-test capture
now runs on native apps, at one more screenshot per test ending on reads. The
docstrings added in this branch are cut to one line, per the comment rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

Python adapter: capture only for commands that can change the page

1 participant