Skip to content

feat: Add test mode with export_test_values() and /dataobj/shinytest snapshot endpoint #2269

Open
4 of 4 issues completed

Description

@schloerke

Summary

Port R Shiny's test mode to py-shiny: a read-only per-session JSON snapshot endpoint exposing {input, output, export}, plus an export_test_values() API so app authors can surface internal reactive values. This is the server-side foundation for a future shinytest2-like snapshot-testing story in Python.

This is Phase A (server-side snapshot endpoint + API). First-class Playwright/automation access to these values is a later Phase B and is out of scope here.

Motivation

R Shiny's test mode powers shinytest2 snapshot testing: it registers a per-session /dataobj/shinytest JSON endpoint returning {input, output, export}, and exportTestValues() lets app authors expose internal reactive values for snapshotting. py-shiny has the building blocks (input serialization, cached output values, a per-session request router) but nothing is wired up, and there is no export_test_values() equivalent.

Scope (Phase A)

Enabling

  • Enable via environment variable SHINY_TESTMODE=1 only (mirrors the existing SHINY_DEV_MODE pattern in html_dependencies.py).
  • A constructor argument (App(..., test_mode=...)) is deferred to a later phase.
  • When off: zero behavioral change — no value recording, the endpoint 404s, export_test_values() is a cheap no-op.

export_test_values() API

  • Module-level function and session method (session.export_test_values(**kwargs)).
  • Called as export_test_values(my_val=lambda: total() + 1, count=my_calc) — each value is a zero-arg callable / reactive.calc, evaluated lazily inside a reactive context at snapshot time.
  • Module-level form resolves the current session via get_current_session().
  • Export names are namespaced with the module's ns prefix (consistent with inputs/outputs; note R does not namespace).
  • Last-registration-wins on duplicate names.

Snapshot endpoint

  • Auto-registered at /session/<token>/dataobj/shinytest (R-compatible path) when test mode is on.
  • Returns { "input": {...}, "output": {...}, "export": {...} } (R's singular keys, kept as-is).
  • Handler wraps work in session_context(self) + isolate() (same pattern as dynamic_route).
  • input — serialized current inputs (dedicated snapshot serialization path, not the bookmark _serialize, so it doesn't write files to a state_dir or silently drop unserializables).
  • output — last-known-value record: maintain a per-session dict of the most recent value computed for each output (recorded where output values are set). Outputs that never computed are absent. (Matches R semantics.)
  • export — each registered callable evaluated lazily at request time.

Serialization fidelity

  • Best-effort coercion; values that can't be JSON-encoded are wrapped in a visible tagged marker (e.g. {"__shiny_serialization_error__": "<message>"}) rather than silently stringified or hard-failing.
  • Sort keys for deterministic, cleanly-diffable snapshots.

URL helper

  • Add session.get_test_snapshot_url() (R parity with session$getTestSnapshotUrl()), including a cache-busting nonce. Included in Phase A.

Out of scope (deferred to Phase B)

  • Registering the built-but-unused shiny/www/shared/shiny-testmode.js eval-driving helper (for driving an app from a parent frame).
  • First-class Playwright controller / page helper access to snapshot values.
  • App(test_mode=...) constructor argument.

Related

Activity

  1. self-assigned this
    on Jun 2, 2026
  2. added
    Priority: MediumValid bug or well-defined request with moderate impact or a workaround.
    ai-triage:doneMarks an issue whose AI triage workflow is complete.
    on Jun 4, 2026
  3. 11 remaining items

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

Metadata

Metadata

Assignees

Labels

Priority: MediumValid bug or well-defined request with moderate impact or a workaround.ai-triage:doneMarks an issue whose AI triage workflow is complete.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions