npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@setshao/visual-regression

v1.1.2

Published

Interface de régression visuelle pour Storybook (React Native / Expo), réutilisable dans tout projet

Readme

@setshao/visual-regression

CI Platform VR Integration

Solution de régression visuelle clé en main pour tout projet qui possède un Storybook, quelle que soit la techno.
Le package fournit une app web de régression dédiée (UI intégrée dans le package) pour parcourir les stories, visualiser les screenshots (NEW / DIFF), voir les heatmaps, naviguer entre devices et gérer l’historique des validations / refus.


Objectif

  • But principal
    Fournir une solution complète de régression visuelle autour de Storybook, en prenant en charge à la fois l’interface web de validation et toute la mécanique de capture/comparaison (Playwright, serveur VR, orchestration).
  • Ce que le package apporte
    • Interface web intégrée : l’UI VisualRegressions est interne au package et exposée via l’app web servie par visual-regression (l’hôte n’a pas à importer de composants UI dans son app).
    • Logique complète de VR : récupération des régressions, affichage des différences (NEW / DIFF), heatmap, historique des refusés, navigation entre stories et devices.
    • Scripts d’orchestration : serveur VR, captures/comparaisons Playwright, launcher qui gèrent Storybook et la génération/lecture des screenshots sans que le projet hôte doive maintenir sa propre “infra VR”.
    • Gestion du dossier de screenshots : le répertoire de screenshots (par défaut public/) est géré par visual-regression ; s’il n’existe pas, le package s’occupe de le créer et de l’utiliser pour stocker/servir les images.
  • Rôle du projet hôte
    • Posséder un Storybook (les stories sont la source de vérité pour les captures).
    • Fournir une configuration VR (vr.config.cjs au format attendu).
    • Lancer les scripts visual-regression depuis la racine du projet (ou via VR_PROJECT_ROOT).

En résumé, visual-regression vise à industrialiser la régression visuelle autour de Storybook, avec un rôle minimal pour le projet hôte : configurer ses devices et lancer les scripts.


Fonctionnement

  • Ce package : app web de régression (UI intégrée), logique cliente (navigation dans les stories/devices, visualisation NEW / DIFF avec heatmap, validation/refus, historique), et les scripts (serveur VR, launcher, comparaison Playwright). Tout tourne en autonomie : le package ne laisse rien à maintenir dans le projet hôte côté scripts ou UI.
  • Le projet hôte : à la racine, un fichier vr.config.cjs et un dossier .storybook/ pour Storybook. Le répertoire de screenshots (par défaut public/) est automatiquement créé/utilisé par le package si nécessaire. On lance les commandes VR depuis la racine du projet hôte (ou avec VR_PROJECT_ROOT pointant vers cette racine) ; les scripts du package utilisent alors cette racine pour charger la config et servir les fichiers.

Prérequis côté projet hôte : vr.config.cjs, .storybook/. Les devices se configurent en format Playwright (voir section suivante). Le dossier public/ est géré automatiquement par visual-regression (créé si absent).


Configuration (vr.config.cjs)

À quoi sert ce fichier ?

vr.config.cjs est le fichier de configuration unique de la régression visuelle. Il se place à la racine de votre projet (à côté de package.json).

Il indique notamment :

  • sur quels écrans capturer (desktop, mobile, tablette…) ;
  • comment comparer les screenshots (incrémental, seuil de diff, etc.) ;
  • comment lancer Storybook et Docker au démarrage.

Règle simple : vous n’avez besoin de modifier que ce qui vous concerne. Tout ce que vous omettez prend une valeur par défaut sensée.

Priorité des réglages

Quand la même option existe à plusieurs endroits, l’ordre est :

variable d’environnement VR_* vr.config.cjs → défaut du package

Exemple : si vr.config.cjs met capture.concurrency: 8 mais que la CI exporte VR_CONCURRENCY=4, c’est 4 qui sera utilisé.


Exemple minimal (pour démarrer)

Seule la section devices est obligatoire :

// vr.config.cjs
module.exports = {
  devices: [
    {
      name: "desktop-fhd",
      viewport: { width: 1920, height: 1080 },
      deviceScaleFactor: 1,
      isMobile: false,
      label: "Desktop FHD",
      icon: "laptop",
      color: "newTheme_primary",
    },
  ],
};

Exemple complet (toutes les sections)

// vr.config.cjs — commentaires = aide, pas besoin de tout copier
module.exports = {
  // ── OBLIGATOIRE ─────────────────────────────────────────────
  devices: [/* voir tableau « devices » ci-dessous */],

  // ── CAPTURE (Playwright + Docker) ───────────────────────────
  capture: {
    concurrency: 8, // captures en parallèle dans le conteneur
    maxTestTime: 10_000, // timeout par story (ms)
    remoteChunkSize: 50, // nb de screenshots par requête HTTP vers Docker
    backend: "docker", // "docker" (défaut) ou "local"
    daemonUrl: "http://localhost:2810",
  },

  // ── COMPARAISON (git, incrémental, diffs) ───────────────────
  compare: {
    mode: "incremental", // "incremental" ou "full"
    base: "origin/main", // branche git de référence
    scope: "all", // quels fichiers git regarder
    includeWorkingTree: true, // inclure vos modifs non commitées
    threshold: 0, // sensibilité pixelmatch (0 = strict)
    diffVerificationMaxAttempts: 3,
    globalTriggers: [".storybook/**", "package.json", "vr.config.cjs"],
    statsFile: "storybook-static/preview-stats.json",
    manifestPath: ".vr-cache/manifest.json",
    // shardIndex: 0,         // optionnel — sharding CI (0-based)
    // shardTotal: 4,
  },

  // ── LANCEMENT (`yarn vr`) ───────────────────────────────────
  launcher: {
    runInitialCompare: true, // comparer au démarrage de `yarn vr`
    // storybookMode: "dev",  // "dev" | "static" | omis = auto
    forceStaticRebuild: false,
  },

  // ── STORYBOOK ───────────────────────────────────────────────
  storybook: {
    url: "http://localhost:6006",
  },

  // ── STABILISATION DES CAPTURES (anti-flake) ─────────────────
  stabilize: {
    freezeAnimations: true,
    waitFonts: true,
    waitNetworkQuietMs: 0,
    maxStabilizeTime: 5_000,
    burstCapture: false,
    burstFrames: 3,
    burstIntervalMs: 100,
  },

  // ── DOCKER (avancé) ─────────────────────────────────────────
  docker: {
    image: "vr-capture:1.61.1",
    playwrightImage: "mcr.microsoft.com/playwright:v1.61.1-jammy",
  },
};

Référence détaillée

devices (obligatoire)

Liste des formats d’écran sur lesquels chaque story sera capturée.
Une story = 1 screenshot par device (ex. 28 stories × 2 devices = 56 images).

| Paramètre | Description | Exemple | | ------------------------------------ | -------------------------------------------------------- | -------------------- | | name | Identifiant unique (utilisé dans les noms de fichiers) | "iphone16" | | viewport.width / viewport.height | Taille de la fenêtre Playwright (px) | 393 × 852 | | deviceScaleFactor | Densité de pixels (1 = desktop, 3 = iPhone Retina) | 3 | | isMobile | Comportement mobile Playwright | true | | label | Nom affiché dans l’UI VR | "iPhone 16" | | icon | Icône Material (laptop, phone-iphone, tablet-mac…) | "phone-iphone" | | color | Couleur dans l’UI (clé du thème du package) | "newTheme_primary" |

Les champs viewport / deviceScaleFactor / isMobile servent à Playwright.
Les champs label / icon / color servent à l’interface web de validation.


capture — vitesse et technique de capture

| Paramètre | Défaut | En bref | | ----------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | concurrency | 28 (selon CPU) | Nombre de captures simultanées dans Docker. Plus haut = plus rapide, mais plus de RAM. | | maxTestTime | 10000 | Temps max (ms) pour attendre qu’une story soit « stable » avant le screenshot. | | remoteChunkSize | 20 | Nombre de captures envoyées par lot au daemon Docker. Augmentez (ex. 56) pour une seule requête ; laissez 20 si vous avez des timeouts réseau. | | backend | "docker" | "docker" = capture dans le conteneur (recommandé). "local" = Playwright sur votre machine (debug avancé). | | daemonUrl | http://localhost:2810 | Adresse du daemon de capture Docker. |

Variable d’env équivalente : VR_CONCURRENCY, VR_MAX_TEST_TIME, VR_CAPTURE_REMOTE_CHUNK, VR_CAPTURE_BACKEND, VR_CAPTURE_DAEMON_URL.


compare — quoi comparer et comment détecter les changements

| Paramètre | Défaut | En bref | | ----------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | mode | "incremental" | "incremental" = ne recapture que les stories impactées par vos changements git. "full" = tout recapturer à chaque run. | | base | "origin/main" | Branche ou commit git de référence pour savoir quels fichiers ont changé. | | scope | "all" | "all" = branche + fichiers locaux (CI). "working-tree" = seulement vos modifs non poussées. "branch" = seulement les commits de la branche. | | includeWorkingTree | true | Si true, les fichiers modifiés localement (non commités) déclenchent une capture. | | threshold | 0 | Tolérance pixelmatch (0 = aucun pixel de différence toléré). | | diffVerificationMaxAttempts | 3 | Si une diff est détectée, combien de recaptures pour confirmer (évite les faux positifs). | | globalTriggers | voir défaut package | Liste de fichiers/globs : s’ils changent, toutes les stories sont recapturées (ex. .storybook/**, package.json). | | statsFile | storybook-static/preview-stats.json | Fichier stats Storybook pour TurboSnap (liens entre fichiers et stories). | | manifestPath | .vr-cache/manifest.json | Cache local quand git n’est pas disponible. | | shardIndex / shardTotal | (absent) | Sharding CI : divise les stories entre plusieurs jobs (index 0-based). En local, laissez absent. Préférez VR_SHARD_INDEX / VR_SHARD_TOTAL en pipeline. |

Variable d’env équivalente : VR_COMPARE_MODE, VR_COMPARE_BASE, VR_COMPARE_SCOPE, VR_THRESHOLD, VR_DIFF_VERIFY_MAX_ATTEMPTS, VR_SHARD_INDEX, VR_SHARD_TOTAL.


launcher — comportement de yarn vr

| Paramètre | Défaut | En bref | | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | runInitialCompare | true | Lance une comparaison automatique au démarrage de yarn vr. Mettez false si vous voulez seulement ouvrir l’UI sans capturer. | | storybookMode | (auto) | "dev" = Storybook avec rechargement à chaud. "static" = build storybook-static puis serve (comme en CI). Omis = auto : statique en Docker avec @storybook/nextjs-vite, sinon dev. | | forceStaticRebuild | false | Si true, rebuild storybook-static avant chaque capture (utile si vos stories changent souvent en mode statique). |

Variable d’env équivalente : VR_RUN_INITIAL_COMPARE, VR_STORYBOOK_MODE (dev static), VR_STORYBOOK_STATIC (alias → static), VR_STORYBOOK_STATIC_REBUILD.


storybook

| Paramètre | Défaut | En bref | | --------- | ----------------------- | -------------------------------------------------------------------------- | | url | http://localhost:6006 | URL où Playwright charge les stories (port forwardé depuis Docker en dev). |

Variable d’env équivalente : VR_STORYBOOK_URL.


stabilize — éviter les screenshots « qui flicker »

Réglages pour attendre que la story soit visuellement stable avant la capture. Voir aussi la section SteadySnap et les tags Storybook (burst-vr, live-animation-vr, etc.).

| Paramètre | Défaut | En bref | | -------------------- | ------- | ---------------------------------------------------------------------------------- | | freezeAnimations | true | Fige les animations CSS pendant la capture. | | waitFonts | true | Attend le chargement des polices. | | waitNetworkQuietMs | 0 | Attend X ms sans requête réseau (ex. 300 pour des stories qui chargent des API). | | maxStabilizeTime | 5000 | Délai max total d’attente (ms). | | burstCapture | false | Prend plusieurs frames et garde la plus stable (global). | | burstFrames | 3 | Nombre de frames si burst activé. | | burstIntervalMs | 100 | Intervalle entre frames burst (ms). |

Vous pouvez surcharger par story via parameters.vr.stabilize dans vos fichiers CSF.


docker — sidecar de capture (avancé)

À modifier seulement si vous maintenez votre propre image Docker.

| Paramètre | Défaut | En bref | | ----------------- | -------------------------------------------- | ------------------------------------------------------ | | image | vr-capture:1.61.1 | Image du conteneur qui exécute Storybook + Playwright. | | playwrightImage | mcr.microsoft.com/playwright:v1.61.1-jammy | Image de base pour builder le sidecar localement. |

Variable d’env équivalente : VR_DOCKER_IMAGE, VR_PLAYWRIGHT_IMAGE.


Migration depuis vr-devices.config.cjs

Si vous aviez l’ancien format (tableau exporté directement), renommez le fichier en vr.config.cjs et enveloppez les devices :

// Avant : module.exports = [ { name: "desktop-fhd", ... } ];
// Après :
module.exports = { devices: [ { name: "desktop-fhd", ... } ] };

Pas de rétrocompatibilité silencieuse : le package affiche un message explicite si vr-devices.config.cjs est encore présent sans vr.config.cjs.

Les scripts (serveur VR, comparaison Playwright) utilisent name, viewport, deviceScaleFactor, isMobile. L’UI utilise name, label, icon, color.

SteadySnap (anti-flake)

Stabilisation des captures inspirée de Chromatic SteadySnap — équivalent self-hosted, burst opt-in par défaut.

| Clé | Défaut | Rôle | | ------------------------------ | ------- | ---------------------------------------------- | | stabilize.freezeAnimations | true | Freeze CSS animations/transitions | | stabilize.waitFonts | true | Attend document.fonts.ready | | stabilize.waitNetworkQuietMs | 0 | Fenêtre sans requête réseau (ms) avant capture | | stabilize.maxStabilizeTime | 5000 | Plafond attente stabilisation | | stabilize.burstCapture | false | Burst N frames pour toutes les stories | | stabilize.burstFrames | 3 | Nombre de frames burst | | stabilize.burstIntervalMs | 100 | Intervalle entre frames burst |

Tags Storybook :

  • live-animation-vropt-out : conserve Reanimated en capture (défaut = figé via preview)
  • burst-vr — burst SteadySnap côté Playwright (stories animées non figées)
  • skip-play-vr — n'exécute pas play() en capture (opt-out du decorator preview)
  • ignore-vr / force-vr — exclusion / inclusion forcée au compare

Storybook preview : en capture (vr-capture=1), le decorator applique ReducedMotionConfig (Reanimated figé à l'état initial). Un second decorator exécute play() puis pose data-vr-ready="true" sur #storybook-root.

Stories play() : exécutées automatiquement avant chaque screenshot VR. Playwright attend data-vr-ready="true" sur les stories taguées play-fn.

Vérification diff : si une capture diffère de la baseline, le moteur relance jusqu'à match ou compare.diffVerificationMaxAttempts (défaut 3). Override global : VR_DIFF_VERIFY_MAX_ATTEMPTS.

Overrides par story (parameters.vr) — fusionnés sur vr.config.cjs (meta ou story CSF) :

| Clé | Rôle | | ------------------------------------------- | ------------------------------------------------------------------------ | | parameters.vr.stabilize.* | Sous-ensemble de stabilize (ex. burstIntervalMs, maxStabilizeTime) | | parameters.vr.diffVerificationMaxAttempts | Recaptures max pour cette story uniquement |

Définir burstIntervalMs ou burstFrames active automatiquement le burst pour la story (sans tag burst-vr).

export const SlowIncrementToThree: Story = {
  parameters: {
    vr: {
      stabilize: { burstIntervalMs: 1000 },
      diffVerificationMaxAttempts: 5,
    },
  },
  play: async ({ canvasElement, step }) => {
    /* … */
  },
};

Attente custom : ajouter data-vr-ready="true" sur #storybook-root quand la story est prête (stories lourdes).

compare: {
  diffVerificationMaxAttempts: 3,
},
stabilize: {
  freezeAnimations: true,
  waitNetworkQuietMs: 300,
  burstCapture: false,
},

Mode incrémental et TurboSnap

Par défaut, seules les stories impactées par les fichiers modifiés sont recapturées :

  1. Détection git (compare.base + working tree) ou manifest .vr-cache/manifest.json
  2. Graphe Webpack via storybook-static/preview-stats.json (généré par yarn storybook:build:stats)
  3. Fallback analyse imports statiques si stats absentes

Global triggers (.storybook/**, package.json, yarn.lock, vr.config.cjs) forcent un run complet.

L'UI expose les actions régénération volontaire en mode full via le serveur VR :

  • POST /compare/selectedcompareSelectedStories
  • POST /compare/by-typecompareByType (new / diff / rejected)
  • POST /compare/all-storiescompareAllStories

POST /compare lance la comparaison selon compare.mode (incrémental par défaut).

API serveur VR (config)

| Route | Description | | --------------------------------- | -------------------------------------------------------------------------------- | | GET /regressions/config | Config publique résolue (compareMode, storybookUrl, captureConcurrency, …) | | GET /regressions/config/devices | Devices pour l'UI |


Installation

Via npm :

npm install @setshao/visual-regression
# ou
yarn add @setshao/visual-regression

En local (développement, lien vers le dossier du package) :

{
  "dependencies": {
    "@setshao/visual-regression": "file:../visual-regression"
  }
}

Prérequis navigateurs Playwright (mode local uniquement)

Avec yarn vr (comportement par défaut), cette étape n'est pas nécessaire.
La capture passe par le sidecar Docker qui embarque déjà Chromium Linux (voir Capture Docker ci-dessous).

L'installation locale de Playwright ne concerne que le mode fallback VR_CAPTURE_BACKEND=local (tests internes du package ou désactivation explicite de Docker) :

cd <projet-hôte>
npx playwright install chromium

Sans ces binaires en mode local, la comparaison peut échouer avec TimeoutError: launch: Timeout 180000ms exceeded. Sur Windows, le script tente d'abord Edge/Chrome système avant Chromium bundlé (plus lent).


Compatibilité Windows / macOS / Linux

| Plateforme | Workflow yarn vr | Captures (screenshots) | Notes | | ------------------------------- | ------------------ | ---------------------- | ------------------------------------------------------------------------------------- | | Windows | ✅ | ✅ via Docker Linux | Plus de workarounds (ports, signaux, chemins) ; Docker Desktop requis | | macOS Intel | ✅ | ✅ via Docker Linux | Expérience généralement plus fluide | | macOS Apple Silicon (M1–M4) | ✅ | ✅ via Docker Linux | Image ghcr.io/.../vr-capture publiée en amd64 + arm64 (pas d'émulation au pull) | | Linux | ✅ | ✅ via Docker Linux | Référence CI (ubuntu-latest) |

Les screenshots sont identiques sur toutes les machines : la capture s'exécute toujours dans le conteneur Linux (Chromium figé). Seule l'expérience développeur (démarrage Docker, signaux Ctrl+C, perf du 1er build) varie.

CI : chaque PR exécute les checks sur ubuntu-latest ([ci.yml](.github/workflows/ci.yml)), la validation statique sur macOS et Windows ([platform.yml](.github/workflows/platform.yml)), et l'intégration Docker sur Linux ([integration.yml](.github/workflows/integration.yml)).


Capture Docker (obligatoire, reproductible)

Pour garantir des screenshots identiques quelle que soit la machine (Windows Intel, Mac M4, CI Linux…), toute capture s'exécute dans un conteneur Docker. Les différences de rendu entre OS (moteur de police DirectWrite vs CoreText, GPU, gestion des couleurs) disparaissent car l'environnement de capture est figé.

Principe

  • Un sidecar Docker (vr-capture) héberge Storybook (dev HMR) + un daemon de capture (Playwright Chromium Linux).
  • L'hôte (serveur VR, UI Expo, comparaison pixelmatch, index) ne lance jamais Playwright : chaque runCaptureBatch est délégué au daemon via HTTP (POST /capture/batch).
  • Résultat : 100 % des screenshots (compare globale, régénération depuis le TreePanel, CI) passent par Docker.
Hôte (vr-server + UI Expo)  ──POST /capture/batch──▶  Conteneur (Storybook + daemon + Playwright)
        ▲                                                        │
        └──────────── screenshots via volume monté ◀────────────┘

Prérequis hôte

  • Docker installé et démarré (Docker Desktop sur Mac/Windows).
  • Un script storybook:build:stats dans le package.json (utilisé en mode statique/CI).

Workflow dev

yarn vr

yarn vr démarre automatiquement le sidecar (build de l'image au premier lancement), attend que le daemon soit prêt, puis lance le serveur VR, la comparaison initiale et l'UI. Storybook tourne dans le conteneur (HMR : vos modifications de stories/composants sont prises en compte sans rebuild) et est forwardé sur http://localhost:6006.

Commandes de contrôle du sidecar :

| Script | Rôle | | ------------------------ | ------------------------------------- | | yarn vr:capture:up | Démarre le sidecar + attend le daemon | | yarn vr:capture:down | Arrête le sidecar | | yarn vr:capture:status | État compose + health du daemon |

Workflow CI

En CI, on utilise le mode Storybook statique (build unique, plus déterministe) en one-shot :

docker compose \
  -f node_modules/@setshao/visual-regression/docker/docker-compose.ci.yml \
  up --build --abort-on-container-exit --exit-code-from vr-capture

Un exemple complet GitHub Actions (cache node_modules + storybook-static, sharding, upload d'artefacts) est fourni pour les projets hôte : [docker/ci/github-actions.example.yml](docker/ci/github-actions.example.yml).

Ce dépôt lib dispose de ses propres workflows CI dans [.github/workflows/](.github/workflows/) (ci.yml sur chaque PR, platform.yml sur macOS/Windows, integration.yml sur les PR touchant les fichiers critiques). L'exemple ci-dessus est à copier dans le dépôt consommateur, pas dans ce package.

Source de vérité : les baselines validées en Docker/CI font foi. Ne régénérez jamais une baseline depuis une capture native OS.

Variables d'environnement Docker

| Variable | Défaut / vr.config.cjs | Rôle | | ------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------ | | VR_CAPTURE_BACKEND | capture.backenddocker | docker = capture déléguée au daemon ; local = capture directe (tests internes / dans le conteneur) | | VR_CAPTURE_DAEMON_URL | capture.daemonUrl | URL du daemon de capture | | VR_CAPTURE_REMOTE_CHUNK | capture.remoteChunkSize20 | Taille des lots envoyés au daemon (évite les timeouts fetch) | | VR_STORYBOOK_MODE | launcher.storybookMode → auto | dev (HMR) ou static (build + serve, CI) | | VR_STORYBOOK_STATIC | alias → static | Rétrocompat : équivalent à VR_STORYBOOK_MODE=static | | VR_DOCKER_IMAGE | docker.image | Image du sidecar (tag aligné sur la version Playwright) | | VR_DOCKER | 1 (dans le conteneur) | Active les flags Chromium déterministes |

Utiliser son propre Docker (image maison)

L'image ghcr.io/sashalarrieu/vr-capture est un raccourci, pas une obligation. Vous pouvez fournir votre propre image (VR_DOCKER_IMAGE) tant qu'elle respecte le contrat :

  • Base Linux avec la même version de Playwright/Chromium que le package (voir docker/Dockerfile).
  • Code du projet hôte monté sur /work (VR_PROJECT_ROOT=/work).
  • node_modules Linux dans un volume dédié (pas les binaires natifs Mac/Windows).
  • VR_DOCKER=1 et VR_CAPTURE_BACKEND=local dans le conteneur.
  • Lancer le daemon (visual-regression capture-daemon) ou la capture one-shot (visual-regression capture-oneshot).

Toute divergence d'environnement (autre OS, autre Chromium, autre Node) peut réintroduire des différences de pixels entre machines.


Utilisation dans un projet hôte

Dans l’utilisation standard, tu n’as rien à importer dans ton app : tu lances les scripts depuis la racine du projet hôte et tu ouvres l’URL de l’app web de régression dans ton navigateur.
L’UI VisualRegressions est entièrement embarquée dans le package.

Intégration Storybook (.storybook/)

Le package expose les decorators, types et helpers utilisés par les stories VR :

| Import | Rôle | | -------------------------------------- | ------------------------------------------------------------------------------------ | | @setshao/visual-regression/storybook | Decorators vrPreviewDecorators (freeze Reanimated + exécution play()) | | @setshao/visual-regression | Tags VR (LIVE_ANIMATION_VR_TAG, SKIP_PLAY_VR_TAG, …) et type VrStoryParameters | | @setshao/visual-regression/play | Helpers play() DOM pour React Native Web (clickByLabel, expectText, …) |

.storybook/preview.tsx :

import type { Preview } from "@storybook/react-webpack5";
import { vrPreviewDecorators } from "@setshao/visual-regression/storybook";

const preview: Preview = {
  decorators: vrPreviewDecorators,
  parameters: { layout: "centered" },
};

export default preview;

.storybook/vr-parameters.d.ts (typage parameters.vr) :

import type { VrStoryParameters } from "@setshao/visual-regression";

declare module "@storybook/react-webpack5" {
  interface Parameters {
    vr?: VrStoryParameters;
  }
}

Un modèle est aussi fourni dans le package : node_modules/@setshao/visual-regression/src/storybook/vr-parameters.d.ts.

Ajoutez @setshao/visual-regression à modulesToTranspile de @storybook/addon-react-native-web si Storybook ne résout pas le package.

1. Scripts fournis par le package (à appeler depuis le projet hôte)

Le package contient ses scripts CLI dans src/scripts/, exposés via la commande visual-regression. Depuis le projet hôte, ajoute des scripts qui appellent le binaire (en lançant depuis la racine du projet pour que process.cwd() soit la racine) :

| Script | Rôle | | --------------------- | --------------------------------------------------------------------------------------------- | | vr | Lance tout : serveur VR + Storybook + Expo + comparaison incrémentale initiale (désactivable) | | vr:server | Lance uniquement le serveur VR (port 2805) | | vr:compare | Lance la comparaison Playwright (régénération des screenshots) | | vr:benchmark | Mesure la concurrency optimale sur 1 machine (1..16) | | vr:benchmark-shards | Simule le sharding CI (shardTotal × concurrency) sans lancer toute la matrix | | vr:test-validation | Checklist Phases 0–8 (--static-only sans Storybook) | | vr:storybook:static | Build Storybook + stats (preview-stats.json) puis serve sur le port 6006 | | vr:app | Lance l’app Expo en mode régression (port 2804) | | vr:capture:up | Démarre le sidecar Docker de capture (+ attend le daemon) | | vr:capture:down | Arrête le sidecar Docker de capture | | vr:capture:status | État du sidecar + health du daemon (port 2810) | | vr:kill-ports | Libère les ports 2804, 2805 et 2810 |

Exemple dans le package.json du projet hôte (à lancer depuis la racine du projet) :

{
  "scripts": {
    "vr": "visual-regression",
    "vr:server": "visual-regression server",
    "vr:compare": "visual-regression compare",
    "vr:app": "visual-regression app"
  }
}

2. Variables d’environnement

| Variable | Description | | ----------------------------------- | ------------------------------------------------------------------------------------ | | VR_PROJECT_ROOT | Racine du projet hôte (défaut : process.cwd()) | | VR_CONCURRENCY | Override de capture.concurrency | | VR_MAX_TEST_TIME | Override de capture.maxTestTime (ms) | | VR_COMPARE_MODE | Override de compare.mode ("incremental" ou "full") | | VR_COMPARE_SCOPE | Override de compare.scope ("all", "branch", "working-tree") | | VR_COMPARE_BASE | Override de compare.base (ex. origin/main) | | VR_THRESHOLD | Override de compare.threshold (seuil pixelmatch) | | VR_DIFF_VERIFY_MAX_ATTEMPTS | Override de compare.diffVerificationMaxAttempts | | VR_RUN_INITIAL_COMPARE | Override de launcher.runInitialCompare | | VR_STORYBOOK_MODE | "dev" ou "static" — override de launcher.storybookMode | | VR_STORYBOOK_STATIC | Alias de VR_STORYBOOK_MODE=static (rétrocompatibilité) | | VR_STORYBOOK_STATIC_REBUILD | Override de launcher.forceStaticRebuild (1 = rebuild forcé) | | VR_STORYBOOK_URL | Override de storybook.url | | VR_SHARD_INDEX / VR_SHARD_TOTAL | Sharding CI (index 0-based). Override de compare.shardIndex / compare.shardTotal | | VR_CAPTURE_BACKEND | Override de capture.backend (docker ou local) | | VR_CAPTURE_DAEMON_URL | Override de capture.daemonUrl | | VR_CAPTURE_REMOTE_CHUNK | Override de capture.remoteChunkSize | | VR_DOCKER_IMAGE | Override de docker.image | | VR_PLAYWRIGHT_IMAGE | Override de docker.playwrightImage |

Benchmark performance (concurrency + sharding CI)

Workflow recommandé pour dimensionner la CI :

# 1. Trouver la concurrency optimale sur 1 machine (Storybook requis)
yarn vr:benchmark

# 2. Simuler le sharding CI (rapide, pas de matrix réelle)
yarn vr:benchmark-shards --ms-per-task 1800 --concurrency 12,15,16

# Ou calibrer ms/tâche automatiquement (20 captures)
yarn vr:benchmark-shards --calibrate --full

Options utiles de vr:benchmark-shards :

| Option | Description | | --------------------------------- | --------------------------------------------------- | | [maxShards] ou --max-shards N | Tester shardTotal de 1 à N (défaut 8) | | --setup-ms N | Coût fixe par job CI en ms (défaut 180000 = 3 min) | | --ms-per-task N | Durée moyenne par capture (issue de vr:benchmark) | | --concurrency 12,15,16 | Liste de concurrencies à tester | | --full | Toutes les tâches (défaut) | | --incremental | Périmètre TurboSnap actuel | | --calibrate | Mesure ms/tâche via un échantillon de 20 captures |

Validez ensuite la config recommandée en vraie CI matrix avec VR_SHARD_INDEX / VR_SHARD_TOTAL.

Validation Phases 0–8

yarn vr:test-validation              # checklist complète (Storybook requis pour partie dynamique)
yarn vr:test-validation --static-only  # config, exports, TurboSnap fichier, sharding — sans Storybook

Couvre : vr.config.cjs, overrides env, TurboSnap, sharding, exports compare UI, incrémental sans changement.


Développement (contributeurs)

Prérequis

  • Node.js 20+
  • Yarn 1.x
  • Docker (pour l'intégration VR locale ou CI)

Commandes utiles

| Commande | Rôle | | --------------------------------------- | ---------------------------------------------- | | yarn typecheck | Vérification TypeScript (tsc --noEmit) | | yarn test | Tests unitaires Vitest (mode watch) | | yarn test:ci | Tests unitaires en CI (run unique) | | yarn lint / yarn lint:fix | ESLint | | yarn format:check / yarn format | Prettier | | yarn vr:test-validation --static-only | Checklist statique Phases 0–9 (sans Storybook) |

Un hook pre-commit Husky exécute lint-staged (Prettier + ESLint sur les fichiers stagés), puis yarn lint et yarn format:check sur tout le dépôt pour bloquer un commit si la CI échouerait.

CI GitHub (ce dépôt)

| Workflow | Déclencheur | Rôle | | ------------------------------------------------------------ | ------------------------------------------- | --------------------------------------------------------------------- | | [ci.yml](.github/workflows/ci.yml) | Chaque PR + push main | Typecheck, lint, format, tests unitaires, validation statique (Linux) | | [platform.yml](.github/workflows/platform.yml) | Chaque PR + push main | Typecheck + validation statique sur macOS et Windows | | [integration.yml](.github/workflows/integration.yml) | PR modifiant scripts/utils/docker/demo/etc. | Capture VR Docker complète sur la demo (VR_COMPARE_MODE=full) | | [docker-publish.yml](.github/workflows/docker-publish.yml) | Tag v* | Publication image ghcr.io/sashalarrieu/vr-capture (amd64 + arm64) |


Tester que tout fonctionne

Test rapide (uniquement l’interface)

  1. Dans le projet hôte : yarn (ou npm install) pour installer le lien vers visual-regression si tu es en local.
  2. Terminal 1 : yarn vr:server → serveur sur http://localhost:2805
  3. Terminal 2 : yarn vr:app → app sur http://localhost:2804
  4. Ouvre http://localhost:2804 : tu dois voir l’interface (panneau « Régressions visuelles », zone de contenu). Au début, le message « Aucune regression détectée, ni nouvelle screenshot » est normal.

Test complet (avec Storybook et comparaison)

Dans le projet hôte :

yarn vr

Puis ouvre http://localhost:2804 : après la comparaison, l’arbre des régressions et les screenshots doivent apparaître.


Publier une nouvelle version sur npm

Prérequis :

  • Node >=20 (voir engines du package)
  • Organisation npm @setshao configurée avec les droits de publication
  • Secret GitHub NPM_TOKEN (token npm automation/publish)
  • 2FA activée sur le compte npm

Workflow recommandé :

  1. Vérifier les changements package : yarn pack:check && yarn pack:verify.
  2. Mettre à jour CHANGELOG.md.
  3. Incrémenter la version : npm version patch (ou minor/major).
  4. Pousser commit + tag : git push && git push --tags.
  5. Le workflow GitHub npm-publish.yml se déclenche sur tag v* et publie automatiquement sur npm.

Publication manuelle exceptionnelle (si nécessaire) :

  1. npm login
  2. yarn prepublishOnly
  3. npm publish

Pour utiliser la version npm dans un projet : yarn add @setshao/visual-regression et garder l’import import { VisualRegressions } from "@setshao/visual-regression";.


Dépannage

  • « Cannot find module '@setshao/visual-regression' »
    Vérifier que yarn / npm install a bien été exécuté et que node_modules/@setshao/visual-regression existe (ou le lien file:../visual-regression).
  • Interface blanche ou crash
    Ouvrir la console du navigateur (F12). Si le serveur VR n’est pas démarré, l’app peut afficher « Aucune regression détectée » ; lancer le serveur VR dans un autre terminal.
  • Port déjà utilisé
    Utiliser la commande du projet hôte pour libérer les ports (ex. yarn vr:kill-ports), puis relancer.
  • « Module not found » pour les scripts VR
    Vérifier que node_modules/.bin/visual-regression est présent. Si absent, réinstaller la dépendance (yarn install ou yarn add @setshao/visual-regression).