Enregistrer des vidéos de démonstration
Rédigez un storyboard Playwright ad-hoc, puis exécutez le moteur autonome depuis n'importe quel répertoire de travail.
N'ajoutez pas le storyboard à un dépôt d'application sauf si l'utilisateur le demande.
Gardez le storyboard autonome ; l'exécuteur place ce fichier dans le moteur avant que Playwright ne le charge,
ne faites donc pas d'imports d'utilitaires relatifs au storyboard.
<skill-dir> désigne le répertoire où ce dépôt est cloné, par exemple
~/.claude/skills/record-demo.
Rédiger le storyboard
Importez le DSL avec cet import portable :
import { demo } from "record-demo/storyboard";
L'exécuteur réécrit ce spécificateur vers le chemin réel du moteur lors de la mise en scène, de sorte
que le fichier storyboard peut se trouver n'importe où. La résolution de type dans l'éditeur pour
record-demo/storyboard provient de paths dans engine/tsconfig.json - en dehors du
dépôt, mappez record-demo/storyboard vers le chemin absolu
<skill-dir>/engine/lib/storyboard.ts dans vos paths tsconfig, ou gardez le
fichier dans engine/scripts/.
Utilisez engine/scripts/example-site.demo.ts comme référence. Préférez les sélecteurs getByRole,
utilisez d.click(locator) au lieu de locator.click(), rythmez les actions avec d.hold(...), et
affirmez ou attendez l'état final.
demo("titre-du-storyboard", async (d) => {
await d.seed(async (page) => {
await page.getByRole("heading", { name: "Exemple de domaine" }).waitFor();
});
await d.step("Lire la page", async (page) => {
const heading = page.getByRole("heading", { name: "Exemple de domaine" });
await d.zoomTo(heading, { padding: 48 });
await d.hold(2_000);
d.zoomOut();
await heading.waitFor({ state: "visible" });
});
});
Le moteur remplace le nom de la timeline par --name ou le nom de base du script, donc la
chaîne passée à demo(...) n'a pas besoin de correspondre au slug de sortie.
Pré-vol (avant toute capture)
Lors de l'enregistrement contre un checkout servi localement : git fetch origin dans le checkout
de service et affirmez que HEAD == origin/main (ou la branche nommée par l'utilisateur) — un checkout
périmé enregistre un design périmé. S'il est périmé, reconstruisez/redémarrez d'abord le
serveur. Imprimez le SHA de service dans les notes de prise afin que chaque enregistrement soit attribuable.
Enregistrer
Nécessite Node >= 22.18 (nvm ou système), ffmpeg, Xvfb et xdpyinfo. Installez les
dépendances une fois dans engine/ avec pnpm install.
node <skill-dir>/engine/record.mjs \
--script /chemin/absolu/ou/relatif-au-répertoire-courant/demo.demo.ts \
--url https://example.org \
[--name slug-de-sortie] [--formats 16x9,1x1,9x16] \
[--chrome none|macos|browser] [--storage-state <chemin>] [--raw]
Utilisez --formats pour sélectionner un ou plusieurs préréglages de composition (par défaut 16x9) et
--chrome pour choisir le cadre de fenêtre rendu (par défaut none). Les formats sont rendus dans
l'ordre demandé, et le premier est le principal utilisé pour encoder le GIF.
Le format 9x16 est un recadrage agressif centré (environ 44 % de la largeur de la page est visible dans le plan large), donc gardez l'action centrée lors de la rédaction pour le portrait.
Utilisez --raw pour vous arrêter après la capture propre. Sans cela, attendez-vous aux livrables suivants :
engine/out/<nom>-raw.mp4engine/out/<nom>-<format>.mp4pour chaque format demandéengine/out/<nom>.gifencodé à partir du format principal
L'exécuteur affiche les chemins absolus des livrables une fois terminé. Traitez la reproductibilité du site en direct comme étant la responsabilité du site cible et n'intégrez jamais de véritables identifiants dans un storyboard.
Sites authentifiés
Enregistrez les flux authentifiés uniquement avec des comptes de staging ou de test ; n'utilisez jamais
les identifiants de production. L'agent doit d'abord se connecter une fois avec ses propres outils, en utilisant un
script Playwright sans tête. Si la connexion nécessite un OTP envoyé par e-mail ou un lien magique, la voie
préférée est l'accès au courrier de l'agent lui-même - un connecteur Gmail/MCP connecté sur Claude Code,
ou un CLI de messagerie sur d'autres runtimes - afin qu'il lise le code lui-même ; sinon, demandez à
l'utilisateur de le coller. Après la connexion, enregistrez la session avec
await context.storageState({ path: storageStatePath }), où storageStatePath
est un chemin temporaire à usage unique créé comme
storageStatePath="$(mktemp -d)/<compte>.storage-state.json". Exécutez ensuite la
commande d'enregistrement avec --storage-state "$storageStatePath".
Playwright storageState restaure uniquement les cookies et le localStorage. Les applications qui conservent l'authentification
dans IndexedDB ou sessionStorage enregistreront silencieusement un écran de déconnexion, donc vérifiez la
page authentifiée avant la capture. Le fichier storage-state est un secret : ne l'imprimez
ou ne le validez jamais. L'enregistreur le supprime immédiatement après l'avoir chargé ; c'est un transfert
à usage unique, donc une nouvelle capture nécessite une nouvelle connexion et rien de lié à l'authentification n'est laissé sur
le disque. Le script d'aide à la connexion unique contient également des identifiants : gardez-le temporaire et
ne le validez jamais.
Modifier une démo enregistrée dans Remotion Studio
Exportez un projet autonome après l'enregistrement, puis installez et ouvrez Studio :
node <skill-dir>/engine/scripts/export-project.mjs <nom>
cd <skill-dir>/engine/out/<nom>-project
pnpm install
pnpm studio
Le panneau de la timeline de Studio affiche des pistes de style éditeur (bande-annonce d'écran, un clip nommé par
légende, projecteur, mouvement de zoom et outro) - faites glisser la tête de lecture pour parcourir, cliquez sur la ligne d'un clip pour
trouver son événement. Utilisez le panneau des propriétés ou fixtures/timeline.json pour modifier les légendes,
le timing des zooms (gardez les mouvements de zoom à ou au-dessus du seuil de 1650ms), le timing du projecteur, et le
titre de l'outro. Modifiez
remotion/theme.ts pour les couleurs, la typographie, l'espacement et le mouvement. Rendez la
vidéo 16:9 terminée avec pnpm render.
Règles de mouvement et de storyboard (ce qui rend un clip publiable)
- Un seul mouvement de caméra par scène, pas par champ. Zoomez une fois sur la section, agissez à l'intérieur. Les zooms par entrée ressemblent à des coupes brusques.
- Plan large lors des transitions de page, restez large sur les pages peu denses.
d.zoomOut()AVANT un clic qui navigue ; ne zoomez jamais sur une pastille d'état sur une page presque vide. - Sections près du bas de la page :
d.zoomTo([...locators], { scroll: "center", padding: 48 })- le bloc de la caméra ne peut pas centrer un rectangle en bord de page ; le centrage par défilement le corrige à la
racine.
Locator[]encadre une section entière via le bbox d'union.
- le bloc de la caméra ne peut pas centrer un rectangle en bord de page ; le centrage par défilement le corrige à la
racine.
d.spotlight([...locators])= mise au point de style Apple (découpe + anneau). GénéreuxholdMs, terminez explicitement pard.spotlightOff()+hold(600)avant la fin de l'étape. Les rectangles du projecteur doivent tenir dans la fenêtre (appliqué) ; les rectangles de zoom peuvent déborder par conception.- Seed attend les données réelles, pas seulement l'élément déclencheur - sinon la vidéo s'ouvre sur un squelette. Attendez un nœud de contenu connu + ~600 ms.
d.settle(locator)avant les légendes de résultat. Les vues qui récupèrent après une soumission se vident brièvement ; un simplewaitForse résout sur la première peinture et la légende de résultat se déclenche sur une page blanche.- Les légendes sont des impératifs liés à l'action du curseur ; mettez une légende de résultat
(
d.caption(...)tard dans l'étape) sur les scènes de clôture ; réduisez les légendes des étapes pour que les impératifs ne pendent jamais sur des actions terminées. d.outro(titre)ferme les clips publiable avec une dalle de fin calme après la scène finale.- Affirmez toujours l'état final - sans cela, le pipeline rend une vidéo soignée d'un flux cassé.
- N'expédiez jamais un clip avec une URL localhost, un nom d'hôte interne, un nom de fournisseur ou de véritables identifiants à l'écran. Masquez les chaînes de présentation uniquement dans la seed (un patch window.open et une réécriture d'entrée MutationObserver).
Vérifier avant de signaler terminé
pnpm typecheck && pnpm testdansengine/.node scripts/score-motion.mjs out/timeline.json- FAIL bloque ; WARN taux de zoom ~0.78/s est l'état stable accepté ; tout verdict de discontinuité est un bug de caméra ; les WARN de silence mort signifient un étirement statique >= 3,5 s avec moins de 40 px de déplacement du curseur - re-rythmez ou légendez-le.node scripts/prove-composition.mjsaprès les modifications du moteur.- Regardez le rendu réel :
ffmpeg -i out/<nom>-16x9.mp4 -vf fps=1 out/frames/%03d.pnget lisez les images. Le clipping du zoom, le décalage des légendes, la désynchronisation de la découpe et les URL de développement à l'écran sont invisibles dans un test vert.
| Symptôme | Cause / correctif |
|---|---|
Abandon Invalid demo viewport ... | La géométrie a dérivé - corrigez les indicateurs de lancement, n'assouplissez jamais l'assertion. |
| Décalage du curseur d'environ 174 px verticalement | Chrome s'est exécuté en onglets, pas en --app. |
| Impossible de trouver les images de clap | La tolérance du détecteur est de 64 (les captures externes sont en moyenne impures) ; s'il manque toujours, vérifiez que la capture n'est pas noire (Chrome sur le mauvais DISPLAY). |
| Décalage des légendes/curseur sur le clip | Synchronisation du clap - ne soustrayez pas l'horloge murale. |
| Découpe du projecteur manquant sa cible | Le rectangle a dépassé un défilement fluide - la boucle de stabilité de measureTarget devrait tenir ; si cela se reproduit, augmentez son délai d'attente. |
| Mots coupés au milieu d'une lettre au bord du recadrage | zoomTo(locator, { padding: 120 }) pour les modales. |
| Le zoom coupe le contenu du cadre | Problème de goût maximumScale (1,45 fonctionne), pas un bug de blocage. |
Vérifier les modifications du moteur
Exécutez depuis engine/ :
pnpm typecheck && pnpm test
node scripts/prove-composition.mjs