Introduction
Qu'allons-nous faire ?
Storybook se définit comme un "workshop" permettant de créer des composants et des pages en isolation. Concrètement, c’est une interface sur laquelle on va pouvoir jouer avec les différents états d’un composant pour vérifier s’il correspond bien au design attendu, trouver de la documentation et récupérer les props à utiliser pour différentes implémentations. C’est un outil qui est de plus en plus utilisé lors de la création de Design Systems, et qui peut être utilisé avec de plus en plus de frameworks bien qu’il soit initialement fait pour React. L’utilisation de Storybook peut être améliorée avec des plugins.
Mais si on passe nos composants sur Storybook, est-ce qu’il est possible de les y tester aussi directement ? La réponse est oui ! Un des outils à notre disposition pour cela, c’est Chromatic.
Chromatic est développé par la même équipe que Storybook (d’ailleurs on peut remarquer que la chaîne officielle de Storybook sur Youtube s’est renommée Chromatic !). C’est un outil qu’on utilise dans la CI, qui permet de faire des tests de non régression visuelle (mais pas que !) sur les stories créées sur Storybook. Chromatic est un outil payant avec une version gratuite que nous allons utiliser et qui va nous permettre d'effectuer 5000 snapshots, ce qui est largement suffisant pour les besoins de ce tuto !
Note
Les tests de non régression visuelle sont parfaits dans le cas d’un Design System car ils permettent de vérifier au pixel près si les modifications sur un composant n’affectent pas d’autres composants, ce qui peut être très compliqué à remarquer sur des projets complexes. Dans ce tuto nous allons voir :
- comment installer Storybook et Chromatic sur un nouveau projet Vite + React
- comment automatiser Chromatic en CI
- comment créer et tester visuellement sur Chromatic un composant simple
- comment faire des tests d’interaction et les lancer avec Chromatic
Prérequis
- Avoir NodeJs en version 16 ou plus installé sur votre machine (
node -vpour vérifier) - Avoir un compte Github
Note
Initialisation du projet
Initialisation du projet (Vite + React + Storybook)
Comme il est probable que vous utilisiez React, nous allons faire un nouveau projet en React appelé "chromatic-tuto-elevenlabs" sur lequel nous allons installer Storybook et React. Storybook comprendra automatiquement qu’il est sur un projet React.
Sur un terminal, placez-vous dans le dossier dans lequel vous souhaitez créer le projet, puis lancez ces commandes :
npm create vite@latest chromatic-tuto-elevenlabs --template react-ts
Acceptez l’installation de create-vite puis sélectionnez en Framework React et en variant Typescript (ou Javascript si vous êtes plus à l’aise, ça ne change pas grand chose dans le cadre de ce tuto mais il faudra adapter les extraits de code en retirant les types). Installez le projet sans le lancer :
cd chromatic-tuto-elevenlabs npm install
Avant de lancer le projet nous allons installer Storybook. Comme dit précédemment, Storybook va automatiquement déterminer dans quel environnement il est installé.
Important
Lancez cette commande, puis acceptez l’installation du package storybook, puis un peu plus tard dans l’installation, installez le plugin ESLint recommandé si vous le souhaitez.
npx storybook@7 init
À la fin de l’installation un onglet s’ouvre par défaut à l’adresse localhost:6006. Si vous avez besoin de relancer Storybook plus tard, faites cette commande :
npm run storybook
Si vous n’avez pas l’habitude de Storybook je vous conseille de suivre le tour proposé par Storybook au premier lancement du projet (préparez-vous à recevoir une myriade de confettis colorés). Je vous suggère aussi de suivre cet article du blog qui présente les bases pour construire un Design System avec React et qui aborde donc Storybook.
Nous avons donc pour l’instant 3 stories : un bouton, un header et une page complète. Nous allons utiliser dans ce tuto le composant le plus simple : le bouton. Mais accrochez-vous parce qu’il va falloir faire un peu de CI...
Utilisation de Chromatic en CI
Nous allons commencer par initialiser notre projet et faire un premier commit.
Utilisez les commandes suivantes pour créer un premier commit sur une branche principale appelée main :
git init git add . git commit -m "init commit" git branch -M main
Ensuite nous allons créer un repo sur Github et y envoyer notre premier commit. Rendez-vous sur cette page https://github.com/new puis créez un repo "chromatic-tuto-elevenlabs". Sur votre terminal, lancez ces commandes (les commandes sont aussi indiquées préremplies sur Github) :
git remote add origin https://github.com/<votre username>/<nom sur repo>.git git push -u origin main
Votre premier commit est maintenant sur Github ! Nous allons désormais passer au moment que vous attendez tous : installer Chromatic !
Nous allons installer Chromatic en devDependency :
npm install -D chromatic
Ensuite, connectez-vous à Chromatic avec votre compte GitHub : https://www.chromatic.com/start.
Choisissez l’option Choose from GitHub, choisissez le repo que nous utilisons pour ce tuto puis récupérez et utilisez la ligne de commande sous "Publish your Storybook", elle va nous permettre de faire le lien entre le projet et Chromatic.
À la fin du processus vous trouverez un lien qui vous donnera accès à une version publiée de votre Storybook. Pas mal, non ? Chromatic vous affichera également un token, gardez-le de côté car on va s'en servir dans quelques instants.
C’est très bien mais nous ce qu’on veut c’est utiliser Chromatic ! Il nous reste une dernière étape avant de rentrer dans le vif du sujet : créer une Github Action !
Tout d’abord on a besoin de créer un secret sur Github. Créez le secret CHROMATIC_PROJECT_TOKEN qui contient le token, que vous avez récupéré précédemment. Pour créer un secret vous pouvez suivre cette documentation.
Retournez sur votre IDE, créez un dossier .github/workflows puis un fichier chromatic.yml et collez-y ce template (qu'on peut également retrouver sur le documentation de Chromatic) :
# .github/workflows/chromatic.yml name: "Chromatic" on: push jobs: chromatic: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install dependencies run: yarn install --immutable --immutable-cache --check-cache - name: Publish to Chromatic uses: chromaui/action@latest with: projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
Créez un commit et poussez-le sur le repo :
git add . git commit -m "GitHub action setup" git push origin main
Vous pouvez trouver le build dans la pipeline après le push.
Nous allons maintenant voir comment fonctionne Chromatic !
Comment fonctionne Chromatic
Chromatic fonctionne avec un système de builds qui vont se créer à chaque lancement du job dans la CI. Chromatic va vérifier s’il y a des différences visuelles par rapport au build précédent. S’il n’y a pas de différence, le build passera et le job sera vert. S’il y a des différences en revanche, le build ne passera pas, le job sera orange ou rouge et il faudra accepter ou non les régressions détectées par Chromatic.
Chromatic va prendre des snapshots, c’est-à-dire des impressions d’écran des stories. Pour déterminer s’il y a une différence, il va superposer le nouveau snapshot avec l’ancien et comparer, au pixel près, si quelque chose a changé. C’est le concept de test de non régression visuelle.
Ce sont les snapshots qui constituent les crédits de Chromatic. La version gratuite permet actuellement de prendre 5000 snapshots par mois, ce qui est suffisant pour nos besoins pour l’instant. Cependant nous verrons plus tard que la quantité de snapshots peut augmenter exponentiellement.
Le snapshot est pris après la fin des animations que Chromatic va tenter d’arrêter à la dernière frame. Il se fera également après la fin des interactions de Storybook. Si pour une quelconque raison le snapshot se prend trop tôt il est possible de rajouter un délai fixe pour éviter les tests flaky :
export const StoryName = { args: { with: "props", }, parameters: { // Sets the delay (in milliseconds) for a specific story. chromatic: { delay: 300 }, };
(Plus d'informations sur le délais ici)
Nous allons approuver le premier build de Chromatic pour avoir une branche main saine et avec laquelle on pourra comparer les modifications d'autres branches.
Dans la pipeline du commit que nous venons de faire vous devriez retrouver l'icône de Chromatic et un job nommé "UI Tests". Cliquez sur "Details" pour vous rendre directement sur l'interface de Chromatic reliée à votre projet, sur le build lié au commit.

Le build est vert : tous les nouveaux snapshots ont été acceptés par défaut ! Tout en bas de la page, dans la partie "Example", on peut retrouver les snapshots qui ont été pris des différents composants. Il y a également un bouton "View Storybook" à droite qui permet d'accéder à son Storybook publié.
Modifions le rendu d'un composant
À partir de maintenant nous allons faire des branches que nous mergerons vers main pour reproduire un workflow standard de projet. Nous allons créer une branche qui va contenir une modification de style :
git checkout -b feat/button-primay-backgroundcolor
Nous allons effectuer une petite modification de couleur pour vérifier que Chromatic nous demande bien de valider la différence sur notre PR. Dans notre fichier de style button.css nous allons changer la couleur primaire du bouton par un bleu légèrement différent :
.storybook-button--primary { color: white; background-color: #6495ed; /* Un bleu un peu différent ! */ }
Avec quel autre build Chromatic va t’il comparer exactement ?
Chromatic va comparer les snapshots avec les snapshots du build d’un ou plusieurs ancêtres.
Le cas le plus simple et peut-être le plus courant est celui d’un commit poussé sur une branche déjà existante et qui possède déjà un build. Chromatic va comparer le build avant le nouveau commit avec les snapshots pris sur le job du nouveau commit.
Un autre cas : celui d’une nouvelle branche. Dans ce cas Chromatic va comparer le dernier build de la branche principale avec le snapshot de la nouvelle branche. C’est pourquoi il est très important d’avoir son build de main à jour !
Le build se lance en réalité 2 fois : une fois dans la PR au moment du push, puis une seconde fois au merge de la branche vers la branche principale. Dans tous les cas à ce moment là le build avec lequel on va comparer sera celui de la branche principale. C’est pourquoi il est très important de lancer un build sur main avec l'option autoAcceptChanges pour éviter de valider 2 fois les mêmes changements. À moins qu’il y ait des erreurs dans les interactions ou autres bugs farfelus, le build sera automatiquement validé comme étant la nouvelle base saine pour les nouvelles branches.
Pour plus d'informations sur les gestion des ancêtres de Chromatic vous pouvez vous rendre sur la documentation.
Nous allons configurer l'acceptation automatique des changements dans notre branche actuelle. Dans le fichier chromatic.yml nous allons ajouter en dessous du ProjectToken une nouvelle ligne qui configure autoAcceptChanges :
with: projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} autoAcceptChanges: "main" # Accepte automatiquement les modifications sur main
Créez un commit avec ces modifications et poussez votre branche :
git add . git commit -m "feat: update button primary backgroundcolor and enable auto-accept on main" git push -u origin feat/button-primay-backgroundcolor
Créez ensuite une pull request. Au bout de quelques secondes vous retrouverez "UI Tests" dans les checks de la PR.

Le build est en orange : il n'est ni validé ni en erreur, il attend simplement d'être review comme indiqué dans la colone Status à côté de chacune de nos stories. Nous allons cliquer sur le bouton bleu "Verify changes" en haut à droite du tableau principal de la page pour pouvoir review chaque storie modifiée une par une.

Sur la nouvelle page nous avons tout en haut à gauche le nom de la première story et du composant : "Button: Primary". En dessous nous pouvons trouver les snapshots.. Nous avons à gauche l'état de base du composant, et à droite le nouvel état détecté par Chromatic... et notre bouton qui est vert fluo au lieu d'être bleu ! Pas de panique, c'est la façon dont Chromatic indique les zones de différences entre les deux snapshots. En haut à droite du snapshot du nouvel état se trouvent des boutons qui permettent de distinguer les zones de différence en ajoutant une ombre ou un effet stroboscopique. En cliquant sur "Diff" vous désactiverez l'overlay vert pour afficher le composant.
Tout en bas de page nous retrouvons le DOM du composant qui est également affiché : il est important de noter que Chromatic ne prend pas en compte les différences de DOM pour déterminer si un composant a changé ou non, c'est uniquement le snapshot visuel qu'il prend en compte. Le DOM est plutôt affiché à but informatif, pour aider à débugger par exemple, mais ne compte pas pour Chromatic. Enfin tout en haut à droite nous avons des flèches pour passer entre les différents composants, puis deux boutons, "Deny" et "Accept" à côté desquels sont accolés leurs versions "batch" pour tout refuser ou tout accepter.
On remarque que le bleu du bouton est effectivement différent et que Chromatic l'a bien repéré. On remarque aussi que modifier un seul composant a eu un impact sur des stories différentes, Header et Pages, que nous n'avons pas modifiées. C'est la force des tests de non régression visuelle : même si on ne sait pas quelles sont les impacts liés à nos modifications, Chromatic est là pour vérifier sur chacune des stories les éventuels impacts que nous n'aurions pas envisagés. C'est un filet de sécurité très rassurant !
Un par un, nous allons accepter les nouveaux snapshots de Chromatic. Une fois tous les snapshots acceptés Chromatic nous renvoie sur la page précédente et nous pouvons voir que le Build est devenu vert ! Youpi !

De retour sur la PR vous verrez que tous les checks sont passés et que vous pouvez désormais la merger. Cliquez sur "Merge Pull Request" puis "Confirm Merge" et "Delete Branch" pour supprimer la branche devenue inutile. Rendez-vous sur la liste des commits du projet et patientez quelques instants : les jobs devraient tous passer au vert sans intervention de votre part ! Si vous cliquez sur "Details" à côté de "Tests UI" vous verrez qu'un nouveau build a été lancé, depuis main, et que vous n'avez pas eu besoin de l'approuver pour qu'il soit vert.

La documentation complète sur l'utilisation de Chromatic avec les Github Actions est ici.
Turbosnap pour économiser des snapshots
Actuellement lorsque le job Chromatic se lance, toutes les stories existantes vont être passées à la moulinette pour prendre un snapshot. Comme expliqué précédemment, chaque snapshot consomme un crédit. Vous pouvez trouver le nombre de crédit consommé dans l'interface de Chromatic dans l'onglet "Manage".
Nous avons aussi vu qu’en réalité pour une PR il y a deux builds qui sont lancés : un premier au moment du push sur la branche, et un seconde au moment du merge de la branche sur la branche principale, que nous acceptons automatiquement. Existerait-il un moyen d’économiser un peu de snapshot ?
Turbosnap arrive à la rescousse ! Il s'agit d’une feature qui va vérifier quels sont les fichiers potentiellement modifiés par le commit afin de ne prendre que les snapshots qui peuvent être dans son scope. Ainsi si j’ai 10 stories sur Storybook mais que je n’en modifie qu’une, alors je n’aurais qu’un snapshot de consommé au lieu de 10 pour chaque build... ou presque. Chaque snapshot évité avec Turbosnap est considéré comme un cinquième d'un snapshot. Pour 5 snapshots évité, on a donc consommé un crédit.
Note

Turbosnap va remonter le plus possible dans les utilisations des fichiers modifiés pour déterminer quelles stories peuvent être affectées. On peut aussi passer des arguments pour reprendre des snapshots lorsque certains fichiers qui ne sont pas directement liés aux stories sont modifiés, comme les assets avec une option externals à ajouter à la configuration de l'Action Github.
On ne peut pas utiliser Turbosnap avant d’avoir fait 10 builds validés. Cela permet apparemment à Turbosnap de bien analyser le projet avant de commencer à exclure des stories.
Une fois que vous aurez 10 builds Chromatic validés sur le projet, vous pourrez ajouter onlyChanged à la configuration des Actions Github :
jobs: chromatic: steps: # ... - name: Publish to Chromatic uses: chromaui/action@latest with: projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} autoAcceptChanges: "main" onlyChanged: true # Active Turbosnap
La documentation complète sur Turbosnap est ici
Les modes (dark/light + viewport + variables globales)
Les snapshots pris par Chromatic ont par défaut un viewport de 1200px de large. Comment faire pour vérifier des rendus sur des viewports différents ? Est-ce qu’on pourrait prendre en compte d’autres éléments comme le darkmode ou la traduction ?
C’est effectivement possible grâce aux modes de Chromatic.
Nous allons ajouter une apparence en darkmode sur le bouton, et configurer la story du bouton pour que Chromatic prenne plusieurs snapshots : en darkmode et lightmode, en 1200px et en 420px de large.
Nous allons commencer par revenir sur main et pull nos modifications, puis créer une nouvelle branche feat/enable-modes :
git checkout main git pull git checkout -b feat/enable-modes
Pour ajouter une gestion basique des thèmes dans Storybook nous allons installer l'addon @storybook/addon-themes
npm i -D @storybook/addon-themes
On l'ajoute ensuite à la suite des addons déjà installés dans .storybook/main.ts :
addons: ['@storybook/addon-themes'],
Relancez Storybook avec npm run storybook, vous verrez apparaître un bouton qui permet de changer de thème dans la barre supérieure de l'interface, tout à droite. Nous allons modifier le CSS du bouton Primary pour avoir un rendu différent si la classe .dark ajoutée par l'addon est présente. Dans le fichier button.css ajoutez les lignes suivantes :
.dark .storybook-button--primary { color: black; background-color: #add8e6; }
Si vous changez de thème vous pourrez maintenant voir le bouton de la story Primary changer d'apparence.
Nous allons maintenant créer les différents Modes utilisés par Chromatic, car pour l'instant les snapshots seraient toujours pris uniquement en version Light.
Nous allons créer un fichier .storybook/modes.ts dans lequel nous allons créer une variable allModes :
export const allModes = { light: { theme: "light", }, dark: { theme: "dark", }, };
On peut décider d'utiliser ces modes sur toutes les stories ou bien sur quelques stories en particulier. Nous allons utiliser cette dernière méthode pour prendre les différents snapshots sur les stories du bouton. Dans le fichier Button.stories.ts ajoutez ces lignes dans les parameters de l'objet meta :
import { allModes } from './../../.storybook/modes'; // ... const meta = { title: 'Example/Button', component: Button, parameters: { layout: 'centered', chromatic: { // On ajoute la configuration des modes ici modes: { light: allModes["light"], dark: allModes["dark"], }, }, }, tags: ['autodocs'], argTypes: { backgroundColor: { control: 'color' }, }, } satisfies Meta<typeof Button>;
Nous allons aussi prendre un snapshot du Header différents viewports. Dans .storybook/preview.ts ajoutez ces lignes aux parameters :
const preview = { parameters: { // ... viewport: { viewports: { sm: { name: "Small", styles: { width: "640px", height: "900px" } }, md: { name: "Medium", styles: { width: "768px", height: "900px" } }, lg: { name: "Large", styles: { width: "1024px", height: "900px" } }, }, }, }, };
En cliquant sur le bouton des viewports du menu en haut de l'interace vous avez maintenant trois choix : Small, Medium et Large, qui correspondent aux noms que nous venons d'ajouter.
Dans allModes nous allons ajouter des propriétés qui référencent les viewports que nous venons d'ajouter. Voici à quoi ressemble allModes maintenant :
export const allModes = { light: { theme: "light", }, dark: { theme: "dark", }, small: { viewport: "sm" }, medium: { viewport: "md" }, large: { viewport: "lg" } };
Ensuite nous allons ajouter la configuration des modes dans la Header.stories.ts, dans meta :
parameters: { // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout layout: 'fullscreen', chromatic: { modes: { small: allModes["small"], medium: allModes["medium"], large: allModes["large"], }, }, },
Et voilà ! Comme tout à l'heure on ajoute les modifications dans un commit, on pousse le tout, on crée la PR puis on va regarder ce que ça donne sur le build.
Pour le bouton chaque story a deux nouveaux snapshots : en effet on a un nouveau snapshot à valider pour le mode dark, mais aussi pour le mode light ! On voit aussi que pour la story Warning on a modifié la couleur du texte du bouton. Pour les stories du Header nous avons maintenant trois variations pour les trois viewports que nous avons ajoutés.
Il aurait été également possible de croiser les thèmes et les viewports !
Note
Info
Voir la documentation complète sur les Modes
Maintenant que nous avons modifié nos stories pour implémenter différents tests de non régression visuelle, nous allons pouvoir passer à la dernière partie de ce tuto, et parler des autres types de tests que nous pouvons faire sur Chromatic !
Les autres tests qu'on peut lancer sur Chromatic
Tests d'interactivité avec Play
Pour l’instant nous avons vu qu’avec Chromatic on pouvait lancer des tests de non régression visuelle. On peut également lancer des tests d’interaction de composant ! On pourra tester si les comportements du composant lorsque l’utilisateur interagit avec correspond bien à ce que l’on souhaite.
Ces tests sont réalisés dans l’onglet Interactions des stories. Pour ajouter une interaction à une story il faut créer une fonction play. À l’intérieur on va pouvoir utiliser les utilitaires fournis par @storybook/tests qui regroupent des utilitaires de Jest et de Testing-library. On pourra utiliser step pour diviser les tests en sous-parties nommées.
Chromatic n’est pas nécessaire pour lancer les tests d’interactivité. Il y a d’autres façons de lancer ces tests de façon automatique, notamment avec @storybook/test-runner. Je parle de ces tests ici car ils sont automatiquement lancés dans Chromatic avant qu'il prenne le snapshot, donc c’est d’une pierre deux coups.
Nous allons voir dans quels cas concrets on peut utiliser des tests d'interactivité.
Création de notre composant de test
Nous allons réaliser une modale très basique, un composant avec un dialog qui s'ouvre et se ferme.
Tout d'abord nous allons nous mettre sur une nouvelle branche, feat/enable-interactions.
git checkout main git checkout -b feat/enable-interactions
Dans src/stories, créez un fichier Modal.tsx. Copiez et collez à l'intérieur de ce fichier le code suivant :
import { useRef } from 'react'; import { Button } from './Button'; export const Modal = ({onOpenModal}: {onOpenModal: () => void}) => { const dialogElement = useRef<HTMLDialogElement>(null); const openModal = () => { dialogElement?.current?.showModal(); onOpenModal(); } const closeModal = () => dialogElement?.current?.close(); return( <> <dialog ref={dialogElement}> <button type="button" aria-label="Fermer la modale" onClick={closeModal}>X</button> <p>Je suis une modale !</p> </dialog> <Button label="Ouvrir la modale" primary onClick={openModal} /> </> ) }
Nous avons donc un Button qui nous permettra d'ouvrir la modale pour tester son comportement. Au clik sur le bouton on appelle une méthode showModal qui appartient au dialog et qui permet comme son nom l'indique d'ouvrir la modale. Sur l'élément dialog nous avons récupéré la référence de l'élément avec ref pour utiliser ses méthodes. Cet élément contient un button avec un aria-label explicite pour améliorer sa compréhension pour les personnes utilisant un lecteur d'écran (puisque "X" n'est pas vraiment un super contenu de bouton, mais c'est pour l'exemple). Enfin on a un p qui affiche le contenu de la modale.
On va ensuite créer un fichier au même niveau, appelé Modal.stories.ts dans lequel on va ajouter ce code :
import type { Meta, StoryObj } from '@storybook/react'; import { Modal } from './Modal'; const meta = { title: 'Example/Modal', component: Modal, parameters: { layout: 'centered', }, tags: ['autodocs'], } satisfies Meta<typeof Modal>; export default meta; type Story = StoryObj<typeof meta>; export const Default: Story = {}
Relancez storybook s'il n'est pas déjà en train de tourner avec npm run storybook, vous devriez voir apparaître une nouvelle story, "Modal".
Prendre le snapshot d’un composant avec un état "ouvert"
Grâce aux interactions nous pouvons résoudre un des problèmes qu’on peut avoir avec Chromatic sur certains composants. Comment faire pour prendre le snapshot d’un composant qui ne s’ouvre qu’au clic, comme un dropdown ou une modale par exemple ? On peut utiliser une interaction pour activer le composant, car Chromatic prend le snapshot après que les interactions ont réussi. Si les interactions sont en erreur, alors le build est lui aussi en erreur.
Dans Default tout en bas de notre story nous allons ajouter une fonction play. Celle-ci va nous permettre de lancer des interactions, visibles dans l'onglet du même nom dans la story Default.
Note
import { userEvent, within } from '@storybook/test'; // On importe ce dont on a besoin depuis @storybook/test // ... export const Default: Story = { play: async ({ canvasElement }) => { // On ajoute play const { getByRole } = within(canvasElement); const openButton = getByRole('button', { name: "Ouvrir la modale" }); await userEvent.click(openButton); } }
canvasElement contient le DOM de notre composant. openButton contient l'élément qui a un rôle de bouton avec un nom accessible "Ouvrir la modale". On utilise userEvent pour cliquer sur le bouton. getByRole et userEvent viennent de testing-library qui est importé avec @storybook/test.
Dans l'onglet Interactions on a bien des lignes qui sont apparues pour décrire le code que nous venons d'ajouter. Aussi on remarque que la modale s'ouvre toute seule ! Chromatic va donc pouvoir prendre en snapshot son contenu.
Plus d'informations sur Testing Library ici
Tester l’accessibilité
Comment tester des composants en prenant en compte des notions d’accessibilité ?
Il y a tout d’abord un addon d'accessibilité, storybook-addon-a11y, qui permet d’afficher les erreurs et warnings possibles sur un composant. On peut aussi installer axe-playwright pour vérifier qu’il n’y a pas de régression d’accessibilité.
On peut tester quelques points d'accessibilité en utilisant les interactions appropriées :
-
on peut naviguer au clavier dans notre composant grâce aux différentes méthodes de
userEvent, pour vérifier que le composant répond bien aux différentes commandes au clavier, si le focus est bien visible et bien placé aux endroits appropriés au moment du snapshot, -
on peut utiliser la query
byRoledetesting-library. Elle permet de tester si le rôle de l’élément est le bon, ce qui est utile pour les technologies d'assistance. On peut également vérifier que le nom accessible de l’élément est correct. On peut utiliserlogRolespour loguer dans la console la liste des rôles présents au moment du log sur le composant, ce qui est très pratique pour débugger. -
Avec
byRoleon peut utiliser des options commename,descriptionet bien d’autres options pour vérifier l’accessibilité du composant. -
Enfin on peut aussi utiliser les matchers de
jest-dompour complétertesting-library(commetoHaveAccessibleName,toHaveFocus...)
Le comportement souhaité d'une modale, c'est que quand on l'ouvre, le focus passe directement sur l'élément permettant sa fermeture. C'est donc ce que nous allons tester !
On va d'abord tester d'ajouter logRoles pour savoir quels éléments sont présents à l'ouverture de la modale.
import { logRoles, userEvent, within } from '@storybook/test'; export const Default: Story = { play: async ({ canvasElement }) => { // ... logRoles(canvasElement); } }
Dans la console du navigateur on peut voir que les éléments ont été loggés, et notamment celui-ci :
button: Name "Fermer la modale": <button aria-label="Fermer la modale" type="button" />
C'est bien notre bouton de fermeture ! Et son nom est comme indiqué dans le aria-label "Fermer la modale" et non "X", son contenu. On peut donc retirer le logRoles et utiliser getByRole pour attraper ce bouton et vérifier qu'il est bien en état de focus lorsqu'on ouvre la modale.
import { expect, userEvent, within } from '@storybook/test'; export const Default: Story = { play: async ({ canvasElement }) => { // ... const closeButton = getByRole('button', { name: "Fermer la modale" }); expect(closeButton).toHaveFocus(); } }
Les interactions sont vertes, le test passe bien ! On aura même une double vérification avec le snapshot de Chromatic qui va montrer l'état de focus par défaut du navigateur sur le bouton de fermeture.
Voici le lien de documentation de byRole, avec toutes les options possibles et la documentation de jest-dom
Tester les événements
On peut tester les events émis par les composants avec les actions de Storybook. Les événements émis s’affichent dans l’onglet Actions mais sont bien utilisables dans la fonction play. On peut vérifier si un événement a bien été émis lors d’une interaction, et avec quelle valeur, s'il en a.
On voudrait vérifier que notre composant Modale émet bien un évènement à l'ouverture de la modale. On a déjà un onOpenModal dans notre composant, c'est lui que nous allons écouter. Dans les argTypes de meta, dans la story, nous allons ajouter cette configuration :
const meta = { title: 'Example/Modal', component: Modal, argTypes: { onOpenModal: { action: 'onOpenModal' } }, // <= nouvelle ligne ici parameters: { layout: 'centered', }, tags: ['autodocs'], } satisfies Meta<typeof Modal>;
Maintenant lorsque l'interaction se lance on a dans l'onglet Actions une entrée onOpenModal ! Il ne nous reste plus qu'à tester que l'action est bien effectuée dans notre fonction play :
export const Default: Story = { play: async ({ canvasElement, args }) => { // On ajoute `args` qui contient onOpenModal const { getByRole } = within(canvasElement); expect(args.onOpenModal).not.toHaveBeenCalled(); // On vérifie que onOpenModal n'a pas encore été appelée const openButton = getByRole('button', { name: "Ouvrir la modale" }); await userEvent.click(openButton); const closeButton = getByRole('button', { name: "Fermer la modale" }); expect(closeButton).toHaveFocus(); expect(args.onOpenModal).toHaveBeenCalled(); // On vérifie que onOpenModal a été appelée }, };
Si tout s'est bien passé les tests sont toujours verts !
Conditionner le lancement des interactions
Lorsqu’on ajoute une interaction, elle est systématiquement jouée à l’ouverture de la story. Ce n’est pas toujours le comportement souhaité. Il est possible de ne lancer les interactions que pour Chromatic, et également de masquer certains éléments à Chromatic pour le snapshot avec la fonction isChromatic. La fonction renverra true si on est sur Chromatic, sinon false. On peut l’utiliser directement dans les stories ou dans la fonction play.
On veut que nos utilisateurs de Storybook puissent tester eux-mêmes l'ouverture de la modale avec le bouton. Nous allons donc conditionner l'ouverture et les tests qui suivent pour ne les lancer que sur Chromatic.
import isChromatic from "chromatic/isChromatic"; // On importe isChromatic // ... export const Default: Story = { play: async ({ canvasElement, args }) => { if (isChromatic()) { // On ajoute la condition autour de nos tests const { getByRole } = within(canvasElement); expect(args.onOpenModal).not.toHaveBeenCalled(); const openButton = getByRole('button', { name: "Ouvrir la modale" }); await userEvent.click(openButton); const closeButton = getByRole('button', { name: "Fermer la modale" }); expect(closeButton).toHaveFocus(); expect(args.onOpenModal).toHaveBeenCalled(); } }, };
Et voilà ! Notre story est redevenue immobile. Il ne reste plus qu'à créer un commit et pousser nos modifications sur la CI :
git add . git commit -m "feat: enable interactions on component Modal" git push -u origin feat/enable-interactions
Sur le build de Chromatic on a bien la modale ouverte avec notre bouton de fermeture entouré de noir, c'est l'outline de focus par défaut de Chrome. C'est exactement ce qu'on voulait !
Conclusion
Eh bien ce tuto était un gros morceau !
On a installé un projet avec Storybook et Chromatic, configuré et installé Chromatic en CI, réalisé toutes sortes de tests de non régression visuelle et d'interactions... J'espère que toutes ces étapes vous ont permis de mieux comprendre les avantages (et les défauts !) de Chromatic, et comment cet outil peut vous aider à être plus confiant à chaque nouvelle feature implémentée sur vos projets.



