@norskvideo/ctl-dev-kit 0.1.82 → 0.1.84

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.
@@ -82,6 +82,17 @@ Per-step treatments (`CaptureTreatment`, from the doc-guide library):
82
82
  component renders, not on incidental structure.
83
83
  - **`spotlight`** — the whole screen with one region lit and everything else
84
84
  dimmed, for "look here, in context."
85
+ - **`livePicture`** — a live video tile the shot must not be taken without.
86
+ Studio's shared MoQ player (`norsk-studio-built-ins/lib/shared/moq-player`)
87
+ stamps `data-moq-phase` on the container it mounts into: `connecting`, then
88
+ `live` on the first decoded frame, or `error` with the reason in
89
+ `data-moq-error`. The player mounts at the moment the source connects, which
90
+ is also when a guide's "it's running" assertion passes — so a capture taken
91
+ then is of a spinner, and a sleep only hides a preview that is broken. Name
92
+ the tile here, or call `awaitLivePicture(page, selector)` before the capture
93
+ (the verify run skips captures, so an explicit wait is also the assertion);
94
+ either way a tile that never shows a picture fails the guide, naming the
95
+ phase it was stuck in and the player's own error.
85
96
 
86
97
  ## The toolchain
87
98
 
@@ -26,13 +26,42 @@ export interface UiFixture {
26
26
  export declare function docsRoot(): string;
27
27
  /** Per-step screenshot treatment. `crop` shoots one component at its own bounds;
28
28
  * `spotlight` keeps the whole screen for context but dims everything except the
29
- * named region and outlines it. Omit both for a plain full-page capture. */
29
+ * named region and outlines it. Omit both for a plain full-page capture.
30
+ * `livePicture` names a MoQ video tile the shot must not be taken without —
31
+ * see `awaitLivePicture`. */
30
32
  export interface CaptureTreatment {
31
33
  crop?: string;
32
34
  spotlight?: string;
33
35
  /** Slack in px around a spotlight region before the dimming starts. */
34
36
  pad?: number;
37
+ /** A live video tile (Studio's shared MoQ player's container) that has to be
38
+ * showing a picture before the capture is taken. */
39
+ livePicture?: string;
40
+ /** How long `livePicture` may take to arrive. Default 60s: a WebTransport
41
+ * session plus a catalog plus a keyframe, on a cold encoder. */
42
+ livePictureTimeoutMs?: number;
35
43
  }
44
+ /** The attributes Studio's shared MoQ player (norsk-studio-built-ins
45
+ * `lib/shared/moq-player`) stamps on the container it mounts into. `live`
46
+ * means the first video frame has decoded — not that the transport is up or
47
+ * the catalog arrived, both of which can hold for seconds with nothing on
48
+ * screen, which is exactly what a capture taken on a clock shows. */
49
+ export declare const MOQ_PHASE_ATTR = "data-moq-phase";
50
+ export declare const MOQ_ERROR_ATTR = "data-moq-error";
51
+ export type MoqPhase = "connecting" | "live" | "error";
52
+ /** The failure a guide sees when a tile never shows a picture: the tile, the
53
+ * phase it was stuck in and, for an error, the player's own reason — so a
54
+ * broken preview reads as what it is rather than as a timeout. Pure, so the
55
+ * wording is pinned without a browser. */
56
+ export declare function livePictureFailure(selector: string, phase: string | null, error: string | null, timeoutMs: number): string;
57
+ /** Block until the tile at `selector` reports a decoded frame, or fail saying
58
+ * why it did not. This is the wait a guide puts between "the source connected"
59
+ * and the capture: the player mounts at that moment, and a screenshot taken
60
+ * then is of a spinner. A wall-clock sleep would hide a broken preview; this
61
+ * turns it into a named failure. */
62
+ export declare function awaitLivePicture(page: Page, selector: string, opts?: {
63
+ timeoutMs?: number;
64
+ }): Promise<void>;
36
65
  /** Produce the screenshot bytes for a capture, applying its treatment. Split out
37
66
  * from `DocGuide.capture` (which also gates on DOCS_GENERATE and writes the file)
38
67
  * so the treatment geometry is testable on its own. */
@@ -10,6 +10,44 @@ export function docsRoot() {
10
10
  return isAbsolute(override) ? override : resolve(process.cwd(), override);
11
11
  return resolve(process.cwd(), "docs/generated");
12
12
  }
13
+ /** The attributes Studio's shared MoQ player (norsk-studio-built-ins
14
+ * `lib/shared/moq-player`) stamps on the container it mounts into. `live`
15
+ * means the first video frame has decoded — not that the transport is up or
16
+ * the catalog arrived, both of which can hold for seconds with nothing on
17
+ * screen, which is exactly what a capture taken on a clock shows. */
18
+ export const MOQ_PHASE_ATTR = "data-moq-phase";
19
+ export const MOQ_ERROR_ATTR = "data-moq-error";
20
+ const LIVE_PICTURE_TIMEOUT_MS = 60_000;
21
+ /** The failure a guide sees when a tile never shows a picture: the tile, the
22
+ * phase it was stuck in and, for an error, the player's own reason — so a
23
+ * broken preview reads as what it is rather than as a timeout. Pure, so the
24
+ * wording is pinned without a browser. */
25
+ export function livePictureFailure(selector, phase, error, timeoutMs) {
26
+ const state = phase === null
27
+ ? "not stamped — is a player mounted there?"
28
+ : phase === "error" && error
29
+ ? `error: ${error}`
30
+ : phase;
31
+ return `the MoQ preview at "${selector}" never decoded a frame in ${timeoutMs}ms (phase: ${state})`;
32
+ }
33
+ /** Block until the tile at `selector` reports a decoded frame, or fail saying
34
+ * why it did not. This is the wait a guide puts between "the source connected"
35
+ * and the capture: the player mounts at that moment, and a screenshot taken
36
+ * then is of a spinner. A wall-clock sleep would hide a broken preview; this
37
+ * turns it into a named failure. */
38
+ export async function awaitLivePicture(page, selector, opts) {
39
+ const timeoutMs = opts?.timeoutMs ?? LIVE_PICTURE_TIMEOUT_MS;
40
+ try {
41
+ await page.waitForFunction(({ selector, attr }) => document.querySelector(selector)?.getAttribute(attr) === "live", { selector, attr: MOQ_PHASE_ATTR }, { timeout: timeoutMs });
42
+ }
43
+ catch {
44
+ const [phase, error] = await page.evaluate(({ selector, phaseAttr, errorAttr }) => {
45
+ const el = document.querySelector(selector);
46
+ return [el?.getAttribute(phaseAttr) ?? null, el?.getAttribute(errorAttr) ?? null];
47
+ }, { selector, phaseAttr: MOQ_PHASE_ATTR, errorAttr: MOQ_ERROR_ATTR });
48
+ throw new Error(livePictureFailure(selector, phase, error, timeoutMs));
49
+ }
50
+ }
13
51
  const SPOTLIGHT_ID = "docguide-spotlight";
14
52
  /** Dim the page except the target region, drawn as one fixed box whose oversized
15
53
  * box-shadow does the dimming — appended to <body> so no ancestor `overflow`
@@ -45,6 +83,8 @@ async function spotlightOff(page) {
45
83
  * from `DocGuide.capture` (which also gates on DOCS_GENERATE and writes the file)
46
84
  * so the treatment geometry is testable on its own. */
47
85
  export async function screenshotFor(page, treat) {
86
+ if (treat?.livePicture)
87
+ await awaitLivePicture(page, treat.livePicture, { timeoutMs: treat.livePictureTimeoutMs });
48
88
  // Wait for web fonts, else the snapshot catches a fallback font mid-swap.
49
89
  await page.evaluate(() => document.fonts.ready);
50
90
  if (treat?.crop)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-dev-kit",
3
- "version": "0.1.82",
3
+ "version": "0.1.84",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./create-product": "./create-product/create-product.ts",