@norskvideo/ctl-dev-kit 0.1.82 → 0.1.83
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/conventions/docs.md +11 -0
- package/doc-guide/doc-guide.d.ts +30 -1
- package/doc-guide/doc-guide.js +40 -0
- package/package.json +1 -1
package/conventions/docs.md
CHANGED
|
@@ -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
|
|
package/doc-guide/doc-guide.d.ts
CHANGED
|
@@ -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. */
|
package/doc-guide/doc-guide.js
CHANGED
|
@@ -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)
|