screenci 0.0.76 → 0.0.78

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 (184) hide show
  1. package/dist/cli.d.ts +51 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +557 -200
  4. package/dist/cli.js.map +1 -1
  5. package/dist/docs/manifest.d.ts +253 -98
  6. package/dist/docs/manifest.d.ts.map +1 -1
  7. package/dist/docs/manifest.js +92 -32
  8. package/dist/docs/manifest.js.map +1 -1
  9. package/dist/docs/video-sources/camera-and-zooming.screenci.d.ts +2 -0
  10. package/dist/docs/video-sources/camera-and-zooming.screenci.d.ts.map +1 -0
  11. package/dist/docs/video-sources/camera-and-zooming.screenci.js +57 -0
  12. package/dist/docs/video-sources/camera-and-zooming.screenci.js.map +1 -0
  13. package/dist/docs/video-sources/installation.screenci.js +14 -3
  14. package/dist/docs/video-sources/installation.screenci.js.map +1 -1
  15. package/dist/docs/video-sources/languages.screenci.d.ts +2 -0
  16. package/dist/docs/video-sources/languages.screenci.d.ts.map +1 -0
  17. package/dist/docs/video-sources/languages.screenci.js +68 -0
  18. package/dist/docs/video-sources/languages.screenci.js.map +1 -0
  19. package/dist/docs/video-sources/locator-highlight-animated.screenci.js +1 -1
  20. package/dist/docs/video-sources/locator-highlight-animated.screenci.js.map +1 -1
  21. package/dist/docs/video-sources/narration.screenci.d.ts +2 -0
  22. package/dist/docs/video-sources/narration.screenci.d.ts.map +1 -0
  23. package/dist/docs/video-sources/narration.screenci.js +71 -0
  24. package/dist/docs/video-sources/narration.screenci.js.map +1 -0
  25. package/dist/docs/video-sources/public-urls-and-embeds.screenci.d.ts +2 -0
  26. package/dist/docs/video-sources/public-urls-and-embeds.screenci.d.ts.map +1 -0
  27. package/dist/docs/video-sources/public-urls-and-embeds.screenci.js +60 -0
  28. package/dist/docs/video-sources/public-urls-and-embeds.screenci.js.map +1 -0
  29. package/dist/docs/video-sources/speaking-style.screenci.d.ts +2 -0
  30. package/dist/docs/video-sources/speaking-style.screenci.d.ts.map +1 -0
  31. package/dist/docs/video-sources/speaking-style.screenci.js +76 -0
  32. package/dist/docs/video-sources/speaking-style.screenci.js.map +1 -0
  33. package/dist/docs/video-sources/vertical-video.screenci.d.ts +2 -0
  34. package/dist/docs/video-sources/vertical-video.screenci.d.ts.map +1 -0
  35. package/dist/docs/video-sources/vertical-video.screenci.js +58 -0
  36. package/dist/docs/video-sources/vertical-video.screenci.js.map +1 -0
  37. package/dist/docs/videos.d.ts +20 -0
  38. package/dist/docs/videos.d.ts.map +1 -1
  39. package/dist/docs/videos.js +25 -0
  40. package/dist/docs/videos.js.map +1 -1
  41. package/dist/e2e/instrument.e2e.js +8 -0
  42. package/dist/e2e/instrument.e2e.js.map +1 -1
  43. package/dist/e2e/redact.e2e.d.ts +2 -0
  44. package/dist/e2e/redact.e2e.d.ts.map +1 -0
  45. package/dist/e2e/redact.e2e.js +161 -0
  46. package/dist/e2e/redact.e2e.js.map +1 -0
  47. package/dist/index.d.ts +10 -3
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +8 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/src/asset.d.ts +154 -13
  52. package/dist/src/asset.d.ts.map +1 -1
  53. package/dist/src/asset.js +262 -32
  54. package/dist/src/asset.js.map +1 -1
  55. package/dist/src/assetHash.d.ts +27 -0
  56. package/dist/src/assetHash.d.ts.map +1 -0
  57. package/dist/src/assetHash.js +0 -0
  58. package/dist/src/assetHash.js.map +1 -0
  59. package/dist/src/audio.d.ts +8 -5
  60. package/dist/src/audio.d.ts.map +1 -1
  61. package/dist/src/audio.js +56 -31
  62. package/dist/src/audio.js.map +1 -1
  63. package/dist/src/builder.d.ts +132 -32
  64. package/dist/src/builder.d.ts.map +1 -1
  65. package/dist/src/builder.js +66 -20
  66. package/dist/src/builder.js.map +1 -1
  67. package/dist/src/changeFocus.d.ts.map +1 -1
  68. package/dist/src/changeFocus.js +103 -34
  69. package/dist/src/changeFocus.js.map +1 -1
  70. package/dist/src/cue.d.ts +29 -17
  71. package/dist/src/cue.d.ts.map +1 -1
  72. package/dist/src/cue.js +138 -61
  73. package/dist/src/cue.js.map +1 -1
  74. package/dist/src/declare.d.ts +8 -4
  75. package/dist/src/declare.d.ts.map +1 -1
  76. package/dist/src/declare.js +28 -10
  77. package/dist/src/declare.js.map +1 -1
  78. package/dist/src/defaults.d.ts +23 -0
  79. package/dist/src/defaults.d.ts.map +1 -1
  80. package/dist/src/defaults.js +23 -0
  81. package/dist/src/defaults.js.map +1 -1
  82. package/dist/src/events.d.ts +289 -25
  83. package/dist/src/events.d.ts.map +1 -1
  84. package/dist/src/events.js +145 -13
  85. package/dist/src/events.js.map +1 -1
  86. package/dist/src/htmlRasterizer.d.ts +10 -0
  87. package/dist/src/htmlRasterizer.d.ts.map +1 -1
  88. package/dist/src/htmlRasterizer.js +12 -2
  89. package/dist/src/htmlRasterizer.js.map +1 -1
  90. package/dist/src/init.d.ts +2 -0
  91. package/dist/src/init.d.ts.map +1 -1
  92. package/dist/src/init.js +64 -18
  93. package/dist/src/init.js.map +1 -1
  94. package/dist/src/instrument.d.ts.map +1 -1
  95. package/dist/src/instrument.js +25 -7
  96. package/dist/src/instrument.js.map +1 -1
  97. package/dist/src/locales.d.ts.map +1 -1
  98. package/dist/src/locales.js +1 -0
  99. package/dist/src/locales.js.map +1 -1
  100. package/dist/src/localize.d.ts +25 -17
  101. package/dist/src/localize.d.ts.map +1 -1
  102. package/dist/src/localize.js +18 -10
  103. package/dist/src/localize.js.map +1 -1
  104. package/dist/src/localizeRuntime.d.ts +18 -13
  105. package/dist/src/localizeRuntime.d.ts.map +1 -1
  106. package/dist/src/localizeRuntime.js +28 -18
  107. package/dist/src/localizeRuntime.js.map +1 -1
  108. package/dist/src/logger.d.ts +5 -0
  109. package/dist/src/logger.d.ts.map +1 -1
  110. package/dist/src/logger.js +7 -0
  111. package/dist/src/logger.js.map +1 -1
  112. package/dist/src/narrationVisibility.d.ts +19 -0
  113. package/dist/src/narrationVisibility.d.ts.map +1 -0
  114. package/dist/src/narrationVisibility.js +26 -0
  115. package/dist/src/narrationVisibility.js.map +1 -0
  116. package/dist/src/recordingData.d.ts +7 -0
  117. package/dist/src/recordingData.d.ts.map +1 -1
  118. package/dist/src/recordingSize.d.ts +32 -0
  119. package/dist/src/recordingSize.d.ts.map +1 -0
  120. package/dist/src/recordingSize.js +54 -0
  121. package/dist/src/recordingSize.js.map +1 -0
  122. package/dist/src/redact.d.ts +68 -0
  123. package/dist/src/redact.d.ts.map +1 -0
  124. package/dist/src/redact.js +138 -0
  125. package/dist/src/redact.js.map +1 -0
  126. package/dist/src/redactController.d.ts +61 -0
  127. package/dist/src/redactController.d.ts.map +1 -0
  128. package/dist/src/redactController.js +232 -0
  129. package/dist/src/redactController.js.map +1 -0
  130. package/dist/src/runtimeContext.d.ts +32 -0
  131. package/dist/src/runtimeContext.d.ts.map +1 -1
  132. package/dist/src/runtimeContext.js +21 -0
  133. package/dist/src/runtimeContext.js.map +1 -1
  134. package/dist/src/runtimeMode.d.ts +15 -7
  135. package/dist/src/runtimeMode.d.ts.map +1 -1
  136. package/dist/src/runtimeMode.js +17 -6
  137. package/dist/src/runtimeMode.js.map +1 -1
  138. package/dist/src/screenAudio.d.ts +34 -4
  139. package/dist/src/screenAudio.d.ts.map +1 -1
  140. package/dist/src/screenAudio.js +0 -0
  141. package/dist/src/screenAudio.js.map +1 -1
  142. package/dist/src/screenAudioSink.d.ts +12 -0
  143. package/dist/src/screenAudioSink.d.ts.map +1 -1
  144. package/dist/src/screenAudioSink.js +30 -0
  145. package/dist/src/screenAudioSink.js.map +1 -1
  146. package/dist/src/screenshot.d.ts +16 -14
  147. package/dist/src/screenshot.d.ts.map +1 -1
  148. package/dist/src/screenshot.js +11 -11
  149. package/dist/src/screenshot.js.map +1 -1
  150. package/dist/src/sourceTrim.d.ts +29 -0
  151. package/dist/src/sourceTrim.d.ts.map +1 -0
  152. package/dist/src/sourceTrim.js +76 -0
  153. package/dist/src/sourceTrim.js.map +1 -0
  154. package/dist/src/studio.d.ts +71 -3
  155. package/dist/src/studio.d.ts.map +1 -1
  156. package/dist/src/studio.js +19 -1
  157. package/dist/src/studio.js.map +1 -1
  158. package/dist/src/timelineOffset.d.ts +46 -0
  159. package/dist/src/timelineOffset.d.ts.map +1 -0
  160. package/dist/src/timelineOffset.js +66 -0
  161. package/dist/src/timelineOffset.js.map +1 -0
  162. package/dist/src/titleValidation.d.ts +38 -0
  163. package/dist/src/titleValidation.d.ts.map +1 -1
  164. package/dist/src/titleValidation.js +42 -0
  165. package/dist/src/titleValidation.js.map +1 -1
  166. package/dist/src/types.d.ts +85 -0
  167. package/dist/src/types.d.ts.map +1 -1
  168. package/dist/src/types.js +1 -1
  169. package/dist/src/types.js.map +1 -1
  170. package/dist/src/video.d.ts +37 -34
  171. package/dist/src/video.d.ts.map +1 -1
  172. package/dist/src/video.js +99 -48
  173. package/dist/src/video.js.map +1 -1
  174. package/dist/src/voiceSampleAudio.d.ts +34 -0
  175. package/dist/src/voiceSampleAudio.d.ts.map +1 -0
  176. package/dist/src/voiceSampleAudio.js +105 -0
  177. package/dist/src/voiceSampleAudio.js.map +1 -0
  178. package/dist/src/voices.d.ts +2 -2
  179. package/dist/src/voices.d.ts.map +1 -1
  180. package/dist/src/voices.js +8 -3
  181. package/dist/src/voices.js.map +1 -1
  182. package/dist/tsconfig.tsbuildinfo +1 -1
  183. package/package.json +3 -2
  184. package/templates/recordings/assets/logo.png +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordingSize.d.ts","sourceRoot":"","sources":["../../src/recordingSize.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAgBxC,MAAM,MAAM,sBAAsB,GAAG;IACnC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAKxD"}
@@ -0,0 +1,54 @@
1
+ import { ScreenciError } from './errors.js';
2
+ import { getActiveHideRecorder } from './timelineBlock.js';
3
+ import { getRuntimeRecordingSize, setRuntimeRecordingSize, } from './runtimeContext.js';
4
+ /**
5
+ * Full-screen size: the recording fills the output frame, with one side
6
+ * touching the background edge.
7
+ */
8
+ const FULL_SCREEN_SIZE = 1;
9
+ function assertValidSize(size) {
10
+ if (!Number.isFinite(size) || size <= 0 || size > 1) {
11
+ throw new ScreenciError(`resizeRecording(size) requires size in (0, 1]; received ${size}`);
12
+ }
13
+ }
14
+ /**
15
+ * Shrinks the recording to a limited size, revealing the styled background
16
+ * around it from this point on.
17
+ *
18
+ * `size` is a 0-1 fraction of the full frame: `1` is full screen, `0.8` shows
19
+ * the recording at 80% of the frame, centered. Call {@link resetRecordingSize}
20
+ * to return to full screen.
21
+ *
22
+ * Pass `duration` to animate the transition over that many milliseconds.
23
+ * Use `easing` to control the animation curve (defaults to `'ease-out'`).
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * await resizeRecording(0.8, { duration: 400, easing: 'ease-in-out' })
28
+ * await page.getByRole('button', { name: 'Continue' }).click()
29
+ * await resetRecordingSize()
30
+ * ```
31
+ */
32
+ export async function resizeRecording(size, options) {
33
+ assertValidSize(size);
34
+ const recorder = getActiveHideRecorder();
35
+ if (options !== undefined) {
36
+ recorder.addRecordingSizeStart(size, options);
37
+ }
38
+ else {
39
+ recorder.addRecordingSizeStart(size);
40
+ }
41
+ setRuntimeRecordingSize(size);
42
+ }
43
+ /**
44
+ * Returns the recording to full screen (size 1) from this point on. No-op when
45
+ * the recording is already full screen.
46
+ */
47
+ export async function resetRecordingSize() {
48
+ if (getRuntimeRecordingSize() === FULL_SCREEN_SIZE)
49
+ return;
50
+ const recorder = getActiveHideRecorder();
51
+ recorder.addRecordingSizeEnd();
52
+ setRuntimeRecordingSize(FULL_SCREEN_SIZE);
53
+ }
54
+ //# sourceMappingURL=recordingSize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordingSize.js","sourceRoot":"","sources":["../../src/recordingSize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,qBAAqB,CAAA;AAG5B;;;GAGG;AACH,MAAM,gBAAgB,GAAG,CAAC,CAAA;AAE1B,SAAS,eAAe,CAAC,IAAY;IACnC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,aAAa,CACrB,2DAA2D,IAAI,EAAE,CAClE,CAAA;IACH,CAAC;AACH,CAAC;AASD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAY,EACZ,OAAgC;IAEhC,eAAe,CAAC,IAAI,CAAC,CAAA;IACrB,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAA;IACxC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,QAAQ,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC/C,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACtC,CAAC;IACD,uBAAuB,CAAC,IAAI,CAAC,CAAA;AAC/B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,uBAAuB,EAAE,KAAK,gBAAgB;QAAE,OAAM;IAC1D,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAA;IACxC,QAAQ,CAAC,mBAAmB,EAAE,CAAA;IAC9B,uBAAuB,CAAC,gBAAgB,CAAC,CAAA;AAC3C,CAAC"}
@@ -0,0 +1,68 @@
1
+ import type { Locator, Page } from '@playwright/test';
2
+ import type { RedactHandle, RedactOptions } from './types.js';
3
+ import { type ResolvedRedactStyle } from './redactController.js';
4
+ /** Default corner radius (px) for the mask. */
5
+ export declare const DEFAULT_REDACT_RADIUS = 12;
6
+ /** Default mask shadow. Reads as a raised chip so a redaction is obvious. */
7
+ export declare const DEFAULT_REDACT_SHADOW = "0 2px 8px rgba(0,0,0,0.25)";
8
+ /**
9
+ * How long `redact()` waits for at least one matching element before masking,
10
+ * so a redact issued just before the element renders does not silently no-op.
11
+ */
12
+ export declare const REDACT_ATTACH_TIMEOUT_MS = 5000;
13
+ /**
14
+ * Resolve user-facing {@link RedactOptions} to the concrete pixel values the
15
+ * in-page controller applies. Pure (no page access), so it is unit-testable on
16
+ * its own.
17
+ */
18
+ export declare function resolveRedactStyle(options?: RedactOptions): ResolvedRedactStyle;
19
+ /**
20
+ * Make sure the in-page controller is installed. `addInitScript` covers every
21
+ * future document (registered once per recording); the extra `evaluate` covers
22
+ * the document that is already open when `redact()` is first called. Both are
23
+ * idempotent.
24
+ */
25
+ export declare function ensureRedactControllerInstalled(page: Page): Promise<void>;
26
+ /**
27
+ * Install the in-page controller on a freshly created page, before its first
28
+ * navigation, so masks apply from the first painted frame. Also registers the
29
+ * `recordOptions.redact` first-paint selectors. Called once per page from the
30
+ * video fixture; the runtime flag is set on the passed context (which is not yet
31
+ * the active context at page-creation time).
32
+ */
33
+ export declare function installRedactController(page: Page, redactState: {
34
+ controllerInstalled: boolean;
35
+ }, configSelectors?: string[]): Promise<void>;
36
+ /**
37
+ * Mask the element(s) matched by `locator` so the content never enters the
38
+ * recording. The mask is applied client side in the live DOM, so the obscured
39
+ * pixels are never captured and never uploaded to screenci. This is true
40
+ * redaction, unlike {@link cover}, which only hides content in the published
41
+ * output.
42
+ *
43
+ * To hide a secret that is already on screen without a visible flash, register
44
+ * the mask before revealing it (inside `hide()` or via the `recordOptions.redact`
45
+ * config for always-secret elements).
46
+ *
47
+ * @example Persistent
48
+ * ```ts
49
+ * const handle = await redact(page.getByTestId('api-key'))
50
+ * // ... later
51
+ * await handle.unredact()
52
+ * ```
53
+ *
54
+ * @example Scoped
55
+ * ```ts
56
+ * await redact(page.getByTestId('balance'), async () => {
57
+ * await page.getByRole('button', { name: 'Reveal' }).click()
58
+ * })
59
+ * ```
60
+ */
61
+ export declare function redact(locator: Locator, options?: RedactOptions): Promise<RedactHandle>;
62
+ export declare function redact(locator: Locator, fn: () => Promise<void> | void, options?: RedactOptions): Promise<void>;
63
+ /**
64
+ * Remove every active mask. Called automatically at the end of each recording
65
+ * so masks never leak across recordings that share a browser worker.
66
+ */
67
+ export declare function unredactAll(): Promise<void>;
68
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/redact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,uBAAuB,CAAA;AAI9B,+CAA+C;AAC/C,eAAO,MAAM,qBAAqB,KAAK,CAAA;AACvC,6EAA6E;AAC7E,eAAO,MAAM,qBAAqB,+BAA+B,CAAA;AAEjE;;;GAGG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAA;AAE5C;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,CAAC,EAAE,aAAa,GACtB,mBAAmB,CAUrB;AAWD;;;;;GAKG;AACH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,IAAI,EACV,WAAW,EAAE;IAAE,mBAAmB,EAAE,OAAO,CAAA;CAAE,EAC7C,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CASf;AAqDD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,MAAM,CACpB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC,CAAA;AACxB,wBAAgB,MAAM,CACpB,OAAO,EAAE,OAAO,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAC9B,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CAAA;AAkBhB;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAYjD"}
@@ -0,0 +1,138 @@
1
+ import { redactApplyConfigSelectors, redactControllerBootstrap, } from './redactController.js';
2
+ import { getRuntimePage, getRuntimeRedactState } from './runtimeContext.js';
3
+ import { logger } from './logger.js';
4
+ /** Default corner radius (px) for the mask. */
5
+ export const DEFAULT_REDACT_RADIUS = 12;
6
+ /** Default mask shadow. Reads as a raised chip so a redaction is obvious. */
7
+ export const DEFAULT_REDACT_SHADOW = '0 2px 8px rgba(0,0,0,0.25)';
8
+ /**
9
+ * How long `redact()` waits for at least one matching element before masking,
10
+ * so a redact issued just before the element renders does not silently no-op.
11
+ */
12
+ export const REDACT_ATTACH_TIMEOUT_MS = 5000;
13
+ /**
14
+ * Resolve user-facing {@link RedactOptions} to the concrete pixel values the
15
+ * in-page controller applies. Pure (no page access), so it is unit-testable on
16
+ * its own.
17
+ */
18
+ export function resolveRedactStyle(options) {
19
+ const style = options?.style;
20
+ return {
21
+ // null means "sample a color from underneath" in the page.
22
+ color: style?.color ?? null,
23
+ radiusPx: style?.radius ?? DEFAULT_REDACT_RADIUS,
24
+ shadow: style?.shadow === false ? null : (style?.shadow ?? DEFAULT_REDACT_SHADOW),
25
+ css: style?.css ?? null,
26
+ };
27
+ }
28
+ let redactCounter = 0;
29
+ function nextRedactId() {
30
+ redactCounter += 1;
31
+ return `__screenci_redact_${redactCounter}`;
32
+ }
33
+ /**
34
+ * Make sure the in-page controller is installed. `addInitScript` covers every
35
+ * future document (registered once per recording); the extra `evaluate` covers
36
+ * the document that is already open when `redact()` is first called. Both are
37
+ * idempotent.
38
+ */
39
+ export async function ensureRedactControllerInstalled(page) {
40
+ const state = getRuntimeRedactState();
41
+ if (!state.controllerInstalled) {
42
+ await page.addInitScript(redactControllerBootstrap);
43
+ state.controllerInstalled = true;
44
+ }
45
+ await page.evaluate(redactControllerBootstrap);
46
+ }
47
+ /**
48
+ * Install the in-page controller on a freshly created page, before its first
49
+ * navigation, so masks apply from the first painted frame. Also registers the
50
+ * `recordOptions.redact` first-paint selectors. Called once per page from the
51
+ * video fixture; the runtime flag is set on the passed context (which is not yet
52
+ * the active context at page-creation time).
53
+ */
54
+ export async function installRedactController(page, redactState, configSelectors) {
55
+ await page.addInitScript(redactControllerBootstrap);
56
+ redactState.controllerInstalled = true;
57
+ if (configSelectors && configSelectors.length > 0) {
58
+ await page.addInitScript(redactApplyConfigSelectors, {
59
+ selectors: configSelectors,
60
+ style: resolveRedactStyle(),
61
+ });
62
+ }
63
+ }
64
+ async function applyRedact(locator, options) {
65
+ const page = locator.page();
66
+ const resolved = resolveRedactStyle(options);
67
+ const id = nextRedactId();
68
+ await ensureRedactControllerInstalled(page);
69
+ // Wait for at least one match so a redact issued a beat before the element
70
+ // renders still masks it instead of silently doing nothing.
71
+ try {
72
+ await locator
73
+ .first()
74
+ .waitFor({ state: 'attached', timeout: REDACT_ATTACH_TIMEOUT_MS });
75
+ }
76
+ catch {
77
+ logger.warn('[screenci] redact() found no matching element to mask. The selector may be wrong or the element may not have rendered yet.');
78
+ }
79
+ // Register inside the page so the controller receives the real elements
80
+ // (this pierces shadow DOM, which a selector re-query could not). The
81
+ // controller positions the mask synchronously here, so no unmasked frame
82
+ // escapes once this resolves.
83
+ await locator.evaluateAll((elements, arg) => {
84
+ const api = window.__screenci_redact;
85
+ if (api)
86
+ api.addElements(arg.id, elements, arg.style);
87
+ }, { id, style: resolved });
88
+ getRuntimeRedactState().activeMasks.set(id, resolved);
89
+ return {
90
+ async unredact() {
91
+ getRuntimeRedactState().activeMasks.delete(id);
92
+ try {
93
+ await page.evaluate((maskId) => {
94
+ const api = window.__screenci_redact;
95
+ if (api)
96
+ api.remove(maskId);
97
+ }, id);
98
+ }
99
+ catch {
100
+ // The page may already be closing; the mask dies with it.
101
+ }
102
+ },
103
+ };
104
+ }
105
+ export async function redact(locator, optionsOrFn, maybeOptions) {
106
+ if (typeof optionsOrFn === 'function') {
107
+ const handle = await applyRedact(locator, maybeOptions);
108
+ try {
109
+ await optionsOrFn();
110
+ }
111
+ finally {
112
+ await handle.unredact();
113
+ }
114
+ return;
115
+ }
116
+ return applyRedact(locator, optionsOrFn);
117
+ }
118
+ /**
119
+ * Remove every active mask. Called automatically at the end of each recording
120
+ * so masks never leak across recordings that share a browser worker.
121
+ */
122
+ export async function unredactAll() {
123
+ getRuntimeRedactState().activeMasks.clear();
124
+ const page = getRuntimePage();
125
+ if (!page)
126
+ return;
127
+ try {
128
+ await page.evaluate(() => {
129
+ const api = window.__screenci_redact;
130
+ if (api)
131
+ api.clear();
132
+ });
133
+ }
134
+ catch {
135
+ // Best effort; the page may already be closing.
136
+ }
137
+ }
138
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/redact.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * In-page redaction controller.
3
+ *
4
+ * screenci records the live browser page, so the only way to keep a secret out
5
+ * of `recording.mp4` (and therefore out of the upload) is to mask it in the DOM
6
+ * before Chrome captures the frame. This module is the browser-side half: a
7
+ * self-contained controller installed via `addInitScript` that covers target
8
+ * elements with an opaque panel (by default tinted to match the surface beneath
9
+ * them), tracking their position every animation frame so the mask follows
10
+ * scroll, layout, and animation.
11
+ *
12
+ * The functions here are serialized and shipped to the page by Playwright, so
13
+ * they must not reference anything outside their own bodies.
14
+ */
15
+ /**
16
+ * A redact style resolved to concrete pixel values, ready to apply in the page.
17
+ * `RedactStyle` (the user-facing option) is resolved to this by
18
+ * `resolveRedactStyle` in `redact.ts`.
19
+ */
20
+ export type ResolvedRedactStyle = {
21
+ /** Fixed fill color, or null to sample one from underneath in the page. */
22
+ color: string | null;
23
+ radiusPx: number;
24
+ shadow: string | null;
25
+ /** Extra CSS for the mask panel, or null. */
26
+ css: string | null;
27
+ };
28
+ /** Window global exposed by the controller for the Node side to drive. */
29
+ export type RedactWindowApi = {
30
+ /** Mask specific element handles (used by runtime `redact`; pierces shadow DOM). */
31
+ addElements: (id: string, elements: Element[], style: ResolvedRedactStyle) => void;
32
+ /** Mask everything matching a CSS selector, re-queried each frame. */
33
+ addSelector: (id: string, selector: string, style: ResolvedRedactStyle) => void;
34
+ /** Remove a mask by id, restoring the element. */
35
+ remove: (id: string) => void;
36
+ /** Remove every mask. */
37
+ clear: () => void;
38
+ };
39
+ /** Attribute stamped on elements masked by a runtime `redact` call. */
40
+ export declare const REDACT_ID_ATTRIBUTE = "data-screenci-redact-id";
41
+ /** Id of the fixed-position container that holds every tint div. */
42
+ export declare const REDACT_ROOT_ID = "__screenci_redact_root";
43
+ /** Name of the window global the controller installs. */
44
+ export declare const REDACT_WINDOW_KEY = "__screenci_redact";
45
+ /**
46
+ * The controller bootstrap. Runs in the page (via `addInitScript`) on every
47
+ * document. Idempotent: a second run is a no-op, so it is safe to inject more
48
+ * than once.
49
+ */
50
+ export declare function redactControllerBootstrap(): void;
51
+ /**
52
+ * Init script that masks the configured first-paint selectors. Registered after
53
+ * the bootstrap so `window.__screenci_redact` already exists. A CSS rule hides
54
+ * matches the instant they paint (race free, no JS timing), and the controller
55
+ * lays the opaque panel over them a frame later.
56
+ */
57
+ export declare function redactApplyConfigSelectors(config: {
58
+ selectors: string[];
59
+ style: ResolvedRedactStyle;
60
+ }): void;
61
+ //# sourceMappingURL=redactController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redactController.d.ts","sourceRoot":"","sources":["../../src/redactController.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,2EAA2E;IAC3E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,6CAA6C;IAC7C,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACnB,CAAA;AAED,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG;IAC5B,oFAAoF;IACpF,WAAW,EAAE,CACX,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,mBAAmB,KACvB,IAAI,CAAA;IACT,sEAAsE;IACtE,WAAW,EAAE,CACX,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,mBAAmB,KACvB,IAAI,CAAA;IACT,kDAAkD;IAClD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,yBAAyB;IACzB,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB,CAAA;AAED,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,4BAA4B,CAAA;AAE5D,oEAAoE;AACpE,eAAO,MAAM,cAAc,2BAA2B,CAAA;AAEtD,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,sBAAsB,CAAA;AAEpD;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAiMhD;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IACjD,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,KAAK,EAAE,mBAAmB,CAAA;CAC3B,GAAG,IAAI,CA0BP"}
@@ -0,0 +1,232 @@
1
+ /**
2
+ * In-page redaction controller.
3
+ *
4
+ * screenci records the live browser page, so the only way to keep a secret out
5
+ * of `recording.mp4` (and therefore out of the upload) is to mask it in the DOM
6
+ * before Chrome captures the frame. This module is the browser-side half: a
7
+ * self-contained controller installed via `addInitScript` that covers target
8
+ * elements with an opaque panel (by default tinted to match the surface beneath
9
+ * them), tracking their position every animation frame so the mask follows
10
+ * scroll, layout, and animation.
11
+ *
12
+ * The functions here are serialized and shipped to the page by Playwright, so
13
+ * they must not reference anything outside their own bodies.
14
+ */
15
+ /** Attribute stamped on elements masked by a runtime `redact` call. */
16
+ export const REDACT_ID_ATTRIBUTE = 'data-screenci-redact-id';
17
+ /** Id of the fixed-position container that holds every tint div. */
18
+ export const REDACT_ROOT_ID = '__screenci_redact_root';
19
+ /** Name of the window global the controller installs. */
20
+ export const REDACT_WINDOW_KEY = '__screenci_redact';
21
+ /**
22
+ * The controller bootstrap. Runs in the page (via `addInitScript`) on every
23
+ * document. Idempotent: a second run is a no-op, so it is safe to inject more
24
+ * than once.
25
+ */
26
+ export function redactControllerBootstrap() {
27
+ const KEY = '__screenci_redact';
28
+ const ROOT_ID = '__screenci_redact_root';
29
+ const win = window;
30
+ if (win[KEY])
31
+ return;
32
+ const entries = new Map();
33
+ let root = null;
34
+ let rafId = null;
35
+ function ensureRoot() {
36
+ if (root && root.isConnected)
37
+ return root;
38
+ // At document-start (init script) the parent may not exist yet; the rAF
39
+ // loop retries next frame rather than throwing and dying.
40
+ const parent = document.body ?? document.documentElement;
41
+ if (!parent)
42
+ return null;
43
+ const existing = document.getElementById(ROOT_ID);
44
+ if (existing) {
45
+ root = existing;
46
+ return root;
47
+ }
48
+ const el = document.createElement('div');
49
+ el.id = ROOT_ID;
50
+ el.setAttribute('aria-hidden', 'true');
51
+ el.style.cssText =
52
+ 'position:fixed;top:0;left:0;width:0;height:0;margin:0;padding:0;' +
53
+ 'border:0;pointer-events:none;z-index:2147483647;';
54
+ // Append to documentElement so it survives <body> swaps in SPAs.
55
+ document.documentElement.appendChild(el);
56
+ root = el;
57
+ return el;
58
+ }
59
+ function isTransparentColor(c) {
60
+ return (!c ||
61
+ c === 'transparent' ||
62
+ /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/.test(c));
63
+ }
64
+ // Pick an opaque fill color from the surface under the element: its own
65
+ // background, or the nearest ancestor (or the body) that paints one. This
66
+ // gives a clean, uniform panel that blends with the surrounding UI.
67
+ function sampleRegionColor(el) {
68
+ let node = el;
69
+ while (node) {
70
+ const bg = getComputedStyle(node).backgroundColor;
71
+ if (!isTransparentColor(bg))
72
+ return bg;
73
+ node = node.parentElement;
74
+ }
75
+ const bodyBg = document.body
76
+ ? getComputedStyle(document.body).backgroundColor
77
+ : '';
78
+ return isTransparentColor(bodyBg) ? '#e5e5e5' : bodyBg;
79
+ }
80
+ function styleMask(div, style, autoColor) {
81
+ // Opaque panel: a fixed color, or one sampled to match what is underneath.
82
+ div.style.background = style.color ?? autoColor ?? '#e5e5e5';
83
+ div.style.borderRadius = `${style.radiusPx}px`;
84
+ div.style.boxShadow = style.shadow ?? 'none';
85
+ // Custom styling, applied last so it can override the defaults.
86
+ if (style.css)
87
+ div.style.cssText += ';' + style.css;
88
+ // Keep the panel non-interactive and absolutely positioned regardless of
89
+ // any custom CSS.
90
+ div.style.position = 'absolute';
91
+ div.style.pointerEvents = 'none';
92
+ }
93
+ function resolveElements(entry) {
94
+ if (entry.selector !== null) {
95
+ return Array.from(document.querySelectorAll(entry.selector));
96
+ }
97
+ return (entry.elements ?? []).filter((el) => el.isConnected);
98
+ }
99
+ function positionFrame() {
100
+ const container = ensureRoot();
101
+ if (!container) {
102
+ // DOM not ready yet; try again next frame while masks are pending.
103
+ rafId = entries.size > 0 ? requestAnimationFrame(positionFrame) : null;
104
+ return;
105
+ }
106
+ for (const entry of entries.values()) {
107
+ const seen = new Set();
108
+ for (const el of resolveElements(entry)) {
109
+ const rect = el.getBoundingClientRect();
110
+ if (rect.width <= 0 || rect.height <= 0)
111
+ continue;
112
+ seen.add(el);
113
+ let div = entry.masks.get(el);
114
+ if (!div) {
115
+ div = document.createElement('div');
116
+ div.style.cssText =
117
+ 'position:absolute;top:0;left:0;pointer-events:none;will-change:transform;';
118
+ const autoColor = entry.style.color === null ? sampleRegionColor(el) : null;
119
+ styleMask(div, entry.style, autoColor);
120
+ container.appendChild(div);
121
+ entry.masks.set(el, div);
122
+ }
123
+ // Inflate just enough that the rounded corners never expose the
124
+ // element's square corners underneath. A rounded rect of radius r covers
125
+ // an inner corner once it extends about 0.29*r beyond it, so this stays
126
+ // tight to the element rather than looking oversized.
127
+ const pad = Math.ceil(entry.style.radiusPx * 0.3);
128
+ div.style.transform = `translate(${rect.left - pad}px, ${rect.top - pad}px)`;
129
+ div.style.width = `${rect.width + pad * 2}px`;
130
+ div.style.height = `${rect.height + pad * 2}px`;
131
+ div.style.display = 'block';
132
+ }
133
+ // Tear down masks for elements that vanished this frame.
134
+ for (const [el, div] of entry.masks) {
135
+ if (seen.has(el))
136
+ continue;
137
+ div.remove();
138
+ entry.masks.delete(el);
139
+ }
140
+ }
141
+ rafId = entries.size > 0 ? requestAnimationFrame(positionFrame) : null;
142
+ }
143
+ function startLoop() {
144
+ if (rafId !== null)
145
+ return;
146
+ // Run one synchronous frame so a freshly added mask covers its element
147
+ // before this call returns (no unmasked frame escapes to the recording),
148
+ // then keep tracking on rAF.
149
+ positionFrame();
150
+ }
151
+ function teardownEntry(entry) {
152
+ for (const [el, div] of entry.masks) {
153
+ div.remove();
154
+ entry.masks.delete(el);
155
+ }
156
+ }
157
+ const api = {
158
+ addElements(id, elements, style) {
159
+ const entry = {
160
+ style: style,
161
+ selector: null,
162
+ elements,
163
+ masks: new Map(),
164
+ };
165
+ const prev = entries.get(id);
166
+ if (prev)
167
+ teardownEntry(prev);
168
+ entries.set(id, entry);
169
+ startLoop();
170
+ },
171
+ addSelector(id, selector, style) {
172
+ const entry = {
173
+ style: style,
174
+ selector,
175
+ elements: null,
176
+ masks: new Map(),
177
+ };
178
+ const prev = entries.get(id);
179
+ if (prev)
180
+ teardownEntry(prev);
181
+ entries.set(id, entry);
182
+ startLoop();
183
+ },
184
+ remove(id) {
185
+ const entry = entries.get(id);
186
+ if (!entry)
187
+ return;
188
+ teardownEntry(entry);
189
+ entries.delete(id);
190
+ },
191
+ clear() {
192
+ for (const entry of entries.values())
193
+ teardownEntry(entry);
194
+ entries.clear();
195
+ },
196
+ };
197
+ win[KEY] = api;
198
+ }
199
+ /**
200
+ * Init script that masks the configured first-paint selectors. Registered after
201
+ * the bootstrap so `window.__screenci_redact` already exists. A CSS rule hides
202
+ * matches the instant they paint (race free, no JS timing), and the controller
203
+ * lays the opaque panel over them a frame later.
204
+ */
205
+ export function redactApplyConfigSelectors(config) {
206
+ const KEY = '__screenci_redact';
207
+ const api = window[KEY];
208
+ if (!api || config.selectors.length === 0)
209
+ return;
210
+ // Hide matches the instant they paint, before the JS panel can position
211
+ // itself, so the secret never flashes on the first frame. `visibility:hidden`
212
+ // keeps the layout box so the panel is sized correctly, and it fails closed if
213
+ // the controller never runs.
214
+ const css = config.selectors
215
+ .map((sel) => `${sel}{visibility:hidden !important;}`)
216
+ .join('');
217
+ const style = document.createElement('style');
218
+ style.setAttribute('data-screenci-redact', 'config');
219
+ style.textContent = css;
220
+ const attach = () => {
221
+ ;
222
+ (document.head ?? document.documentElement).appendChild(style);
223
+ };
224
+ if (document.head)
225
+ attach();
226
+ else
227
+ document.addEventListener('DOMContentLoaded', attach, { once: true });
228
+ config.selectors.forEach((selector, index) => {
229
+ api.addSelector(`__screenci_config_${index}`, selector, config.style);
230
+ });
231
+ }
232
+ //# sourceMappingURL=redactController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redactController.js","sourceRoot":"","sources":["../../src/redactController.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import type { Page } from '@playwright/test';
2
2
  import { type IEventRecorder, type ElementRect } from './events.js';
3
3
  import type { AutoZoomOptions, RecordOptions, RenderOptions } from './types.js';
4
4
  import type { ScreenshotCropRecord } from './crop.js';
5
+ import type { ResolvedRedactStyle } from './redactController.js';
5
6
  export type CurrentZoomViewport = {
6
7
  focusPoint: {
7
8
  x: number;
@@ -32,6 +33,13 @@ export type AutoZoomState = {
32
33
  mode: 'idle' | 'auto' | 'manual';
33
34
  options: AutoZoomOptions;
34
35
  currentZoomViewport: CurrentZoomViewport | null;
36
+ /**
37
+ * Vertical framing bias (0–1) applied when a plain interaction scrolls its
38
+ * target into view without zooming. Populated once at record init from
39
+ * `recordOptions.scrollCentering` (default `DEFAULT_SCROLL_CENTERING`). An
40
+ * explicit per-call `centering` still overrides this.
41
+ */
42
+ scrollCentering: number;
35
43
  };
36
44
  export type ActiveCueRun = {
37
45
  finished: Promise<void>;
@@ -113,6 +121,26 @@ export type ScreenCIRuntimeContext = {
113
121
  activeRuns: Map<string, ActiveAudioRun>;
114
122
  };
115
123
  autoZoom: AutoZoomState;
124
+ /**
125
+ * Tracks the recording's current display size (0-1 fraction of the frame; 1 =
126
+ * full screen) as toggled by `resizeRecording()`/`resetRecordingSize()`. Used
127
+ * to no-op a reset that is already at full screen and to detect redundant
128
+ * resizes. Resets to 1 with each fresh recording context.
129
+ */
130
+ recordingSize: {
131
+ current: number;
132
+ };
133
+ /**
134
+ * Client-side redaction state. `controllerInstalled` guards the one-time
135
+ * `addInitScript` install of the in-page mask controller; `activeMasks` maps
136
+ * each live mask id to its resolved style so masks can be cleaned up at the
137
+ * end of a recording. Redaction is purely client side, so nothing here is
138
+ * serialized to `data.json`.
139
+ */
140
+ redact: {
141
+ controllerInstalled: boolean;
142
+ activeMasks: Map<string, ResolvedRedactStyle>;
143
+ };
116
144
  };
117
145
  export declare function createScreenCIRuntimeContext(overrides?: {
118
146
  recorder?: IEventRecorder | null;
@@ -154,6 +182,8 @@ export declare function getRuntimeCrop(): ScreenshotCropRecord | undefined;
154
182
  export declare function resetCueRuntimeState(): void;
155
183
  export declare function resetAssetRuntimeState(): void;
156
184
  export declare function resetAudioRuntimeState(): void;
185
+ export declare function getRuntimeRedactState(): ScreenCIRuntimeContext['redact'];
186
+ export declare function resetRedactRuntimeState(): void;
157
187
  export declare function pushRuntimeTimelineBlock(block: TimelineBlockState): void;
158
188
  export declare function popRuntimeTimelineBlock(expectedType: TimelineBlockType): TimelineBlockState;
159
189
  export declare function hasRuntimeTimelineBlock(type: TimelineBlockType): boolean;
@@ -161,4 +191,6 @@ export declare function getRuntimeTimelineBlocks(): TimelineBlockState[];
161
191
  export declare function isRuntimeInsideHide(): boolean;
162
192
  export declare function getRuntimeAutoZoomState(): AutoZoomState;
163
193
  export declare function setRuntimeAutoZoomState(state: AutoZoomState): void;
194
+ export declare function getRuntimeRecordingSize(): number;
195
+ export declare function setRuntimeRecordingSize(size: number): void;
164
196
  //# sourceMappingURL=runtimeContext.d.ts.map