Domotion

DemoRecorder

Convenience class for capturing pages from a running server.

DemoRecorder wraps Playwright bring-up and the captureElementTree + elementTreeToSvg pair. It's the most ergonomic way to capture pages from a running app — you give it a base URL once and then call captureUrl(path) as often as you need.

Using the lower-level functions directly is a fine choice when you're capturing static HTML strings or want full control of Playwright. Reach for DemoRecorder when you're sweeping a real running server to produce a gallery of demos.

Constructor

new DemoRecorder(baseUrl: string, opts: CaptureOptions)
baseUrl
string
e.g. "http://localhost:3000". captureUrl(path) resolves path against this.
opts
CaptureOptions
See below.

CaptureOptions

interface CaptureOptions {
  width: number;
  height: number;
  mobile?: boolean;
  devUser?: string;
}
width / height
number
Viewport size (CSS pixels).
mobile
boolean (default false)
Sets isMobile on the Playwright context plus an iPhone user agent.
devUser
string
Optional username to authenticate as via a POST /api/v1/auth/dev-login endpoint on your server. The recorder sets the resulting cookie + a localStorage.sk_token entry. Tailored for the slicekit dev-login convention; ignore if your server doesn't expose it.

Methods

init(opts)

Launches Chromium and creates the browser context + page. Must be called before any capture.

init(opts: CaptureOptions): Promise<void>

captureUrl(path, waitMs?, idPrefix?)

Navigates to baseUrl + path, waits for networkidle plus waitMs, captures the visible viewport, and returns the SVG fragment (no outer <svg> wrapper) — same shape as elementTreeToSvg()'s return. Wrap it for a standalone document, or feed it straight to generateAnimatedSvg as an AnimationFrame.svgContent.

captureUrl(
  path: string,
  waitMs?: number,
  idPrefix?: string,
): Promise<string>
path
string
e.g. "/dashboard".
waitMs
number (default 800)
Extra time after networkidle to let CSS transitions settle.
idPrefix
string
Prefix for clip / gradient / glyph IDs. Required if you'll combine multiple captures into one SVG.

captureCurrent(idPrefix?)

Captures the current page state without navigating. Use this when you've already brought the page to a specific state via recorder.getPage().click(...), etc.

captureCurrent(idPrefix?: string): Promise<string>

captureFullPage(idPrefix?)

Captures the entire scrollable page (viewport-wide, body's full scrollHeight tall). Returns both the SVG content and the page height so you can size the outer SVG correctly.

captureFullPage(idPrefix?: string): Promise<{ svgContent: string; pageHeight: number }>

getPage()

Returns the underlying Playwright Page for ad-hoc interactions — hover, click, fill, evaluate.

getPage(): Page

getBoundingBox(selector)

Convenience wrapper around page.locator(selector).first().boundingBox(). Returns null when the selector matches nothing.

getBoundingBox(selector: string): Promise<{ x; y; width; height } | null>

close()

Closes the browser. Call it from your script's cleanup path — recorders that aren't closed leak Chromium processes.

close(): Promise<void>

Example: capture three URLs in a row

import { DemoRecorder } from "domotion";
import { writeFileSync } from "node:fs";

const recorder = new DemoRecorder("http://localhost:3000", {
  width: 1200, height: 700,
});
await recorder.init({ width: 1200, height: 700 });

for (const path of ["/", "/dashboard", "/settings"]) {
  const inner = await recorder.captureUrl(path);
  // captureUrl returns an SVG fragment — wrapSvg adds the outer <svg>.
  const svg = wrapSvg(inner, 1200, 700);
  writeFileSync(`out${path.replace(/\//g, "_")}.svg`, svg);
}

await recorder.close();

Example: drive an interaction, then capture

await recorder.init({ width: 800, height: 600 });
await recorder.captureUrl("/products/42"); // initial state

const page = recorder.getPage();
await page.click("button.add-to-cart");
await page.waitForSelector(".cart-badge");

const svg = await recorder.captureCurrent(); // after-click state

See also