screenci 0.0.4 → 0.0.6
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.
- package/README.md +227 -0
- package/cli.ts +1111 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +896 -0
- package/dist/cli.js.map +1 -0
- package/dist/e2e/instrument.e2e.d.ts +2 -0
- package/dist/e2e/instrument.e2e.d.ts.map +1 -0
- package/dist/e2e/instrument.e2e.js +661 -0
- package/dist/e2e/instrument.e2e.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/playwright.config.d.ts +3 -0
- package/dist/playwright.config.d.ts.map +1 -0
- package/dist/playwright.config.js +21 -0
- package/dist/playwright.config.js.map +1 -0
- package/dist/reporter.d.ts +9 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +49 -0
- package/dist/reporter.js.map +1 -0
- package/dist/src/asset.d.ts +90 -0
- package/dist/src/asset.d.ts.map +1 -0
- package/dist/src/asset.js +74 -0
- package/dist/src/asset.js.map +1 -0
- package/dist/src/autoZoom.d.ts +40 -0
- package/dist/src/autoZoom.d.ts.map +1 -0
- package/dist/src/autoZoom.js +88 -0
- package/dist/src/autoZoom.js.map +1 -0
- package/dist/src/caption.d.ts +152 -0
- package/dist/src/caption.d.ts.map +1 -0
- package/dist/src/caption.js +240 -0
- package/dist/src/caption.js.map +1 -0
- package/dist/src/caption.test-d.d.ts +2 -0
- package/dist/src/caption.test-d.d.ts.map +1 -0
- package/dist/src/caption.test-d.js +50 -0
- package/dist/src/caption.test-d.js.map +1 -0
- package/dist/src/config.d.ts +42 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +147 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/defaults.d.ts +63 -0
- package/dist/src/defaults.d.ts.map +1 -0
- package/dist/src/defaults.js +66 -0
- package/dist/src/defaults.js.map +1 -0
- package/dist/src/dimensions.d.ts +29 -0
- package/dist/src/dimensions.d.ts.map +1 -0
- package/dist/src/dimensions.js +47 -0
- package/dist/src/dimensions.js.map +1 -0
- package/dist/src/events.d.ts +203 -0
- package/dist/src/events.d.ts.map +1 -0
- package/dist/src/events.js +227 -0
- package/dist/src/events.js.map +1 -0
- package/dist/src/hide.d.ts +27 -0
- package/dist/src/hide.d.ts.map +1 -0
- package/dist/src/hide.js +49 -0
- package/dist/src/hide.js.map +1 -0
- package/dist/src/instrument.d.ts +15 -0
- package/dist/src/instrument.d.ts.map +1 -0
- package/dist/src/instrument.js +910 -0
- package/dist/src/instrument.js.map +1 -0
- package/dist/src/logger.d.ts +7 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +13 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/reporter.d.ts +9 -0
- package/dist/src/reporter.d.ts.map +1 -0
- package/dist/src/reporter.js +50 -0
- package/dist/src/reporter.js.map +1 -0
- package/dist/src/sanitize.d.ts +5 -0
- package/dist/src/sanitize.d.ts.map +1 -0
- package/dist/src/sanitize.js +11 -0
- package/dist/src/sanitize.js.map +1 -0
- package/dist/src/types.d.ts +544 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/video.d.ts +138 -0
- package/dist/src/video.d.ts.map +1 -0
- package/dist/src/video.js +415 -0
- package/dist/src/video.js.map +1 -0
- package/dist/src/voices.d.ts +60 -0
- package/dist/src/voices.d.ts.map +1 -0
- package/dist/src/voices.js +42 -0
- package/dist/src/voices.js.map +1 -0
- package/dist/src/xvfb.d.ts +22 -0
- package/dist/src/xvfb.d.ts.map +1 -0
- package/dist/src/xvfb.js +87 -0
- package/dist/src/xvfb.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +45 -4
- package/bin/index.js +0 -3
- package/index.js +0 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { IEventRecorder } from './events.js';
|
|
2
|
+
import type { VoiceKey, VoiceForLang, Lang } from './voices.js';
|
|
3
|
+
export declare const ONE_FRAME_MS: number;
|
|
4
|
+
/** A percentage string, e.g. `'50%'` or `'100%'`. */
|
|
5
|
+
export type Percentage = `${number}%`;
|
|
6
|
+
export declare function setSleepFn(fn: (ms: number) => void): void;
|
|
7
|
+
export declare function setActiveCaptionRecorder(recorder: IEventRecorder | null): void;
|
|
8
|
+
export declare function resetCaptionChain(): void;
|
|
9
|
+
export interface CaptionController {
|
|
10
|
+
/**
|
|
11
|
+
* Begins voiceover audio and shows the caption overlay.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* await captions.intro.start()
|
|
16
|
+
* await page.goto('/dashboard')
|
|
17
|
+
* await captions.intro.end()
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
start(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Pauses execution until the voiceover audio reaches the given playback position.
|
|
23
|
+
*
|
|
24
|
+
* Use this to time a page interaction to a specific moment in the narration —
|
|
25
|
+
* for example, clicking a button right as the voiceover mentions it.
|
|
26
|
+
*
|
|
27
|
+
* @param progress - Playback position as a percentage string, e.g. `'50%'`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* await captions.intro.start()
|
|
32
|
+
* await captions.intro.waitUntil('70%') // wait until 70% of audio has played
|
|
33
|
+
* await page.locator('#cta').click() // then click
|
|
34
|
+
* await captions.intro.end()
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
waitUntil(progress: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Hides the caption overlay and stops voiceover playback.
|
|
40
|
+
* Always call this after every `start()`.
|
|
41
|
+
*/
|
|
42
|
+
end(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/** A single caption value in a multi-language map: either TTS text or a file-based entry. */
|
|
45
|
+
export type CaptionMapValue = string | {
|
|
46
|
+
path: string;
|
|
47
|
+
subtitle?: string;
|
|
48
|
+
};
|
|
49
|
+
export type Captions<T extends Record<string, CaptionMapValue>> = {
|
|
50
|
+
[K in keyof T]: CaptionController;
|
|
51
|
+
};
|
|
52
|
+
export type VideoCaptionEntry = string | {
|
|
53
|
+
path: string;
|
|
54
|
+
subtitle?: string;
|
|
55
|
+
};
|
|
56
|
+
export type VideoCaptions<T extends Record<string, VideoCaptionEntry>> = {
|
|
57
|
+
[K in keyof T]: CaptionController;
|
|
58
|
+
};
|
|
59
|
+
/** Converts a union type to an intersection: `A | B` → `A & B` */
|
|
60
|
+
type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
|
|
61
|
+
/**
|
|
62
|
+
* Produces a record requiring every key that appears in any language's captions.
|
|
63
|
+
* Uses each language's key set with CaptionMapValue values before intersecting,
|
|
64
|
+
* so value types don't conflict (e.g. string vs { path, subtitle } for the same key).
|
|
65
|
+
*/
|
|
66
|
+
type AllCaptions<M extends Partial<Record<Lang, {
|
|
67
|
+
captions: Record<string, CaptionMapValue>;
|
|
68
|
+
}>>> = UnionToIntersection<{
|
|
69
|
+
[L in keyof M]: M[L] extends {
|
|
70
|
+
captions: infer C;
|
|
71
|
+
} ? Record<keyof C & string, CaptionMapValue> : never;
|
|
72
|
+
}[keyof M]> & Record<string, CaptionMapValue>;
|
|
73
|
+
/**
|
|
74
|
+
* Creates a set of typed caption controllers, one per key in the map.
|
|
75
|
+
*
|
|
76
|
+
* Each controller has `start()`, `waitUntil(percent)`, and `end()`.
|
|
77
|
+
* At render time screenci sends the caption text to ElevenLabs, generates
|
|
78
|
+
* a voiceover, and syncs the audio to the recording. You write text; the
|
|
79
|
+
* voice is handled for you.
|
|
80
|
+
*
|
|
81
|
+
* TypeScript enforces that every language has the same caption keys.
|
|
82
|
+
* Forget a translation key → compile error.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const captions = createCaptions({
|
|
87
|
+
* en: { voice: voices.en.Jude, captions: { intro: 'Welcome.' } },
|
|
88
|
+
* fi: { voice: voices.fi.Martti, captions: { intro: 'Tervetuloa.' } },
|
|
89
|
+
* })
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function createCaptions<M extends Partial<Record<Lang, {
|
|
93
|
+
voice: VoiceKey;
|
|
94
|
+
captions: Record<string, CaptionMapValue>;
|
|
95
|
+
}>>>(languagesMap: M & {
|
|
96
|
+
[L in keyof M]: {
|
|
97
|
+
voice: VoiceForLang<L & string>;
|
|
98
|
+
captions: AllCaptions<M>;
|
|
99
|
+
};
|
|
100
|
+
}): Captions<AllCaptions<M>>;
|
|
101
|
+
type RequireAllSameVideoKeys<M extends Partial<Record<Lang, {
|
|
102
|
+
captions: Record<string, VideoCaptionEntry>;
|
|
103
|
+
}>>> = {
|
|
104
|
+
[L in keyof M]: {
|
|
105
|
+
captions: UnionToIntersection<M[keyof M] extends {
|
|
106
|
+
captions: infer C;
|
|
107
|
+
} ? C : never>;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Creates caption controllers backed by pre-recorded asset files instead of
|
|
112
|
+
* TTS-generated audio. Each entry maps a name to either an asset path string
|
|
113
|
+
* or an object with `assetPath` and an optional `subtitle` text.
|
|
114
|
+
*
|
|
115
|
+
* At render time the asset file is used directly as the voiceover audio.
|
|
116
|
+
* If `subtitle` is provided, words are spread with equal timing across the
|
|
117
|
+
* audio duration (no word-level TTS data available).
|
|
118
|
+
*
|
|
119
|
+
* Same constraints as `createCaptions`: cannot overlap with other captions,
|
|
120
|
+
* and cannot fall inside input events.
|
|
121
|
+
*
|
|
122
|
+
* Two overloads:
|
|
123
|
+
*
|
|
124
|
+
* **1. Single-language:**
|
|
125
|
+
* ```ts
|
|
126
|
+
* const captions = createVideoCaptions({
|
|
127
|
+
* intro: '/assets/intro.mp3',
|
|
128
|
+
* demo: { assetPath: '/assets/demo.mp3', subtitle: 'Watch the demo.' },
|
|
129
|
+
* })
|
|
130
|
+
* ```
|
|
131
|
+
*
|
|
132
|
+
* **2. Multi-language (type-safe):**
|
|
133
|
+
* TypeScript enforces that every language has the same caption keys.
|
|
134
|
+
* ```ts
|
|
135
|
+
* const captions = createVideoCaptions({
|
|
136
|
+
* en: { captions: { intro: '/assets/en/intro.mp3' } },
|
|
137
|
+
* fi: { captions: { intro: '/assets/fi/intro.mp3' } },
|
|
138
|
+
* })
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export declare function createVideoCaptions<T extends Record<string, VideoCaptionEntry>>(captionsMap: T): VideoCaptions<T>;
|
|
142
|
+
export declare function createVideoCaptions<L extends Lang, T extends Record<string, VideoCaptionEntry>>(languagesMap: {
|
|
143
|
+
[K in L]: {
|
|
144
|
+
captions: T;
|
|
145
|
+
};
|
|
146
|
+
} & RequireAllSameVideoKeys<{
|
|
147
|
+
[K in L]: {
|
|
148
|
+
captions: Record<string, VideoCaptionEntry>;
|
|
149
|
+
};
|
|
150
|
+
}>): VideoCaptions<T & Record<string, VideoCaptionEntry>>;
|
|
151
|
+
export {};
|
|
152
|
+
//# sourceMappingURL=caption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caption.d.ts","sourceRoot":"","sources":["../../src/caption.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { isInsideHide } from './hide.js';
|
|
2
|
+
// One frame at 24fps — ensures at least one rendered frame captures each caption state.
|
|
3
|
+
export const ONE_FRAME_MS = 1000 / 24;
|
|
4
|
+
function parsePercentage(percent) {
|
|
5
|
+
const value = parseFloat(percent);
|
|
6
|
+
if (!isFinite(value) || value < 0 || value > 100) {
|
|
7
|
+
throw new Error(`Invalid percentage: "${percent}". Must be a finite number between 0 and 100 followed by %.`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
// Blocking sleep — spin until the elapsed time has passed
|
|
12
|
+
let sleepFn = (ms) => {
|
|
13
|
+
const end = performance.now() + ms;
|
|
14
|
+
while (performance.now() < end) {
|
|
15
|
+
/* spin */
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
export function setSleepFn(fn) {
|
|
19
|
+
sleepFn = fn;
|
|
20
|
+
}
|
|
21
|
+
let activeRecorder = null;
|
|
22
|
+
let captionStarted = false;
|
|
23
|
+
export function setActiveCaptionRecorder(recorder) {
|
|
24
|
+
activeRecorder = recorder;
|
|
25
|
+
}
|
|
26
|
+
export function resetCaptionChain() {
|
|
27
|
+
captionStarted = false;
|
|
28
|
+
}
|
|
29
|
+
function captionWaitEnd() {
|
|
30
|
+
if (activeRecorder === null)
|
|
31
|
+
return;
|
|
32
|
+
if (isInsideHide())
|
|
33
|
+
throw new Error('Cannot call caption.waitEnd inside hide()');
|
|
34
|
+
if (!captionStarted)
|
|
35
|
+
throw new Error('No caption has been started');
|
|
36
|
+
activeRecorder.addCaptionEnd();
|
|
37
|
+
sleepFn(2 * ONE_FRAME_MS);
|
|
38
|
+
}
|
|
39
|
+
function captionWaitUntil(percent) {
|
|
40
|
+
if (activeRecorder === null)
|
|
41
|
+
return;
|
|
42
|
+
if (isInsideHide())
|
|
43
|
+
throw new Error('Cannot call caption.waitUntil inside hide()');
|
|
44
|
+
if (!captionStarted)
|
|
45
|
+
throw new Error('No caption has been started');
|
|
46
|
+
const percentage = parsePercentage(percent);
|
|
47
|
+
activeRecorder.addCaptionUntil(percentage);
|
|
48
|
+
sleepFn(2 * ONE_FRAME_MS);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates a set of typed caption controllers, one per key in the map.
|
|
52
|
+
*
|
|
53
|
+
* Each controller has `start()`, `waitUntil(percent)`, and `end()`.
|
|
54
|
+
* At render time screenci sends the caption text to ElevenLabs, generates
|
|
55
|
+
* a voiceover, and syncs the audio to the recording. You write text; the
|
|
56
|
+
* voice is handled for you.
|
|
57
|
+
*
|
|
58
|
+
* TypeScript enforces that every language has the same caption keys.
|
|
59
|
+
* Forget a translation key → compile error.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const captions = createCaptions({
|
|
64
|
+
* en: { voice: voices.en.Jude, captions: { intro: 'Welcome.' } },
|
|
65
|
+
* fi: { voice: voices.fi.Martti, captions: { intro: 'Tervetuloa.' } },
|
|
66
|
+
* })
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function createCaptions(languagesMap) {
|
|
70
|
+
return buildMultiLangCaptions(languagesMap);
|
|
71
|
+
}
|
|
72
|
+
function buildMultiLangCaptions(languagesMap) {
|
|
73
|
+
const langs = Object.keys(languagesMap);
|
|
74
|
+
const firstLang = langs[0];
|
|
75
|
+
if (firstLang === undefined)
|
|
76
|
+
return {};
|
|
77
|
+
const result = {};
|
|
78
|
+
for (const key in languagesMap[firstLang].captions) {
|
|
79
|
+
const keyStr = key;
|
|
80
|
+
// Determine if any language uses a file-based value for this key.
|
|
81
|
+
// If so, use videoCaptionStart with mixed translations (file or TTS per lang).
|
|
82
|
+
// Otherwise use captionStart with text translations (existing behaviour).
|
|
83
|
+
const hasFileEntry = langs.some((lang) => {
|
|
84
|
+
const val = languagesMap[lang].captions[keyStr];
|
|
85
|
+
return val !== undefined && typeof val !== 'string';
|
|
86
|
+
});
|
|
87
|
+
if (hasFileEntry) {
|
|
88
|
+
const videoTranslations = {};
|
|
89
|
+
for (const lang of langs) {
|
|
90
|
+
const val = languagesMap[lang].captions[keyStr];
|
|
91
|
+
if (val === undefined)
|
|
92
|
+
continue;
|
|
93
|
+
if (typeof val === 'string') {
|
|
94
|
+
videoTranslations[lang] = {
|
|
95
|
+
text: val,
|
|
96
|
+
voice: languagesMap[lang].voice,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const fileTrans = {
|
|
101
|
+
assetPath: val.path,
|
|
102
|
+
...(val.subtitle !== undefined && { subtitle: val.subtitle }),
|
|
103
|
+
};
|
|
104
|
+
videoTranslations[lang] = fileTrans;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
result[key] = {
|
|
108
|
+
async start() {
|
|
109
|
+
if (activeRecorder === null)
|
|
110
|
+
return;
|
|
111
|
+
if (isInsideHide())
|
|
112
|
+
throw new Error('Cannot call caption.start inside hide()');
|
|
113
|
+
captionStarted = true;
|
|
114
|
+
sleepFn(2 * ONE_FRAME_MS);
|
|
115
|
+
activeRecorder.addVideoCaptionStart(keyStr, undefined, undefined, videoTranslations);
|
|
116
|
+
},
|
|
117
|
+
async waitUntil(progress) {
|
|
118
|
+
captionWaitUntil(progress);
|
|
119
|
+
},
|
|
120
|
+
async end() {
|
|
121
|
+
captionWaitEnd();
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const textTranslations = {};
|
|
127
|
+
for (const lang of langs) {
|
|
128
|
+
const val = languagesMap[lang].captions[keyStr];
|
|
129
|
+
if (val !== undefined && typeof val === 'string') {
|
|
130
|
+
textTranslations[lang] = {
|
|
131
|
+
text: val,
|
|
132
|
+
voice: languagesMap[lang].voice,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
result[key] = {
|
|
137
|
+
async start() {
|
|
138
|
+
if (activeRecorder === null)
|
|
139
|
+
return;
|
|
140
|
+
if (isInsideHide())
|
|
141
|
+
throw new Error('Cannot call caption.start inside hide()');
|
|
142
|
+
captionStarted = true;
|
|
143
|
+
sleepFn(2 * ONE_FRAME_MS);
|
|
144
|
+
activeRecorder.addCaptionStart('', keyStr, undefined, textTranslations);
|
|
145
|
+
},
|
|
146
|
+
async waitUntil(progress) {
|
|
147
|
+
captionWaitUntil(progress);
|
|
148
|
+
},
|
|
149
|
+
async end() {
|
|
150
|
+
captionWaitEnd();
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
function entryToVideoTranslation(entry) {
|
|
158
|
+
if (typeof entry === 'string')
|
|
159
|
+
return { assetPath: entry };
|
|
160
|
+
return {
|
|
161
|
+
assetPath: entry.path,
|
|
162
|
+
...(entry.subtitle !== undefined && { subtitle: entry.subtitle }),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export function createVideoCaptions(firstArg) {
|
|
166
|
+
// Distinguish single-language vs multi-language by checking if any value has a `captions` key
|
|
167
|
+
const firstValue = Object.values(firstArg)[0];
|
|
168
|
+
if (firstValue !== undefined &&
|
|
169
|
+
typeof firstValue === 'object' &&
|
|
170
|
+
!Array.isArray(firstValue) &&
|
|
171
|
+
'captions' in firstValue &&
|
|
172
|
+
typeof firstValue.captions === 'object') {
|
|
173
|
+
return buildMultiLangVideoCaptions(firstArg);
|
|
174
|
+
}
|
|
175
|
+
return buildSingleLangVideoCaptions(firstArg);
|
|
176
|
+
}
|
|
177
|
+
function buildSingleLangVideoCaptions(captionsMap) {
|
|
178
|
+
const result = {};
|
|
179
|
+
for (const key in captionsMap) {
|
|
180
|
+
const entry = captionsMap[key];
|
|
181
|
+
if (entry === undefined)
|
|
182
|
+
continue;
|
|
183
|
+
const assetPath = typeof entry === 'string' ? entry : entry.path;
|
|
184
|
+
const subtitle = typeof entry === 'string' ? undefined : entry.subtitle;
|
|
185
|
+
result[key] = {
|
|
186
|
+
async start() {
|
|
187
|
+
if (activeRecorder === null)
|
|
188
|
+
return;
|
|
189
|
+
if (isInsideHide())
|
|
190
|
+
throw new Error('Cannot call videoCaption.start inside hide()');
|
|
191
|
+
captionStarted = true;
|
|
192
|
+
sleepFn(2 * ONE_FRAME_MS);
|
|
193
|
+
activeRecorder.addVideoCaptionStart(key, assetPath, subtitle);
|
|
194
|
+
},
|
|
195
|
+
async waitUntil(progress) {
|
|
196
|
+
captionWaitUntil(progress);
|
|
197
|
+
},
|
|
198
|
+
async end() {
|
|
199
|
+
captionWaitEnd();
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
function buildMultiLangVideoCaptions(languagesMap) {
|
|
206
|
+
const langs = Object.keys(languagesMap);
|
|
207
|
+
const firstLang = langs[0];
|
|
208
|
+
if (firstLang === undefined)
|
|
209
|
+
return {};
|
|
210
|
+
const result = {};
|
|
211
|
+
for (const key in languagesMap[firstLang].captions) {
|
|
212
|
+
const keyStr = key;
|
|
213
|
+
const translations = {};
|
|
214
|
+
for (const lang of langs) {
|
|
215
|
+
const entry = languagesMap[lang].captions[keyStr];
|
|
216
|
+
if (entry !== undefined) {
|
|
217
|
+
translations[lang] = entryToVideoTranslation(entry);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
result[key] = {
|
|
221
|
+
async start() {
|
|
222
|
+
if (activeRecorder === null)
|
|
223
|
+
return;
|
|
224
|
+
if (isInsideHide())
|
|
225
|
+
throw new Error('Cannot call videoCaption.start inside hide()');
|
|
226
|
+
captionStarted = true;
|
|
227
|
+
sleepFn(2 * ONE_FRAME_MS);
|
|
228
|
+
activeRecorder.addVideoCaptionStart(keyStr, undefined, undefined, translations);
|
|
229
|
+
},
|
|
230
|
+
async waitUntil(progress) {
|
|
231
|
+
captionWaitUntil(progress);
|
|
232
|
+
},
|
|
233
|
+
async end() {
|
|
234
|
+
captionWaitEnd();
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=caption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caption.js","sourceRoot":"","sources":["../../src/caption.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caption.test-d.d.ts","sourceRoot":"","sources":["../../src/caption.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, assertType } from 'vitest';
|
|
2
|
+
import { createCaptions } from './caption.js';
|
|
3
|
+
import { voices } from './voices.js';
|
|
4
|
+
describe('createCaptions type constraints', () => {
|
|
5
|
+
it('accepts matching keys across all languages', () => {
|
|
6
|
+
createCaptions({
|
|
7
|
+
en: {
|
|
8
|
+
voice: voices.en.Jude,
|
|
9
|
+
captions: { intro: 'Hello', outro: 'Bye' },
|
|
10
|
+
},
|
|
11
|
+
fi: {
|
|
12
|
+
voice: voices.fi.Martti,
|
|
13
|
+
captions: { intro: 'Hei', outro: 'Näkemiin' },
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
it('accepts mixed value types (string vs file object) for the same key', () => {
|
|
18
|
+
createCaptions({
|
|
19
|
+
en: {
|
|
20
|
+
voice: voices.en.Jude,
|
|
21
|
+
captions: {
|
|
22
|
+
intro: 'Hello',
|
|
23
|
+
clip: { path: '/clip.mp4', subtitle: 'Watch' },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
fi: {
|
|
27
|
+
voice: voices.fi.Martti,
|
|
28
|
+
captions: { intro: 'Hei', clip: 'Katso' },
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
it('rejects a language with a missing caption key', () => {
|
|
33
|
+
createCaptions({
|
|
34
|
+
en: {
|
|
35
|
+
voice: voices.en.Jude,
|
|
36
|
+
captions: { intro: 'Hello', outro: 'Bye' },
|
|
37
|
+
},
|
|
38
|
+
fi: {
|
|
39
|
+
voice: voices.fi.Martti,
|
|
40
|
+
// @ts-expect-error — 'outro' is missing
|
|
41
|
+
captions: { intro: 'Hei' },
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
it('rejects a wrong-language voice', () => {
|
|
46
|
+
// @ts-expect-error — fi voice is not assignable to VoiceForLang<'en'>
|
|
47
|
+
assertType(voices.fi.Martti);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=caption.test-d.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caption.test-d.js","sourceRoot":"","sources":["../../src/caption.test-d.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,cAAc,CAAC;YACb,EAAE,EAAE;gBACF,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI;gBACrB,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE;aAC3C;YACD,EAAE,EAAE;gBACF,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM;gBACvB,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE;aAC9C;SACF,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,cAAc,CAAC;YACb,EAAE,EAAE;gBACF,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI;gBACrB,QAAQ,EAAE;oBACR,KAAK,EAAE,OAAO;oBACd,IAAI,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE;iBAC/C;aACF;YACD,EAAE,EAAE;gBACF,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM;gBACvB,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE;aAC1C;SACF,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,cAAc,CAAC;YACb,EAAE,EAAE;gBACF,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI;gBACrB,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE;aAC3C;YACD,EAAE,EAAE;gBACF,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM;gBACvB,wCAAwC;gBACxC,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;aAC3B;SACF,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,sEAAsE;QACtE,UAAU,CAAqB,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ScreenCIConfig, ExtendedScreenCIConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Defines a screenci configuration file.
|
|
4
|
+
*
|
|
5
|
+
* Extends Playwright's config with screenci-specific options and enforces
|
|
6
|
+
* settings required for reliable video recording. Some Playwright options
|
|
7
|
+
* are locked and cannot be set — `workers`, `fullyParallel`, `retries`,
|
|
8
|
+
* `testDir`, and `testMatch`. Attempting to set them throws at startup.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* Minimal — all options have sensible defaults:
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { defineConfig } from 'screenci'
|
|
14
|
+
* export default defineConfig({})
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* Full config:
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { defineConfig } from 'screenci'
|
|
21
|
+
*
|
|
22
|
+
* export default defineConfig({
|
|
23
|
+
* projectName: 'my-project',
|
|
24
|
+
* videoDir: './videos',
|
|
25
|
+
* use: {
|
|
26
|
+
* baseURL: 'https://app.example.com',
|
|
27
|
+
* recordOptions: {
|
|
28
|
+
* aspectRatio: '16:9', // '16:9' | '9:16' | '1:1' | '4:3' | ...
|
|
29
|
+
* quality: '1080p', // '720p' | '1080p' | '1440p' | '2160p'
|
|
30
|
+
* fps: 30, // 24 | 30 | 60
|
|
31
|
+
* },
|
|
32
|
+
* trace: 'retain-on-failure',
|
|
33
|
+
* sendTraces: true,
|
|
34
|
+
* },
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @param config - screenci configuration options
|
|
39
|
+
* @returns Extended Playwright configuration with enforced sequential execution settings
|
|
40
|
+
*/
|
|
41
|
+
export declare function defineConfig(config: ScreenCIConfig): ExtendedScreenCIConfig;
|
|
42
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAyBxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,sBAAsB,CA+H3E"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { DEFAULT_VIDEO_DIR, DEFAULT_TRACE, DEFAULT_SEND_TRACES, DEFAULT_TIMEOUT, DEFAULT_ACTION_TIMEOUT, DEFAULT_NAVIGATION_TIMEOUT, } from './defaults.js';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, resolve } from 'path';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
// Resolve reporter path - try .js first (production), then .ts (development)
|
|
8
|
+
const reporterPathJs = resolve(__dirname, 'reporter.js');
|
|
9
|
+
const reporterPathTs = resolve(__dirname, 'reporter.ts');
|
|
10
|
+
const reporterPath = existsSync(reporterPathJs)
|
|
11
|
+
? reporterPathJs
|
|
12
|
+
: reporterPathTs;
|
|
13
|
+
/**
|
|
14
|
+
* Defines a screenci configuration file.
|
|
15
|
+
*
|
|
16
|
+
* Extends Playwright's config with screenci-specific options and enforces
|
|
17
|
+
* settings required for reliable video recording. Some Playwright options
|
|
18
|
+
* are locked and cannot be set — `workers`, `fullyParallel`, `retries`,
|
|
19
|
+
* `testDir`, and `testMatch`. Attempting to set them throws at startup.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* Minimal — all options have sensible defaults:
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { defineConfig } from 'screenci'
|
|
25
|
+
* export default defineConfig({})
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* Full config:
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { defineConfig } from 'screenci'
|
|
32
|
+
*
|
|
33
|
+
* export default defineConfig({
|
|
34
|
+
* projectName: 'my-project',
|
|
35
|
+
* videoDir: './videos',
|
|
36
|
+
* use: {
|
|
37
|
+
* baseURL: 'https://app.example.com',
|
|
38
|
+
* recordOptions: {
|
|
39
|
+
* aspectRatio: '16:9', // '16:9' | '9:16' | '1:1' | '4:3' | ...
|
|
40
|
+
* quality: '1080p', // '720p' | '1080p' | '1440p' | '2160p'
|
|
41
|
+
* fps: 30, // 24 | 30 | 60
|
|
42
|
+
* },
|
|
43
|
+
* trace: 'retain-on-failure',
|
|
44
|
+
* sendTraces: true,
|
|
45
|
+
* },
|
|
46
|
+
* })
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @param config - screenci configuration options
|
|
50
|
+
* @returns Extended Playwright configuration with enforced sequential execution settings
|
|
51
|
+
*/
|
|
52
|
+
export function defineConfig(config) {
|
|
53
|
+
// Add the video name validator reporter if not already present
|
|
54
|
+
const existingReporters = config.reporter
|
|
55
|
+
? Array.isArray(config.reporter)
|
|
56
|
+
? config.reporter
|
|
57
|
+
: [config.reporter]
|
|
58
|
+
: ['list'];
|
|
59
|
+
// Check if our validator is already added
|
|
60
|
+
const hasValidator = existingReporters.some((r) => {
|
|
61
|
+
if (Array.isArray(r)) {
|
|
62
|
+
return r[0] === reporterPath || r[0]?.toString().endsWith('reporter.js');
|
|
63
|
+
}
|
|
64
|
+
return r === reporterPath || r?.toString().endsWith('reporter.js');
|
|
65
|
+
});
|
|
66
|
+
// Convert all reporters to tuple format for Playwright validation
|
|
67
|
+
const normalizedReporters = existingReporters.map((r) => Array.isArray(r) ? r : [r]);
|
|
68
|
+
// Check if list reporter is present for CLI output
|
|
69
|
+
const hasListReporter = normalizedReporters.some((r) => {
|
|
70
|
+
const name = Array.isArray(r) ? r[0] : r;
|
|
71
|
+
return name === 'list' || name === 'line' || name === 'dot';
|
|
72
|
+
});
|
|
73
|
+
// Build reporters array with proper typing - all reporters must be tuples
|
|
74
|
+
// Add list reporter if no CLI reporter is present
|
|
75
|
+
const reportersWithCLI = hasListReporter
|
|
76
|
+
? normalizedReporters
|
|
77
|
+
: [['list'], ...normalizedReporters];
|
|
78
|
+
const reporters = hasValidator
|
|
79
|
+
? reportersWithCLI
|
|
80
|
+
: [[reporterPath, {}], ...reportersWithCLI];
|
|
81
|
+
// Runtime check for viewport (check before testDir since test objects may not have testDir defined)
|
|
82
|
+
if (config.use && 'viewport' in config.use) {
|
|
83
|
+
throw new Error('screenci does not support "viewport" option. ' +
|
|
84
|
+
'The viewport is automatically set based on the recordOptions.resolution. ' +
|
|
85
|
+
'Use recordOptions.resolution to control the viewport size.');
|
|
86
|
+
}
|
|
87
|
+
// Runtime check for viewport in projects
|
|
88
|
+
if (config.projects) {
|
|
89
|
+
for (const project of config.projects) {
|
|
90
|
+
if (project.use && 'viewport' in project.use) {
|
|
91
|
+
throw new Error(`screenci does not support "viewport" option in project "${project.name}". ` +
|
|
92
|
+
'The viewport is automatically set based on the recordOptions.resolution. ' +
|
|
93
|
+
'Use recordOptions.resolution to control the viewport size.');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Runtime check for testDir
|
|
98
|
+
if (Object.prototype.hasOwnProperty.call(config, 'testDir') &&
|
|
99
|
+
config.testDir !== undefined) {
|
|
100
|
+
throw new Error('screenci does not support "testDir" option. ' +
|
|
101
|
+
'Use "videoDir" instead to specify the directory containing your *.video.* files. ' +
|
|
102
|
+
'Defaults to "./videos".');
|
|
103
|
+
}
|
|
104
|
+
// Runtime check for testMatch
|
|
105
|
+
if ('testMatch' in config) {
|
|
106
|
+
throw new Error('screenci does not support "testMatch" option. ' +
|
|
107
|
+
'screenci automatically configures tests to only run *.video.* files.');
|
|
108
|
+
}
|
|
109
|
+
// Runtime check for workers
|
|
110
|
+
if ('workers' in config) {
|
|
111
|
+
throw new Error('screenci does not support "workers" option. ' +
|
|
112
|
+
'Tests must run sequentially to ensure proper video recording. ' +
|
|
113
|
+
'screenci automatically configures workers to 1.');
|
|
114
|
+
}
|
|
115
|
+
// Runtime check for fullyParallel
|
|
116
|
+
if ('fullyParallel' in config) {
|
|
117
|
+
throw new Error('screenci does not support "fullyParallel" option. ' +
|
|
118
|
+
'Tests must run sequentially to ensure proper video recording. ' +
|
|
119
|
+
'screenci automatically sets fullyParallel to false.');
|
|
120
|
+
}
|
|
121
|
+
// Runtime check for retries
|
|
122
|
+
if ('retries' in config) {
|
|
123
|
+
throw new Error('screenci does not support "retries" option. ' +
|
|
124
|
+
'Tests must succeed immediately or fail to ensure proper video recording. ' +
|
|
125
|
+
'screenci automatically sets retries to 0.');
|
|
126
|
+
}
|
|
127
|
+
const { videoDir, ...rest } = config;
|
|
128
|
+
// Force sequential execution with single worker and no retries, map videoDir to testDir
|
|
129
|
+
return {
|
|
130
|
+
testDir: videoDir ?? DEFAULT_VIDEO_DIR,
|
|
131
|
+
testMatch: '**/*.video.?(c|m)[jt]s?(x)',
|
|
132
|
+
...rest,
|
|
133
|
+
reporter: reporters,
|
|
134
|
+
use: {
|
|
135
|
+
...rest.use,
|
|
136
|
+
trace: rest.use?.trace ?? DEFAULT_TRACE,
|
|
137
|
+
sendTraces: rest.use?.sendTraces ?? DEFAULT_SEND_TRACES,
|
|
138
|
+
actionTimeout: rest.use?.actionTimeout ?? DEFAULT_ACTION_TIMEOUT,
|
|
139
|
+
navigationTimeout: rest.use?.navigationTimeout ?? DEFAULT_NAVIGATION_TIMEOUT,
|
|
140
|
+
},
|
|
141
|
+
timeout: rest.timeout ?? DEFAULT_TIMEOUT,
|
|
142
|
+
fullyParallel: false,
|
|
143
|
+
workers: 1,
|
|
144
|
+
retries: 0,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=config.js.map
|