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)
"http://localhost:3000". captureUrl(path) resolves path against this.CaptureOptions
interface CaptureOptions { width: number; height: number; mobile?: boolean; devUser?: string; }
isMobile on the Playwright context plus an iPhone user agent.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>
"/dashboard".networkidle to let CSS transitions settle.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