Tester dans un navigateur headless
Depuis plus de 12 ans, je développe des visualisations de données sur le web, et l’automatisation des tests dans un environnement navigateur a toujours été compliqué. C’est possible, mais souvent pénible et difficile à maintenir. De plus, l’automatisation des benchmarks dans un environnement navigateur a toujours été encore plus difficile à mettre en place.
Dans cet article, je vais démontrer à quel point il est facile aujourd’hui de configurer Vite, Vitest et Playwright pour tester et faire des benchmarks de code TypeScript dans un navigateur headless. Cette configuration permet d’exécuter les tests et les benchmarks depuis la ligne de commande et en CI, rendant la configuration beaucoup plus simple.
Pourquoi j’utilise Vitest
En travaillant récemment sur sigma.js v3, j’ai commencé à moderniser tous le tooling. L’état existant consistait en des configurations Webpack obsolètes, des scripts ad hoc et beaucoup trop de boilerplate. C’était difficile à maintenir et à faire évoluer, avec plusieurs problèmes non résolus, notamment l’exécution de tests unitaires dans un environnement navigateur.
J’ai passé des heures à chercher une solution de test moderne et facile à configurer, en vain. Divers articles de blog suggéraient des solutions basées sur Puppeteer, PhantomJS, voire CasperJS, mais elles semblaient toutes obsolètes et/ou difficiles à configurer (et probablement à maintenir).
Vitest pour les tests unitaires
Finalement, je suis tombé sur le mode navigateur de Vitest basé sur Playwright, qui correspondait parfaitement à mes besoins. À ce moment-là, sigma.js dépendait déjà de Playwright pour les tests end to end. J’aurais aimé que cette solution soit plus facile à trouver, et j’espère que cet article en accélérera la découverte pour d’autres.
Vitest pour les benchmarks
Au cours de mes recherches, j’ai également découvert la fonctionnalité de benchmarking de Vitest, basée sur TinyBench. Ça permet à Vitest d’exécuter TinyBench pour réaliser des benchmarks. Et ce n’est pas que du sucre : combinée avec le mode navigateur, ça permet de faire des benchmarks du code TypeScript depuis la ligne de commande dans un environnement navigateur.
Cependant, l’intégration de ces fonctionnalités n’était initialement pas possible en raison d’un problème dans Vitest. L’équipe Vitest a résolu ce ticket en quelques mois, avec une communication claire sur les progrès, ce qui était très agréable à observer.

Tester et benchmarker un projet d’exemple
Pour démontrer à quel point il est facile d’utiliser Vitest et Playwright pour tester et lancer des benchmarks de code TypeScript dans un navigateur headless, j’ai créé un projet d’exemple. Il vise à comparer deux méthodes de dessin de lignes épaisses sur un élément canvas : en utilisant CanvasRenderingContext2D.drawRect
ou en utilisant CanvasRenderingContext2D.stroke
(avec une lineWidth
épaisse).
Le projet est disponible ici : github.com/jacomyal/canvas-benchmark
Le Code Principal
Essentiellement, les deux fonctions que j’ai testées et benchmarkées ressemblent à ceci :
function fillBasedThickLine(ctx, from, to, thickness, color) {
const dx = to.x - from.x;
const dy = to.y - from.y;
const d = Math.sqrt(dx ** 2 + dy ** 2);
const angle = Math.atan2(dy, dx);
ctx.save();
ctx.fillStyle = color;
ctx.translate(from.x, from.y);
ctx.rotate(angle);
ctx.fillRect(0, 0 - thickness / 2, d, thickness);
ctx.restore();
}
function strokeBasedThickLine(ctx, from, to, thickness, color) {
ctx.beginPath();
ctx.moveTo(from.x, from.y);
ctx.lineTo(to.x, to.y);
ctx.lineWidth = thickness;
ctx.strokeStyle = color;
ctx.stroke();
}
Le code exact est disponible dans le fichier src/index.ts
. Pour vérifier qu’elles fonctionnent correctement, j’ai ajouté un petit exemple, où les utilisateurs peuvent cliquer sur un canvas pour dessiner des lignes épaisses. Vous pouvez jouer avec ici :
Tests Unitaires
J’ai commencé par écrire un test pour chaque fonction afin de vérifier que la fonction colorie au moins un pixel sur un canvas de 1x1. Ils ressemblent à ceci :
test("it should colorize pixels", () => {
// Dessiner une ligne épaisse contenant l'unique pixel
drawThickLine(ctx, { x: -1, y: -1 }, { x: 1, y: 1 }, 2, "#ff0000");
// Récupérer la couleur du pixel :
const {
data: [r, g, b, a],
} = ctx.getImageData(0, 0, 1, 1);
expect([r, g, b, a]).toStrictEqual([255, 0, 0, 255]);
});
La version complète avec le boilerplate est disponible dans le fichier src/index.spec.ts
.
Pour exécuter les tests unitaires, j’ai d’abord installé Vitest et Playwright :
npm install --save-dev vitest @vitest/browser playwright
Playwright doit également installer ses navigateurs headless localement :
npx playwright install
Ensuite, j’ai ajouté un fichier vitest.config.mts
, indiquant à Vitest d’utiliser Playwright et le mode navigateur :
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["*.ts"],
dir: "src",
browser: {
provider: "playwright",
name: "chromium",
enabled: true,
headless: true,
},
},
});
À ce stade, lorsque j’ai exécuté npx vitest test src/index.spec.ts
, cela a affiché ce qui suit :
DEV v1.5.3 canvas-benchmark
Browser runner started at http://localhost:5173/
✓ src/index.spec.ts (2)
✓ fillBasedRectangle (1)
✓ it should colorize pixels
✓ strokeBasedRectangle (1)
✓ it should colorize pixels
Test Files 1 passed (1)
Tests 2 passed (2)
Start at 16:58:52
Duration 800ms (transform 0ms, setup 0ms, collect 20ms, tests 6ms, environment 0ms, prepare 0ms)
Les tests unitaires se sont exécutés avec succès depuis la ligne de commande dans un navigateur headless !
Benchmarks
Pour faire des benchmarks de ces fonctions, j’ai écrit quelque chose comme ceci :
bench(
"Canvas methods to draw a thick line",
() => {
const angle = 2 * Math.PI * Math.random();
drawThickLine(
ctx,
{
x: 500 - 500 * Math.cos(angle),
y: 500 - 500 * Math.sin(angle),
},
{
x: 500 + 500 * Math.cos(angle),
y: 500 + 500 * Math.sin(angle),
},
50,
getRandomColor(),
);
},
{ iterations: 1000 },
);
Encore une fois, la version complète avec le boilerplate est disponible dans le fichier src/index.bench.ts
.
Pour activer la fonctionnalité de benchmarking, j’ai ajouté une ligne dans la configuration de Vitest :
export default defineConfig({
mode: "benchmark",
test: {
/* ... */
},
});
Maintenant, exécuter npx vitest bench ./src/index.bench.ts
affiche la sortie suivante :
DEV v1.5.3 canvas-benchmark
Browser runner started at http://localhost:5173/
✓ src/index.bench.ts (2) 8624ms
✓ Canvas methods to draw a thick line (2) 8615ms
name hz min max mean p75 p99 p995 p999 rme samples
· Using a filled rectangle 15,341.30 0.0000 1,785.50 0.0652 0.0000 0.1000 0.1000 0.1000 ±184.32% 29127 fastest
· Using a wide stroke 14,882.96 0.0000 2,427.90 0.0672 0.0000 0.1000 0.1000 0.1000 ±189.11% 37450
BENCH Summary
Using a filled rectangle - src/index.bench.ts > Canvas methods to draw a thick line
1.03x faster than Using a wide stroke
Les deux méthodes affichent des performances très similaires. Et cette analyse est automatisable et reproductible depuis la ligne de commande.
Conclusion
Je développe des bibliothèques JavaScript ciblant le navigateur depuis plus de 10 ans, et les tests unitaires ont toujours été un défi - et c’était encore pire pour les benchmarks. Aujourd’hui, grâce à Vitest et Playwright, l’intégration de ces processus dans n’importe quel projet TypeScript est devenue remarquablement facile.