Module: runner/recorder

DIF-015 — Interactive browser recorder for test creation.

Opens a Playwright browser pointed at the project's URL, streams a live CDP screencast to the frontend via SSE (reusing the existing emitRunEvent channel), and captures raw user interactions (clicks, fills, key-presses, navigations) as Playwright actions.

On stop, the captured action list is transformed into a Playwright test body and returned so the caller (routes/tests.js) can persist a Draft test and run it through the rest of the generation pipeline (assertion enhancement, self-healing transform) just like any other AI-generated test.

Exports

  • startRecording — Launch browser + begin capture.
  • stopRecording — Stop capture; return { actions, playwrightCode }.
  • getRecording — Inspect an in-flight recording (for abort / status).

Design notes

Capture is done entirely in the page context via a single injected listener that posts events back to Node through page.exposeBinding. This is the same approach Playwright's own codegen uses for JavaScript action recording, minus the DevTools UI — we only need the raw event stream.

Source:

Members

(inner, constant) RECORDER_SCRIPT

JS source injected into every page frame. It captures pointer/keyboard events and relays them to Node via the __sentriRecord binding. We de-duplicate by dispatch target + event type so that a single click doesn't emit multiple entries when bubbling through the DOM.

bestSelector intentionally mirrors Playwright's own heuristics loosely: prefer role-based selectors, then data-testid, then aria-label, then a short CSS chain.

Source:

(inner, constant) completedSessions :Map.<string, CompletedRecording>

Short-lived cache of recordings torn down by the MAX_RECORDING_MS safety-net timeout. Entries live for COMPLETED_TTL_MS so a user who clicks "Stop & Save" moments after the timeout fires can still recover their captured actions instead of losing them to a 500 error. Scoped to the in- process recorder so no external store is needed.

Type:
  • Map.<string, CompletedRecording>
Source:

(inner, constant) sessions :Map.<string, RecordingSession>

Type:
  • Map.<string, RecordingSession>
Source:

Methods

(static) actionsToPlaywrightCode(testName, startUrl, actions) → {string}

Convert a list of captured actions into a Playwright test body. The output is wrapped in the repo-standard test(...) shape so the existing runner (codeExecutor, codeParsing) treats it like any AI-generated test.

Parameters:
Name Type Description
testName string
startUrl string
actions Array.<RecordedAction>
Source:
Returns:

Playwright source code.

Type
string

(static) getRecording(sessionId) → {RecordingSession|null}

Look up an in-flight recording session.

Parameters:
Name Type Description
sessionId string
Source:
Returns:
Type
RecordingSession | null

(static) startRecording(args) → {Promise.<RecordingSession>}

Start a new interactive recording session. Opens a Playwright browser, navigates to startUrl, installs the capture script, and begins a CDP screencast on the given session ID (reused as the SSE run ID).

Parameters:
Name Type Description
args Object
Properties
Name Type Description
sessionId string

Unique ID used for SSE + session tracking.

projectId string
startUrl string
Source:
Returns:
Type
Promise.<RecordingSession>

(static) stopRecording(sessionId, optsopt) → {Promise.<{actions: Array.<RecordedAction>, playwrightCode: string, url: string}>}

Stop an in-flight recording. Tears down the browser and returns the captured actions plus a ready-to-persist Playwright test body.

Parameters:
Name Type Attributes Description
sessionId string
opts Object <optional>
Properties
Name Type Attributes Description
testName string <optional>

Optional name to embed in the generated test.

Source:
Throws:

When the session does not exist.

Type
Error
Returns:
Type
Promise.<{actions: Array.<RecordedAction>, playwrightCode: string, url: string}>

(static) takeCompletedRecording(sessionId) → {CompletedRecording|null}

Look up and remove a recording that was auto-torn-down by the safety-net timeout. Returns null if no such recording is cached (either never timed out, or the TTL has expired). The entry is removed on read so callers get at-most-once delivery of the captured actions.

Parameters:
Name Type Description
sessionId string
Source:
Returns:
Type
CompletedRecording | null

(inner) escapeJsSingleQuote(str) → {string}

Escape a user-controlled string so it can be safely interpolated into a JavaScript single-quoted string literal in generated source code. Handles backslash (\), single quote ('), newline (\n), carriage return (\r), line/paragraph separators (U+2028 / U+2029 — these break literals in most engines), and other C0 control characters via \xHH escapes.

Order matters: backslash must be escaped first, otherwise subsequent replacements would double-escape their own inserted backslashes.

Parameters:
Name Type Description
str string
Source:
Returns:
Type
string

Type Definitions

CompletedRecording

Type:
  • Object
Properties:
Name Type Description
projectId string
actions Array.<RecordedAction>
playwrightCode string
url string
completedAt number
reason "auto_timeout" | "manual"
Source:

RecordedAction

Type:
  • Object
Properties:
Name Type Attributes Description
kind "goto" | "click" | "fill" | "press" | "select" | "check" | "uncheck"
selector string <optional>

Best-effort role/label/text/css selector.

value string <optional>

For fill, the final value typed.

url string <optional>

For goto.

key string <optional>

For press.

ts number

Epoch ms when the action was captured.

Source:

RecordingSession

Type:
  • Object
Properties:
Name Type Attributes Description
id string
projectId string
url string

Starting URL.

status "recording" | "stopping" | "stopped"
actions Array.<RecordedAction>
startedAt number
browser Object <optional>

Playwright Browser (internal).

context Object <optional>

Playwright BrowserContext (internal).

page Object <optional>

Playwright Page (internal).

stopScreencast function <optional>

Cleanup fn returned by startScreencast.

Source: