@open-wc/testing est la première - et pratiquement la seule - ressource sur laquelle vous allez tomber. Développée par la communauté open-source Open Web Components avec Lit sous le capot, elle permet de charger un web component, son contenu et ses logiques - car normalement le contenu et les logiques d’un web component ne sont chargés que dans le navigateur.
Associés par défaut à la librairie de tests chai, les utilitaires de @open-wc/testing
sont dissociables et utilisables avec tout autre librairie JavaScript de tests automatisés ou directement node:test (préférer alors @open-wc/testing-helpers).
Seule, l’utilisation de cette librairie permet à date de vérifier les contenus, les balises présentes, ou encore les événements déclenchés :
import "./MonFormulaire.js";
import { fixture, html, oneDefaultPreventedEvent } from "@open-wc/testing";
it("attend un événement au submit", async () => {
// Sachant que
const formulaire = await fixture(html`<mon-formulaire></mon-formulaire>`);
// Lorsque
formulaire.querySelector('button').click();
// Alors
const { detail } = await oneDefaultPreventedEvent(formulaire, 'submit');
expect(detail).to.be.true;
});
Exemple de test inspiré de la documentation : https://open-wc.org/docs/testing/helpers/#events-with-preventdefault
Dans un exemple simple comme celui-ci on peut déduire ce que le test cherche à vérifier mais la lecture nécessite néanmoins un temps de compréhension, même pour un développeur frontend aguerri. Cette approche rend difficile la lecture pour quelqu’un découvrant la base de code, est très couplée à l’implémentation - une refacto de code transparent pour l’utilisateur et sans régression peut malgré tout casser le test correspondant - et, qui plus est, ne simule pas le comportement naturel d’un utilisateur vis-à-vis de notre application.
Testing library est une librairie fournissant des méthodes utilitaires pour requêter les éléments du DOM et simuler le comportement d’un utilisateur vis-à-vis d’un composant en prenant en compte la navigation à la souris et au clavier, ainsi que les spécifications d’accessibilité ARIA (Accessible Rich Internet Applications). Elle s’est popularisée dans les projets React et se décline aujourd’hui pour s’adapter aux frameworks de composants les plus utilisés, mais on peut également utiliser le cœur de la librairie en JS natif pour l’appliquer dans notre cas, avec @open-wc/testing
. Ainsi nous pouvons nous abstraire de l’implémentation tout en écrivant un test compréhensible et représentatif d’un comportement utilisateur :
import "./MonFormulaire.js";
import { fixture, html } from "@open-wc/testing";
import { getByLabelText } from "@testing-library/dom";
import userEvent from "@testing-library/user-event";
it("permet de créer un compte", async () => {
// Sachant que
const utilisateur = userEvent.setup();
const formulaire = await fixture(html`<mon-formulaire></mon-formulaire>`);
const email = getByLabelText(formulaire, "Adresse e-mail");
const motDePasse = getByLabelText(formulaire, "Mot de passe");
const cgus = getByRole(formulaire, "checkbox", {name: "Accepter les conditions générales"});
const valider = getByRole(formulaire, "button", {name: "Créer mon compte"});
// Lorsque
await utilisateur.type(email, "test@example.com");
await utilisateur.type(motDePasse, "MotDeP@sse2024");
await utilisateur.click(cgus);
await utilisateur.click(valider);
// Alors
const messageDeSucces = getByRole(formulaire, "alert", {name: "Votre compte a bien été créé"})
expect(messageDeSucces).toHaveFocus()
});
Exemple de test utilisant les fonctionnalités de @open-wc/testing et @testing-library/dom
Cette solution ne change que très peu les habitudes de tests d’un développeur utilisant déjà Testing Library sur un projet React, Angular, Vue… et nous a permis de vérifier en plus du comportement souhaité :
Plutôt efficace comme solution, mais ce n’est pas la seule !
Une alternative possible à @open-wc/testing est l’utilisation de Cypress en tests de composants ou end-to-end. Tout comme son concurrent, Cypress peut s’utiliser avec ou sans Testing Library et la manière d’écrire un test est quasiment identique :
import "./MonAccordeon.js";
import { html } from "lit";
describe("Design System - Accordeon", () => {
it("affiche le contenu après clic sur le bouton d'ouverture", () => {
// Sachant que
const libelleBoutonOuverture = "Cypress est-elle la solution ?"
cy.mount(html`
<mon-accordeon libelle-bouton="${libelleBoutonOuverture}">
<p>Très bonne question !</p>
</mon-accordeon>
`);
// Lorsque
cy.findByRole("button", {name: libelleBoutonOuverture}).click();
// Alors
cy.findByText("Très bonne question !"}).should("be.visible");
});
});
Exemple de test de composant Cypress avec @testing-library/cypress
On notera l’utilisation, ici aussi, de la méthode utilitaire html
de Lit pour charger le composant dans notre test.
Un avantage majeur de cette technique est qu’elle bénéficie d’une très large communauté avec un vaste écosystème de librairies et de plugins, ce qui assure sa pérennité et peut améliorer l’expérience développeur. Elle permet par ailleurs de visualiser les différents comportements dans une interface, ce qui peut s’avérer utile autant pour un développeur qu’un designer, et donc pertinent dans le cadre d’un Design System.
L’inconvénient principal - parce qu’il y en a toujours - c’est qu’il faille sortir l’artillerie lourde pour des composants qui ont majoritairement vocation à être légers et simples. En conséquence, comparée à une librairie spécifique aux tests unitaires de composants, elle souffre d’un manque de rapidité d’exécution.
Les exemples de tests que j’ai partagés ont un point commun : ils testent des web components sans Shadow DOM. Cette spécificité qui fait partie intégrante du concept même du web component fait barrière lors de l’écriture de nos tests. En même temps c’est son rôle, puisqu’elle permet d’encapsuler les styles et les logiques de composant.
En effet dans le cas de @open-wc/testing
, le composant rendu par la méthode fixture
ne charge pas les web components enfants s’ils ont un shadow DOM. Ça peut être contraignant notamment dans un Design System si l’on considère la philosophie Atomic et qu’on souhaite orchestrer un ou plusieurs web component “molécules” dans un web component “organisme” par exemple.
Par ailleurs dans le cas de Cypress, le sélecteur ne peut pas voir dans un shadow DOM sans passer par la méthode .shadow()
qui permet d’entrer dans le composant. Malheureusement en faisant ainsi, le code de test se couple de nouveau à l’implémentation de notre composant.
L’approche proposée par Testing Library qui a fait ses preuves peut s’appliquer aux web components comme démontré précédemment, ce qui est une bonne nouvelle pour le développement accessible, et donc de Design Systems accessibles implémentés avec des web components : “L'avenir des design systems est dans l'accessibilité”.
Entre @open-wc/testing et Cypress (voire peut-être aussi Playwright?), il faudra choisir entre la simple librairie performante et spécifique avec une communauté plus faible mais qui peut continuer de grossir, et le framework lourd et plus lent mais avec un écosystème déjà bien établi.
Le point commun entre ces deux librairies, c’est l’utilisation de Lit pour rendre le composant dans leur contexte de test. La librairie développée par Google, qui en plus d’être légère simplifie la création de web components réactifs, continue son ascension (1 600 000 téléchargements par semaine sur npm en avril 2024 contre 900 000 en juillet 2023); ce qui est rassurant pour la pérennité de la librairie et donc de ces tests.
Par ailleurs, il est important de noter que cette étude montre des tests agnostiques des librairies utilisées pour créer des web components. Certains frameworks front capables de générer des web components doivent pouvoir les tester avant compilation à travers la librairie Testing Library associée (Svelte ou Solid.js par exemple).
Dans tous les cas, la question du Shadow DOM doit se poser non seulement pour vos tests, mais aussi parce que l'encapsulation de web components avec Shadow DOM pose des soucis d’accessibilité, notamment pour la lecture d’écran. La bonne nouvelle c’est que dans la plupart des cas (Design Systems inclus) on peut s’en passer.