Skip to content

feat: code-mode .screenshot() api#9232

Merged
mscolnick merged 6 commits intomainfrom
ms/screenshot
Apr 20, 2026
Merged

feat: code-mode .screenshot() api#9232
mscolnick merged 6 commits intomainfrom
ms/screenshot

Conversation

@mscolnick
Copy link
Copy Markdown
Contributor

@mscolnick mscolnick commented Apr 16, 2026

Add ctx.screenshot() to code-mode. Adds the ability for agents to capture PNG screenshots of cell outputs during code-mode sessions.

  • Launches a headless Chromium browser (via Playwright) connected to the existing running server in ?kiosk=true mode
  • Browser is lazily initialized on first call and reused across multiple screenshot() calls
  • Cleaned up automatically when the async with context manager exits
  • Auth token is scoped — injected only at the /execute endpoint into request.meta["screenshot_auth_token"], then passed as access_token query param to Playwright

API

  async with cm.get_context() as ctx:
      ctx.create_cell("import altair as alt; chart = alt.Chart(...)")
      ctx.run_cell(...)

  # Screenshot by index, name, cell ID, or NotebookCell
  img = await ctx.screenshot(0)
  img = await ctx.screenshot("my_cell")
  img = await ctx.screenshot(ctx.cells[-1])

  # Options
  url = await ctx.screenshot("my_cell", as_data_url=True)
  await ctx.screenshot("my_cell", save_to="out.png")
  await ctx.screenshot("my_cell", timeout_ms=60_000)

Also adds ctx.find_cell_defining_object(obj) to resolve a live Python object back to the cell that defines it. While often the object is in another cell, this can help with some of the cases.

Copilot AI review requested due to automatic review settings April 16, 2026 18:52
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 16, 2026

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

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Apr 20, 2026 9:10pm

Request Review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a code-mode ctx.screenshot() capability by wiring server auth into the scratchpad execution request and introducing a reusable Playwright-based screenshot session to capture rendered cell outputs.

Changes:

  • Add AsyncCodeModeContext.screenshot() API with Playwright session reuse, optional data-URL output, and optional file saving.
  • Inject screenshot_auth_token into the HTTPRequest.meta used by the /execute scratchpad endpoint so Playwright can authenticate.
  • Add unit tests for PNG data-url encoding and basic auth-token URL/meta behaviors.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
marimo/_server/api/endpoints/execution.py Injects a screenshot auth token into HTTPRequest.meta for scratchpad execution.
marimo/_code_mode/screenshot.py New Playwright-backed screenshot session implementation and helpers.
marimo/_code_mode/_context.py Introduces ctx.screenshot() API and manages session lifecycle on context exit.
tests/_code_mode/test_screenshot.py Adds tests covering _to_data_url and auth-token URL/meta handling.

Comment thread marimo/_code_mode/_context.py Outdated
Comment thread marimo/_code_mode/_context.py
Comment thread marimo/_code_mode/screenshot.py Outdated
Comment thread tests/_code_mode/test_screenshot.py
Comment thread marimo/_code_mode/_context.py Outdated
@mscolnick mscolnick added the enhancement New feature or request label Apr 16, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

Comment thread marimo/_code_mode/screenshot.py
Comment thread marimo/_code_mode/screenshot.py Outdated
Comment thread marimo/_code_mode/screenshot.py Outdated
Comment thread marimo/_server/api/endpoints/execution.py
Comment thread tests/_code_mode/test_screenshot.py Outdated
Comment thread tests/_code_mode/test_screenshot.py
Comment thread marimo/_code_mode/_context.py Outdated
- Add asyncio.Lock to _ensure_ready to prevent concurrent browser launches
- Clean up Playwright/browser on _init_browser failure (no leaked processes)
- Track monotonic deadline in _resolve_output_locator so total probing
  respects timeout_ms
- Inject trusted screenshot_server_url from server config (host/port/base_url)
  instead of deriving from request Host header (prevents spoofing)
- Add public close_screenshot_session(); __aexit__ delegates to it
- Wrap IndexError/KeyError as ScreenshotError in target resolution
- Fix install hint to use pip install (not ctx.install_packages)
manzt
manzt previously approved these changes Apr 20, 2026
Copy link
Copy Markdown
Collaborator

@manzt manzt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Added some additional tests.

)
base_url = app_state.base_url.rstrip("/")
http_req.meta["screenshot_server_url"] = (
f"http://{app_state.host}:{app_state.port}{base_url}"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this is fine (preferred) to hard code scheme here?

Comment thread marimo/_code_mode/_context.py Outdated
Comment on lines +1302 to +1305
try:
globals_map = self.globals
except Exception:
return None
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When/how does this raise an exception?

Comment thread marimo/_code_mode/_context.py Outdated
Comment on lines +1311 to +1314
try:
graph_cells = self.graph.cells
except Exception:
return None
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar here. Can we drop these try/except blocks, i'm not sure how/when they would fail.

Address review feedback from manzt: self.globals and self.graph
are simple kernel property accessors that won't raise, so the
defensive try/except blocks are unnecessary.
Address review feedback from manzt: hardcoding http:// breaks
screenshots when marimo is served over HTTPS. Derive the scheme
from the incoming request so the Playwright browser uses the
same scheme the server is serving.

Host and port are still taken from trusted server config, so only
the scheme is client-derived — a spoofed scheme can't redirect
Playwright to an attacker origin (browser would just fail to
connect to our own server on the wrong scheme).
@mscolnick mscolnick requested a review from manzt April 20, 2026 20:42
@mscolnick mscolnick merged commit ece8660 into main Apr 20, 2026
28 of 43 checks passed
@mscolnick mscolnick deleted the ms/screenshot branch April 20, 2026 20:59
@github-actions
Copy link
Copy Markdown

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.2-dev68

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants