API Overview
API Reference
For full auto-generated details including all type signatures, see the API Reference.
defineConfig(config)
Defines the ScreenCI configuration. Wraps Playwright’s config with ScreenCI defaults and enforces settings required for reliable video recording.
Enforced settings (cannot be overridden):
workers: 1— sequential executionfullyParallel: falseretries: 0testMatch— only*.video.*files
import { defineConfig } from 'screenci'
export default defineConfig({ videoDir: './videos', // Directory containing video test files (default: './videos') use: { videoOptions: { resolution: '1080p', // '720p' | '1080p' | '4k' | { width, height } fps: 30, // 24 | 30 | 60 quality: 'high', // 'low' | 'medium' | 'high' }, trace: 'retain-on-failure', // 'on' | 'off' | 'retain-on-failure' sendTraces: true, },})Any other valid Playwright config options (e.g. timeout, reporter, webServer) are passed through.
video(title, body)
video(title, details, body)
The test fixture for declaring a recorded video. Works like Playwright’s test() with automatic screen recording and event tracking wired in.
import { video } from 'screenci'
video('Title of the video', async ({ page }) => { await page.goto('https://example.com')})With Playwright test details:
video('Checkout flow', { tag: '@critical' }, async ({ page }) => { await page.goto('https://example.com/checkout')})Fixtures available inside video()
All standard Playwright fixtures are available (page, context, browser, request), plus:
The page fixture returns a ScreenCIPage — a thin wrapper whose .locator() and related methods return ScreenCILocator instead of Playwright’s Locator, adding animated cursor and typed input behaviour.
| Fixture | Type | Description |
|---|---|---|
videoOptions | RecordOptions | Current video recording options |
Overriding options per test
video.use({ videoOptions: { resolution: '4k', fps: 60 } })
video('4K demo', async ({ page }) => { await page.goto('https://example.com')})autoZoom(fn, options?)
Zooms in on each interaction inside the callback, panning to follow clicks and fills. The camera zooms back out after the callback resolves.
Cannot be nested — calling autoZoom() inside another autoZoom() throws.
import { video, autoZoom } from 'screenci'
video('Settings demo', async ({ page }) => { await page.goto('https://example.com/settings')
await autoZoom( async () => { await page.locator('#name').fill('Jane') await page.locator('#email').fill('jane@example.com') await page.locator('button[type="submit"]').click() }, { duration: 400, easing: 'ease-in-out', amount: 0.4 } )})Options
| Option | Type | Default | Description |
|---|---|---|---|
duration | number | 400 | Zoom-in and zoom-out transition duration in milliseconds |
easing | string | 'ease-in-out' | CSS easing for the zoom transitions |
amount | number | 0.5 | Fraction of output dimensions visible when zoomed (0–1) |
hide(fn)
Hides recording events (mouse movements, clicks) while the callback runs. The hidden section is cut from the final video, making navigation and setup steps invisible to viewers.
Cannot be nested — calling hide() inside another hide() throws.
import { video, hide } from 'screenci'
video('Dashboard demo', async ({ page }) => { await hide(async () => { await page.goto('https://example.com/dashboard') await page.waitForLoadState('networkidle') await page.waitForTimeout(2000) })
// Recording resumes here — viewer sees the dashboard ready to go await page.locator('#reports').click()})caption(content)
Displays a text caption on the video. Returns a CaptionHandle with methods to control timing. Only one caption can be active at a time — a second start()/until() call queues behind the first and waits for it to be ended.
Caption text appears word by word, with each word taking 0.5 seconds. Starting a caption emits a captionStart event; ending one emits a captionEnd event.
caption(text).start()
Resolves after all words have appeared (0.5s per word). The caption stays visible until caption.end() is called.
import { video, caption } from 'screenci'
video('Docs walkthrough', async ({ page }) => { await caption('Navigating to the docs').start() await page.goto('https://example.com/docs') await caption.end()
await caption('Reading the introduction').start() await page.waitForTimeout(3000) await caption.end()})Awaiting is optional — if you don’t need to wait for the animation to finish before continuing, just call it without await.
caption(text).until(percent)
Resolves when the given percentage of the animation has elapsed. The full animation always runs to completion and the caption stays visible until caption.end() is called.
percent must be a Percentage string (\${number}%“). The editor will show a type error for anything else.
video('Feature walkthrough', async ({ page }) => { // Resolves after 50% of words have appeared, then continues immediately await caption('Clicking get started').until('50%') await page.getByRole('link', { name: 'Get started' }).click() await caption.end()
// Resolves immediately, caption animates in the background caption('Loading dashboard').until('0%') await page.waitForURL('**/dashboard') await caption.end()})| Value | Resolves when |
|---|---|
'0%' | Immediately, before any word appears |
'50%' | After half the words have appeared |
'100%' | After all words have appeared (same as .start()) |
Throws at runtime if the value is not a finite number between 0 and 100.
caption.end()
Ends the currently active caption and emits a captionEnd event. Call it after every .start() or .until().
await caption('Step one').start()await page.goto('https://example.com')await caption.end()Calling caption.end() when no caption is active is a no-op.
Queuing
A second caption call queues behind the first — its animation does not begin until the previous caption’s end() has been called:
caption('First caption').start() // begins immediatelycaption('Second caption').start() // waits for first's end()
// ... do some work ...
await caption.end() // ends first, second beginsawait vi.advanceTimersByTimeAsync(...)await caption.end() // ends secondBehavior
- One at a time: only one caption is active at a time. A second call queues rather than interrupting.
- Word timing: each word takes 0.5 seconds to appear.
- Awaiting is optional: all methods return promises but do not need to be awaited.
Types
RecordOptions
type RecordOptions = { resolution?: Resolution // default: '1080p' fps?: FPS // default: 30 quality?: Quality // default: 'high'}RenderOptions
Rendering options written as-is to data.json. Mirrors the renderOptions shape consumed by the rendering pipeline.
type RenderOptions = { recording?: { size?: number // 0-1: 1=one side touches background edge roundness?: number // 0-1: 0=sharp corners, 1=shorter side is half circle shape?: 'rounded' | 'squircle' dropShadow?: string // CSS drop-shadow filter } voiceOvers?: { size?: number // 0-1: 1=mask size equals shorter side of output roundness?: number // 0-1: 0=square, 1=circle shape?: 'rounded' | 'squircle' dropShadow?: string // CSS drop-shadow filter corner?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' padding?: number // 0-1 } cursor?: { size?: number // 0-1: 0=missing, 1=height of video } zooms?: { easing?: string duration?: number } output?: { resolution?: '1280x720' | '1920x1080' | '3840x2160' | `${number}x${number}` // ... background?: { assetPath: string } | { backgroundCss: string } }}Resolution
type Resolution = | '720p' // 1280×720 | '1080p' // 1920×1080 | '4k' // 3840×2160 | { width: number; height: number } // customFPS
type FPS = 24 | 30 | 60| Value | Use case |
|---|---|
24 | Cinematic look, smallest file size |
30 | Standard — good balance of smoothness and size |
60 | Smooth motion, best for fast interactions or scroll-heavy demos |
Quality
type Quality = 'low' | 'medium' | 'high'| Value | CRF | Description |
|---|---|---|
'low' | 28 | Smaller files, lower visual fidelity |
'medium' | 23 | Balanced |
'high' | 18 | Best quality, larger files |
Trace
type Trace = 'on' | 'off' | 'retain-on-failure'Controls Playwright trace recording. 'retain-on-failure' keeps traces only when tests fail.
ScreenCIConfig
The config shape accepted by defineConfig. Extends Playwright’s config — the following fields are managed by ScreenCI and cannot be set directly: fullyParallel, workers, retries, testDir, testMatch.
type ScreenCIConfig = { videoDir?: string use?: { videoOptions?: RecordOptions trace?: Trace sendTraces?: boolean // ...all other Playwright use options } // ...all other Playwright config options}