screenci 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +227 -0
  2. package/cli.ts +1111 -0
  3. package/dist/cli.d.ts +4 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +896 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/e2e/instrument.e2e.d.ts +2 -0
  8. package/dist/e2e/instrument.e2e.d.ts.map +1 -0
  9. package/dist/e2e/instrument.e2e.js +661 -0
  10. package/dist/e2e/instrument.e2e.js.map +1 -0
  11. package/dist/index.d.ts +18 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +15 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/playwright.config.d.ts +3 -0
  16. package/dist/playwright.config.d.ts.map +1 -0
  17. package/dist/playwright.config.js +21 -0
  18. package/dist/playwright.config.js.map +1 -0
  19. package/dist/reporter.d.ts +9 -0
  20. package/dist/reporter.d.ts.map +1 -0
  21. package/dist/reporter.js +49 -0
  22. package/dist/reporter.js.map +1 -0
  23. package/dist/src/asset.d.ts +90 -0
  24. package/dist/src/asset.d.ts.map +1 -0
  25. package/dist/src/asset.js +74 -0
  26. package/dist/src/asset.js.map +1 -0
  27. package/dist/src/autoZoom.d.ts +40 -0
  28. package/dist/src/autoZoom.d.ts.map +1 -0
  29. package/dist/src/autoZoom.js +88 -0
  30. package/dist/src/autoZoom.js.map +1 -0
  31. package/dist/src/caption.d.ts +152 -0
  32. package/dist/src/caption.d.ts.map +1 -0
  33. package/dist/src/caption.js +240 -0
  34. package/dist/src/caption.js.map +1 -0
  35. package/dist/src/caption.test-d.d.ts +2 -0
  36. package/dist/src/caption.test-d.d.ts.map +1 -0
  37. package/dist/src/caption.test-d.js +50 -0
  38. package/dist/src/caption.test-d.js.map +1 -0
  39. package/dist/src/config.d.ts +42 -0
  40. package/dist/src/config.d.ts.map +1 -0
  41. package/dist/src/config.js +147 -0
  42. package/dist/src/config.js.map +1 -0
  43. package/dist/src/defaults.d.ts +63 -0
  44. package/dist/src/defaults.d.ts.map +1 -0
  45. package/dist/src/defaults.js +66 -0
  46. package/dist/src/defaults.js.map +1 -0
  47. package/dist/src/dimensions.d.ts +29 -0
  48. package/dist/src/dimensions.d.ts.map +1 -0
  49. package/dist/src/dimensions.js +47 -0
  50. package/dist/src/dimensions.js.map +1 -0
  51. package/dist/src/events.d.ts +203 -0
  52. package/dist/src/events.d.ts.map +1 -0
  53. package/dist/src/events.js +227 -0
  54. package/dist/src/events.js.map +1 -0
  55. package/dist/src/hide.d.ts +27 -0
  56. package/dist/src/hide.d.ts.map +1 -0
  57. package/dist/src/hide.js +49 -0
  58. package/dist/src/hide.js.map +1 -0
  59. package/dist/src/instrument.d.ts +15 -0
  60. package/dist/src/instrument.d.ts.map +1 -0
  61. package/dist/src/instrument.js +910 -0
  62. package/dist/src/instrument.js.map +1 -0
  63. package/dist/src/logger.d.ts +7 -0
  64. package/dist/src/logger.d.ts.map +1 -0
  65. package/dist/src/logger.js +13 -0
  66. package/dist/src/logger.js.map +1 -0
  67. package/dist/src/reporter.d.ts +9 -0
  68. package/dist/src/reporter.d.ts.map +1 -0
  69. package/dist/src/reporter.js +50 -0
  70. package/dist/src/reporter.js.map +1 -0
  71. package/dist/src/sanitize.d.ts +5 -0
  72. package/dist/src/sanitize.d.ts.map +1 -0
  73. package/dist/src/sanitize.js +11 -0
  74. package/dist/src/sanitize.js.map +1 -0
  75. package/dist/src/types.d.ts +544 -0
  76. package/dist/src/types.d.ts.map +1 -0
  77. package/dist/src/types.js +2 -0
  78. package/dist/src/types.js.map +1 -0
  79. package/dist/src/video.d.ts +138 -0
  80. package/dist/src/video.d.ts.map +1 -0
  81. package/dist/src/video.js +415 -0
  82. package/dist/src/video.js.map +1 -0
  83. package/dist/src/voices.d.ts +60 -0
  84. package/dist/src/voices.d.ts.map +1 -0
  85. package/dist/src/voices.js +42 -0
  86. package/dist/src/voices.js.map +1 -0
  87. package/dist/src/xvfb.d.ts +22 -0
  88. package/dist/src/xvfb.d.ts.map +1 -0
  89. package/dist/src/xvfb.js +87 -0
  90. package/dist/src/xvfb.js.map +1 -0
  91. package/dist/tsconfig.tsbuildinfo +1 -0
  92. package/package.json +45 -4
  93. package/bin/index.js +0 -3
  94. package/index.js +0 -1
@@ -0,0 +1,138 @@
1
+ import type { TestType, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestDetails, TestInfo } from '@playwright/test';
2
+ import type { RecordOptions, RenderOptions, ScreenCIPage } from './types.js';
3
+ export { getDimensions } from './dimensions.js';
4
+ type VideoFixtureOptions = {
5
+ recordOptions: RecordOptions;
6
+ renderOptions: RenderOptions | undefined;
7
+ _xvfbSetup: void;
8
+ };
9
+ type VideoType = TestType<PlaywrightTestArgs & PlaywrightTestOptions & VideoFixtureOptions & PlaywrightWorkerArgs & PlaywrightWorkerOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
10
+ type VideoArgs = Omit<PlaywrightTestArgs, 'page'> & {
11
+ page: ScreenCIPage;
12
+ } & PlaywrightTestOptions & VideoFixtureOptions & PlaywrightWorkerArgs & PlaywrightWorkerOptions;
13
+ type VideoBody = (args: VideoArgs, testInfo: TestInfo) => void | Promise<void>;
14
+ /** Conditional overloads shared by skip / fixme / fail */
15
+ type ConditionalOverloads = ((condition?: boolean, description?: string) => void) & ((condition?: boolean, callback?: () => string) => void);
16
+ interface VideoCallSignatures {
17
+ /**
18
+ * Declares a ScreenCI video recording test.
19
+ *
20
+ * Tests automatically record browser interactions as video. The viewport is
21
+ * configured based on `recordOptions.aspectRatio` and `recordOptions.quality`.
22
+ *
23
+ * @param title - Test title (used as the video filename)
24
+ * @param body - Test body containing page interactions to record
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { video } from 'screenci'
29
+ *
30
+ * video('Product demo', async ({ page }) => {
31
+ * await page.goto('https://example.com')
32
+ * await page.click('text=Get Started')
33
+ * // Video recorded at 16:9 1080p, 30fps (defaults)
34
+ * })
35
+ * ```
36
+ *
37
+ * @example
38
+ * Configure video options:
39
+ * ```ts
40
+ * video.use({ recordOptions: { aspectRatio: '16:9', quality: '2160p', fps: 60 } })
41
+ *
42
+ * video('4K demo', async ({ page }) => {
43
+ * await page.goto('https://example.com')
44
+ * })
45
+ * ```
46
+ */
47
+ (title: string, body: VideoBody): void;
48
+ /**
49
+ * Declares a ScreenCI video recording test with additional details.
50
+ *
51
+ * Tests automatically record browser interactions as video. The viewport is
52
+ * configured based on `recordOptions.aspectRatio` and `recordOptions.quality`.
53
+ *
54
+ * @param title - Test title (used as the video filename)
55
+ * @param details - Additional test configuration (tags, annotations, etc.)
56
+ * @param body - Test body containing page interactions to record
57
+ *
58
+ * @example
59
+ * Using tags:
60
+ * ```ts
61
+ * import { video } from 'screenci'
62
+ *
63
+ * video('Checkout flow', {
64
+ * tag: '@critical',
65
+ * }, async ({ page }) => {
66
+ * await page.goto('https://example.com/checkout')
67
+ * })
68
+ * ```
69
+ *
70
+ * @example
71
+ * Using annotations:
72
+ * ```ts
73
+ * video('Sign up', {
74
+ * annotation: { type: 'issue', description: 'https://github.com/...' },
75
+ * }, async ({ page }) => {
76
+ * await page.goto('https://example.com/signup')
77
+ * })
78
+ * ```
79
+ */
80
+ (title: string, details: TestDetails, body: VideoBody): void;
81
+ }
82
+ /**
83
+ * Recursive interface so `.only`, `.skip`, `.fixme`, `.fail`, and `.slow`
84
+ * all surface `page: ScreenCIPage` instead of the raw Playwright `page: Page`.
85
+ *
86
+ * Properties that don't receive per-test fixture args (`describe`, `beforeAll`,
87
+ * `afterAll`, `use`, `extend`, `step`, `info`, `expect`, `setTimeout`) are
88
+ * forwarded from `VideoType` unchanged.
89
+ */
90
+ interface Video extends VideoCallSignatures {
91
+ /** Run only this test. */
92
+ only: Video;
93
+ /** Skip this test, with optional conditional overloads. */
94
+ skip: Video & ConditionalOverloads;
95
+ /** Mark this test as fixme, with optional conditional overloads. */
96
+ fixme: Video & ConditionalOverloads;
97
+ /** Mark this test as expected to fail, with optional conditional overloads. */
98
+ fail: Video & ConditionalOverloads;
99
+ /** Mark this test as slow, with optional conditional overload. */
100
+ slow: Video & ((condition?: boolean, description?: string) => void);
101
+ /** Run a hook before each test in the current suite. */
102
+ beforeEach(inner: (args: VideoArgs, testInfo: TestInfo) => Promise<void> | void): void;
103
+ beforeEach(title: string, inner: (args: VideoArgs, testInfo: TestInfo) => Promise<void> | void): void;
104
+ /** Run a hook after each test in the current suite. */
105
+ afterEach(inner: (args: VideoArgs, testInfo: TestInfo) => Promise<void> | void): void;
106
+ afterEach(title: string, inner: (args: VideoArgs, testInfo: TestInfo) => Promise<void> | void): void;
107
+ describe: VideoType['describe'];
108
+ beforeAll: VideoType['beforeAll'];
109
+ afterAll: VideoType['afterAll'];
110
+ use: VideoType['use'];
111
+ extend: VideoType['extend'];
112
+ step: VideoType['step'];
113
+ info: VideoType['info'];
114
+ expect: VideoType['expect'];
115
+ setTimeout: VideoType['setTimeout'];
116
+ }
117
+ /**
118
+ * ScreenCI video recording test fixture.
119
+ *
120
+ * Extended Playwright test that automatically records browser interactions as video.
121
+ * Configure recording options globally with `video.use()` or in your config file.
122
+ *
123
+ * @example
124
+ * Basic usage:
125
+ * ```ts
126
+ * import { video, caption } from 'screenci'
127
+ *
128
+ * video('Tutorial', async ({ page }) => {
129
+ * await page.goto('https://example.com')
130
+ * caption('User navigates to homepage')
131
+ *
132
+ * await page.click('text=Sign up')
133
+ * caption('Clicks sign up button')
134
+ * })
135
+ * ```
136
+ */
137
+ export declare const video: Video;
138
+ //# sourceMappingURL=video.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video.d.ts","sourceRoot":"","sources":["../../src/video.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,415 @@
1
+ import { test as base } from '@playwright/test';
2
+ import { mkdir, rm } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import { spawn } from 'child_process';
5
+ import { sanitizeVideoName } from './sanitize.js';
6
+ export { getDimensions } from './dimensions.js';
7
+ import { getDimensions } from './dimensions.js';
8
+ import { isHeadless, startXvfb } from './xvfb.js';
9
+ import { setActiveCaptionRecorder, resetCaptionChain } from './caption.js';
10
+ import { setActiveHideRecorder } from './hide.js';
11
+ import { setActiveAutoZoomRecorder } from './autoZoom.js';
12
+ import { setActiveAssetRecorder } from './asset.js';
13
+ import { DEFAULT_VIDEO_OPTIONS, DEFAULT_ASPECT_RATIO, DEFAULT_QUALITY, DEFAULT_FPS, } from './defaults.js';
14
+ import { EventRecorder } from './events.js';
15
+ import { setActiveClickRecorder, instrumentBrowser, instrumentContext, } from './instrument.js';
16
+ import { logger } from './logger.js';
17
+ async function setupMouseTracking(page, recorder) {
18
+ /*
19
+ await page.exposeFunction(
20
+ '__screenci_recordMouseMove',
21
+ (x: number, y: number) => {
22
+ recorder.addMouseMove(x, y)
23
+ }
24
+ )
25
+
26
+ await page.addInitScript(() => {
27
+ // Throttle mouse moves to ~60fps (16ms) to keep data.json size manageable
28
+ let lastMouseMove = 0
29
+ document.addEventListener(
30
+ 'mousemove',
31
+ (e) => {
32
+ const now = Date.now()
33
+ if (now - lastMouseMove >= 16) {
34
+ lastMouseMove = now
35
+ ;(
36
+ window as Window & {
37
+ __screenci_recordMouseMove?: (x: number, y: number) => void
38
+ }
39
+ ).__screenci_recordMouseMove?.(e.clientX, e.clientY)
40
+ }
41
+ },
42
+ { capture: true }
43
+ )
44
+ })
45
+ */
46
+ }
47
+ /**
48
+ * Get the path to the system ffmpeg binary
49
+ */
50
+ function getSystemFfmpegPath() {
51
+ // Use system ffmpeg from standard locations
52
+ return '/usr/bin/ffmpeg';
53
+ }
54
+ /**
55
+ * Get CRF value (encoding quality) for a given quality preset.
56
+ *
57
+ * Higher resolution presets use a lower CRF (better quality) because the
58
+ * larger file size budget is expected at those resolutions.
59
+ * - `'720p'` → CRF 21 (medium – balanced size/quality for HD)
60
+ * - `'1080p'` → CRF 16 (high – best quality for Full HD)
61
+ * - `'1440p'` → CRF 16 (high – best quality for Quad HD)
62
+ * - `'2160p'` → CRF 16 (high – best quality for UHD)
63
+ */
64
+ function getCrfForQuality(quality) {
65
+ switch (quality) {
66
+ case '720p':
67
+ return 21;
68
+ case '1080p':
69
+ return 16;
70
+ case '1440p':
71
+ return 16;
72
+ case '2160p':
73
+ return 16;
74
+ }
75
+ }
76
+ /**
77
+ * Start ffmpeg screen recording
78
+ */
79
+ function startScreenRecording(outputPath, fps, quality, aspectRatio) {
80
+ const { width, height } = getDimensions(aspectRatio, quality);
81
+ const crf = getCrfForQuality(quality);
82
+ const display = process.env.DISPLAY || ':0';
83
+ const ffmpegArgs = [
84
+ '-f',
85
+ 'x11grab',
86
+ '-draw_mouse',
87
+ '0',
88
+ '-video_size',
89
+ `${width}x${height}`,
90
+ '-framerate',
91
+ `${fps}`,
92
+ '-i',
93
+ `${display}+0,0`,
94
+ '-c:v',
95
+ 'libx264',
96
+ '-tune',
97
+ 'zerolatency',
98
+ '-preset',
99
+ 'fast',
100
+ '-crf',
101
+ `${crf}`,
102
+ '-pix_fmt',
103
+ 'yuv444p',
104
+ '-movflags',
105
+ 'frag_keyframe+empty_moov',
106
+ '-y',
107
+ outputPath,
108
+ ];
109
+ const ffmpegPath = getSystemFfmpegPath();
110
+ const ffmpeg = spawn(ffmpegPath, ffmpegArgs);
111
+ activeFFmpegProcesses.add(ffmpeg);
112
+ ffmpeg.on('close', () => activeFFmpegProcesses.delete(ffmpeg));
113
+ let hasStarted = false;
114
+ let hasErrored = false;
115
+ const startedPromise = new Promise((resolve) => {
116
+ const timeout = setTimeout(() => {
117
+ if (!hasStarted && !hasErrored) {
118
+ hasStarted = true;
119
+ resolve(true);
120
+ }
121
+ }, 1000);
122
+ ffmpeg.stderr.on('data', (data) => {
123
+ const output = data.toString();
124
+ // ffmpeg outputs info to stderr, check for actual errors
125
+ if (output.includes('frame=') || output.includes('fps=')) {
126
+ if (!hasStarted) {
127
+ hasStarted = true;
128
+ clearTimeout(timeout);
129
+ resolve(true);
130
+ }
131
+ }
132
+ else if (output.includes('error') ||
133
+ output.includes('Error') ||
134
+ output.includes('Invalid')) {
135
+ if (!hasErrored) {
136
+ hasErrored = true;
137
+ clearTimeout(timeout);
138
+ logger.error('FFmpeg error:', output);
139
+ resolve(false);
140
+ }
141
+ }
142
+ });
143
+ ffmpeg.on('error', (err) => {
144
+ if (!hasErrored) {
145
+ hasErrored = true;
146
+ clearTimeout(timeout);
147
+ logger.error('Failed to start FFmpeg:', err);
148
+ resolve(false);
149
+ }
150
+ });
151
+ ffmpeg.on('close', (code) => {
152
+ clearTimeout(timeout);
153
+ if (code === 0) {
154
+ logger.info('Screen recording completed successfully');
155
+ }
156
+ else if (code !== null && !hasErrored) {
157
+ logger.error(`FFmpeg exited with code ${code}`);
158
+ }
159
+ });
160
+ });
161
+ return { process: ffmpeg, started: startedPromise };
162
+ }
163
+ /**
164
+ * Stop ffmpeg recording gracefully.
165
+ *
166
+ * SIGTERM tells ffmpeg to flush buffers and write the moov atom before exiting.
167
+ * SIGKILL is a last resort after timeoutMs and will leave the file corrupt.
168
+ */
169
+ async function stopScreenRecording(ffmpegProcess, timeoutMs = 10000) {
170
+ // Already exited (e.g. display closed under it) — nothing to signal
171
+ if (ffmpegProcess.exitCode !== null || ffmpegProcess.killed) {
172
+ return;
173
+ }
174
+ return new Promise((resolve) => {
175
+ const sigkillTimer = setTimeout(() => {
176
+ logger.warn('FFmpeg did not stop gracefully, forcing SIGKILL (file may be corrupt)');
177
+ ffmpegProcess.kill('SIGKILL');
178
+ resolve();
179
+ }, timeoutMs);
180
+ ffmpegProcess.on('close', () => {
181
+ clearTimeout(sigkillTimer);
182
+ resolve();
183
+ });
184
+ ffmpegProcess.kill('SIGTERM');
185
+ });
186
+ }
187
+ // Internal state for xvfb management (not exposed to users)
188
+ let currentXvfb = null;
189
+ let originalDisplay;
190
+ // Track active ffmpeg processes so they can be killed if the process is
191
+ // interrupted before the fixture teardown has a chance to stop them.
192
+ const activeFFmpegProcesses = new Set();
193
+ function killActiveFFmpegProcesses() {
194
+ for (const proc of activeFFmpegProcesses) {
195
+ try {
196
+ proc.kill('SIGTERM');
197
+ }
198
+ catch { }
199
+ }
200
+ activeFFmpegProcesses.clear();
201
+ }
202
+ // Clean up Xvfb and any active ffmpeg processes when the worker process exits
203
+ // so lock/socket files don't persist and block the next run.
204
+ process.on('exit', () => {
205
+ killActiveFFmpegProcesses();
206
+ if (currentXvfb) {
207
+ try {
208
+ currentXvfb.process.kill('SIGTERM');
209
+ }
210
+ catch { }
211
+ }
212
+ });
213
+ // Forward SIGTERM/SIGINT: kill ffmpeg and Xvfb, then let the process exit.
214
+ const handleTermSignal = (signal) => {
215
+ logger.info(`Worker received ${signal}, killing ffmpeg and Xvfb...`);
216
+ killActiveFFmpegProcesses();
217
+ if (currentXvfb) {
218
+ try {
219
+ currentXvfb.process.kill('SIGTERM');
220
+ }
221
+ catch { }
222
+ }
223
+ process.exit(0);
224
+ };
225
+ process.on('SIGTERM', handleTermSignal);
226
+ process.on('SIGINT', handleTermSignal);
227
+ const _videoBase = base.extend({
228
+ recordOptions: [DEFAULT_VIDEO_OPTIONS, { option: true }],
229
+ renderOptions: [undefined, { option: true }],
230
+ // Internal worker fixture to manage xvfb per test
231
+ _xvfbSetup: [
232
+ async ({ recordOptions }, use) => {
233
+ const shouldRecord = process.env.SCREENCI_RECORD === 'true';
234
+ if (shouldRecord && !currentXvfb && isHeadless()) {
235
+ // Start Xvfb once per worker at 3840×3840 — a square large enough to
236
+ // contain any quality/aspect-ratio combination. Per-test sizing is
237
+ // handled by the Playwright viewport override and the FFmpeg capture
238
+ // region. The framebuffer costs ~56 MB of RAM, negligible on any runner.
239
+ logger.info('Starting Xvfb at 3840×3840');
240
+ currentXvfb = await startXvfb(3840, 3840);
241
+ logger.info(`Xvfb started on ${currentXvfb.display}`);
242
+ if (originalDisplay === undefined) {
243
+ originalDisplay = process.env.DISPLAY;
244
+ }
245
+ process.env.DISPLAY = currentXvfb.display;
246
+ }
247
+ await use();
248
+ // Xvfb stays alive between tests — cleanup happens on process exit.
249
+ },
250
+ { scope: 'test', auto: true },
251
+ ],
252
+ browser: async ({ playwright }, use) => {
253
+ // Launch browser with kiosk mode when recording for fullscreen capture
254
+ // Note: _xvfbSetup runs automatically before this due to auto: true
255
+ const shouldRecord = process.env.SCREENCI_RECORD === 'true';
256
+ const launchOptions = shouldRecord
257
+ ? {
258
+ headless: false,
259
+ args: [
260
+ '--window-position=0,0',
261
+ '--kiosk',
262
+ '--disable-translate',
263
+ '--disable-spell-checking',
264
+ '--disable-notifications', // no permission popups
265
+ '--disable-save-password-bubble', // no "save password?" dialog
266
+ '--disable-infobars', // no "Chrome is being controlled by..." bar
267
+ '--no-first-run', // skip first-run UI
268
+ '--hide-scrollbars', // scrollbars invisible in recordings
269
+ ],
270
+ }
271
+ : undefined;
272
+ const browser = await playwright.chromium.launch(launchOptions);
273
+ instrumentBrowser(browser);
274
+ await use(browser);
275
+ await browser.close();
276
+ },
277
+ context: async ({ browser, recordOptions }, use) => {
278
+ // Configure browser context
279
+ const aspectRatio = recordOptions.aspectRatio ?? DEFAULT_ASPECT_RATIO;
280
+ const quality = recordOptions.quality ?? DEFAULT_QUALITY;
281
+ const dimensions = getDimensions(aspectRatio, quality);
282
+ const context = await browser.newContext({
283
+ viewport: dimensions,
284
+ });
285
+ instrumentContext(context);
286
+ await use(context);
287
+ await context.close();
288
+ },
289
+ page: async ({ context, recordOptions, renderOptions }, use, testInfo) => {
290
+ // Only record when explicitly enabled (record command)
291
+ const shouldRecord = process.env.SCREENCI_RECORD === 'true';
292
+ if (!shouldRecord) {
293
+ // Skip recording, just create page
294
+ resetCaptionChain();
295
+ setActiveCaptionRecorder(null);
296
+ setActiveClickRecorder(null);
297
+ setActiveHideRecorder(null);
298
+ setActiveAutoZoomRecorder(null);
299
+ setActiveAssetRecorder(null);
300
+ const page = await context.newPage();
301
+ await use(page);
302
+ await page.close();
303
+ return;
304
+ }
305
+ // Get video options
306
+ const aspectRatio = recordOptions.aspectRatio ?? DEFAULT_ASPECT_RATIO;
307
+ const quality = recordOptions.quality ?? DEFAULT_QUALITY;
308
+ const fps = recordOptions.fps ?? DEFAULT_FPS;
309
+ const dimensions = getDimensions(aspectRatio, quality);
310
+ // Sanitize video title to create a valid directory name
311
+ const sanitizedVideoName = sanitizeVideoName(testInfo.title);
312
+ // Create directory path: .screenci/[video-name]/
313
+ const videoDir = join(process.cwd(), '.screenci', sanitizedVideoName);
314
+ // Delete old directory if it exists (start fresh)
315
+ try {
316
+ await rm(videoDir, { recursive: true, force: true });
317
+ }
318
+ catch {
319
+ // Ignore errors if directory doesn't exist
320
+ }
321
+ // Create the directory
322
+ await mkdir(videoDir, { recursive: true });
323
+ // Video output path - always use recording.mp4
324
+ const videoPath = join(videoDir, 'recording.mp4');
325
+ logger.info(`Recording video to: ${videoPath}`);
326
+ logger.info(`Recording with ${aspectRatio} ${quality} (${dimensions.width}x${dimensions.height}), ${fps}fps`);
327
+ const recorder = new EventRecorder(renderOptions, recordOptions);
328
+ resetCaptionChain();
329
+ setActiveCaptionRecorder(recorder);
330
+ setActiveClickRecorder(recorder);
331
+ setActiveHideRecorder(recorder);
332
+ setActiveAutoZoomRecorder(recorder);
333
+ setActiveAssetRecorder(recorder);
334
+ // Create page FIRST to ensure browser window is rendered
335
+ const page = await context.newPage();
336
+ await setupMouseTracking(page, recorder);
337
+ // Navigate to blank page to ensure window is ready and rendered
338
+ await page.goto('about:blank');
339
+ // Wait for browser window to be fully rendered before starting recording
340
+ // This prevents black screen captures
341
+ await page.waitForTimeout(1500);
342
+ // Hide browser toolbar via CDP fullscreen. --kiosk alone is not reliable on
343
+ // Xvfb; the CDP call triggers Chromium's internal fullscreen mode which
344
+ // removes all browser UI chrome. No explicit dims here — those conflict with
345
+ // windowState and are not needed (FFmpeg captures from +0,0 at test dims).
346
+ try {
347
+ const cdpSession = await context.newCDPSession(page);
348
+ const { windowId } = await cdpSession.send('Browser.getWindowForTarget', {});
349
+ await cdpSession.send('Browser.setWindowBounds', {
350
+ windowId,
351
+ bounds: { windowState: 'fullscreen' },
352
+ });
353
+ await cdpSession.detach();
354
+ }
355
+ catch (cdpError) {
356
+ logger.warn('CDP fullscreen failed, toolbar may be visible:', cdpError);
357
+ }
358
+ // Start ffmpeg recording after browser window is ready
359
+ const { process: ffmpegProcess, started } = startScreenRecording(videoPath, fps, quality, aspectRatio);
360
+ // Wait for ffmpeg to start or fail
361
+ const didStart = await started;
362
+ if (didStart) {
363
+ // Mark the moment the video recording actually begins
364
+ recorder.start();
365
+ }
366
+ else {
367
+ logger.warn('Warning: Screen recording failed to start. Test will continue without recording.');
368
+ logger.warn(`Note: Recording at ${dimensions.width}x${dimensions.height} requires a display of at least that size.`);
369
+ }
370
+ await use(page);
371
+ await page.close();
372
+ setActiveCaptionRecorder(null);
373
+ setActiveClickRecorder(null);
374
+ setActiveHideRecorder(null);
375
+ setActiveAutoZoomRecorder(null);
376
+ setActiveAssetRecorder(null);
377
+ // Stop ffmpeg recording after test (only if it started)
378
+ if (didStart) {
379
+ logger.info('Stopping recording...');
380
+ await stopScreenRecording(ffmpegProcess);
381
+ logger.info(`Video saved to: ${videoPath}`);
382
+ // Write recorded events next to the video
383
+ await recorder.writeToFile(videoDir, testInfo.title);
384
+ logger.info(`Events saved to: ${join(videoDir, 'data.json')}`);
385
+ }
386
+ else {
387
+ // Kill the process if it's still running
388
+ if (!ffmpegProcess.killed) {
389
+ ffmpegProcess.kill('SIGKILL');
390
+ }
391
+ }
392
+ },
393
+ });
394
+ /**
395
+ * ScreenCI video recording test fixture.
396
+ *
397
+ * Extended Playwright test that automatically records browser interactions as video.
398
+ * Configure recording options globally with `video.use()` or in your config file.
399
+ *
400
+ * @example
401
+ * Basic usage:
402
+ * ```ts
403
+ * import { video, caption } from 'screenci'
404
+ *
405
+ * video('Tutorial', async ({ page }) => {
406
+ * await page.goto('https://example.com')
407
+ * caption('User navigates to homepage')
408
+ *
409
+ * await page.click('text=Sign up')
410
+ * caption('Clicks sign up button')
411
+ * })
412
+ * ```
413
+ */
414
+ export const video = _videoBase;
415
+ //# sourceMappingURL=video.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video.js","sourceRoot":"","sources":["../../src/video.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Named voices available for use with `createCaptions`.
3
+ *
4
+ * Voices are grouped by language. Only voices belonging to a language may be
5
+ * used with captions written in that language — the TypeScript types enforce
6
+ * this when using the multi-language overload of `createCaptions`.
7
+ *
8
+ * The actual provider voice IDs are intentionally kept out of this package
9
+ * and are only known to the rendering infrastructure.
10
+ *
11
+ * Example:
12
+ * ```ts
13
+ * import { voices } from 'screenci'
14
+ *
15
+ * createCaptions({
16
+ * en: { voice: voices.en.Ava, captions: { intro: 'Hello' } },
17
+ * fi: { voice: voices.fi.Selma, captions: { intro: 'Hei' } },
18
+ * })
19
+ * ```
20
+ */
21
+ export declare const voices: {
22
+ readonly fi: {
23
+ readonly Martti: "fi.Martti";
24
+ readonly Selma: "fi.Selma";
25
+ readonly Noora: "fi.Noora";
26
+ };
27
+ readonly en: {
28
+ readonly Jude: "en.Jude";
29
+ readonly Ava: "en.Ava";
30
+ readonly Andrew: "en.Andrew";
31
+ readonly Adam: "en.Adam";
32
+ readonly Alloy: "en.Alloy";
33
+ readonly Aria: "en.Aria";
34
+ readonly Bree: "en.Bree";
35
+ readonly Brian: "en.Brian";
36
+ readonly Davis: "en.Davis";
37
+ readonly Emma: "en.Emma";
38
+ readonly Emma2: "en.Emma2";
39
+ readonly Jane: "en.Jane";
40
+ };
41
+ };
42
+ type Voices = typeof voices;
43
+ /**
44
+ * Union of all valid voice keys, e.g. `'fi.Martti' | 'en.Ava'`.
45
+ */
46
+ export type VoiceKey = {
47
+ [L in keyof Voices]: Voices[L][keyof Voices[L]];
48
+ }[keyof Voices];
49
+ /**
50
+ * Narrows the set of valid voice keys to those belonging to language `L`.
51
+ *
52
+ * - `VoiceForLang<'fi'>` → `'fi.Martti' | 'fi.Selma' | 'fi.Noora'`
53
+ * - `VoiceForLang<'en'>` → `'en.Jude' | 'en.Ava' | ...`
54
+ * - `VoiceForLang<'de'>` → `never` (no German voices defined)
55
+ */
56
+ export type VoiceForLang<L extends string> = L extends keyof Voices ? Voices[L][keyof Voices[L]] : never;
57
+ /** Union of supported language codes, e.g. `'en' | 'fi'`. */
58
+ export type Lang = keyof Voices;
59
+ export {};
60
+ //# sourceMappingURL=voices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voices.d.ts","sourceRoot":"","sources":["../../src/voices.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;CAoBT,CAAA;AAEV,KAAK,MAAM,GAAG,OAAO,MAAM,CAAA;AAE3B;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;KACpB,CAAC,IAAI,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;CAChD,CAAC,MAAM,MAAM,CAAC,CAAA;AAEf;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,MAAM,MAAM,GAC/D,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC,GAC1B,KAAK,CAAA;AAET,6DAA6D;AAC7D,MAAM,MAAM,IAAI,GAAG,MAAM,MAAM,CAAA"}