mirror of
https://github.com/screenci/screenci.git
synced 2026-09-19 08:57:46 +08:00
docs(screenci): refresh package documentation
This commit is contained in:
@@ -10,23 +10,20 @@ Record product walkthroughs as code. When the UI ships, run `npx screenci record
|
||||
npm install screenci
|
||||
```
|
||||
|
||||
## Init a new project
|
||||
## Init a project
|
||||
|
||||
```bash
|
||||
npx screenci init "My Project"
|
||||
cd screenci
|
||||
npm install
|
||||
npx screenci init
|
||||
```
|
||||
|
||||
This scaffolds a ready-to-run project:
|
||||
This scaffolds a ready-to-run project in the current directory:
|
||||
|
||||
```
|
||||
screenci/
|
||||
screenci.config.ts ← video settings
|
||||
videos/
|
||||
example.video.ts ← your first video script
|
||||
package.json
|
||||
.gitignore
|
||||
screenci.config.ts ← video settings
|
||||
videos/
|
||||
example.video.ts ← your first video script
|
||||
package.json
|
||||
.gitignore
|
||||
```
|
||||
|
||||
## Write a video
|
||||
@@ -47,7 +44,7 @@ video('Onboarding flow', async ({ page }) => {
|
||||
|
||||
Each `video()` call → one `.mp4`. The title becomes the filename.
|
||||
|
||||
> **Full reference:** [Writing Video Tests](http://localhost:4321/reference/video-tests)
|
||||
> **Full reference:** [Write Video Scripts](http://localhost:4321/docs/write-video-scripts)
|
||||
|
||||
### `ScreenCIPage` — animated interactions
|
||||
|
||||
|
||||
+263
-1120
File diff suppressed because it is too large
Load Diff
@@ -13,10 +13,10 @@ import {
|
||||
stat,
|
||||
writeFile,
|
||||
} from 'fs/promises'
|
||||
import { dirname, relative as pathRelative, resolve } from 'path'
|
||||
import { basename, dirname, relative as pathRelative, resolve } from 'path'
|
||||
import { fileURLToPath, pathToFileURL } from 'url'
|
||||
import { Command, CommanderError } from 'commander'
|
||||
import { input, confirm, select } from '@inquirer/prompts'
|
||||
import { input } from '@inquirer/prompts'
|
||||
import ora from 'ora'
|
||||
import pc from 'picocolors'
|
||||
import { logger } from './src/logger.js'
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
formatDuplicateTitlesMessage,
|
||||
} from './src/titleValidation.js'
|
||||
|
||||
const SCREENCI_DOCS_URL = 'https://screenci.com/docs/intro/'
|
||||
const SCREENCI_DOCS_URL = 'https://screenci.com/docs'
|
||||
const SCREENCI_MOCK_RECORD_DOCS_URL =
|
||||
'https://screenci.com/docs/reference/cli/#--mock-record'
|
||||
|
||||
@@ -1537,7 +1537,7 @@ This project uses ScreenCI + Playwright to create and upload polished product vi
|
||||
|
||||
Write video scripts in \`videos/*.video.ts\` and use \`video(...)\` calls to create product videos. These are very similar to Playwright \`.test.ts\` and \`test(...)\` calls.
|
||||
|
||||
Learn more: https://screenci.com/docs/intro/
|
||||
Learn more: https://screenci.com/docs
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -1564,14 +1564,7 @@ node_modules/
|
||||
`
|
||||
}
|
||||
|
||||
function generateGithubAction(workingDirectory: string): string {
|
||||
const packageLockPath =
|
||||
workingDirectory === '.'
|
||||
? 'package-lock.json'
|
||||
: `${workingDirectory}/package-lock.json`
|
||||
const envFilePath =
|
||||
workingDirectory === '.' ? './.env' : `./${workingDirectory}/.env`
|
||||
|
||||
function generateGithubAction(): string {
|
||||
return `name: ScreenCI
|
||||
|
||||
on:
|
||||
@@ -1591,7 +1584,7 @@ jobs:
|
||||
SCREENCI_SECRET: \${{ secrets.SCREENCI_SECRET }}
|
||||
run: |
|
||||
if [ -z "$SCREENCI_SECRET" ]; then
|
||||
echo "::error::SCREENCI_SECRET is not set. Copy it from https://app.screenci.com/secrets or ${envFilePath}, add it under Settings → Secrets and variables → Actions → Repository secrets, and then rerun this action."
|
||||
echo "::error::SCREENCI_SECRET is not set. Copy it from https://app.screenci.com/secrets or ./.env, add it under Settings → Secrets and variables → Actions → Repository secrets, and then rerun this action."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1601,10 +1594,10 @@ jobs:
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
cache-dependency-path: ${packageLockPath}
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ${workingDirectory}
|
||||
working-directory: .
|
||||
run: npm ci
|
||||
|
||||
- name: Cache Playwright Chromium
|
||||
@@ -1612,16 +1605,16 @@ jobs:
|
||||
id: pw-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-\${{ runner.os }}-\${{ hashFiles('${packageLockPath}') }}
|
||||
key: playwright-\${{ runner.os }}-\${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Install Chromium
|
||||
if: steps.pw-cache.outputs.cache-hit != 'true'
|
||||
working-directory: ${workingDirectory}
|
||||
run: npx playwright install chromium --with-deps
|
||||
working-directory: .
|
||||
run: npx playwright install chromium
|
||||
|
||||
- id: record
|
||||
name: Record
|
||||
working-directory: ${workingDirectory}
|
||||
working-directory: .
|
||||
env:
|
||||
SCREENCI_SECRET: \${{ secrets.SCREENCI_SECRET }}
|
||||
run: npx screenci record
|
||||
@@ -1741,58 +1734,75 @@ video('See the next steps in ScreenCI docs', async ({ page }) => {
|
||||
`
|
||||
}
|
||||
|
||||
function getDefaultInitProjectName(): string {
|
||||
const directoryName = basename(getInitProjectRoot())
|
||||
return directoryName.length > 0 ? directoryName : 'screenci-project'
|
||||
}
|
||||
|
||||
async function promptProjectName(): Promise<string> {
|
||||
return input({ message: 'Project name:' })
|
||||
}
|
||||
|
||||
async function promptInitDependencies(): Promise<boolean> {
|
||||
return confirm({
|
||||
message:
|
||||
'Install dependencies now, including Chromium for Playwright? (Y/n)',
|
||||
default: true,
|
||||
return input({
|
||||
message: 'Project name:',
|
||||
default: getDefaultInitProjectName(),
|
||||
})
|
||||
}
|
||||
|
||||
async function promptInitAiAuthoring(): Promise<boolean> {
|
||||
return confirm({
|
||||
message:
|
||||
'Do you want to write videos with an AI agent based on a URL and not just source code? If yes, playwright-cli will be also installed.',
|
||||
default: true,
|
||||
async function promptYesNo(
|
||||
message: string,
|
||||
defaultValue: boolean
|
||||
): Promise<boolean> {
|
||||
const answer = await input({
|
||||
message,
|
||||
default: defaultValue ? 'y' : 'n',
|
||||
validate: (value) => {
|
||||
const normalized = value.trim().toLowerCase()
|
||||
if (
|
||||
normalized === '' ||
|
||||
normalized === 'y' ||
|
||||
normalized === 'yes' ||
|
||||
normalized === 'n' ||
|
||||
normalized === 'no'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return 'Enter y or n'
|
||||
},
|
||||
})
|
||||
|
||||
const normalized = answer.trim().toLowerCase()
|
||||
if (normalized === '') return defaultValue
|
||||
return normalized === 'y' || normalized === 'yes'
|
||||
}
|
||||
|
||||
async function promptInitGithubActionCi(): Promise<boolean> {
|
||||
return confirm({
|
||||
message: 'Do you want to add Github Action CI? (Y/n)',
|
||||
default: true,
|
||||
})
|
||||
async function promptInitGithubActionWorkflow(): Promise<boolean> {
|
||||
return promptYesNo('Add a GitHub Actions workflow? (Y/n)', true)
|
||||
}
|
||||
|
||||
type InitRepositoryMode = 'standalone' | 'existing-repository'
|
||||
|
||||
async function promptInitRepositoryMode(): Promise<InitRepositoryMode> {
|
||||
return select({
|
||||
message:
|
||||
'Initialize ScreenCI as a standalone project or part of the existing repository?',
|
||||
default: 'standalone',
|
||||
choices: [
|
||||
{
|
||||
name: 'Standalone project',
|
||||
value: 'standalone',
|
||||
description: 'Create a project directory with its own GitHub Action.',
|
||||
},
|
||||
{
|
||||
name: 'Part of existing repository',
|
||||
value: 'existing-repository',
|
||||
description:
|
||||
'Create ./screenci and add the GitHub Action at the repository root.',
|
||||
},
|
||||
],
|
||||
})
|
||||
async function promptInitPlaywrightBrowsers(): Promise<boolean> {
|
||||
return promptYesNo(
|
||||
"Install Playwright browsers (can be done manually via 'npx playwright install chromium')? (Y/n)",
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
function projectNameToDirectoryName(projectName: string): string {
|
||||
return projectName.trim().replace(/\s+/g, '-')
|
||||
async function promptInitPlaywrightOsDependencies(): Promise<boolean> {
|
||||
return promptYesNo(
|
||||
"Install Playwright operating system dependencies (might require sudo / root and can be done manually via 'npx playwright install-deps chromium')? (y/N)",
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
async function promptInitScreenCISkill(): Promise<boolean> {
|
||||
return promptYesNo(
|
||||
"Install the ScreenCI skill for AI agents (can be done manually via 'npx -y skills add screenci/screenci --skill screenci -y')? (Y/n)",
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
async function promptInitPlaywrightCliSkill(): Promise<boolean> {
|
||||
return promptYesNo(
|
||||
"Install playwright-cli for URL-based browser inspection (can be done manually via 'npx -y skills add screenci/screenci --skill playwright-cli -y && npm install @playwright/cli')? (Y/n)",
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
function getInitProjectRoot(): string {
|
||||
@@ -1831,10 +1841,7 @@ export async function ensureScreenciSecret(): Promise<string | undefined> {
|
||||
|
||||
type InitOptions = {
|
||||
verbose: boolean
|
||||
install: boolean
|
||||
yes: boolean
|
||||
skill: boolean
|
||||
ci: boolean
|
||||
agent?: string
|
||||
}
|
||||
|
||||
@@ -1842,14 +1849,13 @@ async function runInit(
|
||||
projectNameArg: string | undefined,
|
||||
options: InitOptions
|
||||
): Promise<void> {
|
||||
const { verbose, install, yes, skill, ci, agent } = options
|
||||
const { verbose, yes, agent } = options
|
||||
const initCwd = getInitProjectRoot()
|
||||
const existingRepositoryDetected = existsSync(resolve(initCwd, '.git'))
|
||||
|
||||
let projectName = projectNameArg?.trim()
|
||||
|
||||
if (!projectName) {
|
||||
projectName = await promptProjectName()
|
||||
projectName = yes ? getDefaultInitProjectName() : await promptProjectName()
|
||||
}
|
||||
|
||||
if (!projectName) {
|
||||
@@ -1857,78 +1863,60 @@ async function runInit(
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (existingRepositoryDetected) {
|
||||
logger.info('Existing repository detected')
|
||||
}
|
||||
|
||||
const repositoryMode: InitRepositoryMode = existingRepositoryDetected
|
||||
? yes
|
||||
? 'standalone'
|
||||
: await promptInitRepositoryMode()
|
||||
: 'standalone'
|
||||
const isPartOfExistingRepository = repositoryMode === 'existing-repository'
|
||||
|
||||
const dirName = isPartOfExistingRepository
|
||||
? 'screenci'
|
||||
: projectNameToDirectoryName(projectName)
|
||||
const projectDir = resolve(initCwd, dirName)
|
||||
const githubRootDir = isPartOfExistingRepository ? initCwd : projectDir
|
||||
const githubDir = resolve(githubRootDir, '.github')
|
||||
const githubWorkflowsDir = resolve(githubDir, 'workflows')
|
||||
const projectDir = initCwd
|
||||
const githubWorkflowsDir = resolve(projectDir, '.github', 'workflows')
|
||||
const githubActionPath = resolve(githubWorkflowsDir, 'screenci.yaml')
|
||||
const githubActionProjectDir = isPartOfExistingRepository ? 'screenci' : '.'
|
||||
|
||||
if (existsSync(projectDir)) {
|
||||
logger.error(`Error: Directory "${dirName}" already exists`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const shouldInstallDependencies = yes
|
||||
const shouldAddGithubActionWorkflow = yes
|
||||
? true
|
||||
: install
|
||||
? true
|
||||
: await promptInitDependencies()
|
||||
const shouldAddPlaywrightCli = yes
|
||||
: await promptInitGithubActionWorkflow()
|
||||
const shouldInstallPlaywrightBrowsers = yes
|
||||
? true
|
||||
: skill
|
||||
? true
|
||||
: await promptInitAiAuthoring()
|
||||
const shouldAddGithubActionCi = yes
|
||||
: await promptInitPlaywrightBrowsers()
|
||||
const shouldInstallPlaywrightOsDependencies = yes
|
||||
? false
|
||||
: await promptInitPlaywrightOsDependencies()
|
||||
const shouldInstallScreenCISkill = yes
|
||||
? true
|
||||
: ci
|
||||
? true
|
||||
: await promptInitGithubActionCi()
|
||||
: await promptInitScreenCISkill()
|
||||
const shouldInstallPlaywrightCli = yes
|
||||
? true
|
||||
: await promptInitPlaywrightCliSkill()
|
||||
|
||||
if (shouldAddGithubActionCi && existsSync(githubActionPath)) {
|
||||
if (shouldAddGithubActionWorkflow && existsSync(githubActionPath)) {
|
||||
logger.error(
|
||||
'Error: GitHub Actions workflow ".github/workflows/screenci.yaml" already exists'
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const skillsArgs = [
|
||||
'--yes',
|
||||
'skills',
|
||||
'add',
|
||||
'screenci/screenci',
|
||||
...(agent ? ['--agent', agent] : []),
|
||||
'--skill',
|
||||
'screenci',
|
||||
...(shouldAddPlaywrightCli ? ['--skill', 'playwright-cli'] : []),
|
||||
'-y',
|
||||
]
|
||||
const skillsCommand = `npx ${skillsArgs.join(' ')}`
|
||||
const skills: string[] = []
|
||||
if (shouldInstallScreenCISkill) {
|
||||
skills.push('screenci')
|
||||
}
|
||||
if (shouldInstallPlaywrightCli) {
|
||||
skills.push('playwright-cli')
|
||||
}
|
||||
const skillsArgs =
|
||||
skills.length === 0
|
||||
? null
|
||||
: [
|
||||
'-y',
|
||||
'skills',
|
||||
'add',
|
||||
'screenci/screenci',
|
||||
...(agent ? ['--agent', agent] : []),
|
||||
...skills.flatMap((skillName) => ['--skill', skillName]),
|
||||
'-y',
|
||||
]
|
||||
const skillsCommand =
|
||||
skillsArgs === null ? null : `npx ${skillsArgs.join(' ')}`
|
||||
const screenciDependency =
|
||||
getInitScreenciDependencyOverride() ?? (await readCurrentScreenciVersion())
|
||||
|
||||
logger.info("Initializing project in '.'")
|
||||
await mkdir(resolve(projectDir, 'videos'), { recursive: true })
|
||||
if (shouldAddGithubActionCi) {
|
||||
if (!existsSync(githubDir)) {
|
||||
await mkdir(githubDir)
|
||||
}
|
||||
if (!existsSync(githubWorkflowsDir)) {
|
||||
await mkdir(githubWorkflowsDir)
|
||||
}
|
||||
if (shouldAddGithubActionWorkflow) {
|
||||
await mkdir(githubWorkflowsDir, { recursive: true })
|
||||
}
|
||||
await writeFile(
|
||||
resolve(projectDir, 'screenci.config.ts'),
|
||||
@@ -1936,7 +1924,7 @@ async function runInit(
|
||||
)
|
||||
await writeFile(
|
||||
resolve(projectDir, 'package.json'),
|
||||
generatePackageJson(shouldAddPlaywrightCli, screenciDependency)
|
||||
generatePackageJson(shouldInstallPlaywrightCli, screenciDependency)
|
||||
)
|
||||
await writeFile(resolve(projectDir, 'tsconfig.json'), generateTsconfig())
|
||||
await writeFile(resolve(projectDir, 'README.md'), generateReadme(projectName))
|
||||
@@ -1945,15 +1933,12 @@ async function runInit(
|
||||
resolve(projectDir, 'videos', 'example.video.ts'),
|
||||
generateExampleVideo()
|
||||
)
|
||||
if (shouldAddGithubActionCi) {
|
||||
await writeFile(
|
||||
githubActionPath,
|
||||
generateGithubAction(githubActionProjectDir)
|
||||
)
|
||||
if (shouldAddGithubActionWorkflow) {
|
||||
await writeFile(githubActionPath, generateGithubAction())
|
||||
}
|
||||
await writeFile(resolve(projectDir, '.env'), '')
|
||||
|
||||
logger.info(`Initialized screenci project "${projectName}" in ${projectDir}/`)
|
||||
logger.info(`Initialized screenci project "${projectName}" in .`)
|
||||
logger.info('Files created:')
|
||||
logger.info(' screenci.config.ts')
|
||||
logger.info(' package.json')
|
||||
@@ -1961,68 +1946,74 @@ async function runInit(
|
||||
logger.info(' README.md')
|
||||
logger.info(' .gitignore')
|
||||
logger.info(' videos/example.video.ts')
|
||||
if (shouldAddGithubActionCi) {
|
||||
const githubActionDisplayPath = isPartOfExistingRepository
|
||||
? '.github/workflows/screenci.yaml (outside ./screenci, at repository root)'
|
||||
: '.github/workflows/screenci.yaml'
|
||||
logger.info(` ${githubActionDisplayPath}`)
|
||||
if (shouldAddGithubActionWorkflow) {
|
||||
logger.info(' .github/workflows/screenci.yaml')
|
||||
}
|
||||
logger.info(' .env (empty placeholder)')
|
||||
logger.info('')
|
||||
|
||||
if (shouldInstallDependencies) {
|
||||
if (skillsArgs !== null) {
|
||||
if (verbose) {
|
||||
logger.info(`Running '${skillsCommand}'...`)
|
||||
await spawnInherited('npx', skillsArgs, projectDir, 'screenci init')
|
||||
} else {
|
||||
const spinner = ora('Adding ScreenCI skills...').start()
|
||||
const spinner = ora('Adding selected AI skills...').start()
|
||||
try {
|
||||
await spawnSilent('npx', skillsArgs, projectDir)
|
||||
spinner.succeed('ScreenCI skills added')
|
||||
spinner.succeed('Selected AI skills added')
|
||||
} catch (err) {
|
||||
spinner.fail('ScreenCI skills install failed')
|
||||
spinner.fail('AI skills install failed')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
const installArgs = ['install', '--include=dev']
|
||||
logger.info(`Running 'npm ${installArgs.join(' ')}'...`)
|
||||
await spawnInherited('npm', installArgs, projectDir, 'screenci init')
|
||||
} else {
|
||||
const spinner = ora('Running npm install...').start()
|
||||
try {
|
||||
const installArgs = ['install', '--include=dev', '--prefix', projectDir]
|
||||
await spawnSilent('npm', installArgs)
|
||||
spinner.succeed('npm install complete')
|
||||
} catch (err) {
|
||||
spinner.fail('npm install failed')
|
||||
throw err
|
||||
}
|
||||
const installArgs = ['install', '--include=dev']
|
||||
if (verbose) {
|
||||
logger.info(`Running 'npm ${installArgs.join(' ')}'...`)
|
||||
await spawnInherited('npm', installArgs, projectDir, 'screenci init')
|
||||
} else {
|
||||
const spinner = ora('Running npm install...').start()
|
||||
try {
|
||||
await spawnSilent('npm', installArgs, projectDir)
|
||||
spinner.succeed('npm install complete')
|
||||
} catch (err) {
|
||||
spinner.fail('npm install failed')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldInstallPlaywrightBrowsers) {
|
||||
logger.info(
|
||||
"Local development requires Chromium for Playwright, running 'npx playwright install chromium --with-deps'..."
|
||||
"Installing Playwright Chromium with 'npx playwright install chromium'..."
|
||||
)
|
||||
await spawnInherited(
|
||||
'npx',
|
||||
['playwright', 'install', 'chromium', '--with-deps'],
|
||||
['playwright', 'install', 'chromium'],
|
||||
projectDir,
|
||||
'screenci init'
|
||||
)
|
||||
logger.info(`${pc.green('✔')} Playwright installed successfully`)
|
||||
} else {
|
||||
logger.info('Dependencies were not installed automatically.')
|
||||
logger.info('Run these commands when you are ready:')
|
||||
logger.info(` ${skillsCommand}`)
|
||||
logger.info(' npm install --include=dev')
|
||||
logger.info(' npx playwright install chromium --with-deps')
|
||||
logger.info(`${pc.green('✔')} Playwright Chromium installed successfully`)
|
||||
}
|
||||
|
||||
if (shouldInstallPlaywrightOsDependencies) {
|
||||
logger.info(
|
||||
"Installing Playwright operating system dependencies with 'npx playwright install-deps chromium'..."
|
||||
)
|
||||
await spawnInherited(
|
||||
'npx',
|
||||
['playwright', 'install-deps', 'chromium'],
|
||||
projectDir,
|
||||
'screenci init'
|
||||
)
|
||||
logger.info(
|
||||
`${pc.green('✔')} Playwright operating system dependencies installed successfully`
|
||||
)
|
||||
}
|
||||
logger.info('')
|
||||
logger.info('Next steps:')
|
||||
logger.info(` cd ${dirName}`)
|
||||
logger.info(' Read README.md for setup and recording flow')
|
||||
logger.info(' Docs: https://screenci.com/docs/intro/')
|
||||
logger.info(' Docs: https://screenci.com/docs')
|
||||
logger.info(' npx screenci test')
|
||||
logger.info(' npx screenci record')
|
||||
}
|
||||
@@ -2246,27 +2237,18 @@ export async function main() {
|
||||
program
|
||||
.command('init [name]')
|
||||
.description('Initialize a new screenci project')
|
||||
.option(
|
||||
'--install',
|
||||
'install skills, dependencies, and Chromium without prompting'
|
||||
)
|
||||
.option('--ci', 'add GitHub Action CI without prompting')
|
||||
.option(
|
||||
'--agent <name>',
|
||||
'target agent for skills install, e.g. opencode. Supported agents: https://github.com/vercel-labs/skills#supported-agents'
|
||||
)
|
||||
.option('--skill', 'enable playwright-cli without prompting')
|
||||
.option('-y, --yes', 'answer yes to all init prompts')
|
||||
.option('-y, --yes', 'accept init defaults')
|
||||
.option('-v, --verbose', 'verbose output')
|
||||
.action(
|
||||
async (name: string | undefined, options: Record<string, unknown>) => {
|
||||
const agent = options['agent'] as string | undefined
|
||||
await runInit(name, {
|
||||
verbose: (options['verbose'] as boolean | undefined) ?? false,
|
||||
install: (options['install'] as boolean | undefined) ?? false,
|
||||
yes: (options['yes'] as boolean | undefined) ?? false,
|
||||
skill: (options['skill'] as boolean | undefined) ?? false,
|
||||
ci: (options['ci'] as boolean | undefined) ?? false,
|
||||
...(agent !== undefined ? { agent } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
+25
-26
@@ -1,34 +1,33 @@
|
||||
# Screenci Docs
|
||||
# ScreenCI Docs
|
||||
|
||||
Developer documentation for the `screenci` library.
|
||||
Source docs for the ScreenCI documentation site.
|
||||
|
||||
## Prerequisites
|
||||
## IA
|
||||
|
||||
Before using screenci you only need **Node.js** and **npm**. npm is included with the standard Node.js installer.
|
||||
The docs follow a Playwright-like reader journey:
|
||||
|
||||
If you are unsure whether Node.js is installed, check:
|
||||
### Getting Started
|
||||
|
||||
```bash
|
||||
node --version
|
||||
```
|
||||
- [Installation](./installation.md)
|
||||
- [Write Video Scripts](./write-video-scripts.md)
|
||||
- [Generating Videos](./generating-videos.md)
|
||||
- [Run and Debug Videos](./run-and-debug-videos.md)
|
||||
- [Record and Publish](./record-and-publish.md)
|
||||
- [ScreenCI vs Playwright](./screenci-vs-playwright.md)
|
||||
- [CI Setup](./ci-setup.md)
|
||||
|
||||
If that command is missing or prints an old version, install Node.js from the [official Node.js installation docs](https://nodejs.org/en/download). Node.js 20+ is recommended.
|
||||
### Guides
|
||||
|
||||
## Contents
|
||||
- [Narration and Localization](./narration-and-localization.md)
|
||||
- [Camera and Zooming](./camera-and-zooming.md)
|
||||
- [Assets and Overlays](./assets-and-overlays.md)
|
||||
- [Public URLs and Embeds](./public-urls-and-embeds.md)
|
||||
- [Update ScreenCI](./update-screenci.md)
|
||||
|
||||
| Doc | Description |
|
||||
| ----------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| [Introduction](./intro.md) | Overview of ScreenCI and where to start |
|
||||
| [Getting Started](./getting-started.md) | Record video and subtitles from a ScreenCI project |
|
||||
| [Create Videos](./creating-videos.md) | Understand the generated files and write your own ScreenCI videos |
|
||||
| [Playwright vs ScreenCI](./playwright-vs-screenci.md) | Compare Playwright tests with ScreenCI video scripts |
|
||||
| [AI-Supported Editing](./ai-editing.md) | AI-facing docs access, llms.txt, and MCP workflows |
|
||||
| [Localization & Narrations](./localization.md) | Multi-language narration and localized UI videos |
|
||||
| [Assets](./assets.md) | Image and video overlays with `createAssets` |
|
||||
| [Public URLs](./public-urls.md) | Publish stable public video, thumbnail, and subtitle URLs |
|
||||
| [Update screenci with npm](./updating-with-npm.md) | Update the installed `screenci` package and refresh skills |
|
||||
| [CLI Commands](./cli.md) | Complete command reference for `screenci` |
|
||||
| [Configuration](./configuration.md) | `defineConfig` options, per-test overrides, defaults |
|
||||
| [Writing Video Tests](./video-tests.md) | How to use `video()`, `createNarration()`, multiple tests, auth, etc. |
|
||||
| [API Reference](./api.md) | Full reference for all exported functions and types |
|
||||
| [Public API](./public-api.md) | Public endpoints for published videos, thumbnails, and subtitles |
|
||||
### Reference
|
||||
|
||||
- [CLI](./cli.md)
|
||||
- [Configuration](./configuration.md)
|
||||
- [Video Authoring API Overview](./video-authoring-api-overview.md)
|
||||
- [Public Delivery API](./public-delivery-api.md)
|
||||
- Full API Reference via generated typedoc
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Assets and Overlays
|
||||
|
||||
Assets let you place additional media on top of the recording timeline. Use them for intros, transitions, corner branding, or short contextual clips that would be awkward to build inside the browser automation itself.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [how to define assets](#define-assets)
|
||||
- [when to use full-screen versus overlay assets](#full-screen-vs-overlay)
|
||||
- [how timing and ordering work](#timing-behavior)
|
||||
- [how to organize files for maintainable projects](#file-organization)
|
||||
|
||||
## Define assets
|
||||
|
||||
```ts
|
||||
import { createAssets, video } from 'screenci'
|
||||
|
||||
const assets = createAssets({
|
||||
intro: { path: './assets/intro.mp4', audio: 1, fullScreen: true },
|
||||
logo: { path: './assets/logo.png', audio: 0, fullScreen: false },
|
||||
})
|
||||
|
||||
video('Overview', async ({ page }) => {
|
||||
await assets.intro
|
||||
await page.goto('/dashboard')
|
||||
await assets.logo
|
||||
})
|
||||
```
|
||||
|
||||
Each key becomes an awaitable asset controller.
|
||||
|
||||
## Full-screen vs overlay
|
||||
|
||||
Use `fullScreen: true` for:
|
||||
|
||||
- intro clips
|
||||
- transition clips
|
||||
- end cards
|
||||
|
||||
Use `fullScreen: false` for:
|
||||
|
||||
- corner logos
|
||||
- picture-in-picture presenter clips
|
||||
- lightweight callout overlays
|
||||
|
||||
## Timing behavior
|
||||
|
||||
Asset timing is explicit in the script:
|
||||
|
||||
- `await assets.intro` inserts the asset at that point in the timeline
|
||||
- full-screen assets take over the output frame
|
||||
- overlay assets stay on top of the recording while the underlying screen continues
|
||||
|
||||
That means you do not need separate timing math just to line an intro clip up with the next step.
|
||||
|
||||
## File organization
|
||||
|
||||
A simple structure is usually enough:
|
||||
|
||||
```text
|
||||
assets/
|
||||
intro.mp4
|
||||
transition.mp4
|
||||
logo.png
|
||||
```
|
||||
|
||||
Keep reusable brand assets separate from throwaway experiment files so the project stays readable.
|
||||
|
||||
## Authoring advice
|
||||
|
||||
- Use overlays sparingly.
|
||||
- Mute assets that should not compete with narration.
|
||||
- Keep intros and transitions short.
|
||||
- Prefer consistent placement and sizing across videos in the same series.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Camera and Zooming
|
||||
|
||||
ScreenCI has two camera styles: `autoZoom()` for sections where the camera should follow the interaction automatically, and manual zoom helpers when you want exact framing. Treat these as direction tools, not decorative effects.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [when to use `autoZoom()`](#automatic-zoom)
|
||||
- [when to use manual framing](#manual-zoom)
|
||||
- [how to direct attention with multiple zooms](#manual-zoom)
|
||||
- [how to keep camera motion readable](#manual-zoom)
|
||||
|
||||
## Automatic zoom
|
||||
|
||||
```ts
|
||||
import { autoZoom, video } from 'screenci'
|
||||
|
||||
video('Edit profile', async ({ page }) => {
|
||||
await page.goto('/settings/profile')
|
||||
|
||||
await autoZoom(async () => {
|
||||
await page.getByLabel('Name').fill('Jane Doe')
|
||||
await page.getByLabel('Email').fill('jane@screenci.com')
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
Use it when the viewer should follow a group of related actions, such as filling a form or editing a focused panel.
|
||||
|
||||
`autoZoom()` works best when it covers a real interaction cluster, not a single isolated click. Wrap the whole related sequence so ScreenCI can zoom in, follow the active area, and zoom back out once the section is finished.
|
||||
|
||||
For locator targets, ScreenCI compares your chosen zoom `amount` with a padded fit around the target and keeps the larger viewport. The default `padding` is `0.2`, which gives the viewer some breathing room instead of cropping tightly around the element.
|
||||
|
||||
Use automatic zoom when the camera should react to the flow instead of following a storyboard you planned in advance.
|
||||
|
||||
## Manual zoom
|
||||
|
||||
```ts
|
||||
import { resetZoom, video, zoomTo } from 'screenci'
|
||||
|
||||
video('Dashboard walkthrough', async ({ page }) => {
|
||||
await page.goto('/dashboard')
|
||||
|
||||
await zoomTo(page.getByText('Net revenue'))
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
await zoomTo(page.getByText('Conversion rate'))
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
await zoomTo({ x: 1200, y: 680 })
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
await resetZoom()
|
||||
})
|
||||
```
|
||||
|
||||
Manual framing is better when:
|
||||
|
||||
- you want to frame something before interacting with it
|
||||
- the important target is not the next clicked element
|
||||
- you want a deliberate pan between two exact points
|
||||
|
||||
`zoomTo()` accepts either a locator or an explicit viewport point like `{ x, y }`. Use a locator when you want framing to stay tied to a real UI target. Use a point when you want a very deliberate composition or pan that is not attached to the next clickable element.
|
||||
|
||||
Manual zoom becomes more useful when one focused sequence has multiple camera beats:
|
||||
|
||||
- zoom to one panel
|
||||
- stay close while the viewer reads it
|
||||
- pan to another panel or metric
|
||||
- finish on a hand-picked point
|
||||
- reset to the full frame at the end
|
||||
|
||||
`resetZoom()` returns from the current manual zoom state to the full recording viewport, so it is the natural last step after multiple `zoomTo()` calls.
|
||||
|
||||
Keep manual zoom readable:
|
||||
|
||||
- use one camera idea for a segment
|
||||
- do not zoom every interaction just because you can
|
||||
- keep movement slower and simpler than your first instinct
|
||||
- reset to the full frame after a focused sequence
|
||||
|
||||
Do not mix `autoZoom()` and manual zooming at the same time. In practice, that means:
|
||||
|
||||
- do not call `zoomTo()` or `resetZoom()` inside `autoZoom()`
|
||||
- do not start `autoZoom()` while a manual zoom is still active
|
||||
@@ -0,0 +1,59 @@
|
||||
# CI Setup
|
||||
|
||||
ScreenCI can generate a GitHub Actions workflow during `init`, and that workflow is meant to be a usable default, not a placeholder. It records the same way you do locally, but with repository secrets and a deterministic CI environment.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [what the generated workflow does](#generated-workflow)
|
||||
- [which secret is required](#required-secret)
|
||||
- [when to record on push and when to use manual dispatch](#push-vs-manual-dispatch)
|
||||
- [how to keep CI recordings predictable](#keep-recordings-deterministic)
|
||||
|
||||
## Generated workflow
|
||||
|
||||
When you opt into CI during `init`, ScreenCI writes:
|
||||
|
||||
```text
|
||||
.github/workflows/screenci.yaml
|
||||
```
|
||||
|
||||
The generated workflow:
|
||||
|
||||
- runs on pushes to `main`
|
||||
- also supports `workflow_dispatch`
|
||||
- checks that `SCREENCI_SECRET` exists
|
||||
- checks out the repository
|
||||
- installs Node.js 24 and caches npm dependencies
|
||||
- installs Chromium if the Playwright cache is cold
|
||||
- runs `npx screenci record`
|
||||
|
||||
## Required secret
|
||||
|
||||
Add `SCREENCI_SECRET` as a repository secret in GitHub Actions.
|
||||
|
||||
The generated workflow fails early if the secret is missing so you do not spend time waiting for a recording job that cannot upload anything.
|
||||
|
||||
## Push vs manual dispatch
|
||||
|
||||
Start simple:
|
||||
|
||||
- keep `push` to `main` when you want docs and product videos to stay current automatically
|
||||
- use `workflow_dispatch` when you want a manual review or approval step before recording
|
||||
|
||||
You can narrow the trigger later, but the generated default is intentionally easy to adopt.
|
||||
|
||||
## Keep recordings deterministic
|
||||
|
||||
CI recordings work best when:
|
||||
|
||||
- the target environment is stable
|
||||
- feature flags are fixed
|
||||
- seeded demo data is predictable
|
||||
- authentication is handled before visible recording starts
|
||||
- visible waits are tied to UI state instead of luck
|
||||
|
||||
If a flow only works when everything is timed perfectly, fix the script locally before pushing CI responsibility onto it.
|
||||
|
||||
## Relation to accepted and latest renders
|
||||
|
||||
CI uses the same ScreenCI upload and render pipeline as local recording. The main difference is that it becomes repeatable and repository-driven, which is useful when published videos should follow the shipped app.
|
||||
+46
-136
@@ -1,194 +1,109 @@
|
||||
---
|
||||
title: CLI Commands
|
||||
description: Complete reference for the screenci CLI, including recording, testing, upload, project info, and public URL commands.
|
||||
---
|
||||
# CLI
|
||||
|
||||
# CLI Commands
|
||||
The `screenci` CLI keeps the workflow small: initialize a project, iterate locally, record final output, and manage public delivery when needed. Most commands resolve `screenci.config.ts` from the current directory unless you pass `--config <path>`.
|
||||
|
||||
The `screenci` CLI wraps the Playwright workflow used by ScreenCI projects and adds project-aware commands for uploads and public URLs.
|
||||
## Command overview
|
||||
|
||||
Most commands look for `screenci.config.ts` in the current directory. Use `--config <path>` when your config lives elsewhere.
|
||||
|
||||
## Commands overview
|
||||
|
||||
| Command | What it does |
|
||||
| --------------------------------- | ---------------------------------------------------------------- |
|
||||
| `screenci init [name]` | Scaffold a new ScreenCI project |
|
||||
| `screenci test [args...]` | Forward directly to `playwright test` using your ScreenCI config |
|
||||
| `screenci record [args...]` | Record videos with local Playwright |
|
||||
| `screenci info` | Print remote project info as JSON |
|
||||
| `screenci make-public <videoId>` | Enable public URLs for a video |
|
||||
| `screenci make-private <videoId>` | Disable public URLs for a video |
|
||||
| Command | Purpose |
|
||||
| ---------------------------------- | ----------------------------------------------------- |
|
||||
| `screenci init [name]` | Scaffold a ScreenCI project |
|
||||
| `screenci test [playwrightArgs]` | Run `.video.ts` files locally without final recording |
|
||||
| `screenci record [playwrightArgs]` | Record videos and upload results when configured |
|
||||
| `screenci info` | Print remote project info as JSON |
|
||||
| `screenci make-public <videoId>` | Enable public delivery for a video |
|
||||
| `screenci make-private <videoId>` | Disable public delivery for a video |
|
||||
|
||||
## `screenci init [name]`
|
||||
|
||||
Creates a new `screenci/` directory with a starter config, example video, and optional workflow file. The optional GitHub Actions workflow is written at `.github/workflows/screenci.yaml` in the current directory. `init` does not authenticate. If `SCREENCI_SECRET` is missing, `screenci record` will open a browser window and complete the login flow before recording starts.
|
||||
Create a new ScreenCI project in the current directory:
|
||||
|
||||
```bash
|
||||
npx screenci@latest init
|
||||
# or: npx screenci@latest init "My Product"
|
||||
# or: npx screenci@latest init "My Product" --yes
|
||||
cd screenci
|
||||
npx screenci@latest init "My Product"
|
||||
```
|
||||
|
||||
The optional `[name]` is the ScreenCI project display name, not the directory name.
|
||||
Because init writes ScreenCI files into `screenci/` and the optional workflow into `.github/workflows/screenci.yaml`, it works well inside existing projects without mixing generated files into your app source.
|
||||
Common options:
|
||||
|
||||
Options:
|
||||
- `-y, --yes` accepts all defaults
|
||||
- `--agent <name>` passes an agent name to the selected skills install command
|
||||
- `-v, --verbose` prints underlying command output
|
||||
|
||||
- `-v, --verbose` prints underlying command output instead of spinners
|
||||
- `--install` install ScreenCI skills, npm dependencies, and Chromium without prompting
|
||||
- `--ci` add GitHub Action CI without prompting
|
||||
- `--skill` answer yes to the AI authoring question and include `playwright-cli`
|
||||
- `-y, --yes` answer yes to all init prompts
|
||||
Interactive defaults create the GitHub Actions workflow, install npm dependencies, install Chromium, skip OS dependency installation, install the ScreenCI skill, and install `playwright-cli` support.
|
||||
|
||||
Use this command in [Installation](/docs).
|
||||
|
||||
## `screenci test [playwrightArgs...]`
|
||||
|
||||
Forwards Playwright test arguments in normal `playwright test` syntax while still resolving `screenci.config.ts`.
|
||||
Run videos locally without the final recording pipeline:
|
||||
|
||||
```bash
|
||||
npx screenci test
|
||||
npx screenci test --grep "checkout"
|
||||
npx screenci test --project=chromium
|
||||
npx screenci test tests/onboarding.video.ts --grep "step 2"
|
||||
npx screenci test videos/onboarding.video.ts
|
||||
npx screenci test --grep "billing"
|
||||
npx screenci test --ui
|
||||
```
|
||||
|
||||
Use this when you want normal Playwright execution without recording.
|
||||
|
||||
By default, `screenci test` skips ScreenCI's recording-only pacing so it stays fast:
|
||||
|
||||
- cursor moves become instant instead of animated
|
||||
- built-in sleeps for click, hide, and zoom timing are skipped
|
||||
- no screen recording is started
|
||||
|
||||
That makes `test` the right command while you are iterating on selectors, app state, and assertions.
|
||||
Use this during normal authoring. Most trailing arguments are forwarded to Playwright.
|
||||
|
||||
### `--mock-record`
|
||||
|
||||
Use `--mock-record` when you want `screenci test` to keep the same animated timing model as `screenci record` without starting the actual browser screen capture:
|
||||
|
||||
```bash
|
||||
npx screenci test --mock-record
|
||||
npx screenci test --mock-record --grep "checkout"
|
||||
```
|
||||
|
||||
This is mainly a troubleshooting option. Reach for it when:
|
||||
|
||||
- `screenci test` passes, but `screenci record` fails
|
||||
- a timing issue only shows up with animated cursor moves or ScreenCI's built-in pauses
|
||||
- you want to debug recording-like pacing without paying the full cost of local recording
|
||||
|
||||
You can also pass normal Playwright test arguments through `screenci test`. That means you can run only some tests while iterating by using the same filters you would use with `playwright test`, such as a file path or `--grep`:
|
||||
|
||||
```bash
|
||||
npx screenci test videos/onboarding.video.ts
|
||||
npx screenci test --grep "checkout"
|
||||
npx screenci test videos/onboarding.video.ts --grep "step 2"
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Most arguments after `test` are passed through as-is to `playwright test`
|
||||
- `screenci test` still injects your resolved `screenci.config.ts` automatically
|
||||
- `--config` / `-c` are reserved for the `screenci` CLI itself, so use them to point to a different `screenci.config.ts`
|
||||
- `--verbose` / `-v` are reserved for the `screenci` CLI itself for extra CLI logging, not forwarded to Playwright
|
||||
- `--mock-record` is handled by `screenci` itself and is not forwarded to Playwright
|
||||
This keeps recording-like pacing enabled without starting the real recording capture path. Use it when `test` passes but `record` exposes timing differences.
|
||||
|
||||
## `screenci record [playwrightArgs...]`
|
||||
|
||||
Records videos with ScreenCI by running local Playwright with `SCREENCI_RECORDING=true`, then uploads results if `SCREENCI_SECRET` is set. If the secret is missing, `record` prompts for login before recording begins.
|
||||
|
||||
By default, if some recording tests fail, ScreenCI still uploads the successful recordings. To opt out, set `record.upload: 'all-or-nothing'` in `screenci.config.ts`.
|
||||
Record final output:
|
||||
|
||||
```bash
|
||||
npx screenci record
|
||||
npx screenci record --project=chromium
|
||||
```
|
||||
|
||||
Options:
|
||||
Behavior:
|
||||
|
||||
- `-c, --config <path>` use a custom config path
|
||||
- `-v, --verbose` show full command output during local development setup
|
||||
- enables recording timing
|
||||
- writes local output into `.screenci/`
|
||||
- uploads successful recordings when `SCREENCI_SECRET` is available
|
||||
|
||||
Restrictions:
|
||||
Relevant options:
|
||||
|
||||
- `--retries` is rejected because ScreenCI forces retries to `0`
|
||||
- `-c, --config <path>`
|
||||
- `-v, --verbose`
|
||||
|
||||
`--workers`, `-j`, and `--fully-parallel` pass through to Playwright unchanged.
|
||||
Important restriction:
|
||||
|
||||
During `screenci record`, ScreenCI now waits for deferred recording finalization at the end of the run and shows a `Finalizing recordings...` spinner before reporting `Recordings finalized`.
|
||||
- `--retries` is not supported because ScreenCI forces retries to `0`
|
||||
|
||||
Troubleshooting:
|
||||
|
||||
- If `screenci test` works but `screenci record` fails, retry with `screenci test --mock-record` to reproduce recording-like timing without starting the real capture pipeline.
|
||||
Use this command in [Record and Publish](/docs/record-and-publish).
|
||||
|
||||
## `screenci info`
|
||||
|
||||
Fetches the current remote project info for the local `projectName` and prints it as 2-space-formatted JSON.
|
||||
|
||||
```bash
|
||||
npx screenci info
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```json
|
||||
{
|
||||
"projectName": "my-project",
|
||||
"videos": [
|
||||
{
|
||||
"name": "Onboarding",
|
||||
"id": "video_123",
|
||||
"isPublic": true,
|
||||
"videoURL": "https://api.screenci.com/public/video_123/en/video",
|
||||
"thumbnailURL": "https://api.screenci.com/public/video_123/en/thumbnail",
|
||||
"subtitlesURL": "https://api.screenci.com/public/video_123/en/subtitle"
|
||||
},
|
||||
{
|
||||
"name": "Settings",
|
||||
"id": "video_456",
|
||||
"isPublic": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- `SCREENCI_SECRET` must be set
|
||||
- the CLI uses your local `projectName` from `screenci.config.ts`
|
||||
Prints remote project data for the current `projectName`, including video IDs and whether public delivery is enabled.
|
||||
|
||||
## `screenci make-public <videoId>`
|
||||
|
||||
Turns on public URLs for a video and publishes the currently selected versions.
|
||||
|
||||
```bash
|
||||
npx screenci make-public video_123
|
||||
```
|
||||
|
||||
Get `<videoId>` from `screenci info`.
|
||||
|
||||
Requirements:
|
||||
|
||||
- `SCREENCI_SECRET` must be set
|
||||
- `<videoId>` must belong to the organisation associated with that secret
|
||||
Enables public delivery for a video. Get the ID from `screenci info`.
|
||||
|
||||
## `screenci make-private <videoId>`
|
||||
|
||||
Disables public URLs for a video and removes the public manifest.
|
||||
|
||||
```bash
|
||||
npx screenci make-private video_123
|
||||
```
|
||||
|
||||
Get `<videoId>` from `screenci info`.
|
||||
Disables public delivery for a video.
|
||||
|
||||
Requirements:
|
||||
|
||||
- `SCREENCI_SECRET` must be set
|
||||
- `<videoId>` must belong to the organisation associated with that secret
|
||||
|
||||
## Shared `--config` option
|
||||
## Shared environment and config behavior
|
||||
|
||||
These commands support `--config <path>`:
|
||||
|
||||
@@ -198,15 +113,10 @@ These commands support `--config <path>`:
|
||||
- `make-public`
|
||||
- `make-private`
|
||||
|
||||
## Environment
|
||||
`SCREENCI_SECRET` is used for:
|
||||
|
||||
### `SCREENCI_SECRET`
|
||||
- uploads
|
||||
- project info
|
||||
- public delivery changes
|
||||
|
||||
Used for authenticated ScreenCI API actions:
|
||||
|
||||
- upload recordings
|
||||
- fetch project info
|
||||
- make videos public
|
||||
- make videos private
|
||||
|
||||
If your config sets `envFile`, the CLI loads it automatically before these commands run.
|
||||
If `envFile` is configured in `screenci.config.ts`, the CLI loads it automatically.
|
||||
|
||||
+69
-91
@@ -1,136 +1,114 @@
|
||||
---
|
||||
title: Configuration
|
||||
description: Configure screenci with defineConfig — set defaults for video quality, FPS, trace recording, and more.
|
||||
---
|
||||
|
||||
# Configuration
|
||||
|
||||
## `screenci.config.ts`
|
||||
`screenci.config.ts` is where project defaults live. Keep it small at first, then add settings when you have a concrete need such as a shared `baseURL`, a different video directory, or a different recording default.
|
||||
|
||||
The config file is where you set global defaults for all your video tests. Create it at the root of your project (or wherever you run Playwright from).
|
||||
|
||||
### Minimal config
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'screenci'
|
||||
|
||||
export default defineConfig({})
|
||||
```
|
||||
|
||||
All options have sensible defaults — this is enough to get started.
|
||||
|
||||
### Full config with all options
|
||||
## Minimal config
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'screenci'
|
||||
|
||||
export default defineConfig({
|
||||
projectName: 'my-product',
|
||||
})
|
||||
```
|
||||
|
||||
That is enough to get started.
|
||||
|
||||
## Common full config
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'screenci'
|
||||
|
||||
export default defineConfig({
|
||||
projectName: 'my-product',
|
||||
envFile: '.env',
|
||||
// Directory containing your *.video.ts files (default: './videos')
|
||||
videoDir: './videos',
|
||||
|
||||
// Control what happens after partial recording failures.
|
||||
// 'passed-only' uploads only successful recordings (default).
|
||||
// 'all-or-nothing' skips all uploads if any recording test fails.
|
||||
record: {
|
||||
upload: 'passed-only', // 'passed-only' | 'all-or-nothing'
|
||||
upload: 'passed-only',
|
||||
},
|
||||
|
||||
// Global timeout per test in ms (Playwright default applies if omitted)
|
||||
timeout: 60_000,
|
||||
|
||||
use: {
|
||||
videoOptions: {
|
||||
resolution: '1080p', // '720p' | '1080p' | '4k' | { width, height }
|
||||
baseURL: 'https://staging.screenci.com',
|
||||
recordOptions: {
|
||||
aspectRatio: '16:9',
|
||||
quality: '1080p',
|
||||
fps: 60, // 24 | 30 | 60
|
||||
quality: 'high', // 'low' | 'medium' | 'high'
|
||||
},
|
||||
|
||||
// Playwright trace recording
|
||||
trace: 'retain-on-failure', // 'on' | 'off' | 'retain-on-failure'
|
||||
|
||||
// Any other Playwright 'use' options work here
|
||||
baseURL: 'https://staging.example.com',
|
||||
trace: 'retain-on-failure',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Custom resolution
|
||||
## Config areas
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'screenci'
|
||||
### Project identity
|
||||
|
||||
export default defineConfig({
|
||||
use: {
|
||||
videoOptions: {
|
||||
resolution: { width: 1440, height: 900 }, // Custom dimensions
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
- `projectName` identifies the project in ScreenCI.
|
||||
- `envFile` points to the file that holds `SCREENCI_SECRET` and related variables.
|
||||
|
||||
## Per-test overrides
|
||||
### File locations
|
||||
|
||||
Options set via `video.use()` override the global config for all subsequent tests in that file:
|
||||
- `videoDir` controls where ScreenCI discovers `*.video.ts` files.
|
||||
|
||||
### Recording behavior
|
||||
|
||||
- `record.upload: 'passed-only'` uploads successful recordings even if another one failed.
|
||||
- `record.upload: 'all-or-nothing'` skips uploads when any recording fails.
|
||||
|
||||
### Rendering defaults
|
||||
|
||||
Set shared `recordOptions` under `use`:
|
||||
|
||||
- `aspectRatio`
|
||||
- `quality`
|
||||
- `fps`
|
||||
|
||||
### Playwright integration
|
||||
|
||||
ScreenCI passes through most normal Playwright config such as:
|
||||
|
||||
- `timeout`
|
||||
- `reporter`
|
||||
- `workers`
|
||||
- `fullyParallel`
|
||||
- `webServer`
|
||||
|
||||
## Per-file overrides
|
||||
|
||||
Use `video.use()` when one file needs different defaults:
|
||||
|
||||
```ts
|
||||
import { video } from 'screenci'
|
||||
|
||||
// Apply 4K + 60fps to all tests in this file
|
||||
video.use({
|
||||
videoOptions: {
|
||||
resolution: '4k',
|
||||
recordOptions: {
|
||||
aspectRatio: '9:16',
|
||||
quality: '1440p',
|
||||
fps: 60,
|
||||
},
|
||||
})
|
||||
|
||||
video('High resolution demo', async ({ page }) => {
|
||||
await page.goto('https://example.com')
|
||||
})
|
||||
|
||||
video('Another 4K test', async ({ page }) => {
|
||||
await page.goto('https://example.com/features')
|
||||
})
|
||||
```
|
||||
|
||||
## Upload policy for `screenci record`
|
||||
|
||||
Use the top-level `record.upload` setting to control whether ScreenCI uploads recordings after a partial Playwright failure.
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'screenci'
|
||||
|
||||
export default defineConfig({
|
||||
record: {
|
||||
upload: 'all-or-nothing',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `passed-only` (default): if some recording tests fail, ScreenCI still uploads the successful recordings
|
||||
- `all-or-nothing`: if any recording test fails, ScreenCI skips all uploads
|
||||
|
||||
`record.upload` only affects the `screenci record` CLI command. It does not change how Playwright runs the tests themselves.
|
||||
|
||||
## Default values
|
||||
|
||||
| Option | Default |
|
||||
| --------------- | --------------------- |
|
||||
| `videoDir` | `'./videos'` |
|
||||
| `record.upload` | `'passed-only'` |
|
||||
| `resolution` | `'1080p'` |
|
||||
| `fps` | `60` |
|
||||
| `quality` | `'high'` |
|
||||
| `trace` | `'retain-on-failure'` |
|
||||
| Option | Default |
|
||||
| --------------------------- | --------------------- |
|
||||
| `videoDir` | `'./videos'` |
|
||||
| `record.upload` | `'passed-only'` |
|
||||
| `recordOptions.aspectRatio` | `'16:9'` |
|
||||
| `recordOptions.quality` | `'1080p'` |
|
||||
| `recordOptions.fps` | `60` |
|
||||
| `trace` | `'retain-on-failure'` |
|
||||
|
||||
## What `defineConfig` enforces
|
||||
## ScreenCI-managed behavior
|
||||
|
||||
These Playwright settings are still managed automatically by ScreenCI:
|
||||
ScreenCI still owns a small set of Playwright behavior:
|
||||
|
||||
| Setting | Value | Reason |
|
||||
| ----------- | -------------- | ------------------------------------------ |
|
||||
| `retries` | `0` | Retrying would overwrite the video |
|
||||
| `testMatch` | `**/*.video.*` | Scopes Playwright to video test files only |
|
||||
|
||||
`workers` and `fullyParallel` now use normal Playwright behavior unless you set them yourself.
|
||||
Everything else should stay problem-driven. Add config only when it helps a real workflow.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Generating Videos
|
||||
|
||||
Use this page when you want help producing a first draft quickly, whether that draft comes from an AI agent working from source code, an AI agent working from a live URL, or Playwright codegen.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [how to generate a video from source code](#ai-generation-based-on-source-code)
|
||||
- [how to generate a video from a live URL](#ai-generation-based-on-url-with-playwright-cli)
|
||||
- [how to use Playwright codegen as a starter tool](#playwright-codegen)
|
||||
|
||||
## AI generation based on source code
|
||||
|
||||
This is usually the best path when the app you want to show already lives in the same repository.
|
||||
|
||||
The agent can inspect routes, labels, components, and state logic directly from source instead of guessing everything from browser behavior alone.
|
||||
|
||||
Good setup:
|
||||
|
||||
- initialize ScreenCI inside the existing repository
|
||||
- accept the ScreenCI skill install during `screenci init`, or run `npx -y skills add screenci/screenci --skill screenci -y` later
|
||||
- open the repository root in the editor, not just `screenci/`
|
||||
- point the agent at the routes and components that define the flow
|
||||
- ask it to write the final `videos/*.video.ts` script directly
|
||||
|
||||
Example prompt for a repo-based workflow:
|
||||
|
||||
```text
|
||||
Create a ScreenCI video for the onboarding flow.
|
||||
|
||||
Inspect @src/routes/onboarding and @src/components/signup-form.tsx.
|
||||
Write the final script in @screenci/videos/onboarding.video.ts.
|
||||
Keep setup inside hide().
|
||||
Add concise narration and iterate with npx screenci test.
|
||||
```
|
||||
|
||||
This is usually faster and more reliable than URL-only exploration because the agent can inspect the real application structure before it writes selectors and narration.
|
||||
|
||||
## AI generation based on URL with `playwright-cli`
|
||||
|
||||
Use this path when you want to create a video from a live site and do not have the application source code in the same repo.
|
||||
|
||||
Here the agent should inspect the real site first, confirm the clean visible path, and only then write the ScreenCI script.
|
||||
|
||||
Good setup:
|
||||
|
||||
- initialize a standalone ScreenCI project
|
||||
- accept the ScreenCI skill install during `screenci init`, or run `npx -y skills add screenci/screenci --skill screenci -y` later
|
||||
- accept the optional `playwright-cli` install during `screenci init`, or run `npx -y skills add screenci/screenci --skill playwright-cli -y` and `npm install @playwright/cli` later
|
||||
- inspect the live site before writing the script
|
||||
- use `playwright-cli` to confirm selectors and visible flow
|
||||
- keep cookie handling and other setup inside `hide()`
|
||||
|
||||
Example prompt for a deployed-site workflow:
|
||||
|
||||
```text
|
||||
Create a ScreenCI video for https://screenci.com that shows how to export a report.
|
||||
|
||||
Inspect the live site first with playwright-cli, then write the final ScreenCI script in videos/export-report.video.ts.
|
||||
Keep setup hidden and use narration only where it improves the walkthrough.
|
||||
```
|
||||
|
||||
This works best when the prompt clearly names the URL to inspect, the output file to create, and what setup should stay hidden from the final video.
|
||||
|
||||
## Playwright codegen
|
||||
|
||||
This is the ScreenCI equivalent of Playwright's [Generating tests](https://playwright.dev/docs/codegen-intro). Use it when you want to inspect a live flow, capture the basic interactions, and harvest strong locators quickly.
|
||||
|
||||
Run it with:
|
||||
|
||||
```bash
|
||||
npx playwright codegen https://screenci.com
|
||||
```
|
||||
|
||||
Codegen opens a browser window and the Playwright Inspector. As you click, type, and navigate, it generates Playwright actions for the flow.
|
||||
|
||||
That is not a final ScreenCI video yet, but it is often the fastest way to get:
|
||||
|
||||
- a first pass at navigation and interactions
|
||||
- role-based and text-based locators
|
||||
- a concrete visible path through the app
|
||||
|
||||
Treat codegen output as raw material, not final authoring.
|
||||
|
||||
When you bring a generated flow into ScreenCI:
|
||||
|
||||
1. use Playwright codegen to inspect the real flow
|
||||
2. move the useful actions into `videos/*.video.ts`
|
||||
3. replace generic test framing with `video()`
|
||||
4. hide setup and authentication with `hide()`
|
||||
5. simplify noisy steps that do not help the viewer
|
||||
6. add narration and pacing only after the interaction flow is correct
|
||||
|
||||
The goal is a clean viewer-facing sequence, not a literal copy of every interaction codegen captured.
|
||||
@@ -0,0 +1,111 @@
|
||||
# Installation
|
||||
|
||||
ScreenCI is a Playwright-based workflow for producing viewer-facing product videos as code. If you already know Playwright, the setup will feel familiar: install, scaffold a project, run the generated script locally, then record the final output when the flow looks right.
|
||||
|
||||
<video controls crossorigin="anonymous" poster="https://api.screenci.com/public/kh7dq5rk3vabtxya45w6zm1fmd871jdx/en/thumbnail" style="max-width:100%; border: 1px solid #ccc;">
|
||||
<source src="https://api.screenci.com/public/kh7dq5rk3vabtxya45w6zm1fmd871jdx/en/video" type="video/mp4" />
|
||||
<track kind="subtitles" src="https://api.screenci.com/public/kh7dq5rk3vabtxya45w6zm1fmd871jdx/en/subtitle" srclang="en" label="English" default />
|
||||
</video>
|
||||
|
||||
<details>
|
||||
<summary>Show source</summary>
|
||||
|
||||
```ts
|
||||
import { autoZoom, createNarration, hide, video, voices } from 'screenci'
|
||||
|
||||
const narration = createNarration({
|
||||
voice: { name: voices.Sophie, style: 'Clear, friendly product walkthrough' },
|
||||
languages: {
|
||||
en: {
|
||||
cues: {
|
||||
intro:
|
||||
'This video shows how to get started with ScreenCI [pronounce: screen see eye].',
|
||||
docs: 'You can find the documentation linked right on the front page.',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
video('How to get started', async ({ page }) => {
|
||||
await hide(async () => {
|
||||
await page.goto('https://screenci.com/')
|
||||
await page.getByText('ScreenCI').first().waitFor()
|
||||
})
|
||||
|
||||
await narration.intro()
|
||||
await narration.docs()
|
||||
|
||||
await autoZoom(async () => {
|
||||
await page.getByRole('link', { name: 'View Documentation' }).click()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [how to install ScreenCI](#install-screenci)
|
||||
- [what `screenci init` creates](#what-gets-created)
|
||||
- [how to test the starter script](#run-the-example)
|
||||
- [how to record the first final video](#record-the-final-result)
|
||||
|
||||
## Install ScreenCI
|
||||
|
||||
Before you start, make sure Node.js and npm are available:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
If either command is missing or too old, install Node.js from the [official Node.js download page](https://nodejs.org/en/download). Node.js 20 or newer is recommended.
|
||||
|
||||
Initialize a new ScreenCI project with:
|
||||
|
||||
```bash
|
||||
npx screenci@latest init
|
||||
```
|
||||
|
||||
`init` writes ScreenCI files into the current directory.
|
||||
|
||||
## What gets created
|
||||
|
||||
The generated project includes the files you need for the first recording:
|
||||
|
||||
```text
|
||||
screenci.config.ts
|
||||
package.json
|
||||
tsconfig.json
|
||||
README.md
|
||||
.gitignore
|
||||
.env
|
||||
videos/
|
||||
example.video.ts
|
||||
.github/workflows/screenci.yaml
|
||||
```
|
||||
|
||||
You do not need to understand every file before the first run. The main ones are:
|
||||
|
||||
- `videos/example.video.ts` for the starter script.
|
||||
- `screenci.config.ts` for project-wide defaults.
|
||||
|
||||
## Run the example
|
||||
|
||||
Run the starter script locally from the same directory:
|
||||
|
||||
```bash
|
||||
npx screenci test
|
||||
```
|
||||
|
||||
This is the fast authoring loop. It runs the `.video.ts` file with ScreenCI's Playwright base but skips the final recording pipeline so you can iterate on selectors, timing, and app state quickly.
|
||||
|
||||
## Record the final result
|
||||
|
||||
When the starter script behaves correctly, record it:
|
||||
|
||||
```bash
|
||||
npx screenci record
|
||||
```
|
||||
|
||||
`record` captures the browser session locally, uploads the raw recording and metadata, and lets ScreenCI render the final viewer-facing output with narration, subtitles, zooms, and overlays.
|
||||
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* Source of truth for ScreenCI docs information architecture.
|
||||
* Slugs are web-facing and stable even if source filenames change.
|
||||
*/
|
||||
|
||||
export const docsManifest = [
|
||||
{
|
||||
source: 'installation.md',
|
||||
slug: 'docs',
|
||||
section: 'Getting Started',
|
||||
order: 1,
|
||||
navLabel: 'Installation',
|
||||
title: 'Installation',
|
||||
description:
|
||||
'Install ScreenCI, initialize a project, run the starter video locally, and record your first final video.',
|
||||
prev: null,
|
||||
next: 'docs/write-video-scripts',
|
||||
},
|
||||
{
|
||||
source: 'write-video-scripts.md',
|
||||
slug: 'docs/write-video-scripts',
|
||||
section: 'Getting Started',
|
||||
order: 2,
|
||||
navLabel: 'Write video scripts',
|
||||
title: 'Write Video Scripts',
|
||||
description:
|
||||
'Author .video.ts files with Playwright-like APIs, ScreenCI narration and camera helpers, and workflow-aware pacing.',
|
||||
prev: 'docs',
|
||||
next: 'docs/generating-videos',
|
||||
},
|
||||
{
|
||||
source: 'generating-videos.md',
|
||||
slug: 'docs/generating-videos',
|
||||
section: 'Getting Started',
|
||||
order: 3,
|
||||
navLabel: 'Generating videos',
|
||||
title: 'Generating Videos',
|
||||
description:
|
||||
'Use Playwright codegen and AI-assisted workflows to generate a first ScreenCI draft, then refine it into a real video script.',
|
||||
prev: 'docs/write-video-scripts',
|
||||
next: 'docs/run-and-debug-videos',
|
||||
},
|
||||
{
|
||||
source: 'run-and-debug-videos.md',
|
||||
slug: 'docs/run-and-debug-videos',
|
||||
section: 'Getting Started',
|
||||
order: 4,
|
||||
navLabel: 'Run and debug videos',
|
||||
title: 'Run and Debug Videos',
|
||||
description:
|
||||
'Use screenci test to iterate locally, inspect failures, and debug pacing, selectors, and visible state before recording.',
|
||||
prev: 'docs/generating-videos',
|
||||
next: 'docs/record-and-publish',
|
||||
},
|
||||
{
|
||||
source: 'record-and-publish.md',
|
||||
slug: 'docs/record-and-publish',
|
||||
section: 'Getting Started',
|
||||
order: 5,
|
||||
navLabel: 'Record and publish',
|
||||
title: 'Record and Publish',
|
||||
description:
|
||||
'Record the final browser session with screenci record, upload the results, and understand how published outputs behave.',
|
||||
prev: 'docs/run-and-debug-videos',
|
||||
next: 'docs/screenci-vs-playwright',
|
||||
},
|
||||
{
|
||||
source: 'screenci-vs-playwright.md',
|
||||
slug: 'docs/screenci-vs-playwright',
|
||||
section: 'Getting Started',
|
||||
order: 6,
|
||||
navLabel: 'ScreenCI vs Playwright',
|
||||
title: 'ScreenCI vs Playwright',
|
||||
description:
|
||||
'See what stays the same from Playwright, what ScreenCI adds for polished video authoring, and when to use each tool.',
|
||||
prev: 'docs/record-and-publish',
|
||||
next: 'docs/ci-setup',
|
||||
},
|
||||
{
|
||||
source: 'ci-setup.md',
|
||||
slug: 'docs/ci-setup',
|
||||
section: 'Getting Started',
|
||||
order: 7,
|
||||
navLabel: 'CI setup',
|
||||
title: 'CI Setup',
|
||||
description:
|
||||
'Understand the generated GitHub Actions workflow, required secrets, and how to keep CI recordings deterministic.',
|
||||
prev: 'docs/screenci-vs-playwright',
|
||||
next: 'docs/guides/narration-and-localization',
|
||||
},
|
||||
{
|
||||
source: 'narration-and-localization.md',
|
||||
slug: 'docs/guides/narration-and-localization',
|
||||
section: 'Guides',
|
||||
order: 1,
|
||||
navLabel: 'Narration and localization',
|
||||
title: 'Narration and Localization',
|
||||
description:
|
||||
'Create spoken cues, overlap narration with visible UI motion, and keep multi-language variants consistent and type-safe.',
|
||||
prev: 'docs/ci-setup',
|
||||
next: 'docs/guides/camera-and-zooming',
|
||||
},
|
||||
{
|
||||
source: 'camera-and-zooming.md',
|
||||
slug: 'docs/guides/camera-and-zooming',
|
||||
section: 'Guides',
|
||||
order: 3,
|
||||
navLabel: 'Camera and zooming',
|
||||
title: 'Camera and Zooming',
|
||||
description:
|
||||
'Choose between autoZoom and manual framing, and use camera direction to guide attention without making the video frantic.',
|
||||
prev: 'docs/guides/narration-and-localization',
|
||||
next: 'docs/guides/assets-and-overlays',
|
||||
},
|
||||
{
|
||||
source: 'assets-and-overlays.md',
|
||||
slug: 'docs/guides/assets-and-overlays',
|
||||
section: 'Guides',
|
||||
order: 4,
|
||||
navLabel: 'Assets and overlays',
|
||||
title: 'Assets and Overlays',
|
||||
description:
|
||||
'Add intro clips, corner logos, transition assets, and timed overlays to ScreenCI recordings.',
|
||||
prev: 'docs/guides/camera-and-zooming',
|
||||
next: 'docs/guides/public-urls-and-embeds',
|
||||
},
|
||||
{
|
||||
source: 'public-urls-and-embeds.md',
|
||||
slug: 'docs/guides/public-urls-and-embeds',
|
||||
section: 'Guides',
|
||||
order: 5,
|
||||
navLabel: 'Public URLs and embeds',
|
||||
title: 'Public URLs and Embeds',
|
||||
description:
|
||||
'Enable public delivery for a video, understand stable language-specific URLs, and embed ScreenCI outputs in other sites.',
|
||||
prev: 'docs/guides/assets-and-overlays',
|
||||
next: 'docs/guides/update-screenci',
|
||||
},
|
||||
{
|
||||
source: 'update-screenci.md',
|
||||
slug: 'docs/guides/update-screenci',
|
||||
section: 'Guides',
|
||||
order: 5,
|
||||
navLabel: 'Update ScreenCI',
|
||||
title: 'Update ScreenCI',
|
||||
description:
|
||||
'Upgrade the screenci package, refresh Playwright when needed, and verify that existing videos still behave as expected.',
|
||||
prev: 'docs/guides/public-urls-and-embeds',
|
||||
next: 'docs/reference/cli',
|
||||
},
|
||||
{
|
||||
source: 'cli.md',
|
||||
slug: 'docs/reference/cli',
|
||||
section: 'Reference',
|
||||
order: 1,
|
||||
navLabel: 'CLI',
|
||||
title: 'CLI',
|
||||
description:
|
||||
'Command reference for screenci init, test, record, project info, and public delivery commands.',
|
||||
prev: 'docs/guides/update-screenci',
|
||||
next: 'docs/reference/configuration',
|
||||
},
|
||||
{
|
||||
source: 'configuration.md',
|
||||
slug: 'docs/reference/configuration',
|
||||
section: 'Reference',
|
||||
order: 2,
|
||||
navLabel: 'Configuration',
|
||||
title: 'Configuration',
|
||||
description:
|
||||
'Configure project identity, file locations, rendering defaults, upload behavior, and Playwright integration in screenci.config.ts.',
|
||||
prev: 'docs/reference/cli',
|
||||
next: 'docs/reference/video-authoring-api-overview',
|
||||
},
|
||||
{
|
||||
source: 'video-authoring-api-overview.md',
|
||||
slug: 'docs/reference/video-authoring-api-overview',
|
||||
section: 'Reference',
|
||||
order: 3,
|
||||
navLabel: 'Video authoring API overview',
|
||||
title: 'Video Authoring API Overview',
|
||||
description:
|
||||
'Human-curated reference for the main ScreenCI authoring primitives, with minimal examples and links into the full typedoc.',
|
||||
prev: 'docs/reference/configuration',
|
||||
next: 'docs/reference/public-delivery-api',
|
||||
},
|
||||
{
|
||||
source: 'public-delivery-api.md',
|
||||
slug: 'docs/reference/public-delivery-api',
|
||||
section: 'Reference',
|
||||
order: 4,
|
||||
navLabel: 'Public delivery API',
|
||||
title: 'Public Delivery API',
|
||||
description:
|
||||
'Technical reference for the unauthenticated endpoints that serve published videos, thumbnails, subtitles, and metadata.',
|
||||
prev: 'docs/reference/video-authoring-api-overview',
|
||||
next: 'docs/reference/api',
|
||||
},
|
||||
]
|
||||
|
||||
export const docsSections = ['Getting Started', 'Guides', 'Reference']
|
||||
|
||||
export function getDocBySlug(slug) {
|
||||
return docsManifest.find((entry) => entry.slug === slug)
|
||||
}
|
||||
|
||||
export function getOutputPathFromSlug(slug) {
|
||||
if (slug === 'docs') return 'index.md'
|
||||
return `${slug.replace(/^docs\//, '')}.md`
|
||||
}
|
||||
|
||||
export function getGeneratedDocsManifest() {
|
||||
return docsManifest.map((entry) => ({
|
||||
...entry,
|
||||
outputPath: getOutputPathFromSlug(entry.slug),
|
||||
}))
|
||||
}
|
||||
|
||||
function slugToPath(slug) {
|
||||
return `/${slug}`
|
||||
}
|
||||
|
||||
function toSidebarItem(entry) {
|
||||
return {
|
||||
label: entry.navLabel,
|
||||
slug: entry.slug,
|
||||
}
|
||||
}
|
||||
|
||||
export function getDocsSidebarConfig(typedocSidebarGroup) {
|
||||
return docsSections.map((section) => {
|
||||
const items = docsManifest
|
||||
.filter((entry) => entry.section === section)
|
||||
.sort((a, b) => a.order - b.order)
|
||||
.map(toSidebarItem)
|
||||
|
||||
if (section === 'Reference' && typedocSidebarGroup) {
|
||||
items.push(typedocSidebarGroup)
|
||||
}
|
||||
|
||||
return {
|
||||
label: section,
|
||||
items,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getPrevNextLinkConfig(slug) {
|
||||
if (!slug) return false
|
||||
if (slug === 'docs/reference/api') {
|
||||
return {
|
||||
label: 'Full API Reference',
|
||||
link: '/docs/reference/api',
|
||||
}
|
||||
}
|
||||
|
||||
const entry = getDocBySlug(slug)
|
||||
if (!entry) {
|
||||
throw new Error(`Unknown docs slug in prev/next config: ${slug}`)
|
||||
}
|
||||
|
||||
return {
|
||||
label: entry.navLabel,
|
||||
link: slugToPath(entry.slug),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
# Narration and Localization
|
||||
|
||||
ScreenCI narration is cue-based. You define named spoken lines, then place those cues into the visible flow where speech should start, overlap, and end. This keeps the script readable and makes multi-language output easier to maintain.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [how to define narration cues](#start-with-one-language)
|
||||
- [how to overlap narration with visible motion](#timing-modes)
|
||||
- [how to localize the same video](#add-localization)
|
||||
- [how to keep translations consistent](#add-localization)
|
||||
|
||||
## Start with one language
|
||||
|
||||
```ts
|
||||
import { createNarration, video, voices } from 'screenci'
|
||||
|
||||
const narration = createNarration({
|
||||
voice: { name: voices.Sophie },
|
||||
languages: {
|
||||
en: {
|
||||
cues: {
|
||||
intro: 'Open the settings page.',
|
||||
save: 'Save the changes when you are ready.',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
video('Settings', async ({ page }) => {
|
||||
await narration.intro()
|
||||
await page.goto('/settings')
|
||||
|
||||
await narration.save.start()
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await narration.save.end()
|
||||
})
|
||||
```
|
||||
|
||||
## Timing modes
|
||||
|
||||
Use the cue methods intentionally:
|
||||
|
||||
- `await narration.key()` waits for the full spoken line to finish.
|
||||
- `await narration.key.start()` begins the cue and keeps the script moving.
|
||||
- `await narration.key.end()` closes the same cue later.
|
||||
|
||||
That is the main tool for overlapping speech with UI motion without losing control of the timeline.
|
||||
|
||||
## Add localization
|
||||
|
||||
Add more languages by keeping the same cue keys:
|
||||
|
||||
```ts
|
||||
const narration = createNarration({
|
||||
voice: { name: voices.Sophie },
|
||||
languages: {
|
||||
en: {
|
||||
cues: {
|
||||
intro: 'Open the settings page.',
|
||||
},
|
||||
},
|
||||
fi: {
|
||||
cues: {
|
||||
intro: 'Avaa asetussivu.',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
TypeScript helps here: every language must provide the same cue structure, which catches drift early.
|
||||
|
||||
## Available voices
|
||||
|
||||
ScreenCI ships with built-in voices that you can use across supported languages through the `voices` export.
|
||||
|
||||
| Name | Gender | Character |
|
||||
| ---------- | ------ | ------------------------------ |
|
||||
| `Adrian` | Male | Clear, direct, and structured |
|
||||
| `Aria` | Female | Soft and calm |
|
||||
| `Ava` | Female | Bright and optimistic |
|
||||
| `Clara` | Female | Cheerful and energetic |
|
||||
| `Daniel` | Male | Clear and educational |
|
||||
| `Elena` | Female | Smooth and composed |
|
||||
| `Emma` | Female | Youthful and playful |
|
||||
| `Ethan` | Male | Warm and approachable |
|
||||
| `Evan` | Male | Casual and relaxed |
|
||||
| `Grace` | Female | Gentle and caring |
|
||||
| `Hassan` | Male | Insightful and reliable |
|
||||
| `Helena` | Female | Mature and authoritative |
|
||||
| `Isabella` | Female | Confident and proactive |
|
||||
| `Julian` | Male | Polished and fluid |
|
||||
| `Layla` | Female | Warm and empathetic |
|
||||
| `Leo` | Male | High-energy and enthusiastic |
|
||||
| `Lily` | Female | Light and effortless |
|
||||
| `Marcus` | Male | Firm and directive |
|
||||
| `Max` | Male | Upbeat and lively |
|
||||
| `Maya` | Female | Relaxed and flexible |
|
||||
| `Miles` | Male | Grounded and assertive |
|
||||
| `Noah` | Male | Soft and intimate |
|
||||
| `Nora` | Female | Strong and decisive |
|
||||
| `Omar` | Male | Detailed and explanatory |
|
||||
| `Ryan` | Male | Dynamic and spirited |
|
||||
| `Sam` | Male | Relaxed and conversational |
|
||||
| `Sophie` | Female | Precise and easy to understand |
|
||||
| `Thomas` | Male | Balanced and steady |
|
||||
| `Victor` | Male | Deep and serious |
|
||||
| `Zoe` | Female | Positive and motivating |
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
import { createNarration, voices } from 'screenci'
|
||||
|
||||
const narration = createNarration({
|
||||
voice: { name: voices.Nora },
|
||||
languages: {
|
||||
en: { cues: { intro: "Let's get started." } },
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Pick the voice for clarity first. A polished walkthrough usually benefits more from consistency than from novelty.
|
||||
|
||||
## Available languages
|
||||
|
||||
The `languages` map accepts supported language codes as keys. ScreenCI's type system keeps all language variants aligned around the same cue keys, which is the main safeguard against translation drift inside the script.
|
||||
|
||||
| Code | Language | Code | Language |
|
||||
| ----- | ----------- | ---- | ------------------ |
|
||||
| `ar` | Arabic | `lt` | Lithuanian |
|
||||
| `az` | Azerbaijani | `lv` | Latvian |
|
||||
| `bg` | Bulgarian | `mk` | Macedonian |
|
||||
| `bn` | Bengali | `ml` | Malayalam |
|
||||
| `ca` | Catalan | `mn` | Mongolian |
|
||||
| `cs` | Czech | `mr` | Marathi |
|
||||
| `da` | Danish | `ms` | Malay |
|
||||
| `de` | German | `my` | Burmese |
|
||||
| `el` | Greek | `nb` | Norwegian Bokmål |
|
||||
| `en` | English | `ne` | Nepali |
|
||||
| `es` | Spanish | `nl` | Dutch |
|
||||
| `et` | Estonian | `pa` | Punjabi |
|
||||
| `eu` | Basque | `pl` | Polish |
|
||||
| `fa` | Persian | `pt` | Portuguese |
|
||||
| `fi` | Finnish | `ro` | Romanian |
|
||||
| `fil` | Filipino | `ru` | Russian |
|
||||
| `fr` | French | `si` | Sinhala |
|
||||
| `gl` | Galician | `sk` | Slovak |
|
||||
| `gu` | Gujarati | `sl` | Slovenian |
|
||||
| `he` | Hebrew | `sq` | Albanian |
|
||||
| `hi` | Hindi | `sr` | Serbian |
|
||||
| `hr` | Croatian | `sv` | Swedish |
|
||||
| `hu` | Hungarian | `sw` | Swahili |
|
||||
| `hy` | Armenian | `ta` | Tamil |
|
||||
| `id` | Indonesian | `te` | Telugu |
|
||||
| `is` | Icelandic | `th` | Thai |
|
||||
| `it` | Italian | `tr` | Turkish |
|
||||
| `ja` | Japanese | `uk` | Ukrainian |
|
||||
| `ka` | Georgian | `ur` | Urdu |
|
||||
| `kn` | Kannada | `vi` | Vietnamese |
|
||||
| `ko` | Korean | `zh` | Chinese (Mandarin) |
|
||||
|
||||
## Region selection
|
||||
|
||||
When a language has multiple regional variants, set `region` explicitly so the synthesis matches the audience more closely.
|
||||
|
||||
```ts
|
||||
import { createNarration, languageRegions, voices } from 'screenci'
|
||||
|
||||
const narration = createNarration({
|
||||
voice: { name: voices.Ava },
|
||||
languages: {
|
||||
en: {
|
||||
region: languageRegions.en.US,
|
||||
cues: { intro: 'Welcome.' },
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Available regions per language:
|
||||
|
||||
| Language | Regions |
|
||||
| -------- | ---------------------------------- |
|
||||
| `ar` | `ar-SA`, `ar-AE`, `ar-EG` |
|
||||
| `bn` | `bn-BD`, `bn-IN` |
|
||||
| `de` | `de-DE`, `de-AT`, `de-CH` |
|
||||
| `en` | `en-US`, `en-GB`, `en-AU`, `en-IN` |
|
||||
| `es` | `es-ES`, `es-MX`, `es-US`, `es-AR` |
|
||||
| `fr` | `fr-FR`, `fr-CA`, `fr-BE`, `fr-CH` |
|
||||
| `nl` | `nl-NL`, `nl-BE` |
|
||||
| `pt` | `pt-BR`, `pt-PT` |
|
||||
| `sw` | `sw-KE`, `sw-TZ` |
|
||||
| `ta` | `ta-IN`, `ta-LK` |
|
||||
| `zh` | `zh-CN`, `zh-TW`, `zh-HK` |
|
||||
|
||||
Languages not listed above have a single region variant and do not require an explicit `region`.
|
||||
|
||||
## Per-language voice overrides
|
||||
|
||||
When a project needs different regional or voice choices, use language-specific settings instead of forcing one voice profile to fit every market.
|
||||
|
||||
Common cases:
|
||||
|
||||
- different English regions
|
||||
- slower pacing for one language
|
||||
- a different voice choice for accessibility or brand reasons
|
||||
|
||||
```ts
|
||||
import { createNarration, languageRegions, voices } from 'screenci'
|
||||
|
||||
const narration = createNarration({
|
||||
voice: { name: voices.Ava },
|
||||
languages: {
|
||||
en: {
|
||||
region: languageRegions.en.US,
|
||||
cues: {
|
||||
intro: 'Welcome to the dashboard.',
|
||||
},
|
||||
},
|
||||
fi: {
|
||||
voice: { name: voices.Nora, pacing: 0.95 },
|
||||
cues: {
|
||||
intro: 'Tervetuloa hallintapaneeliin.',
|
||||
},
|
||||
},
|
||||
de: {
|
||||
voice: {
|
||||
name: voices.Julian,
|
||||
modelType: 'expressive',
|
||||
style: 'A friendly and energetic German speaker.',
|
||||
},
|
||||
cues: {
|
||||
intro: 'Willkommen im Dashboard.',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Use the top-level `voice` as the default and override only the languages that genuinely need a different voice, region, or delivery profile.
|
||||
|
||||
## Model type
|
||||
|
||||
Use `modelType` when you need to choose between consistency and expressiveness.
|
||||
|
||||
- `consistent` is the safer default for docs and product walkthroughs
|
||||
- `expressive` is useful when you want a more natural, less uniform delivery
|
||||
|
||||
In practice:
|
||||
|
||||
- use `consistent` when you want cues to sound more uniform throughout the video
|
||||
- use `expressive` when you want to prompt the spoken style, get a more natural feel, and allow a bit more variance between cues
|
||||
|
||||
```ts
|
||||
import { createNarration, voices } from 'screenci'
|
||||
|
||||
const narration = createNarration({
|
||||
voice: {
|
||||
name: voices.Ava,
|
||||
modelType: 'consistent',
|
||||
pacing: 0.9,
|
||||
},
|
||||
languages: {
|
||||
en: {
|
||||
cues: {
|
||||
intro: 'Welcome.',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
```ts
|
||||
import { createNarration, voices } from 'screenci'
|
||||
|
||||
const narration = createNarration({
|
||||
voice: {
|
||||
name: voices.Ava,
|
||||
modelType: 'expressive',
|
||||
style: 'A warm and natural product guide.',
|
||||
pacing: 'Measured and deliberate, with brief pauses between key points.',
|
||||
},
|
||||
languages: {
|
||||
en: {
|
||||
cues: {
|
||||
intro: 'Welcome.',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
With `consistent`, `pacing` is a numeric speaking rate. With `expressive`, `pacing` becomes a natural-language direction for tempo and rhythm.
|
||||
|
||||
## Style, accent, and pacing
|
||||
|
||||
These controls matter once the base script is already good.
|
||||
|
||||
- `style` describes the speaker's persona or delivery style
|
||||
- `accent` gives the model a more specific regional target
|
||||
- `pacing` controls how quickly lines are spoken
|
||||
|
||||
Examples:
|
||||
|
||||
```ts
|
||||
voice: {
|
||||
name: voices.Nora,
|
||||
style: 'A calm and confident product guide.',
|
||||
accent: 'Received Pronunciation British English',
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
voice: {
|
||||
name: voices.Nora,
|
||||
modelType: 'consistent',
|
||||
pacing: 1.1,
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
const narration = createNarration({
|
||||
voice: {
|
||||
name: voices.Nora,
|
||||
modelType: 'expressive',
|
||||
style:
|
||||
'A calm and confident product guide, speaking clearly and at a measured pace.',
|
||||
accent: 'Received Pronunciation British English',
|
||||
pacing: 'Steady and deliberate, with brief pauses between key points.',
|
||||
},
|
||||
languages: {
|
||||
en: {
|
||||
cues: {
|
||||
intro: "Let's walk through the settings page.",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
With `modelType: 'consistent'`, use `pacing` numbers around `1` and adjust modestly. With `modelType: 'expressive'`, `style`, `accent`, and text pacing notes work better than over-specifying every line.
|
||||
|
||||
Keep these settings modest. Over-directing the voice usually hurts more than it helps.
|
||||
@@ -0,0 +1,154 @@
|
||||
# Public Delivery API
|
||||
|
||||
The public delivery API is the narrow technical surface for published ScreenCI media. It is unauthenticated and meant for stable consumption from websites, docs systems, and other embedding clients.
|
||||
|
||||
## Endpoint families
|
||||
|
||||
All public routes live under:
|
||||
|
||||
```text
|
||||
/public/:id
|
||||
```
|
||||
|
||||
## `GET /public/:id/metadata`
|
||||
|
||||
Returns the list of currently available languages for the published video.
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"languages": ["en", "de", "fi"]
|
||||
}
|
||||
```
|
||||
|
||||
Error responses:
|
||||
|
||||
| Status | Condition |
|
||||
| ------ | ----------------------------------------- |
|
||||
| `404` | The video ID has no public URL configured |
|
||||
|
||||
## `GET /public/:id/:language/video`
|
||||
|
||||
Serves the rendered MP4 for one language variant.
|
||||
|
||||
Useful query parameters:
|
||||
|
||||
- `filename`
|
||||
- `download=1`
|
||||
|
||||
Response headers:
|
||||
|
||||
```text
|
||||
Content-Type: video/mp4
|
||||
Content-Length: <bytes>
|
||||
Accept-Ranges: bytes
|
||||
Access-Control-Allow-Origin: *
|
||||
```
|
||||
|
||||
Error responses:
|
||||
|
||||
| Status | Condition |
|
||||
| ------ | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `404` | No public URL configured, the requested language is not available, or the video file is missing from storage |
|
||||
|
||||
When the language is not available, the response can include the languages that do exist:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Language not available: fr",
|
||||
"availableLanguages": ["en", "de"]
|
||||
}
|
||||
```
|
||||
|
||||
## `GET /public/:id/:language/thumbnail`
|
||||
|
||||
Serves the published thumbnail image for one language variant.
|
||||
|
||||
Useful query parameters:
|
||||
|
||||
- `filename`
|
||||
- `download=1`
|
||||
|
||||
Response headers:
|
||||
|
||||
```text
|
||||
Content-Type: image/jpeg
|
||||
Content-Length: <bytes>
|
||||
Access-Control-Allow-Origin: *
|
||||
```
|
||||
|
||||
Error responses:
|
||||
|
||||
| Status | Condition |
|
||||
| ------ | ---------------------------------------------------------------------------------------------- |
|
||||
| `404` | No public URL configured, the requested language is not available, or the thumbnail is missing |
|
||||
|
||||
## `GET /public/:id/:language/subtitle`
|
||||
|
||||
Serves the WebVTT subtitle file for one language variant when subtitles exist for the selected render.
|
||||
|
||||
Useful query parameters:
|
||||
|
||||
- `filename`
|
||||
- `download=1`
|
||||
|
||||
Response headers:
|
||||
|
||||
```text
|
||||
Content-Type: text/vtt
|
||||
Content-Length: <bytes>
|
||||
Access-Control-Allow-Origin: *
|
||||
```
|
||||
|
||||
Error responses:
|
||||
|
||||
| Status | Condition |
|
||||
| ------ | ------------------------------------------------------------------------------------------------- |
|
||||
| `404` | No rendered version found, no subtitles for this version, or the VTT file is missing from storage |
|
||||
|
||||
## Response behavior
|
||||
|
||||
These routes are designed for embedding:
|
||||
|
||||
- CORS-friendly
|
||||
- stable URLs
|
||||
- language-specific outputs
|
||||
- 404 when the requested language or asset is not publicly available
|
||||
|
||||
All media endpoints are served with `Access-Control-Allow-Origin: *`, so they can be embedded from other origins without credentials.
|
||||
|
||||
## Example embed
|
||||
|
||||
```html
|
||||
<video
|
||||
controls
|
||||
crossorigin="anonymous"
|
||||
poster="https://api.screenci.com/public/YOUR_VIDEO_ID/en/thumbnail"
|
||||
>
|
||||
<source
|
||||
src="https://api.screenci.com/public/YOUR_VIDEO_ID/en/video"
|
||||
type="video/mp4"
|
||||
/>
|
||||
<track
|
||||
kind="subtitles"
|
||||
src="https://api.screenci.com/public/YOUR_VIDEO_ID/en/subtitle"
|
||||
srclang="en"
|
||||
label="English"
|
||||
default
|
||||
/>
|
||||
</video>
|
||||
```
|
||||
|
||||
## Example metadata fetch
|
||||
|
||||
```ts
|
||||
const res = await fetch(
|
||||
'https://api.screenci.com/public/YOUR_VIDEO_ID/metadata'
|
||||
)
|
||||
const { languages } = await res.json()
|
||||
```
|
||||
|
||||
## Relation to the guide
|
||||
|
||||
Use [Public URLs and Embeds](/docs/guides/public-urls-and-embeds) for the workflow, publishing model, and embed examples. Use this page when you want the route patterns and response expectations directly.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Public URLs and Embeds
|
||||
|
||||
Public URLs give a ScreenCI video a stable delivery surface outside the app. Once enabled, each selected language gets its own video, thumbnail, and subtitle URL that you can embed in docs, changelogs, support articles, or product pages.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [what public URLs are](#what-public-urls-look-like)
|
||||
- [how language-specific outputs behave](#latest-vs-selected-output)
|
||||
- [where to use them](#good-use-cases)
|
||||
- [how they relate to the public delivery API](#whats-next)
|
||||
|
||||
## What public URLs look like
|
||||
|
||||
Public delivery starts from a stable base:
|
||||
|
||||
```text
|
||||
https://api.screenci.com/public/<videoId>
|
||||
```
|
||||
|
||||
Then each published language exposes:
|
||||
|
||||
- `/public/<videoId>/<language>/video`
|
||||
- `/public/<videoId>/<language>/thumbnail`
|
||||
- `/public/<videoId>/<language>/subtitle`
|
||||
|
||||
## Latest vs selected output
|
||||
|
||||
Public delivery can either:
|
||||
|
||||
- follow the latest finished render automatically
|
||||
- stay pinned to the selected accepted version for each language
|
||||
|
||||
That lets you choose between automatic freshness and manual editorial control.
|
||||
|
||||
## Typical embed
|
||||
|
||||
```html
|
||||
<video
|
||||
controls
|
||||
crossorigin="anonymous"
|
||||
poster="https://api.screenci.com/public/YOUR_VIDEO_ID/en/thumbnail"
|
||||
>
|
||||
<source
|
||||
src="https://api.screenci.com/public/YOUR_VIDEO_ID/en/video"
|
||||
type="video/mp4"
|
||||
/>
|
||||
<track
|
||||
kind="subtitles"
|
||||
src="https://api.screenci.com/public/YOUR_VIDEO_ID/en/subtitle"
|
||||
srclang="en"
|
||||
label="English"
|
||||
default
|
||||
/>
|
||||
</video>
|
||||
```
|
||||
|
||||
## Good use cases
|
||||
|
||||
- product documentation
|
||||
- changelog posts
|
||||
- support content
|
||||
- release landing pages
|
||||
|
||||
## Privacy and stability
|
||||
|
||||
Only enable public URLs for videos that are meant to be accessible publicly. Once enabled, the route is stable by design, even though the selected underlying render can change later.
|
||||
@@ -0,0 +1,69 @@
|
||||
# Record and Publish
|
||||
|
||||
When the local script is stable, switch from `screenci test` to `screenci record`. This is the ScreenCI-specific step where browser automation turns into a rendered video that can be reviewed, published, and embedded elsewhere.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [when to use `test` and when to use `record`](#record-the-final-browser-session)
|
||||
- [what `npx screenci record` does](#record-the-final-browser-session)
|
||||
- [what gets uploaded and rendered](#what-gets-rendered)
|
||||
- [how local recording relates to CI and public delivery](#local-vs-ci-recording)
|
||||
|
||||
## Record the final browser session
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx screenci record
|
||||
```
|
||||
|
||||
During `record`, ScreenCI:
|
||||
|
||||
1. runs the `.video.ts` file with recording timing enabled
|
||||
2. captures the browser session locally
|
||||
3. writes the raw output into `.screenci/`
|
||||
4. uploads successful recordings when `SCREENCI_SECRET` is available
|
||||
5. finalizes the remote render
|
||||
|
||||
## What gets rendered
|
||||
|
||||
The final output can include:
|
||||
|
||||
- the recorded browser video
|
||||
- subtitles
|
||||
- synthesized narration
|
||||
- zooms and framing changes
|
||||
- overlays and assets
|
||||
|
||||
That is why `record` is the final-authoring step rather than a heavier version of `test`.
|
||||
|
||||
## Local vs CI recording
|
||||
|
||||
Use local recording when you are still polishing the flow. Use CI recording when you want repeatable updates from the repository workflow.
|
||||
|
||||
Both use the same command:
|
||||
|
||||
```bash
|
||||
npx screenci record
|
||||
```
|
||||
|
||||
The difference is where it runs and how `SCREENCI_SECRET` is supplied.
|
||||
|
||||
## Accepted and latest output behavior
|
||||
|
||||
ScreenCI can keep a stable project and video identity while the underlying render changes over time.
|
||||
|
||||
In practice:
|
||||
|
||||
- a newly finished render can become the latest output
|
||||
- public delivery can be configured to follow the latest output automatically
|
||||
- teams can also keep manual control over what stays publicly selected
|
||||
|
||||
See [Public URLs and Embeds](/docs/guides/public-urls-and-embeds) for the public-facing behavior.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Authentication problems: make sure `SCREENCI_SECRET` exists in `.env` or CI secrets.
|
||||
- Upload skipped: `record` can still finish locally if no secret is configured, but it will not upload.
|
||||
- Partial failures: `record.upload` controls whether successful videos still upload when another video fails.
|
||||
- `record` fails but `test` passes: retry with `npx screenci test --mock-record` to reproduce recording-like pacing.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Run and Debug Videos
|
||||
|
||||
`screenci test` is the main iteration command for ScreenCI authors. Use it the way Playwright users use `playwright test`: run the script locally, tighten the flow, then switch to `record` only when the visible sequence is ready.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [how to run video scripts locally](#run-the-local-authoring-loop)
|
||||
- [how to iterate before recording](#test-vs-record)
|
||||
- [how to debug selector and timing issues](#debug-visible-pacing)
|
||||
- [how to inspect failures and artifacts](#artifacts-and-traces)
|
||||
|
||||
## Run the local authoring loop
|
||||
|
||||
Use the default command first:
|
||||
|
||||
```bash
|
||||
npx screenci test
|
||||
```
|
||||
|
||||
ScreenCI resolves `screenci.config.ts`, discovers `*.video.ts` files, and runs them without starting the full local capture and upload flow.
|
||||
|
||||
## `test` vs `record`
|
||||
|
||||
Use `test` when you are still changing the script:
|
||||
|
||||
- it is faster
|
||||
- animated recording-only pacing is skipped by default
|
||||
- there is no final recording upload step
|
||||
|
||||
Use `record` when you want the final timing and rendered output.
|
||||
|
||||
If you need recording-like timing without starting the real recording pipeline, use:
|
||||
|
||||
```bash
|
||||
npx screenci test --mock-record
|
||||
```
|
||||
|
||||
## Run one file or a subset
|
||||
|
||||
`screenci test` forwards normal Playwright arguments, so you can narrow the run while iterating:
|
||||
|
||||
```bash
|
||||
npx screenci test videos/onboarding.video.ts
|
||||
npx screenci test --grep "billing"
|
||||
npx screenci test --ui
|
||||
```
|
||||
|
||||
That makes it practical to work on one video at a time in larger projects.
|
||||
|
||||
## Debug visible pacing
|
||||
|
||||
For ScreenCI scripts, passing tests are not enough. The visible flow also needs to look intentional.
|
||||
|
||||
Check for:
|
||||
|
||||
- clicks happening before the target is clearly visible
|
||||
- loading states still on screen when narration begins
|
||||
- typing or cursor movement that feels rushed
|
||||
- setup steps leaking into the visible recording instead of staying in `hide()`
|
||||
|
||||
Prefer waiting for real UI state:
|
||||
|
||||
```ts
|
||||
await page.getByRole('heading', { name: 'Dashboard' }).waitFor()
|
||||
```
|
||||
|
||||
Use `waitForTimeout()` only when you intentionally want visible breathing room between steps.
|
||||
|
||||
## Common failure modes
|
||||
|
||||
- Bad selectors: switch to role-based or text-stable locators when possible.
|
||||
- Navigation still loading: wait for the element the viewer should actually see, not only the URL change.
|
||||
- Narration timing mismatch: use `await narration.key.start()` and `await narration.key.end()` when speech should overlap with motion.
|
||||
- Hidden setup leaking into the final output: move authentication, cookie handling, and cleanup into `hide()`.
|
||||
|
||||
## Artifacts and traces
|
||||
|
||||
ScreenCI keeps Playwright behavior available for local debugging:
|
||||
|
||||
- use `--ui` when you want the Playwright UI
|
||||
- keep `trace` enabled in config when you need deeper failure investigation
|
||||
- inspect the generated `.screenci/` output after recording runs
|
||||
@@ -0,0 +1,68 @@
|
||||
# ScreenCI vs Playwright
|
||||
|
||||
ScreenCI keeps the Playwright mental model and changes the goal. You are still automating a browser with locators, navigation, and page interactions, but the output is a polished video for viewers rather than an assertion-heavy verification suite.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [what stays the same from Playwright](#what-stays-the-same)
|
||||
- [what ScreenCI adds for video authoring](#what-changes)
|
||||
- [when to use Playwright alone](#when-to-use-playwright-alone)
|
||||
- [how to move a Playwright flow into `.video.ts`](#migrating-a-playwright-flow)
|
||||
|
||||
## What stays the same
|
||||
|
||||
If you already know Playwright, most of the scripting model transfers directly:
|
||||
|
||||
- `page.goto()`
|
||||
- `page.getByRole()`
|
||||
- `locator.click()`
|
||||
- `locator.fill()`
|
||||
- waiting for visible UI state
|
||||
|
||||
ScreenCI builds on Playwright instead of replacing it.
|
||||
|
||||
## What changes
|
||||
|
||||
ScreenCI adds authoring behavior around the same interactions:
|
||||
|
||||
- `video()` instead of `test()`
|
||||
- visible pacing matters
|
||||
- cursor movement and typing are animation-aware
|
||||
- narration, zooming, subtitles, and assets are first-class tools
|
||||
- `hide()` lets you remove setup from the viewer-facing output
|
||||
|
||||
## When to use Playwright alone
|
||||
|
||||
Stay with Playwright when your goal is:
|
||||
|
||||
- product verification
|
||||
- assertions and regression coverage
|
||||
- browser automation with no viewer-facing recording output
|
||||
|
||||
## When to use ScreenCI
|
||||
|
||||
Use ScreenCI when your goal is:
|
||||
|
||||
- a reusable product demo
|
||||
- onboarding or documentation videos
|
||||
- release and support walkthroughs
|
||||
- published videos that should stay current with the app
|
||||
|
||||
## When to combine them
|
||||
|
||||
Many teams keep both in the same repository:
|
||||
|
||||
- Playwright tests for correctness
|
||||
- ScreenCI videos for customer-facing communication
|
||||
|
||||
That is usually the cleanest setup when the same user flows need both validation and presentation.
|
||||
|
||||
## Migrating a Playwright flow
|
||||
|
||||
Start with a test that already demonstrates a user flow, then:
|
||||
|
||||
1. move it into `videos/*.video.ts`
|
||||
2. replace `test()` with `video()`
|
||||
3. remove assertion-heavy steps that are not part of the viewer narrative
|
||||
4. hide setup with `hide()`
|
||||
5. add narration and camera direction only after the visible flow works
|
||||
@@ -0,0 +1,107 @@
|
||||
# Update ScreenCI
|
||||
|
||||
Updating ScreenCI should be a small maintenance task, not a reinvention of the project. Upgrade the package, refresh Playwright when needed, and verify that your existing scripts still look right.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [how to update ScreenCI](#update-the-package)
|
||||
- [when to refresh Playwright too](#refresh-dependencies)
|
||||
- [what to verify after updating](#verify-the-project)
|
||||
- [how to keep the upgrade routine lightweight](#verify-the-project)
|
||||
|
||||
## Update the package
|
||||
|
||||
In a generated ScreenCI project:
|
||||
|
||||
```bash
|
||||
npm install screenci@latest @playwright/test@latest
|
||||
```
|
||||
|
||||
This is the default upgrade path. ScreenCI uses `@playwright/test` underneath, so keeping both packages current together is usually the cleanest choice.
|
||||
|
||||
If your project manages Playwright separately and you intentionally want to hold it back for a while, you can update only ScreenCI first, but that should be the exception rather than the normal path.
|
||||
|
||||
## Refresh dependencies
|
||||
|
||||
After a version bump:
|
||||
|
||||
- let the lockfile update
|
||||
- reinstall if your package manager requires it
|
||||
- refresh Chromium if Playwright asks for it
|
||||
- refresh any local AI authoring skills if you installed them earlier
|
||||
|
||||
Common follow-up commands:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npx playwright install chromium
|
||||
```
|
||||
|
||||
You do not need to reinstall browsers on every upgrade, only when Playwright reports that the local browser build is missing or outdated.
|
||||
|
||||
If you installed the ScreenCI skill during `screenci init`, refresh it after updating the package:
|
||||
|
||||
```bash
|
||||
npx -y skills add screenci/screenci --skill screenci -y
|
||||
```
|
||||
|
||||
If you also installed the optional `playwright-cli` skill for URL-based inspection or AI-assisted authoring, refresh both:
|
||||
|
||||
```bash
|
||||
npx -y skills add screenci/screenci --skill screenci --skill playwright-cli -y
|
||||
```
|
||||
|
||||
## When to update Playwright too
|
||||
|
||||
Update Playwright along with ScreenCI when:
|
||||
|
||||
- ScreenCI depends on newer Playwright behavior
|
||||
- you want the latest locator or browser fixes
|
||||
- the generated project template has moved to a newer Playwright baseline
|
||||
|
||||
If the project already pins Playwright separately, avoid leaving ScreenCI updated while Playwright stays far behind for no reason.
|
||||
|
||||
In practice, unless you have a deliberate compatibility reason not to, use the combined install command and keep them moving together.
|
||||
|
||||
## Check the generated project shape
|
||||
|
||||
After an upgrade, confirm that the project still matches current expectations:
|
||||
|
||||
- `screenci.config.ts` still uses the expected option names
|
||||
- `videos/*.video.ts` still compile without deprecated imports
|
||||
- the GitHub Actions workflow still matches your package manager and lockfile
|
||||
- local `.env` loading still behaves the same way
|
||||
|
||||
This matters most when the project was initialized a while ago and has not been touched since.
|
||||
|
||||
## Verify the project
|
||||
|
||||
Run the normal checks:
|
||||
|
||||
```bash
|
||||
npx screenci test
|
||||
```
|
||||
|
||||
Focus on behavioral verification, not only compilation:
|
||||
|
||||
- selector stability
|
||||
- narration timing
|
||||
- camera motion
|
||||
- CI workflow still using the expected dependency cache inputs
|
||||
|
||||
If you publish videos publicly, also verify:
|
||||
|
||||
- public URLs still resolve to the expected outputs
|
||||
- subtitles still align with the rendered narration
|
||||
- accepted/latest behavior still matches your release workflow
|
||||
|
||||
## Refresh AI setup if needed
|
||||
|
||||
If your team uses AI-assisted authoring, check that the tooling still matches the current package version and docs workflow:
|
||||
|
||||
- reinstall skills or local helpers after package upgrades when they are part of your normal setup
|
||||
- update Playwright-based inspection tooling when browser automation starts failing
|
||||
- keep prompt instructions aligned with the current docs structure
|
||||
- make sure generated scripts still target `videos/*.video.ts` and use the current ScreenCI helpers
|
||||
|
||||
This should be the last step, not the first one. The core upgrade check here is still `npx screenci test`.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Video Authoring API Overview
|
||||
|
||||
This page is the curated bridge into the full typedoc. Use it when you want a quick reminder of what the main ScreenCI authoring primitives are for, then jump into the generated API reference for the exhaustive signatures.
|
||||
|
||||
## `video()`
|
||||
|
||||
Declare one recorded video:
|
||||
|
||||
```ts
|
||||
import { video } from 'screenci'
|
||||
|
||||
video('Checkout flow', async ({ page }) => {
|
||||
await page.goto('/checkout')
|
||||
})
|
||||
```
|
||||
|
||||
Full reference: [/docs/reference/api/variables/video](/docs/reference/api/variables/video)
|
||||
|
||||
## `createNarration()`
|
||||
|
||||
Define typed narration cues:
|
||||
|
||||
```ts
|
||||
import { createNarration, voices } from 'screenci'
|
||||
|
||||
const narration = createNarration({
|
||||
voice: { name: voices.Sophie },
|
||||
languages: {
|
||||
en: { cues: { intro: 'Open the dashboard.' } },
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Use it with [Narration and Localization](/docs/guides/narration-and-localization).
|
||||
|
||||
## `hide()`
|
||||
|
||||
Cut setup or cleanup out of the visible recording:
|
||||
|
||||
```ts
|
||||
await hide(async () => {
|
||||
await page.goto('/login')
|
||||
})
|
||||
```
|
||||
|
||||
Full reference: [/docs/reference/api/functions/hide](/docs/reference/api/functions/hide)
|
||||
|
||||
## `autoZoom()`
|
||||
|
||||
Let ScreenCI follow a focused interaction block:
|
||||
|
||||
```ts
|
||||
await autoZoom(async () => {
|
||||
await page.getByLabel('Name').fill('Jane Doe')
|
||||
})
|
||||
```
|
||||
|
||||
Full reference: [/docs/reference/api/functions/autozoom](/docs/reference/api/functions/autozoom)
|
||||
|
||||
## `zoomTo()` and `resetZoom()`
|
||||
|
||||
Take manual control of framing:
|
||||
|
||||
```ts
|
||||
await zoomTo(page.getByText('Net revenue'))
|
||||
await resetZoom()
|
||||
```
|
||||
|
||||
Use these when the camera should follow your direction instead of the next interaction.
|
||||
|
||||
## `createAssets()`
|
||||
|
||||
Add timed media overlays:
|
||||
|
||||
```ts
|
||||
const assets = createAssets({
|
||||
intro: { path: './assets/intro.mp4', audio: 1, fullScreen: true },
|
||||
})
|
||||
```
|
||||
|
||||
Use it with [Assets and Overlays](/docs/guides/assets-and-overlays).
|
||||
|
||||
## Voices and helpers
|
||||
|
||||
ScreenCI also exports:
|
||||
|
||||
- `voices`
|
||||
- `modelTypes`
|
||||
- `languageRegions`
|
||||
|
||||
These help you define narration behavior without falling back to raw strings everywhere.
|
||||
|
||||
## Full API reference
|
||||
|
||||
For the exhaustive generated reference, go to [Full API Reference](/docs/reference/api).
|
||||
@@ -0,0 +1,100 @@
|
||||
# Write Video Scripts
|
||||
|
||||
This page follows the same teaching job as Playwright's [Writing tests](https://playwright.dev/docs/writing-tests), but for ScreenCI videos instead of assertion-heavy tests. Video scripts are Playwright-like files with ScreenCI-specific behavior around pacing, narration, and camera direction.
|
||||
|
||||
#### You will learn
|
||||
|
||||
- [how to structure a `.video.ts` file](#anatomy-of-a-video-script)
|
||||
- [how to navigate and interact](#author-with-locators)
|
||||
- [how ScreenCI behavior differs from plain Playwright](#what-screenci-changes)
|
||||
- [how to control visible pacing](#control-pacing)
|
||||
|
||||
## Minimal example
|
||||
|
||||
```ts
|
||||
import { hide, video } from 'screenci'
|
||||
|
||||
video('Create a project', async ({ page }) => {
|
||||
await hide(async () => {
|
||||
await page.goto('https://screenci.com/dashboard')
|
||||
})
|
||||
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await page.getByLabel('Name').fill('Release notes')
|
||||
await page.getByRole('button', { name: 'Create' }).click()
|
||||
})
|
||||
```
|
||||
|
||||
## Anatomy of a video script
|
||||
|
||||
Most ScreenCI files have the same building blocks:
|
||||
|
||||
- imports from `screenci`
|
||||
- one or more `video()` calls
|
||||
- Playwright-style `page` interactions
|
||||
- a hidden setup block when the visible recording should start from a ready state
|
||||
|
||||
## Author with locators
|
||||
|
||||
Prefer the same locator style you would use in a reliable Playwright test:
|
||||
|
||||
```ts
|
||||
await page.getByRole('button', { name: 'Invite teammate' }).click()
|
||||
await page.getByLabel('Email').fill('jane@screenci.com')
|
||||
```
|
||||
|
||||
Role-based and label-based locators usually age better than CSS selectors copied from transient DOM structure.
|
||||
|
||||
## What ScreenCI changes
|
||||
|
||||
Inside `video()`, ScreenCI wraps the normal Playwright page and locators so visible interactions look like a recording instead of a robotic test:
|
||||
|
||||
- cursor moves are animated
|
||||
- typing is visible
|
||||
- helper APIs such as `hide()`, `autoZoom()`, `zoomTo()`, and `createNarration()` integrate with the recording timeline
|
||||
|
||||
Most standard Playwright APIs still work as expected.
|
||||
|
||||
## Setup vs visible sequence
|
||||
|
||||
Keep setup out of the final video when it does not help the viewer:
|
||||
|
||||
```ts
|
||||
await hide(async () => {
|
||||
await page.goto('/login')
|
||||
await page.getByLabel('Email').fill(process.env.DEMO_EMAIL!)
|
||||
await page.getByLabel('Password').fill(process.env.DEMO_PASSWORD!)
|
||||
await page.getByRole('button', { name: 'Sign in' }).click()
|
||||
})
|
||||
```
|
||||
|
||||
Then let the visible sequence begin where the viewer would want to start watching.
|
||||
|
||||
## Control pacing
|
||||
|
||||
Visible pacing is part of authoring quality.
|
||||
|
||||
Prefer:
|
||||
|
||||
- waiting for the UI the viewer should actually see
|
||||
- narration overlap when speech and motion should happen together
|
||||
- short explicit pauses only when the viewer needs breathing room
|
||||
|
||||
Use `waitForTimeout()` deliberately, not as a substitute for state-based synchronization.
|
||||
|
||||
## Multiple videos per project
|
||||
|
||||
Create more than one `.video.ts` file when the flows are distinct:
|
||||
|
||||
```text
|
||||
videos/
|
||||
onboarding.video.ts
|
||||
admin-billing.video.ts
|
||||
changelog.video.ts
|
||||
```
|
||||
|
||||
That keeps each video focused and makes iteration easier.
|
||||
|
||||
## Relation to Playwright APIs
|
||||
|
||||
Use ScreenCI for the viewer-facing layer and Playwright for the browser automation layer underneath. When you need a deeper method, check the standard Playwright docs first, then add ScreenCI helpers only where the recording needs them.
|
||||
@@ -12,15 +12,14 @@ npx screenci init "My Project" --verbose
|
||||
|
||||
## What It Creates
|
||||
|
||||
`screenci init` creates a ready-to-run project containing:
|
||||
`screenci init` creates a ready-to-run project in the current directory containing:
|
||||
|
||||
```text
|
||||
screenci/
|
||||
screenci.config.ts
|
||||
videos/
|
||||
example.video.ts
|
||||
package.json
|
||||
.gitignore
|
||||
screenci.config.ts
|
||||
videos/
|
||||
example.video.ts
|
||||
package.json
|
||||
.gitignore
|
||||
.github/workflows/screenci.yaml (optional)
|
||||
```
|
||||
|
||||
@@ -30,18 +29,17 @@ screenci/
|
||||
|
||||
## Notes
|
||||
|
||||
- If no name is passed, the command prompts for one.
|
||||
- The name is used as the ScreenCI project display name. Files are always created in `screenci/`.
|
||||
- If no name is passed, the command prompts for one and defaults to the current directory name.
|
||||
- The name is used as the ScreenCI project display name. Files are always created in the current directory.
|
||||
- `--yes` accepts the defaults.
|
||||
- `--agent <name>` is passed to the selected skills install command.
|
||||
- `--verbose` shows more setup output.
|
||||
- After scaffolding, run `npm install`.
|
||||
- `record` uses local Playwright.
|
||||
|
||||
## Typical Flow
|
||||
|
||||
```bash
|
||||
npx screenci init "My Project"
|
||||
cd screenci
|
||||
npm install
|
||||
npx screenci test # verify the video works
|
||||
npx screenci record # capture the final recording
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user