screenci 0.0.71 → 0.0.73

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 (113) hide show
  1. package/README.md +13 -13
  2. package/dist/cli.d.ts +14 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +257 -81
  5. package/dist/cli.js.map +1 -1
  6. package/dist/docs/manifest.d.ts +3 -3
  7. package/dist/docs/manifest.js +1 -1
  8. package/dist/docs/manifest.js.map +1 -1
  9. package/dist/docs/video-sources/installation.screenci.js +13 -12
  10. package/dist/docs/video-sources/installation.screenci.js.map +1 -1
  11. package/dist/docs/video-sources/landing.screenci.js +16 -9
  12. package/dist/docs/video-sources/landing.screenci.js.map +1 -1
  13. package/dist/docs/video-sources/public-urls.screenci.js +16 -9
  14. package/dist/docs/video-sources/public-urls.screenci.js.map +1 -1
  15. package/dist/docs/video-sources/studio.screenci.js +42 -27
  16. package/dist/docs/video-sources/studio.screenci.js.map +1 -1
  17. package/dist/index.d.ts +7 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +3 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/src/asset.d.ts +82 -51
  22. package/dist/src/asset.d.ts.map +1 -1
  23. package/dist/src/asset.js +93 -46
  24. package/dist/src/asset.js.map +1 -1
  25. package/dist/src/audio.d.ts +6 -24
  26. package/dist/src/audio.d.ts.map +1 -1
  27. package/dist/src/audio.js +10 -35
  28. package/dist/src/audio.js.map +1 -1
  29. package/dist/src/builder.d.ts +214 -0
  30. package/dist/src/builder.d.ts.map +1 -0
  31. package/dist/src/builder.js +212 -0
  32. package/dist/src/builder.js.map +1 -0
  33. package/dist/src/config.d.ts +1 -1
  34. package/dist/src/config.d.ts.map +1 -1
  35. package/dist/src/config.js +13 -7
  36. package/dist/src/config.js.map +1 -1
  37. package/dist/src/cue.d.ts +40 -123
  38. package/dist/src/cue.d.ts.map +1 -1
  39. package/dist/src/cue.js +190 -30
  40. package/dist/src/cue.js.map +1 -1
  41. package/dist/src/defaults.d.ts +2 -2
  42. package/dist/src/defaults.d.ts.map +1 -1
  43. package/dist/src/defaults.js +2 -2
  44. package/dist/src/defaults.js.map +1 -1
  45. package/dist/src/events.d.ts +80 -15
  46. package/dist/src/events.d.ts.map +1 -1
  47. package/dist/src/events.js +72 -6
  48. package/dist/src/events.js.map +1 -1
  49. package/dist/src/init.d.ts.map +1 -1
  50. package/dist/src/init.js +80 -39
  51. package/dist/src/init.js.map +1 -1
  52. package/dist/src/locales.d.ts +19 -0
  53. package/dist/src/locales.d.ts.map +1 -0
  54. package/dist/src/locales.js +60 -0
  55. package/dist/src/locales.js.map +1 -0
  56. package/dist/src/localize.d.ts +135 -0
  57. package/dist/src/localize.d.ts.map +1 -0
  58. package/dist/src/localize.js +195 -0
  59. package/dist/src/localize.js.map +1 -0
  60. package/dist/src/localizeRuntime.d.ts +53 -0
  61. package/dist/src/localizeRuntime.d.ts.map +1 -0
  62. package/dist/src/localizeRuntime.js +79 -0
  63. package/dist/src/localizeRuntime.js.map +1 -0
  64. package/dist/src/mouse.d.ts.map +1 -1
  65. package/dist/src/mouse.js +6 -0
  66. package/dist/src/mouse.js.map +1 -1
  67. package/dist/src/recordingData.d.ts +5 -4
  68. package/dist/src/recordingData.d.ts.map +1 -1
  69. package/dist/src/runtimeContext.d.ts +22 -4
  70. package/dist/src/runtimeContext.d.ts.map +1 -1
  71. package/dist/src/runtimeContext.js +11 -0
  72. package/dist/src/runtimeContext.js.map +1 -1
  73. package/dist/src/runtimeMode.d.ts +47 -0
  74. package/dist/src/runtimeMode.d.ts.map +1 -1
  75. package/dist/src/runtimeMode.js +124 -0
  76. package/dist/src/runtimeMode.js.map +1 -1
  77. package/dist/src/screenshot.d.ts +56 -2
  78. package/dist/src/screenshot.d.ts.map +1 -1
  79. package/dist/src/screenshot.js +49 -10
  80. package/dist/src/screenshot.js.map +1 -1
  81. package/dist/src/stillCapture.d.ts +10 -4
  82. package/dist/src/stillCapture.d.ts.map +1 -1
  83. package/dist/src/stillCapture.js +11 -0
  84. package/dist/src/stillCapture.js.map +1 -1
  85. package/dist/src/studio.d.ts +55 -22
  86. package/dist/src/studio.d.ts.map +1 -1
  87. package/dist/src/studio.js +38 -12
  88. package/dist/src/studio.js.map +1 -1
  89. package/dist/src/types.d.ts +20 -7
  90. package/dist/src/types.d.ts.map +1 -1
  91. package/dist/src/types.js +1 -1
  92. package/dist/src/types.js.map +1 -1
  93. package/dist/src/video.d.ts +149 -13
  94. package/dist/src/video.d.ts.map +1 -1
  95. package/dist/src/video.js +98 -25
  96. package/dist/src/video.js.map +1 -1
  97. package/dist/src/voiceConfig.d.ts +110 -0
  98. package/dist/src/voiceConfig.d.ts.map +1 -0
  99. package/dist/src/voiceConfig.js +2 -0
  100. package/dist/src/voiceConfig.js.map +1 -0
  101. package/dist/src/voices.d.ts +27 -23
  102. package/dist/src/voices.d.ts.map +1 -1
  103. package/dist/src/voices.js +7 -4
  104. package/dist/src/voices.js.map +1 -1
  105. package/dist/tsconfig.tsbuildinfo +1 -1
  106. package/package.json +1 -1
  107. package/skills/screenci/SKILL.md +51 -47
  108. package/skills/screenci/references/init.md +1 -1
  109. package/skills/screenci/references/record.md +6 -6
  110. package/dist/docs/video-sources/product-pitch.screenci.d.ts +0 -2
  111. package/dist/docs/video-sources/product-pitch.screenci.d.ts.map +0 -1
  112. package/dist/docs/video-sources/product-pitch.screenci.js +0 -136
  113. package/dist/docs/video-sources/product-pitch.screenci.js.map +0 -1
@@ -1,21 +1,29 @@
1
- import { autoZoom, createNarration, hide, resetZoom, video, voices, zoomTo, } from 'screenci';
1
+ import { hide, resetZoom, video, voices, zoomTo } from 'screenci';
2
+ video.use({
3
+ renderOptions: {
4
+ narration: {
5
+ voice: { name: voices.Sophie, style: 'Friendly product guide' },
6
+ },
7
+ },
8
+ });
9
+ const appUrl = process.env.SCREENCI_APP_URL ?? 'https://app.screenci.com/';
2
10
  // Studio walkthrough recorded against the ScreenCI app (app.screenci.com), for
3
11
  // the "Remix in Studio on the web" tile on the landing page. Studio lives behind
4
12
  // auth, so recording this needs a logged-in session: set SCREENCI_APP_STORAGE_STATE
5
13
  // to a Playwright storageState JSON (see screenci.config.ts). The walkthrough is
6
14
  // self-contained: it opens the first project and video, then enters Studio.
7
- const narration = createNarration({
8
- voice: { name: voices.Sophie, style: 'Friendly product guide' },
9
- en: {
10
- intro: 'Open any finished video in Studio to edit it right in the browser.',
11
- edit: 'Change narration, add a language, or restyle render options, then render a new version. No code, no re-recording.',
12
- },
13
- es: {
14
- intro: 'Abre cualquier video terminado en Studio para editarlo directamente en el navegador.',
15
- edit: 'Cambia la narracion, agrega un idioma o ajusta las opciones de render, y genera una nueva version. Sin codigo y sin volver a grabar.',
15
+ video.localize({
16
+ narration: {
17
+ en: {
18
+ intro: 'Open any finished video in Studio to edit it right in the browser.',
19
+ edit: 'Review render options in the Studio panel, then render a new version. No code, no re-recording.',
20
+ },
21
+ es: {
22
+ intro: 'Abre cualquier video terminado en Studio para editarlo directamente en el navegador.',
23
+ edit: 'Revisa las opciones de render en el panel de Studio y genera una nueva version. Sin codigo y sin volver a grabar.',
24
+ },
16
25
  },
17
- });
18
- video('Studio web editing', async ({ page }) => {
26
+ })('Studio web editing', async ({ page, narration }) => {
19
27
  // Studio lives behind auth, so this recording only works with a logged-in
20
28
  // session (SCREENCI_APP_STORAGE_STATE, see screenci.config.ts). When it is not
21
29
  // configured (e.g. CI without the session secret), skip instead of timing out
@@ -23,35 +31,42 @@ video('Studio web editing', async ({ page }) => {
23
31
  video.skip(!process.env.SCREENCI_APP_STORAGE_STATE, 'Requires SCREENCI_APP_STORAGE_STATE (a logged-in app session).');
24
32
  // Navigate from the dashboard into Studio without showing it in the recording.
25
33
  await hide(async () => {
26
- await page.goto('https://app.screenci.com/');
34
+ await page.goto(appUrl);
27
35
  await page.waitForLoadState('networkidle');
28
36
  await page
29
37
  .getByRole('heading', { name: 'Projects' })
30
38
  .waitFor({ timeout: 30000 });
31
39
  await page.getByTestId('projects-list').getByRole('link').first().click();
32
- // Open the first video in the project, then enter Studio.
33
- await page.getByRole('link', { name: /video/i }).first().click();
40
+ // Open the first video, then its first language page where Studio is shown
41
+ // inline below the preview.
42
+ await page.locator('a[href*="/video/"]').first().click();
34
43
  await page
35
- .getByRole('heading', { name: /languages/i })
44
+ .getByRole('heading', { name: /language versions/i })
36
45
  .waitFor({ timeout: 15000 });
37
- await page.getByRole('link', { name: /open in studio/i }).click();
46
+ await page
47
+ .getByRole('link', { name: /^open /i })
48
+ .first()
49
+ .click();
38
50
  await page
39
51
  .getByRole('heading', { name: /^studio$/i })
40
52
  .waitFor({ timeout: 30000 });
41
53
  });
42
54
  await narration.intro();
43
- // Show adding a language: a visible edit that does not trigger a render.
55
+ // Show the Studio panel without depending on a specific video having editable
56
+ // narration cues in the seeded dev data.
44
57
  await narration.edit.start();
45
- await autoZoom(async () => {
46
- const addLanguage = page.getByPlaceholder(/add language/i);
47
- await addLanguage.scrollIntoViewIfNeeded();
48
- await addLanguage.click();
49
- await addLanguage.fill('French');
50
- });
51
- await narration.edit.end();
52
- // Highlight the Render action without clicking it (avoid kicking off a render).
53
- await zoomTo(page.getByRole('button', { name: /^render( [a-z-]+)?$/i }));
58
+ await zoomTo(page.getByRole('heading', { name: /^studio$/i }));
54
59
  await page.waitForTimeout(900);
55
60
  await resetZoom();
61
+ await narration.edit.end();
62
+ const createOneOffButton = page.getByRole('button', {
63
+ name: 'Create one-off version',
64
+ });
65
+ if (await createOneOffButton.isVisible().catch(() => false)) {
66
+ // Highlight the render entry point without clicking it.
67
+ await zoomTo(createOneOffButton);
68
+ await page.waitForTimeout(900);
69
+ await resetZoom();
70
+ }
56
71
  });
57
72
  //# sourceMappingURL=studio.screenci.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"studio.screenci.js","sourceRoot":"","sources":["../../../docs/video-sources/studio.screenci.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"studio.screenci.js","sourceRoot":"","sources":["../../../docs/video-sources/studio.screenci.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -6,20 +6,22 @@ export { video } from './src/video.js';
6
6
  export { screenshot } from './src/screenshot.js';
7
7
  export type { CropTarget, CropRegion, CropOptions, ScreenshotCrop, ScreenshotCropRecord, ResolvedCropPadding, } from './src/crop.js';
8
8
  export type { ScreenCIPage, ScreenCILocator } from './src/types.js';
9
- export { createNarration, createStudioNarration } from './src/cue.js';
10
9
  export type { NarrationCue, Cues, TopLevelVoiceConfig } from './src/cue.js';
11
- export { isStudioRenderOptions } from './src/studio.js';
12
- export type { StudioRenderOptionsSentinel } from './src/studio.js';
10
+ export type { LangNarrationOverride } from './src/voiceConfig.js';
11
+ export type { EachVariant, VideoBuilder } from './src/builder.js';
12
+ export type { LocalizeSpec, LocalizeMode, LocalizeVoiceSpec, NarrationByLang, TextByLang, VoiceConfig, LocalizeNarrationValue, } from './src/localize.js';
13
+ export { resolveLocaleForLanguage, DEFAULT_LANGUAGE_LOCALES, } from './src/locales.js';
14
+ export type { StudioDeclaration } from './src/studio.js';
13
15
  export { hide } from './src/hide.js';
14
16
  export { speed } from './src/speed.js';
15
17
  export { time } from './src/time.js';
16
18
  export { autoZoom } from './src/autoZoom.js';
17
19
  export { zoomTo, resetZoom } from './src/manualZoom.js';
18
- export { createOverlays, createStudioOverlays, setOverlayCss, MAX_AUDIO_LEVEL, } from './src/asset.js';
20
+ export { createOverlays, setOverlayCss, MAX_AUDIO_LEVEL } from './src/asset.js';
19
21
  export type { OverlayController, OverlayConfig, OverlayConfigFactory, OverlayInput, OverlayInputOrFactory, Overlays, OverlayControllerFor, ReactElementLike, OverlayPlacement, } from './src/asset.js';
20
22
  export { overlayRect } from './src/overlayRect.js';
21
23
  export type { OverlayRect, OverlayRectOptions } from './src/overlayRect.js';
22
- export { createAudio, createStudioAudio } from './src/audio.js';
24
+ export { createAudio } from './src/audio.js';
23
25
  export type { AudioController, AudioConfig, AudioInput, AudioTracks, } from './src/audio.js';
24
26
  export type { ZoomTarget, ZoomTargetPoint } from './src/manualZoom.js';
25
27
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,WAAW,EACX,GAAG,EACH,OAAO,EACP,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,MAAM,EACN,kBAAkB,GACnB,MAAM,gBAAgB,CAAA;AAGvB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACpD,YAAY,EACV,QAAQ,EACR,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,SAAS,GACV,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAG9C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,YAAY,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACrE,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACvD,YAAY,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,eAAe,GAChB,MAAM,gBAAgB,CAAA;AACvB,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAC/D,YAAY,EACV,eAAe,EACf,WAAW,EACX,UAAU,EACV,WAAW,GACZ,MAAM,gBAAgB,CAAA;AACvB,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,WAAW,EACX,GAAG,EACH,OAAO,EACP,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,MAAM,EACN,kBAAkB,GACnB,MAAM,gBAAgB,CAAA;AAGvB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACpD,YAAY,EACV,QAAQ,EACR,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,SAAS,GACV,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAG9C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,YAAY,EACV,UAAU,EACV,UAAU,EACV,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAKnE,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAC3E,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AACjE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACjE,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,UAAU,EACV,WAAW,EACX,sBAAsB,GACvB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAC/E,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,YAAY,EACV,eAAe,EACf,WAAW,EACX,UAAU,EACV,WAAW,GACZ,MAAM,gBAAgB,CAAA;AACvB,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA"}
package/dist/index.js CHANGED
@@ -5,14 +5,13 @@ export { defineConfig } from './src/config.js';
5
5
  // Re-export video fixture and narration cue
6
6
  export { video } from './src/video.js';
7
7
  export { screenshot } from './src/screenshot.js';
8
- export { createNarration, createStudioNarration } from './src/cue.js';
9
- export { isStudioRenderOptions } from './src/studio.js';
8
+ export { resolveLocaleForLanguage, DEFAULT_LANGUAGE_LOCALES, } from './src/locales.js';
10
9
  export { hide } from './src/hide.js';
11
10
  export { speed } from './src/speed.js';
12
11
  export { time } from './src/time.js';
13
12
  export { autoZoom } from './src/autoZoom.js';
14
13
  export { zoomTo, resetZoom } from './src/manualZoom.js';
15
- export { createOverlays, createStudioOverlays, setOverlayCss, MAX_AUDIO_LEVEL, } from './src/asset.js';
14
+ export { createOverlays, setOverlayCss, MAX_AUDIO_LEVEL } from './src/asset.js';
16
15
  export { overlayRect } from './src/overlayRect.js';
17
- export { createAudio, createStudioAudio } from './src/audio.js';
16
+ export { createAudio } from './src/audio.js';
18
17
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAiBA,mBAAmB;AACnB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AASpD,4BAA4B;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE9C,4CAA4C;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAUhD,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAErE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,eAAe,GAChB,MAAM,gBAAgB,CAAA;AAYvB,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAElD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAiBA,mBAAmB;AACnB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AASpD,4BAA4B;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE9C,4CAA4C;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AA0BhD,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAY/E,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA"}
@@ -15,30 +15,15 @@ export type ReactElementLike = {
15
15
  key?: string | null;
16
16
  };
17
17
  /**
18
- * Display options for an overlay. Placement fields are flat (not nested) and use
19
- * CSS pixels in the recording viewport (the same space as Playwright's
20
- * `boundingBox()`, `page.mouse`, and `viewportSize()`), each defaulting
21
- * independently: `relativeTo: 'recording'`, `x: 0`, `y: 0`. Provide exactly one
22
- * of `width`/`height` (the other follows the source aspect, or `aspectRatio`).
23
- * When no placement field is set, the overlay fills the recording area.
18
+ * Placement and capture fields shared by every overlay variant. Placement is
19
+ * flat (not nested) and uses CSS pixels in the recording viewport (the same
20
+ * space as Playwright's `boundingBox()`, `page.mouse`, and `viewportSize()`),
21
+ * each defaulting independently: `relativeTo: 'recording'`, `x: 0`, `y: 0`.
22
+ * Provide exactly one of `width`/`height` (the other follows the source aspect,
23
+ * or `aspectRatio`). When no placement field is set, the overlay fills the
24
+ * recording area.
24
25
  */
25
- export type OverlayConfig = {
26
- /** File path: `.html` (rendered), `.svg`/`.png` (image), or `.mp4` (video). */
27
- path?: string;
28
- /**
29
- * A React element, rendered to a transparent PNG. Use this for overlays built
30
- * in JSX. Provide exactly one source: `path`, `element`, or `html`.
31
- */
32
- element?: ReactElementLike;
33
- /**
34
- * An inline HTML fragment, rendered to a transparent PNG. Use this when you
35
- * want plain HTML without a React dependency or a separate `.html` file. It
36
- * must be a fragment (for example `'<div class="badge">New</div>'`), never a
37
- * full document: `<!doctype>`, `<html>`, `<head>`, and `<body>` tags are
38
- * rejected because screenci wraps the markup in its own document. Provide
39
- * exactly one source: `path`, `element`, or `html`.
40
- */
41
- html?: string;
26
+ type OverlayCommon = {
42
27
  /** Reference box for placement coordinates. Defaults to `'recording'`. */
43
28
  relativeTo?: 'screen' | 'recording';
44
29
  /** Left edge in CSS px of the recording viewport. Defaults to `0`. */
@@ -54,13 +39,18 @@ export type OverlayConfig = {
54
39
  * you provide, instead of the source's intrinsic aspect. Optional.
55
40
  */
56
41
  aspectRatio?: number;
57
- /** Fill the whole output frame. Overrides x/y/width/height. */
58
- fullScreen?: boolean;
42
+ /**
43
+ * Fill the whole frame instead of positioning the overlay. `'recording'`
44
+ * fills the recording area (the same as omitting every placement field);
45
+ * `'screen'` fills the entire output frame, including any padding around the
46
+ * recording. Overrides `x`/`y`/`width`/`height`.
47
+ */
48
+ fill?: 'recording' | 'screen';
59
49
  /**
60
50
  * Position the overlay over a live element, captured at recording time from
61
51
  * the locator's bounding box. The overlay is sized to that box (plus
62
52
  * {@link margin}) and fills it, so it frames the element exactly. Overrides
63
- * `x`/`y`/`width`/`height`/`relativeTo`/`fullScreen` (placement is always
53
+ * `x`/`y`/`width`/`height`/`relativeTo`/`fill` (placement is always
64
54
  * recording-relative). HTML files, inline `html`, and React elements only;
65
55
  * your content should fill its box (for example `width:100%;height:100%`).
66
56
  */
@@ -101,13 +91,16 @@ export type OverlayConfig = {
101
91
  * HTML files and React elements only.
102
92
  */
103
93
  capturePadding?: number;
94
+ };
95
+ /** Fields that only apply to a `.mp4` video overlay (a file `path`). */
96
+ type OverlayVideoFields = {
104
97
  /**
105
98
  * Soundtrack level for `.mp4` overlays as a linear gain. `1` (the default)
106
99
  * plays the source at its natural level, `0` mutes it, and values above `1`
107
100
  * boost it (e.g. `2` is twice the natural level). Capped at
108
101
  * {@link MAX_AUDIO_LEVEL}.
109
102
  */
110
- audio?: number;
103
+ volume?: number;
111
104
  /**
112
105
  * Playback-rate multiplier for `.mp4` overlays. `2` plays the clip (and its
113
106
  * audio) twice as fast, `0.5` at half speed; `1` (the default) is the natural
@@ -126,6 +119,59 @@ export type OverlayConfig = {
126
119
  */
127
120
  time?: number;
128
121
  };
122
+ /**
123
+ * An overlay drawn from a file `path`: `.svg`/`.png` (image), `.mp4` (video),
124
+ * or `.html` (rendered). Only this variant accepts the {@link OverlayVideoFields}
125
+ * (`volume`/`speed`/`time`), which apply to `.mp4` files; they are rejected at
126
+ * recording time for image and HTML files.
127
+ */
128
+ export type FileOverlayConfig = OverlayCommon & OverlayVideoFields & {
129
+ /** File path: `.html` (rendered), `.svg`/`.png` (image), or `.mp4` (video). */
130
+ path: string;
131
+ element?: never;
132
+ html?: never;
133
+ };
134
+ /**
135
+ * An overlay rendered from a React `element` to a transparent PNG (or animated
136
+ * clip). Use this for overlays built in JSX. Video-only fields
137
+ * (`volume`/`speed`/`time`) do not apply.
138
+ */
139
+ export type ElementOverlayConfig = OverlayCommon & {
140
+ /** A React element, rendered to a transparent PNG. */
141
+ element: ReactElementLike;
142
+ path?: never;
143
+ html?: never;
144
+ volume?: never;
145
+ speed?: never;
146
+ time?: never;
147
+ };
148
+ /**
149
+ * An overlay rendered from an inline `html` fragment to a transparent PNG (or
150
+ * animated clip). Use this when you want plain HTML without a React dependency
151
+ * or a separate `.html` file. The markup must be a single-rooted fragment (for
152
+ * example `'<div class="badge">New</div>'`), never a full document: it must
153
+ * contain exactly one top-level element, and `<!doctype>`, `<html>`, `<head>`,
154
+ * and `<body>` tags are rejected because screenci wraps the markup in its own
155
+ * document. Video-only fields (`volume`/`speed`/`time`) do not apply.
156
+ */
157
+ export type HtmlOverlayConfig = OverlayCommon & {
158
+ /** An inline HTML fragment (single root element), rendered to a PNG. */
159
+ html: string;
160
+ path?: never;
161
+ element?: never;
162
+ volume?: never;
163
+ speed?: never;
164
+ time?: never;
165
+ };
166
+ /**
167
+ * Display options for an overlay. An overlay draws its content from exactly one
168
+ * source, which selects the variant: a file {@link FileOverlayConfig.path}, a
169
+ * React {@link ElementOverlayConfig.element}, or an inline
170
+ * {@link HtmlOverlayConfig.html} fragment. The `path` variant additionally
171
+ * accepts the video-only `volume`/`speed`/`time` fields (for `.mp4` files); the
172
+ * others reject them at compile time.
173
+ */
174
+ export type OverlayConfig = FileOverlayConfig | ElementOverlayConfig | HtmlOverlayConfig;
129
175
  /**
130
176
  * Upper bound for an audio level (linear gain). `4` is +12 dB, plenty of
131
177
  * headroom for a boost while guarding against accidental extreme distortion.
@@ -236,7 +282,7 @@ export type Overlays<T extends Record<string, OverlayInputOrFactory>> = {
236
282
  * badge: <Badge label="New" />, // React element
237
283
  * note: { html: '<div class="note">Tip</div>', x: 1340, y: 110, width: 380 },
238
284
  * logo: { path: 'logo.png', x: 96, y: 96, width: 240 },
239
- * intro: { path: 'intro.mp4', fullScreen: true },
285
+ * intro: { path: 'intro.mp4', fill: 'screen' },
240
286
  * })
241
287
  *
242
288
  * video('Product demo', async ({ page }) => {
@@ -254,31 +300,16 @@ export type Overlays<T extends Record<string, OverlayInputOrFactory>> = {
254
300
  */
255
301
  export declare function createOverlays<const T extends Record<string, OverlayInputOrFactory>>(overlays: T): Overlays<T>;
256
302
  /**
257
- * Creates typed overlay controllers whose files and display options are
258
- * configured on the ScreenCI Studio page instead of in code. Business tier
259
- * only.
260
- *
261
- * Each key becomes a callable overlay controller with the same timeline
262
- * behavior as {@link createOverlays} controllers, including `start()`/`end()`.
263
- * The file (`.svg`, `.png`, or `.mp4`), placement, image duration, and video
264
- * audio level all come from Studio.
303
+ * Builds overlay controllers for Studio-managed overlays declared via
304
+ * `video.studio({ overlays: [...] })`. Their file (`.svg`, `.png`, or `.mp4`),
305
+ * placement, image duration, and video audio level are configured on the
306
+ * ScreenCI Studio page instead of in code. Each name becomes a callable overlay
307
+ * controller with the same timeline behavior as a {@link createOverlays}
308
+ * controller, including `start()`/`end()`.
265
309
  *
266
- * On the first upload of a studio-mode video, rendering is held until the
267
- * video is configured in Studio (the CLI prints a direct link). Later uploads
268
- * reuse the saved Studio configuration automatically.
269
- *
270
- * @example
271
- * ```ts
272
- * const overlays = createStudioOverlays('intro', 'logo')
273
- *
274
- * video('Product demo', async ({ page }) => {
275
- * await overlays.intro()
276
- * await page.goto('/dashboard')
277
- * await overlays.logo()
278
- * })
279
- * ```
310
+ * Internal: the `overlays` fixture exposes these to the test body.
280
311
  */
281
- export declare function createStudioOverlays<const K extends readonly [string, ...string[]]>(...keys: K): Record<K[number], OverlayController>;
312
+ export declare function buildStudioOverlays(names: readonly string[]): Record<string, OverlayController>;
282
313
  /**
283
314
  * Upper bound for a playback-speed multiplier. `16` is already an extreme rate;
284
315
  * the cap guards against accidental runaway values producing degenerate output.
@@ -1 +1 @@
1
- {"version":3,"file":"asset.d.ts","sourceRoot":"","sources":["../../src/asset.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"asset.d.ts","sourceRoot":"","sources":["../../src/asset.ts"],"names":[],"mappings":"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"}
package/dist/src/asset.js CHANGED
@@ -90,7 +90,7 @@ async function validateAssetPath(assetPath, testFilePath) {
90
90
  * badge: <Badge label="New" />, // React element
91
91
  * note: { html: '<div class="note">Tip</div>', x: 1340, y: 110, width: 380 },
92
92
  * logo: { path: 'logo.png', x: 96, y: 96, width: 240 },
93
- * intro: { path: 'intro.mp4', fullScreen: true },
93
+ * intro: { path: 'intro.mp4', fill: 'screen' },
94
94
  * })
95
95
  *
96
96
  * video('Product demo', async ({ page }) => {
@@ -157,7 +157,7 @@ function buildOverlayFromConfig(name, config) {
157
157
  hasHtml,
158
158
  hasPath,
159
159
  });
160
- const fullScreen = config.fullScreen ?? false;
160
+ const fullScreen = config.fill === 'screen';
161
161
  const animate = config.animate === true;
162
162
  if (config.fps !== undefined && !animate) {
163
163
  throw new Error(`[screenci] Overlay "${name}" sets "fps" without "animate: true". "fps" only applies to animated overlays.`);
@@ -221,8 +221,8 @@ function buildOverlayFromConfig(name, config) {
221
221
  }
222
222
  // File-backed image / video overlays.
223
223
  if (extension === '.svg' || extension === '.png') {
224
- if (config.audio !== undefined) {
225
- throw new Error(`[screenci] Overlay "${name}" (${path}) is an image and must not provide audio. Use durationMs instead.`);
224
+ if (config.volume !== undefined) {
225
+ throw new Error(`[screenci] Overlay "${name}" (${path}) is an image and must not provide volume. Use durationMs instead.`);
226
226
  }
227
227
  if (config.durationMs !== undefined) {
228
228
  validateDurationMs(name, path, config.durationMs);
@@ -240,11 +240,11 @@ function buildOverlayFromConfig(name, config) {
240
240
  if (config.durationMs !== undefined) {
241
241
  throw new Error(`[screenci] Overlay "${name}" (${path}) is a video and must not provide durationMs. Its natural media duration is used instead.`);
242
242
  }
243
- if (config.audio !== undefined &&
244
- (!Number.isFinite(config.audio) ||
245
- config.audio < 0 ||
246
- config.audio > MAX_AUDIO_LEVEL)) {
247
- throw new Error(`[screenci] Overlay "${name}" (${path}) must provide a finite audio value between 0 and ${MAX_AUDIO_LEVEL} for .mp4 overlays. 1 is the natural level, 0 is silent, and values above 1 boost it.`);
243
+ if (config.volume !== undefined &&
244
+ (!Number.isFinite(config.volume) ||
245
+ config.volume < 0 ||
246
+ config.volume > MAX_AUDIO_LEVEL)) {
247
+ throw new Error(`[screenci] Overlay "${name}" (${path}) must provide a finite volume between 0 and ${MAX_AUDIO_LEVEL} for .mp4 overlays. 1 is the natural level, 0 is silent, and values above 1 boost it.`);
248
248
  }
249
249
  validateSpeedTime(`Overlay "${name}" (${path})`, config.speed, config.time);
250
250
  registeredAssetPaths.add(path);
@@ -253,7 +253,7 @@ function buildOverlayFromConfig(name, config) {
253
253
  path,
254
254
  ...(placement !== undefined && { placement }),
255
255
  fullScreen,
256
- ...(config.audio !== undefined && { audio: config.audio }),
256
+ ...(config.volume !== undefined && { audio: config.volume }),
257
257
  ...(config.speed !== undefined && { speed: config.speed }),
258
258
  ...(config.time !== undefined && { time: config.time }),
259
259
  });
@@ -283,6 +283,71 @@ function validateInlineHtmlFragment(name, html) {
283
283
  throw new Error(`[screenci] Overlay "${name}" inline "html" must be a fragment, not a full HTML document. Remove the ${label} tag; screenci wraps the markup in a document for you.`);
284
284
  }
285
285
  }
286
+ validateSingleRootElement(name, html);
287
+ }
288
+ /**
289
+ * HTML void elements: they never have a closing tag, so they do not open a
290
+ * nesting level when counting top-level nodes.
291
+ */
292
+ const VOID_ELEMENTS = new Set([
293
+ 'area',
294
+ 'base',
295
+ 'br',
296
+ 'col',
297
+ 'embed',
298
+ 'hr',
299
+ 'img',
300
+ 'input',
301
+ 'link',
302
+ 'meta',
303
+ 'param',
304
+ 'source',
305
+ 'track',
306
+ 'wbr',
307
+ ]);
308
+ /**
309
+ * Ensures an inline `html` fragment has exactly one top-level (root) element and
310
+ * no loose top-level text, so it wraps cleanly into screenci's overlay document
311
+ * and sizes predictably. Multiple siblings (for example `<div/><div/>`) or stray
312
+ * text outside the root are rejected. The markup inside the root may be anything,
313
+ * including `<script>`/`<style>`, which are left to the overlay renderer.
314
+ *
315
+ * This is a lightweight tag scanner, not a full HTML parser: it tracks nesting
316
+ * depth across opening, closing, void, and self-closing tags (skipping comments
317
+ * and quoted attribute values) which covers ordinary fragment markup.
318
+ */
319
+ function validateSingleRootElement(name, html) {
320
+ const fail = () => {
321
+ throw new Error(`[screenci] Overlay "${name}" inline "html" must contain a single root element (for example '<div class="badge">New</div>'). Wrap multiple top-level nodes in one container.`);
322
+ };
323
+ // Drop comments so they never count as top-level content.
324
+ const stripped = html.replace(/<!--[\s\S]*?-->/g, '');
325
+ const tagRe = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)((?:[^>"']|"[^"]*"|'[^']*')*?)(\/?)>/g;
326
+ let depth = 0;
327
+ let rootElements = 0;
328
+ let lastIndex = 0;
329
+ let match;
330
+ while ((match = tagRe.exec(stripped)) !== null) {
331
+ const before = stripped.slice(lastIndex, match.index);
332
+ if (depth === 0 && before.trim().length > 0)
333
+ fail();
334
+ lastIndex = match.index + match[0].length;
335
+ const isClosing = match[1] === '/';
336
+ const tagName = match[2].toLowerCase();
337
+ const selfClosing = match[4] === '/';
338
+ if (isClosing) {
339
+ depth = Math.max(0, depth - 1);
340
+ continue;
341
+ }
342
+ if (depth === 0)
343
+ rootElements += 1;
344
+ if (!selfClosing && !VOID_ELEMENTS.has(tagName))
345
+ depth += 1;
346
+ }
347
+ if (stripped.slice(lastIndex).trim().length > 0 && depth === 0)
348
+ fail();
349
+ if (rootElements !== 1)
350
+ fail();
286
351
  }
287
352
  async function renderElementToMarkup(name, element) {
288
353
  let reactDomServer;
@@ -347,41 +412,19 @@ async function readHtmlOverlayFile(path) {
347
412
  return readFile(resolved, 'utf-8');
348
413
  }
349
414
  /**
350
- * Creates typed overlay controllers whose files and display options are
351
- * configured on the ScreenCI Studio page instead of in code. Business tier
352
- * only.
415
+ * Builds overlay controllers for Studio-managed overlays declared via
416
+ * `video.studio({ overlays: [...] })`. Their file (`.svg`, `.png`, or `.mp4`),
417
+ * placement, image duration, and video audio level are configured on the
418
+ * ScreenCI Studio page instead of in code. Each name becomes a callable overlay
419
+ * controller with the same timeline behavior as a {@link createOverlays}
420
+ * controller, including `start()`/`end()`.
353
421
  *
354
- * Each key becomes a callable overlay controller with the same timeline
355
- * behavior as {@link createOverlays} controllers, including `start()`/`end()`.
356
- * The file (`.svg`, `.png`, or `.mp4`), placement, image duration, and video
357
- * audio level all come from Studio.
358
- *
359
- * On the first upload of a studio-mode video, rendering is held until the
360
- * video is configured in Studio (the CLI prints a direct link). Later uploads
361
- * reuse the saved Studio configuration automatically.
362
- *
363
- * @example
364
- * ```ts
365
- * const overlays = createStudioOverlays('intro', 'logo')
366
- *
367
- * video('Product demo', async ({ page }) => {
368
- * await overlays.intro()
369
- * await page.goto('/dashboard')
370
- * await overlays.logo()
371
- * })
372
- * ```
422
+ * Internal: the `overlays` fixture exposes these to the test body.
373
423
  */
374
- export function createStudioOverlays(...keys) {
375
- const seen = new Set();
376
- for (const key of keys) {
377
- if (seen.has(key)) {
378
- throw new Error(`Duplicate overlay key "${key}" passed to createStudioOverlays. Overlay keys must be unique.`);
379
- }
380
- seen.add(key);
381
- }
424
+ export function buildStudioOverlays(names) {
382
425
  const result = {};
383
- for (const key of keys) {
384
- result[key] = createStudioAssetController(key);
426
+ for (const name of names) {
427
+ result[name] = createStudioAssetController(name);
385
428
  }
386
429
  return result;
387
430
  }
@@ -682,9 +725,13 @@ function validatePlacement(name, placement) {
682
725
  * since the recording size is known there.
683
726
  */
684
727
  function resolveOverlayPlacement(name, config) {
685
- if (config.fullScreen === true) {
728
+ if (config.fill === 'screen') {
686
729
  return { fullScreen: true };
687
730
  }
731
+ if (config.fill === 'recording') {
732
+ // Fill the recording area (resolved by the renderer, which knows its size).
733
+ return undefined;
734
+ }
688
735
  if (config.width !== undefined && config.height !== undefined) {
689
736
  throw new Error(`[screenci] Overlay "${name}" must set only one of width or height (the other is derived from the aspect ratio).`);
690
737
  }
@@ -695,7 +742,7 @@ function resolveOverlayPlacement(name, config) {
695
742
  config.relativeTo !== undefined ||
696
743
  config.aspectRatio !== undefined;
697
744
  if (positioned) {
698
- throw new Error(`[screenci] Overlay "${name}" must set "width" or "height" (in CSS px) when positioning it. Omit all placement fields to fill the recording area, or set "fullScreen".`);
745
+ throw new Error(`[screenci] Overlay "${name}" must set "width" or "height" (in CSS px) when positioning it. Omit all placement fields to fill the recording area, or set "fill".`);
699
746
  }
700
747
  // Fill the recording area (resolved by the renderer, which knows its size).
701
748
  return undefined;
@@ -740,8 +787,8 @@ function resolvePlacementSource(name, config, flags) {
740
787
  if (!isRendered) {
741
788
  throw new Error(`[screenci] Overlay "${name}" can only use "over" with a React element, inline "html", or an .html file (the overlay is sized to the element's box).`);
742
789
  }
743
- if (config.fullScreen === true) {
744
- throw new Error(`[screenci] Overlay "${name}" cannot set both "over" and "fullScreen".`);
790
+ if (config.fill !== undefined) {
791
+ throw new Error(`[screenci] Overlay "${name}" cannot set both "over" and "fill".`);
745
792
  }
746
793
  if (config.x !== undefined ||
747
794
  config.y !== undefined ||