Skip to content

feat: add record subcommand for session recording#2

Merged
gadenbuie merged 9 commits intomainfrom
feat/record
Mar 18, 2026
Merged

feat: add record subcommand for session recording#2
gadenbuie merged 9 commits intomainfrom
feat/record

Conversation

@gadenbuie
Copy link
Collaborator

Summary

Adds a shinyloadtest record subcommand that captures a user's interaction with a Shiny app into a recording file for later replay with shinyloadtest replay.

The recording system works as a reverse proxy that sits between the browser and the Shiny app, intercepting HTTP and WebSocket traffic. Key components:

  • HTTP reverse proxy — forwards requests to the target app, captures initial GET as REQ_HOME
  • WebSocket proxy — relays messages between client and server, recording WS_OPEN, WS_RECV, WS_SEND, and WS_CLOSE events with relative timestamps
  • Token replacement — detects and tokenizes session-specific values (session ID, worker ID, Shiny tokens) so recordings are replayable across sessions
  • Recording writer — streams events to a .log file in the existing shinycannon recording format
  • Terminal UI — shows live event count, connection status, timestamps, and a quit hint via ora spinners
  • Graceful shutdown — tracks active WS connections and defers shutdown by 500ms to handle brief connection overlaps

Also refactors replay-specific modules (output.ts, session.ts, ui.ts, websocket.ts, worker.ts) into src/replay/ to cleanly separate concerns.

Verification

# Record a session against a running Shiny app
npx @posit-dev/shinyloadtest record http://localhost:3838

# Interact with the app in the browser at the printed proxy URL,
# then press Ctrl+C to stop. A .log recording file is created.

# Replay the recording
npx @posit-dev/shinyloadtest replay recording.log http://localhost:3838

Tests: 6 new test files with ~1450 lines covering events, tokens, writer, HTTP proxy, WS proxy, and E2E lifecycle.

@gadenbuie gadenbuie marked this pull request as ready for review March 18, 2026 19:05
@gadenbuie gadenbuie requested a review from schloerke March 18, 2026 19:05
Move output.ts, session.ts, ui.ts, websocket.ts, and worker.ts into
src/replay/ to cleanly separate replay concerns from the upcoming
record feature. Update all import paths in source and test files.
Add events module with discriminated union types for recording events
(REQ_HOME, REQ_SINF, WS_OPEN, WS_RECV, WS_SEND, WS_CLOSE), token
replacement for session-specific values, and a RecordingWriter that
streams events to .log files in the shinycannon recording format.
Implement RecordingProxy that sits between the browser and a Shiny app,
intercepting HTTP and WebSocket traffic. HTTP requests are forwarded to
the target app with REQ_HOME captured on initial GET. WebSocket messages
are relayed bidirectionally, recording WS_OPEN/RECV/SEND/CLOSE events
with relative timestamps. Includes token discovery for session IDs,
worker IDs, and Shiny tokens, plus graceful shutdown with active
connection tracking and a 500ms grace period.
Add record orchestrator that coordinates the recording lifecycle: starts
the reverse proxy, waits for the first browser connection, handles
Ctrl+C for graceful shutdown, and writes the final recording file.

Add RecordTerminalUI with ora spinners showing live event count,
connection status, timestamps, and a quit hint.
Wire up the record orchestrator as 'shinyloadtest record <app-url>'
with options for output file, port, and open-browser. Update main.ts
to route to replay-specific modules under src/replay/.
Add comprehensive tests for the recording feature:
- record-events: event creation and serialization
- record-tokens: token discovery and replacement
- record-writer: file writing and event counting
- record-proxy: HTTP reverse proxy behavior
- record-ws-proxy: WebSocket relay and message recording
- record-e2e: full lifecycle integration test
- cli: record subcommand argument parsing
@gadenbuie gadenbuie merged commit ec1e6e0 into main Mar 18, 2026
6 checks passed
@gadenbuie gadenbuie deleted the feat/record branch March 18, 2026 20:28
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.

1 participant