@navipartner/demo-video 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -100
- package/bin/demo-video.mjs +101 -30
- package/bin/init.d.mts +2 -0
- package/bin/init.mjs +21 -22
- package/bin/pick.d.mts +0 -4
- package/bin/pick.mjs +0 -39
- package/dist/{adapter-CDOXdUZI.d.ts → adapter-bOi1ZBRa.d.ts} +36 -7
- package/dist/{chunk-D7UYGNSF.js → chunk-RMBVQL2J.js} +199 -48
- package/dist/index.d.ts +119 -20
- package/dist/index.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/index.js +527 -180
- package/package.json +1 -1
- package/skills/np-demo-video/SKILL.md +213 -230
- package/skills/np-demo-video/look.md +169 -79
- package/skills/np-demo-video/reference.md +81 -30
package/dist/runner/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CARD_IN_MS,
|
|
3
3
|
CARD_OUT_MS,
|
|
4
|
+
CURSOR_MAX_MS,
|
|
5
|
+
CURSOR_MIN_MS,
|
|
4
6
|
DEFAULT_VIEWPORT,
|
|
5
7
|
LINGER_MS,
|
|
6
8
|
MIN_SCALE,
|
|
@@ -15,11 +17,14 @@ import {
|
|
|
15
17
|
STAGE_HEIGHT,
|
|
16
18
|
STAGE_PATH,
|
|
17
19
|
STAGE_WIDTH,
|
|
20
|
+
STILLS_PATH,
|
|
18
21
|
StoryboardError,
|
|
22
|
+
TYPE_CHAR_MS,
|
|
19
23
|
ZOOM_MS,
|
|
20
24
|
cardSeconds,
|
|
21
25
|
coverage,
|
|
22
26
|
fittingScale,
|
|
27
|
+
isBehindCard,
|
|
23
28
|
isCoreStep,
|
|
24
29
|
lintStoryboard,
|
|
25
30
|
loadStoryboard,
|
|
@@ -27,10 +32,11 @@ import {
|
|
|
27
32
|
shows,
|
|
28
33
|
stageHtml,
|
|
29
34
|
stageLayout,
|
|
35
|
+
titleSeconds,
|
|
30
36
|
unionRect,
|
|
31
37
|
visibleRect,
|
|
32
38
|
wideShot
|
|
33
|
-
} from "../chunk-
|
|
39
|
+
} from "../chunk-RMBVQL2J.js";
|
|
34
40
|
|
|
35
41
|
// src/runner/config.ts
|
|
36
42
|
import path from "path";
|
|
@@ -67,8 +73,8 @@ var createDemoConfig = (adapter, demoDir, runnerDir, mode = process.env.DEMO_MOD
|
|
|
67
73
|
});
|
|
68
74
|
|
|
69
75
|
// src/runner/register.ts
|
|
70
|
-
import
|
|
71
|
-
import
|
|
76
|
+
import fs4 from "fs";
|
|
77
|
+
import path5 from "path";
|
|
72
78
|
|
|
73
79
|
// src/brand.ts
|
|
74
80
|
import fs from "fs";
|
|
@@ -249,6 +255,10 @@ var missingNeeds = (brand, needs = {}) => {
|
|
|
249
255
|
];
|
|
250
256
|
};
|
|
251
257
|
|
|
258
|
+
// src/director.ts
|
|
259
|
+
import fs2 from "fs";
|
|
260
|
+
import path3 from "path";
|
|
261
|
+
|
|
252
262
|
// src/changes.ts
|
|
253
263
|
var watchChanges = (target) => target.evaluate((el) => {
|
|
254
264
|
const changed = /* @__PURE__ */ new Set();
|
|
@@ -303,6 +313,8 @@ var biggestChange = (app) => app.evaluate(() => {
|
|
|
303
313
|
var TARGET_TIMEOUT_MS = 15e3;
|
|
304
314
|
var SHOWN = 0.5;
|
|
305
315
|
var FRAME_MAX_SCALE = 1.8;
|
|
316
|
+
var PUSH_WORTHY = 1.4;
|
|
317
|
+
var STILL_MAX_PX = 1600;
|
|
306
318
|
var testIdSelector = (selector) => /^[\w-]+$/.test(selector) ? `[data-testid="${selector}"]` : selector;
|
|
307
319
|
var normalise = (text) => text.replace(/\s+/g, " ").trim();
|
|
308
320
|
var sameShot = (a, b) => Math.abs(a.k - b.k) < 1e-3 && Math.abs(a.tx - b.tx) < 1 && Math.abs(a.ty - b.ty) < 1;
|
|
@@ -327,6 +339,17 @@ var Director = class {
|
|
|
327
339
|
* what the click did.
|
|
328
340
|
*/
|
|
329
341
|
unseen;
|
|
342
|
+
/** Behind a card: no cursor, no camera moves, nothing for the viewer. */
|
|
343
|
+
covered = false;
|
|
344
|
+
/**
|
|
345
|
+
* What clicks in the current wide shot used: their controls and what they
|
|
346
|
+
* changed. When the wide shot ends, all of it must not have fit a push-in.
|
|
347
|
+
*/
|
|
348
|
+
wideUse = {
|
|
349
|
+
rects: [],
|
|
350
|
+
names: []
|
|
351
|
+
};
|
|
352
|
+
cdp;
|
|
330
353
|
get fast() {
|
|
331
354
|
return this.options.fast;
|
|
332
355
|
}
|
|
@@ -410,6 +433,7 @@ var Director = class {
|
|
|
410
433
|
}
|
|
411
434
|
// --- camera -----------------------------------------------------------------
|
|
412
435
|
async zoomTo(t, scale) {
|
|
436
|
+
this.closeWide();
|
|
413
437
|
const rect = await this.appRect(t);
|
|
414
438
|
const next = shotFor(this.layout, rect, scale);
|
|
415
439
|
this.requireSeen("zoom", next);
|
|
@@ -418,13 +442,25 @@ var Director = class {
|
|
|
418
442
|
}
|
|
419
443
|
this.shot = next;
|
|
420
444
|
this.shotLabel = `the shot on "${t.target}" at ${scale}`;
|
|
445
|
+
await this.moveCamera(next);
|
|
446
|
+
}
|
|
447
|
+
// Films a camera move. Behind a card the shot is simply there when the
|
|
448
|
+
// card goes, so the chapter opens on it.
|
|
449
|
+
async moveCamera(shot) {
|
|
421
450
|
if (this.fast) {
|
|
422
451
|
return;
|
|
423
452
|
}
|
|
424
|
-
|
|
425
|
-
await this.
|
|
453
|
+
const instant = this.covered;
|
|
454
|
+
await this.page.evaluate(([s, i]) => window.__demo.setShot(s, i), [
|
|
455
|
+
shot,
|
|
456
|
+
instant
|
|
457
|
+
]);
|
|
458
|
+
if (!instant) {
|
|
459
|
+
await this.wait(ZOOM_MS + SETTLE_MS);
|
|
460
|
+
}
|
|
426
461
|
}
|
|
427
462
|
async frame(targets, options = {}) {
|
|
463
|
+
this.closeWide();
|
|
428
464
|
const names = targets.map((t) => `"${t.target}"`).join(" and ");
|
|
429
465
|
for (const t of targets) {
|
|
430
466
|
await this.appRect(t);
|
|
@@ -461,11 +497,7 @@ var Director = class {
|
|
|
461
497
|
}
|
|
462
498
|
this.shot = next;
|
|
463
499
|
this.shotLabel = `the shot framing ${names}`;
|
|
464
|
-
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
await this.page.evaluate((s) => window.__demo.setShot(s), next);
|
|
468
|
-
await this.wait(ZOOM_MS + SETTLE_MS);
|
|
500
|
+
await this.moveCamera(next);
|
|
469
501
|
}
|
|
470
502
|
async zoomOut(options = {}) {
|
|
471
503
|
this.requireSeen("zoomOut");
|
|
@@ -492,7 +524,7 @@ var Director = class {
|
|
|
492
524
|
// --- cursor + input -----------------------------------------------------------
|
|
493
525
|
/** Rests the cursor in empty space, off the content. */
|
|
494
526
|
async park() {
|
|
495
|
-
if (this.fast) {
|
|
527
|
+
if (this.fast || this.covered) {
|
|
496
528
|
return;
|
|
497
529
|
}
|
|
498
530
|
const ms = await this.page.evaluate(
|
|
@@ -513,7 +545,7 @@ var Director = class {
|
|
|
513
545
|
`"${t.target}" is outside ${this.shotLabel}. Zoom on an area that holds everything this shot uses, or zoomOut first.`
|
|
514
546
|
);
|
|
515
547
|
}
|
|
516
|
-
return this.toStagePoint(rect);
|
|
548
|
+
return { point: this.toStagePoint(rect), rect };
|
|
517
549
|
}
|
|
518
550
|
async click(t, options = {}) {
|
|
519
551
|
if (options.zoom !== void 0) {
|
|
@@ -521,16 +553,20 @@ var Director = class {
|
|
|
521
553
|
} else {
|
|
522
554
|
this.requireSeen("other");
|
|
523
555
|
}
|
|
524
|
-
const point = await this.pointAt(t);
|
|
556
|
+
const { point, rect } = await this.pointAt(t);
|
|
525
557
|
const zoomed = this.shot.k > this.layout.fit * 1.01;
|
|
526
|
-
|
|
558
|
+
const wide = !zoomed && !this.covered;
|
|
559
|
+
if (zoomed || wide) {
|
|
527
560
|
await watchChanges(this.locator(t));
|
|
528
561
|
}
|
|
529
562
|
if (this.fast) {
|
|
530
563
|
await this.page.mouse.click(point.x, point.y);
|
|
531
|
-
if (zoomed) {
|
|
564
|
+
if (zoomed || wide) {
|
|
532
565
|
await this.page.waitForTimeout(POST_CLICK_MS);
|
|
533
566
|
}
|
|
567
|
+
} else if (this.covered) {
|
|
568
|
+
await this.page.mouse.click(point.x, point.y);
|
|
569
|
+
await this.wait(POST_CLICK_MS);
|
|
534
570
|
} else {
|
|
535
571
|
const ms = await this.page.evaluate(
|
|
536
572
|
([x, y]) => window.__demo.cursorTo(x, y),
|
|
@@ -546,6 +582,27 @@ var Director = class {
|
|
|
546
582
|
}
|
|
547
583
|
if (zoomed) {
|
|
548
584
|
await this.assertChangeInShot(t);
|
|
585
|
+
} else if (wide) {
|
|
586
|
+
const biggest = await biggestChange(this.app);
|
|
587
|
+
this.wideUse.rects.push(rect, ...biggest ? [biggest.rect] : []);
|
|
588
|
+
this.wideUse.names.push(t.target);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
// The wide shot ends (a push-in, a card, the end). Its clicks must have
|
|
592
|
+
// needed it: if their controls and results all fit a push-in, the wide
|
|
593
|
+
// shot showed them too small to read on a phone.
|
|
594
|
+
closeWide() {
|
|
595
|
+
const { rects, names } = this.wideUse;
|
|
596
|
+
this.wideUse = { rects: [], names: [] };
|
|
597
|
+
if (names.length === 0) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const scale = fittingScale(this.layout, unionRect(rects));
|
|
601
|
+
if (scale >= PUSH_WORTHY) {
|
|
602
|
+
const clicked = [...new Set(names)].map((n) => `"${n}"`).join(", ");
|
|
603
|
+
throw new Error(
|
|
604
|
+
`${clicked} ${names.length > 1 ? "are" : "is"} clicked in the wide shot, but the controls and what they change fit a push-in at ${Math.min(scale, FRAME_MAX_SCALE).toFixed(1)}: frame them so the shot reads on a phone, or do it behindCard if it's setup`
|
|
605
|
+
);
|
|
549
606
|
}
|
|
550
607
|
}
|
|
551
608
|
// A zoomed shot is about what the click does. If the biggest thing the
|
|
@@ -579,10 +636,14 @@ var Director = class {
|
|
|
579
636
|
}
|
|
580
637
|
throw new Error(unseen.message);
|
|
581
638
|
}
|
|
582
|
-
async type(t, text, perCharMs =
|
|
639
|
+
async type(t, text, perCharMs = TYPE_CHAR_MS, options = {}) {
|
|
583
640
|
await this.click(t);
|
|
584
|
-
if (
|
|
641
|
+
if (options.replace) {
|
|
642
|
+
await this.page.keyboard.press("ControlOrMeta+A");
|
|
643
|
+
}
|
|
644
|
+
if (this.fast || this.covered) {
|
|
585
645
|
await this.page.keyboard.type(text);
|
|
646
|
+
await this.wait(POST_KEY_MS);
|
|
586
647
|
return;
|
|
587
648
|
}
|
|
588
649
|
for (const char of text) {
|
|
@@ -603,6 +664,7 @@ var Director = class {
|
|
|
603
664
|
/** After the last step: a zoomed click's change must have been shown. */
|
|
604
665
|
end() {
|
|
605
666
|
this.requireSeen("other");
|
|
667
|
+
this.closeWide();
|
|
606
668
|
}
|
|
607
669
|
async during(work) {
|
|
608
670
|
this.requireSeen("other");
|
|
@@ -651,24 +713,84 @@ var Director = class {
|
|
|
651
713
|
// --- overlays --------------------------------------------------------------------
|
|
652
714
|
async card(spec, options = {}) {
|
|
653
715
|
this.requireSeen("other");
|
|
716
|
+
this.closeWide();
|
|
654
717
|
this.shot = wideShot(this.layout);
|
|
655
718
|
this.shotLabel = "the wide shot";
|
|
656
719
|
if (this.fast) {
|
|
720
|
+
await this.behind(options.behind);
|
|
657
721
|
return;
|
|
658
722
|
}
|
|
659
|
-
|
|
660
|
-
|
|
723
|
+
if (!options.opening) {
|
|
724
|
+
await this.showCard(spec);
|
|
725
|
+
await this.wait(CARD_IN_MS);
|
|
726
|
+
}
|
|
661
727
|
await this.page.evaluate(() => {
|
|
662
728
|
window.__demo.resetCamera();
|
|
663
729
|
window.__demo.parkNow();
|
|
664
730
|
});
|
|
665
731
|
await this.page.mouse.move(PARK.x, PARK.y);
|
|
666
|
-
|
|
732
|
+
const started = this.clockMs();
|
|
733
|
+
await this.behind(options.behind);
|
|
734
|
+
await this.page.mouse.move(PARK.x, PARK.y);
|
|
735
|
+
const readMs = (options.opening ? titleSeconds(spec, options.kind) : cardSeconds(spec)) * 1e3;
|
|
736
|
+
await this.wait(readMs - (this.clockMs() - started));
|
|
667
737
|
if (!options.keepUp) {
|
|
668
738
|
await this.hideCard();
|
|
669
739
|
await this.wait(CARD_OUT_MS);
|
|
670
740
|
}
|
|
671
741
|
}
|
|
742
|
+
async behind(work) {
|
|
743
|
+
if (!work) {
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
this.covered = true;
|
|
747
|
+
try {
|
|
748
|
+
await work();
|
|
749
|
+
} finally {
|
|
750
|
+
this.covered = false;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
async capture(t, file) {
|
|
754
|
+
const locator = this.locator(t);
|
|
755
|
+
if (!await locator.isVisible().catch(() => false)) {
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
const r = await this.readRect(locator);
|
|
759
|
+
const { width, height } = this.layout.viewport;
|
|
760
|
+
const x0 = Math.max(0, r.x);
|
|
761
|
+
const y0 = Math.max(0, r.y);
|
|
762
|
+
const x1 = Math.min(width, r.x + r.width);
|
|
763
|
+
const y1 = Math.min(height, r.y + r.height);
|
|
764
|
+
if (x1 - x0 < 1 || y1 - y0 < 1) {
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
const shot = this.fast ? wideShot(this.layout) : this.shot;
|
|
768
|
+
const k = shot.k;
|
|
769
|
+
const left = Math.ceil(this.layout.left + shot.tx + k * x0);
|
|
770
|
+
const top = Math.ceil(this.layout.top + shot.ty + k * y0);
|
|
771
|
+
const right = Math.floor(this.layout.left + shot.tx + k * x1);
|
|
772
|
+
const bottom = Math.floor(this.layout.top + shot.ty + k * y1);
|
|
773
|
+
if (right - left < 1 || bottom - top < 1) {
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
this.cdp ??= await this.page.context().newCDPSession(this.page);
|
|
777
|
+
const { data } = await this.cdp.send("Page.captureScreenshot", {
|
|
778
|
+
format: "png",
|
|
779
|
+
clip: {
|
|
780
|
+
x: left,
|
|
781
|
+
y: top,
|
|
782
|
+
width: right - left,
|
|
783
|
+
height: bottom - top,
|
|
784
|
+
scale: Math.min(
|
|
785
|
+
2 / k,
|
|
786
|
+
STILL_MAX_PX / Math.max(right - left, bottom - top)
|
|
787
|
+
)
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
fs2.mkdirSync(path3.dirname(file), { recursive: true });
|
|
791
|
+
fs2.writeFileSync(file, Buffer.from(data, "base64"));
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
672
794
|
/** `instant` puts the card up with no dissolve (the video's first frame). */
|
|
673
795
|
async showCard(spec, instant = false) {
|
|
674
796
|
if (!this.fast) {
|
|
@@ -698,10 +820,321 @@ var Director = class {
|
|
|
698
820
|
}
|
|
699
821
|
};
|
|
700
822
|
|
|
823
|
+
// src/run.ts
|
|
824
|
+
var CAMERA_MOVE_MS = ZOOM_MS + SETTLE_MS;
|
|
825
|
+
var CARD_DISSOLVE_MS = CARD_IN_MS + CARD_OUT_MS;
|
|
826
|
+
var IDLE_MARGIN_MS = 1e4;
|
|
827
|
+
var INTERACTIONS = /* @__PURE__ */ new Set(["click", "type", "press"]);
|
|
828
|
+
var CAMERA_MOVES = /* @__PURE__ */ new Set(["zoom", "frame", "zoomOut"]);
|
|
829
|
+
var describeTarget = (t) => typeof t === "string" ? t : typeof t === "object" && t !== null && "target" in t ? `${String(t.target)}${"nth" in t && typeof t.nth === "number" ? `[${t.nth}]` : ""}` : "";
|
|
830
|
+
var describeStep = (step) => {
|
|
831
|
+
if ("targets" in step && Array.isArray(step.targets)) {
|
|
832
|
+
return `${step.do} ${step.targets.map(describeTarget).join(" + ")}`;
|
|
833
|
+
}
|
|
834
|
+
const target = "target" in step ? describeTarget(step) : "";
|
|
835
|
+
return `${step.do}${target ? ` ${target}` : ""}`;
|
|
836
|
+
};
|
|
837
|
+
var StepError = class extends Error {
|
|
838
|
+
constructor(index, step, reason, visible) {
|
|
839
|
+
super(`Step ${index + 1} (${describeStep(step)}) failed: ${reason}`);
|
|
840
|
+
this.index = index;
|
|
841
|
+
this.step = step;
|
|
842
|
+
this.reason = reason;
|
|
843
|
+
this.visible = visible;
|
|
844
|
+
}
|
|
845
|
+
index;
|
|
846
|
+
step;
|
|
847
|
+
reason;
|
|
848
|
+
visible;
|
|
849
|
+
};
|
|
850
|
+
var reasonOf = (cause) => {
|
|
851
|
+
if (cause && typeof cause === "object" && "issues" in cause && Array.isArray(cause.issues)) {
|
|
852
|
+
return cause.issues.map((i) => `${i.path.join(".") || "(params)"}: ${i.message}`).join("; ");
|
|
853
|
+
}
|
|
854
|
+
const text = cause instanceof Error ? cause.message : String(cause);
|
|
855
|
+
return text.split("\n")[0].trim();
|
|
856
|
+
};
|
|
857
|
+
var runStep = async (d, step, adapter) => {
|
|
858
|
+
if (isCoreStep(step)) {
|
|
859
|
+
switch (step.do) {
|
|
860
|
+
case "click":
|
|
861
|
+
return d.click(step, { zoom: step.zoom });
|
|
862
|
+
case "type":
|
|
863
|
+
return d.type(step, step.text, step.perCharMs, {
|
|
864
|
+
replace: step.replace
|
|
865
|
+
});
|
|
866
|
+
case "press":
|
|
867
|
+
return d.press(step.key);
|
|
868
|
+
case "zoom":
|
|
869
|
+
return d.zoomTo(step, step.scale);
|
|
870
|
+
case "frame":
|
|
871
|
+
return d.frame(
|
|
872
|
+
step.targets.map((t) => typeof t === "string" ? { target: t } : t),
|
|
873
|
+
{ maxScale: step.maxScale }
|
|
874
|
+
);
|
|
875
|
+
case "zoomOut":
|
|
876
|
+
return d.zoomOut({ park: step.park });
|
|
877
|
+
case "park":
|
|
878
|
+
return d.park();
|
|
879
|
+
case "waitFor":
|
|
880
|
+
return d.waitFor(step, {
|
|
881
|
+
hidden: step.hidden,
|
|
882
|
+
timeoutMs: step.timeout * 1e3
|
|
883
|
+
});
|
|
884
|
+
case "expectText":
|
|
885
|
+
return d.expectText(step, step.text, {
|
|
886
|
+
contains: step.contains,
|
|
887
|
+
timeoutMs: step.timeout * 1e3
|
|
888
|
+
});
|
|
889
|
+
case "hold":
|
|
890
|
+
return d.hold(step.seconds * 1e3);
|
|
891
|
+
case "card":
|
|
892
|
+
throw new Error("a card is run by runSteps, not runStep");
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const action = adapter.actions?.[step.do];
|
|
896
|
+
if (!action) {
|
|
897
|
+
throw new Error(`unknown step "${step.do}"`);
|
|
898
|
+
}
|
|
899
|
+
const params = Object.fromEntries(
|
|
900
|
+
Object.entries(step).filter(([key]) => key !== "do")
|
|
901
|
+
);
|
|
902
|
+
const parsed = action.params.parse(params);
|
|
903
|
+
await d.during(() => action.run(d, parsed));
|
|
904
|
+
};
|
|
905
|
+
var needsLinger = (previous) => previous !== void 0 && previous.do !== "hold" && previous.do !== "card" && !isBehindCard(previous);
|
|
906
|
+
var TITLE = -1;
|
|
907
|
+
var planCards = (steps, hasTitle) => {
|
|
908
|
+
const plans = /* @__PURE__ */ new Map();
|
|
909
|
+
const starts = [
|
|
910
|
+
...hasTitle ? [TITLE] : [],
|
|
911
|
+
...steps.flatMap((s, i) => s.do === "card" ? [i] : [])
|
|
912
|
+
];
|
|
913
|
+
for (const start of starts) {
|
|
914
|
+
const behind = [];
|
|
915
|
+
let j = start + 1;
|
|
916
|
+
while (j < steps.length && isBehindCard(steps[j])) {
|
|
917
|
+
behind.push(j++);
|
|
918
|
+
}
|
|
919
|
+
if (j < steps.length && (steps[j].do === "zoom" || steps[j].do === "frame")) {
|
|
920
|
+
behind.push(j++);
|
|
921
|
+
}
|
|
922
|
+
plans.set(start, {
|
|
923
|
+
behind,
|
|
924
|
+
opens: j < steps.length && steps[j].do !== "card"
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
[...plans.entries()].filter(([at, plan]) => at !== TITLE && plan.opens).forEach(([, plan], n) => {
|
|
928
|
+
plan.chapter = n + 1;
|
|
929
|
+
});
|
|
930
|
+
return plans;
|
|
931
|
+
};
|
|
932
|
+
var toTarget = (t) => typeof t === "string" ? { target: t } : t;
|
|
933
|
+
var cardSpecFor = (card, plan, still) => ({
|
|
934
|
+
eyebrow: card.eyebrow,
|
|
935
|
+
heading: card.heading,
|
|
936
|
+
sub: card.sub,
|
|
937
|
+
logo: card.logo,
|
|
938
|
+
seconds: card.seconds,
|
|
939
|
+
style: card.style ?? (plan?.chapter !== void 0 && plan.chapter % 2 === 0 ? "brand" : "pale"),
|
|
940
|
+
...still ? { still } : {}
|
|
941
|
+
});
|
|
942
|
+
var stillCards = (steps, title) => [
|
|
943
|
+
...title?.show !== void 0 ? [TITLE] : [],
|
|
944
|
+
...steps.flatMap(
|
|
945
|
+
(s, i) => s.do === "card" && s.show !== void 0 ? [i] : []
|
|
946
|
+
)
|
|
947
|
+
];
|
|
948
|
+
var WAITS = /* @__PURE__ */ new Set(["waitFor", "expectText"]);
|
|
949
|
+
var runSteps = async (d, steps, adapter, options = {}) => {
|
|
950
|
+
const idleLimitMs = adapter.idleTimeoutSeconds ? adapter.idleTimeoutSeconds * 1e3 - IDLE_MARGIN_MS : Number.POSITIVE_INFINITY;
|
|
951
|
+
let quietMs = options.initialQuietMs ?? 0;
|
|
952
|
+
const tooQuiet = () => new Error(
|
|
953
|
+
`the app would sit untouched for ${Math.round(quietMs / 1e3)} s; its idle timeout is ${adapter.idleTimeoutSeconds} s. Add an interaction or shorten the holds.`
|
|
954
|
+
);
|
|
955
|
+
const plans = planCards(steps, Boolean(options.title));
|
|
956
|
+
const covered = new Set([...plans.values()].flatMap((p) => p.behind));
|
|
957
|
+
let pending;
|
|
958
|
+
const hero = options.title?.show && options.stillFile ? { target: toTarget(options.title.show), seen: false } : void 0;
|
|
959
|
+
const fail = async (index, cause) => {
|
|
960
|
+
if (cause instanceof StepError) {
|
|
961
|
+
throw cause;
|
|
962
|
+
}
|
|
963
|
+
const visible = await d.visibleTestIds().catch(() => []);
|
|
964
|
+
throw new StepError(index, steps[index], reasonOf(cause), visible);
|
|
965
|
+
};
|
|
966
|
+
const settlePending = async () => {
|
|
967
|
+
const was = pending;
|
|
968
|
+
pending = void 0;
|
|
969
|
+
if (was && !was.seen) {
|
|
970
|
+
await fail(
|
|
971
|
+
was.card,
|
|
972
|
+
new Error(
|
|
973
|
+
`"${was.target.target}" (show) never appears in the chapter after this card, so there is no still to put on it`
|
|
974
|
+
)
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
const runCard = async (at) => {
|
|
979
|
+
const plan = plans.get(at);
|
|
980
|
+
const card = at === TITLE ? options.title : steps[at];
|
|
981
|
+
const show = card.show ? toTarget(card.show) : void 0;
|
|
982
|
+
if (show && options.stillFile && at !== TITLE) {
|
|
983
|
+
if (plan?.opens) {
|
|
984
|
+
pending = { card: at, target: show, seen: false };
|
|
985
|
+
} else if (!await d.capture(show, options.stillFile(at))) {
|
|
986
|
+
throw new Error(
|
|
987
|
+
`"${show.target}" (show) isn't on screen when this card comes up`
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
const behind = plan?.behind ?? [];
|
|
992
|
+
const last = behind.at(-1) ?? at;
|
|
993
|
+
const next = steps[last + 1] ?? (last === steps.length - 1 && options.keepLastCardUp ? { do: "card" } : void 0);
|
|
994
|
+
await d.card(
|
|
995
|
+
cardSpecFor(
|
|
996
|
+
card,
|
|
997
|
+
plan,
|
|
998
|
+
show && options.stillUrl ? options.stillUrl(at) : void 0
|
|
999
|
+
),
|
|
1000
|
+
{
|
|
1001
|
+
keepUp: next?.do === "card",
|
|
1002
|
+
opening: at === TITLE,
|
|
1003
|
+
kind: options.kind,
|
|
1004
|
+
behind: behind.length ? async () => {
|
|
1005
|
+
for (const index of behind) {
|
|
1006
|
+
await runOne(index);
|
|
1007
|
+
}
|
|
1008
|
+
} : void 0
|
|
1009
|
+
}
|
|
1010
|
+
);
|
|
1011
|
+
};
|
|
1012
|
+
const runOne = async (index) => {
|
|
1013
|
+
const step = steps[index];
|
|
1014
|
+
if (step.do === "card") {
|
|
1015
|
+
await settlePending();
|
|
1016
|
+
}
|
|
1017
|
+
try {
|
|
1018
|
+
const linger = step.do === "card" && needsLinger(steps[index - 1]);
|
|
1019
|
+
if (step.do === "hold" || step.do === "card" || CAMERA_MOVES.has(step.do) && !covered.has(index)) {
|
|
1020
|
+
quietMs += step.do === "hold" ? Number(step.seconds) * 1e3 : step.do === "card" ? cardSeconds(step) * 1e3 + CARD_DISSOLVE_MS + (linger ? LINGER_MS : 0) : CAMERA_MOVE_MS;
|
|
1021
|
+
if (quietMs > idleLimitMs) {
|
|
1022
|
+
throw tooQuiet();
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (linger) {
|
|
1026
|
+
await d.hold(LINGER_MS);
|
|
1027
|
+
}
|
|
1028
|
+
const started = d.clockMs();
|
|
1029
|
+
if (step.do === "card") {
|
|
1030
|
+
await runCard(index);
|
|
1031
|
+
} else {
|
|
1032
|
+
await runStep(d, step, adapter);
|
|
1033
|
+
}
|
|
1034
|
+
if (WAITS.has(step.do)) {
|
|
1035
|
+
quietMs += d.clockMs() - started;
|
|
1036
|
+
if (quietMs > idleLimitMs) {
|
|
1037
|
+
throw tooQuiet();
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if (INTERACTIONS.has(step.do) || adapter.actions?.[step.do]) {
|
|
1041
|
+
quietMs = 0;
|
|
1042
|
+
}
|
|
1043
|
+
} catch (cause) {
|
|
1044
|
+
await fail(index, cause);
|
|
1045
|
+
}
|
|
1046
|
+
if (pending && step.do !== "card" && options.stillFile) {
|
|
1047
|
+
const seen = await d.capture(
|
|
1048
|
+
pending.target,
|
|
1049
|
+
options.stillFile(pending.card)
|
|
1050
|
+
);
|
|
1051
|
+
pending.seen ||= seen;
|
|
1052
|
+
}
|
|
1053
|
+
if (hero && step.do !== "card" && options.stillFile) {
|
|
1054
|
+
hero.seen ||= await d.capture(hero.target, options.stillFile(TITLE));
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
if (options.title) {
|
|
1058
|
+
quietMs += titleSeconds(options.title, options.kind) * 1e3 + CARD_OUT_MS;
|
|
1059
|
+
await runCard(TITLE);
|
|
1060
|
+
}
|
|
1061
|
+
for (const index of steps.keys()) {
|
|
1062
|
+
if (!covered.has(index)) {
|
|
1063
|
+
await runOne(index);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
await settlePending();
|
|
1067
|
+
if (hero && !hero.seen) {
|
|
1068
|
+
throw new Error(
|
|
1069
|
+
`title.show: "${hero.target.target}" never appears in the story, so the open card has no snapshot`
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
// src/estimate.ts
|
|
1075
|
+
var CURSOR_MS = (CURSOR_MIN_MS + CURSOR_MAX_MS) / 2;
|
|
1076
|
+
var CLICK_MS = CURSOR_MS + PRE_CLICK_MS + PRESS_MS + POST_CLICK_MS;
|
|
1077
|
+
var CAMERA_MS = ZOOM_MS + SETTLE_MS;
|
|
1078
|
+
var stepMs = (step) => {
|
|
1079
|
+
const s = step;
|
|
1080
|
+
switch (step.do) {
|
|
1081
|
+
case "click":
|
|
1082
|
+
return CLICK_MS + (s.zoom !== void 0 ? CAMERA_MS : 0);
|
|
1083
|
+
case "type":
|
|
1084
|
+
return CLICK_MS + String(s.text).length * Number(s.perCharMs ?? TYPE_CHAR_MS) + POST_KEY_MS;
|
|
1085
|
+
case "press":
|
|
1086
|
+
return POST_KEY_MS;
|
|
1087
|
+
case "zoom":
|
|
1088
|
+
case "frame":
|
|
1089
|
+
case "zoomOut":
|
|
1090
|
+
return CAMERA_MS;
|
|
1091
|
+
case "park":
|
|
1092
|
+
return CURSOR_MS;
|
|
1093
|
+
case "hold":
|
|
1094
|
+
return Number(s.seconds) * 1e3;
|
|
1095
|
+
case "waitFor":
|
|
1096
|
+
case "expectText":
|
|
1097
|
+
return 0;
|
|
1098
|
+
default:
|
|
1099
|
+
return POST_ACTION_MS + CURSOR_MS;
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
var estimateSeconds = (story) => {
|
|
1103
|
+
const steps = story.steps;
|
|
1104
|
+
const plans = planCards(steps, Boolean(story.title));
|
|
1105
|
+
const covered = new Set([...plans.values()].flatMap((p) => p.behind));
|
|
1106
|
+
const after = (at) => steps[(plans.get(at)?.behind.at(-1) ?? at) + 1];
|
|
1107
|
+
let ms = 0;
|
|
1108
|
+
if (story.title) {
|
|
1109
|
+
ms += titleSeconds(story.title, story.kind) * 1e3;
|
|
1110
|
+
ms += after(TITLE)?.do === "card" ? 0 : CARD_OUT_MS;
|
|
1111
|
+
}
|
|
1112
|
+
steps.forEach((step, i) => {
|
|
1113
|
+
if (covered.has(i)) {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
if (step.do !== "card") {
|
|
1117
|
+
ms += stepMs(step);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const previous = steps[i - 1];
|
|
1121
|
+
const linger = previous !== void 0 && previous.do !== "hold" && previous.do !== "card" && !isBehindCard(previous);
|
|
1122
|
+
ms += (linger ? LINGER_MS : 0) + CARD_IN_MS;
|
|
1123
|
+
ms += cardSeconds(step) * 1e3;
|
|
1124
|
+
ms += after(i)?.do === "card" || !after(i) && story.closing ? 0 : CARD_OUT_MS;
|
|
1125
|
+
});
|
|
1126
|
+
if (story.closing) {
|
|
1127
|
+
const last = steps.at(-1)?.do;
|
|
1128
|
+
ms += last !== "card" && last !== "hold" ? LINGER_MS : 0;
|
|
1129
|
+
ms += CARD_IN_MS + titleSeconds(story.closing, story.kind) * 1e3;
|
|
1130
|
+
}
|
|
1131
|
+
return Math.round(ms / 1e3);
|
|
1132
|
+
};
|
|
1133
|
+
|
|
701
1134
|
// src/frames.ts
|
|
702
1135
|
import { spawn } from "child_process";
|
|
703
|
-
import
|
|
704
|
-
import
|
|
1136
|
+
import fs3 from "fs";
|
|
1137
|
+
import path4 from "path";
|
|
705
1138
|
import ffmpegPath from "ffmpeg-static";
|
|
706
1139
|
var FPS = 60;
|
|
707
1140
|
var DRAFT_FPS = 30;
|
|
@@ -712,8 +1145,8 @@ var FrameRecorder = class {
|
|
|
712
1145
|
this.workDir = workDir;
|
|
713
1146
|
this.stem = stem;
|
|
714
1147
|
this.fps = fps;
|
|
715
|
-
this.rawDir =
|
|
716
|
-
this.videoPath =
|
|
1148
|
+
this.rawDir = path4.join(workDir, "raw");
|
|
1149
|
+
this.videoPath = path4.join(this.rawDir, "stage.mkv");
|
|
717
1150
|
}
|
|
718
1151
|
page;
|
|
719
1152
|
workDir;
|
|
@@ -748,13 +1181,13 @@ var FrameRecorder = class {
|
|
|
748
1181
|
}
|
|
749
1182
|
/** Starts writing frames. Time advanced before this is not filmed. */
|
|
750
1183
|
start() {
|
|
751
|
-
if (!ffmpegPath || !
|
|
1184
|
+
if (!ffmpegPath || !fs3.existsSync(ffmpegPath)) {
|
|
752
1185
|
throw new Error(
|
|
753
1186
|
"ffmpeg-static has no binary; with pnpm >= 10, allow its install script (onlyBuiltDependencies) and reinstall."
|
|
754
1187
|
);
|
|
755
1188
|
}
|
|
756
|
-
|
|
757
|
-
|
|
1189
|
+
fs3.rmSync(this.rawDir, { recursive: true, force: true });
|
|
1190
|
+
fs3.mkdirSync(this.rawDir, { recursive: true });
|
|
758
1191
|
const ffmpeg = spawn(
|
|
759
1192
|
ffmpegPath,
|
|
760
1193
|
[
|
|
@@ -963,138 +1396,14 @@ var FrameRecorder = class {
|
|
|
963
1396
|
endAt: null,
|
|
964
1397
|
storyboard: this.stem
|
|
965
1398
|
};
|
|
966
|
-
|
|
967
|
-
|
|
1399
|
+
fs3.writeFileSync(
|
|
1400
|
+
path4.join(this.workDir, "manifest.json"),
|
|
968
1401
|
JSON.stringify(manifest, null, 2)
|
|
969
1402
|
);
|
|
970
1403
|
return manifest;
|
|
971
1404
|
}
|
|
972
1405
|
};
|
|
973
1406
|
|
|
974
|
-
// src/run.ts
|
|
975
|
-
var CAMERA_MOVE_MS = ZOOM_MS + SETTLE_MS;
|
|
976
|
-
var CARD_DISSOLVE_MS = CARD_IN_MS + CARD_OUT_MS;
|
|
977
|
-
var IDLE_MARGIN_MS = 1e4;
|
|
978
|
-
var INTERACTIONS = /* @__PURE__ */ new Set(["click", "type", "press"]);
|
|
979
|
-
var CAMERA_MOVES = /* @__PURE__ */ new Set(["zoom", "frame", "zoomOut"]);
|
|
980
|
-
var describeTarget = (t) => typeof t === "string" ? t : typeof t === "object" && t !== null && "target" in t ? `${String(t.target)}${"nth" in t && typeof t.nth === "number" ? `[${t.nth}]` : ""}` : "";
|
|
981
|
-
var describeStep = (step) => {
|
|
982
|
-
if ("targets" in step && Array.isArray(step.targets)) {
|
|
983
|
-
return `${step.do} ${step.targets.map(describeTarget).join(" + ")}`;
|
|
984
|
-
}
|
|
985
|
-
const target = "target" in step ? describeTarget(step) : "";
|
|
986
|
-
return `${step.do}${target ? ` ${target}` : ""}`;
|
|
987
|
-
};
|
|
988
|
-
var StepError = class extends Error {
|
|
989
|
-
constructor(index, step, reason, visible) {
|
|
990
|
-
super(`Step ${index + 1} (${describeStep(step)}) failed: ${reason}`);
|
|
991
|
-
this.index = index;
|
|
992
|
-
this.step = step;
|
|
993
|
-
this.reason = reason;
|
|
994
|
-
this.visible = visible;
|
|
995
|
-
}
|
|
996
|
-
index;
|
|
997
|
-
step;
|
|
998
|
-
reason;
|
|
999
|
-
visible;
|
|
1000
|
-
};
|
|
1001
|
-
var reasonOf = (cause) => {
|
|
1002
|
-
if (cause && typeof cause === "object" && "issues" in cause && Array.isArray(cause.issues)) {
|
|
1003
|
-
return cause.issues.map((i) => `${i.path.join(".") || "(params)"}: ${i.message}`).join("; ");
|
|
1004
|
-
}
|
|
1005
|
-
const text = cause instanceof Error ? cause.message : String(cause);
|
|
1006
|
-
return text.split("\n")[0].trim();
|
|
1007
|
-
};
|
|
1008
|
-
var runStep = async (d, step, adapter, next) => {
|
|
1009
|
-
if (isCoreStep(step)) {
|
|
1010
|
-
switch (step.do) {
|
|
1011
|
-
case "click":
|
|
1012
|
-
return d.click(step, { zoom: step.zoom });
|
|
1013
|
-
case "type":
|
|
1014
|
-
return d.type(step, step.text, step.perCharMs);
|
|
1015
|
-
case "press":
|
|
1016
|
-
return d.press(step.key);
|
|
1017
|
-
case "zoom":
|
|
1018
|
-
return d.zoomTo(step, step.scale);
|
|
1019
|
-
case "frame":
|
|
1020
|
-
return d.frame(
|
|
1021
|
-
step.targets.map((t) => typeof t === "string" ? { target: t } : t),
|
|
1022
|
-
{ maxScale: step.maxScale }
|
|
1023
|
-
);
|
|
1024
|
-
case "zoomOut":
|
|
1025
|
-
return d.zoomOut({ park: step.park });
|
|
1026
|
-
case "park":
|
|
1027
|
-
return d.park();
|
|
1028
|
-
case "waitFor":
|
|
1029
|
-
return d.waitFor(step, {
|
|
1030
|
-
hidden: step.hidden,
|
|
1031
|
-
timeoutMs: step.timeout * 1e3
|
|
1032
|
-
});
|
|
1033
|
-
case "expectText":
|
|
1034
|
-
return d.expectText(step, step.text, {
|
|
1035
|
-
contains: step.contains,
|
|
1036
|
-
timeoutMs: step.timeout * 1e3
|
|
1037
|
-
});
|
|
1038
|
-
case "hold":
|
|
1039
|
-
return d.hold(step.seconds * 1e3);
|
|
1040
|
-
case "card":
|
|
1041
|
-
return d.card(step, { keepUp: next?.do === "card" });
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
const action = adapter.actions?.[step.do];
|
|
1045
|
-
if (!action) {
|
|
1046
|
-
throw new Error(`unknown step "${step.do}"`);
|
|
1047
|
-
}
|
|
1048
|
-
const params = Object.fromEntries(
|
|
1049
|
-
Object.entries(step).filter(([key]) => key !== "do")
|
|
1050
|
-
);
|
|
1051
|
-
const parsed = action.params.parse(params);
|
|
1052
|
-
await d.during(() => action.run(d, parsed));
|
|
1053
|
-
};
|
|
1054
|
-
var needsLinger = (previous) => previous !== void 0 && previous.do !== "hold" && previous.do !== "card";
|
|
1055
|
-
var WAITS = /* @__PURE__ */ new Set(["waitFor", "expectText"]);
|
|
1056
|
-
var runSteps = async (d, steps, adapter, options = {}) => {
|
|
1057
|
-
const idleLimitMs = adapter.idleTimeoutSeconds ? adapter.idleTimeoutSeconds * 1e3 - IDLE_MARGIN_MS : Number.POSITIVE_INFINITY;
|
|
1058
|
-
let quietMs = options.initialQuietMs ?? 0;
|
|
1059
|
-
const tooQuiet = () => new Error(
|
|
1060
|
-
`the app would sit untouched for ${Math.round(quietMs / 1e3)} s; its idle timeout is ${adapter.idleTimeoutSeconds} s. Add an interaction or shorten the holds.`
|
|
1061
|
-
);
|
|
1062
|
-
for (const [index, step] of steps.entries()) {
|
|
1063
|
-
try {
|
|
1064
|
-
const linger = step.do === "card" && needsLinger(steps[index - 1]);
|
|
1065
|
-
if (step.do === "hold" || step.do === "card" || CAMERA_MOVES.has(step.do)) {
|
|
1066
|
-
quietMs += step.do === "hold" ? Number(step.seconds) * 1e3 : step.do === "card" ? cardSeconds(step) * 1e3 + CARD_DISSOLVE_MS + (linger ? LINGER_MS : 0) : CAMERA_MOVE_MS;
|
|
1067
|
-
if (quietMs > idleLimitMs) {
|
|
1068
|
-
throw tooQuiet();
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
if (linger) {
|
|
1072
|
-
await d.hold(LINGER_MS);
|
|
1073
|
-
}
|
|
1074
|
-
const isLast = index === steps.length - 1;
|
|
1075
|
-
const started = d.clockMs();
|
|
1076
|
-
await runStep(
|
|
1077
|
-
d,
|
|
1078
|
-
step,
|
|
1079
|
-
adapter,
|
|
1080
|
-
steps[index + 1] ?? (isLast && options.keepLastCardUp ? { do: "card" } : void 0)
|
|
1081
|
-
);
|
|
1082
|
-
if (WAITS.has(step.do)) {
|
|
1083
|
-
quietMs += d.clockMs() - started;
|
|
1084
|
-
if (quietMs > idleLimitMs) {
|
|
1085
|
-
throw tooQuiet();
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
if (INTERACTIONS.has(step.do) || adapter.actions?.[step.do]) {
|
|
1089
|
-
quietMs = 0;
|
|
1090
|
-
}
|
|
1091
|
-
} catch (cause) {
|
|
1092
|
-
const visible = await d.visibleTestIds().catch(() => []);
|
|
1093
|
-
throw new StepError(index, step, reasonOf(cause), visible);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
};
|
|
1097
|
-
|
|
1098
1407
|
// src/runner/env.ts
|
|
1099
1408
|
var readRunEnv = (env = process.env) => {
|
|
1100
1409
|
const storyboardPath = env.DEMO_STORYBOARD;
|
|
@@ -1111,7 +1420,8 @@ var readRunEnv = (env = process.env) => {
|
|
|
1111
1420
|
workDir: env.DEMO_WORK || `${outDir}/.work`,
|
|
1112
1421
|
brand: env.DEMO_BRAND || void 0,
|
|
1113
1422
|
brandsDir: env.DEMO_BRANDS || void 0,
|
|
1114
|
-
draft: env.DEMO_DRAFT === "1"
|
|
1423
|
+
draft: env.DEMO_DRAFT === "1",
|
|
1424
|
+
stillsDir: env.DEMO_STILLS || void 0
|
|
1115
1425
|
};
|
|
1116
1426
|
};
|
|
1117
1427
|
|
|
@@ -1156,20 +1466,20 @@ var bootFailure = (adapter, frameUrl, headers, cause) => {
|
|
|
1156
1466
|
return `The app didn't get ready: ${reason}. Check that the adapter's setup mocks what the app calls at start, and that "${adapter.ready}" is shown once it booted.`;
|
|
1157
1467
|
};
|
|
1158
1468
|
var writeJson = (file, value) => {
|
|
1159
|
-
|
|
1160
|
-
|
|
1469
|
+
fs4.mkdirSync(path5.dirname(file), { recursive: true });
|
|
1470
|
+
fs4.writeFileSync(file, JSON.stringify(value, null, 2));
|
|
1161
1471
|
};
|
|
1162
1472
|
var registerDemoTests = (test, adapter, demoDir) => {
|
|
1163
1473
|
if (process.env.DEMO_MODE === "needs") {
|
|
1164
1474
|
test("demo", async () => {
|
|
1165
|
-
writeJson(
|
|
1475
|
+
writeJson(path5.join(process.env.DEMO_WORK ?? demoDir, "needs.json"), {
|
|
1166
1476
|
app: adapter.app,
|
|
1167
1477
|
viewport: adapter.viewport ?? DEFAULT_VIEWPORT,
|
|
1168
1478
|
video: VIDEO_NEEDS,
|
|
1169
1479
|
appNeeds: describeNeeds(adapter.brandNeeds),
|
|
1170
1480
|
example: JSON.parse(
|
|
1171
|
-
|
|
1172
|
-
|
|
1481
|
+
fs4.readFileSync(
|
|
1482
|
+
path5.join(sharedBrandsDir(), "navipartner.json"),
|
|
1173
1483
|
"utf8"
|
|
1174
1484
|
)
|
|
1175
1485
|
)
|
|
@@ -1180,10 +1490,15 @@ var registerDemoTests = (test, adapter, demoDir) => {
|
|
|
1180
1490
|
test("demo", async ({ page }) => {
|
|
1181
1491
|
const env = readRunEnv();
|
|
1182
1492
|
if (env.mode === "check") {
|
|
1183
|
-
const reportPath =
|
|
1493
|
+
const reportPath = path5.join(env.workDir, "check.json");
|
|
1184
1494
|
try {
|
|
1185
|
-
const
|
|
1186
|
-
writeJson(reportPath, {
|
|
1495
|
+
const story = await perform(page, env);
|
|
1496
|
+
writeJson(reportPath, {
|
|
1497
|
+
ok: true,
|
|
1498
|
+
steps: story.steps.length,
|
|
1499
|
+
kind: story.kind ?? "showcase",
|
|
1500
|
+
seconds: estimateSeconds(story)
|
|
1501
|
+
});
|
|
1187
1502
|
} catch (error) {
|
|
1188
1503
|
writeJson(reportPath, failureReport(error));
|
|
1189
1504
|
throw error;
|
|
@@ -1198,7 +1513,7 @@ var registerDemoTests = (test, adapter, demoDir) => {
|
|
|
1198
1513
|
if (issues.length > 0) {
|
|
1199
1514
|
throw new StoryboardError(
|
|
1200
1515
|
[
|
|
1201
|
-
`${
|
|
1516
|
+
`${path5.basename(env.storyboardPath)} won't film well:`,
|
|
1202
1517
|
...issues.map((i) => ` ${i}`)
|
|
1203
1518
|
].join("\n")
|
|
1204
1519
|
);
|
|
@@ -1235,6 +1550,31 @@ var registerDemoTests = (test, adapter, demoDir) => {
|
|
|
1235
1550
|
})
|
|
1236
1551
|
);
|
|
1237
1552
|
const fast = env.mode === "check";
|
|
1553
|
+
const stillsDir = env.stillsDir ?? path5.join(env.workDir, "stills");
|
|
1554
|
+
const stillName = (index) => index === TITLE ? "card-title.png" : `card-${index}.png`;
|
|
1555
|
+
const stillFile = (index) => path5.join(stillsDir, stillName(index));
|
|
1556
|
+
const stillUrl = (index) => `${STILLS_PATH}/${stillName(index)}`;
|
|
1557
|
+
const stills = stillCards(storyboard.steps, storyboard.title);
|
|
1558
|
+
if (!fast && stills.length > 0) {
|
|
1559
|
+
const missing2 = stills.filter((i) => !fs4.existsSync(stillFile(i)));
|
|
1560
|
+
if (missing2.length > 0) {
|
|
1561
|
+
throw new Error(
|
|
1562
|
+
`No still for the card at ${missing2.map((i) => i === TITLE ? "title" : `steps.${i}`).join(", ")}: render the story with the demo-video CLI, which checks it first and captures them`
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
await page.route(
|
|
1566
|
+
`**${STILLS_PATH}/*`,
|
|
1567
|
+
(route) => route.fulfill({
|
|
1568
|
+
contentType: "image/png",
|
|
1569
|
+
body: fs4.readFileSync(
|
|
1570
|
+
path5.join(
|
|
1571
|
+
stillsDir,
|
|
1572
|
+
path5.basename(new URL(route.request().url()).pathname)
|
|
1573
|
+
)
|
|
1574
|
+
)
|
|
1575
|
+
})
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1238
1578
|
if (!fast) {
|
|
1239
1579
|
await FrameRecorder.prepare(page);
|
|
1240
1580
|
}
|
|
@@ -1262,39 +1602,44 @@ var registerDemoTests = (test, adapter, demoDir) => {
|
|
|
1262
1602
|
cause: error
|
|
1263
1603
|
});
|
|
1264
1604
|
}
|
|
1605
|
+
const title = storyboard.title;
|
|
1265
1606
|
if (fast) {
|
|
1266
|
-
await runSteps(boot, storyboard.steps, adapter
|
|
1607
|
+
await runSteps(boot, storyboard.steps, adapter, {
|
|
1608
|
+
title,
|
|
1609
|
+
kind: storyboard.kind,
|
|
1610
|
+
...stills.length > 0 ? { stillFile } : {}
|
|
1611
|
+
});
|
|
1267
1612
|
boot.end();
|
|
1268
|
-
return storyboard
|
|
1613
|
+
return storyboard;
|
|
1269
1614
|
}
|
|
1270
1615
|
await page.evaluate(() => document.fonts.ready.then(() => void 0));
|
|
1271
1616
|
await frame.evaluate(() => document.fonts.ready.then(() => void 0));
|
|
1617
|
+
await page.evaluate(
|
|
1618
|
+
(urls) => window.__demo.preload(urls),
|
|
1619
|
+
stills.map(stillUrl)
|
|
1620
|
+
);
|
|
1272
1621
|
await page.waitForTimeout(300);
|
|
1273
1622
|
const recorder = new FrameRecorder(
|
|
1274
1623
|
page,
|
|
1275
1624
|
env.workDir,
|
|
1276
|
-
`${
|
|
1625
|
+
`${path5.basename(env.storyboardPath, ".json")}-${brand.name}${env.draft ? "-draft" : ""}`,
|
|
1277
1626
|
env.draft ? DRAFT_FPS : FPS
|
|
1278
1627
|
);
|
|
1279
1628
|
await recorder.freeze();
|
|
1280
1629
|
const d = new Director(page, frame, { fast: false, viewport, recorder });
|
|
1281
|
-
const title = storyboard.title;
|
|
1282
1630
|
if (title) {
|
|
1283
|
-
await d.showCard(
|
|
1631
|
+
await d.showCard(
|
|
1632
|
+
cardSpecFor(title, void 0, title.show ? stillUrl(TITLE) : void 0),
|
|
1633
|
+
true
|
|
1634
|
+
);
|
|
1284
1635
|
}
|
|
1285
1636
|
recorder.start();
|
|
1286
|
-
if (title) {
|
|
1287
|
-
await recorder.advance(cardSeconds(title) * 1e3);
|
|
1288
|
-
if (storyboard.steps[0]?.do !== "card") {
|
|
1289
|
-
await d.hideCard();
|
|
1290
|
-
await recorder.advance(CARD_OUT_MS);
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
1637
|
const closing = storyboard.closing;
|
|
1294
1638
|
await runSteps(d, storyboard.steps, adapter, {
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
keepLastCardUp: Boolean(closing)
|
|
1639
|
+
title,
|
|
1640
|
+
kind: storyboard.kind,
|
|
1641
|
+
keepLastCardUp: Boolean(closing),
|
|
1642
|
+
stillUrl
|
|
1298
1643
|
});
|
|
1299
1644
|
d.end();
|
|
1300
1645
|
if (closing) {
|
|
@@ -1303,11 +1648,13 @@ var registerDemoTests = (test, adapter, demoDir) => {
|
|
|
1303
1648
|
await recorder.advance(LINGER_MS);
|
|
1304
1649
|
}
|
|
1305
1650
|
await d.showCard(closing);
|
|
1306
|
-
await recorder.advance(
|
|
1651
|
+
await recorder.advance(
|
|
1652
|
+
CARD_IN_MS + titleSeconds(closing, storyboard.kind) * 1e3
|
|
1653
|
+
);
|
|
1307
1654
|
}
|
|
1308
1655
|
await recorder.advance(300);
|
|
1309
1656
|
await recorder.stop();
|
|
1310
|
-
return storyboard
|
|
1657
|
+
return storyboard;
|
|
1311
1658
|
};
|
|
1312
1659
|
};
|
|
1313
1660
|
export {
|