Tests e2e avec Playwright sur un site Astro : mon setup CI/CD complet
TL;DR : Playwright + Astro + GitLab CI = tests e2e automatis�s qui tournent � chaque push. Mais il y a des pi�ges - notamment les race conditions au d�marrage du serveur. Voici comment j’ai tout mis en place, et ce que j’ai d� corriger.
Pourquoi des tests e2e sur un site statique ?
Mon site sevan.zone est construit avec Astro 4 et d�ploy� en statique sur nginx via Unraid. On pourrait se dire : “c’est statique, pas besoin de tests”. Sauf que :
- Le build Astro peut casser du jour au lendemain (d�pendances, configs, composants)
- Les liens de navigation peuvent �tre cass�s silencieusement
- Les pages blog, projets, apps ont des logiques dynamiques (filtres, FAQ, navigation)
- Le deploy se fait automatiquement - mieux vaut tester avant de pousser en prod
J’ai donc mis en place une suite de 25 tests e2e avec Playwright qui v�rifient l’int�gralit� du site � chaque push sur master.
L’architecture du pipeline
Voici ce que mon .gitlab-ci.yml fait � chaque commit :
test ? security ? build ? e2e ? deploy
| Stage | Job | Ce qu’il fait |
|---|---|---|
| test | unit-tests | Vitest sur les composants |
| security | npm-audit | V�rifie les vuln�rabilit�s npm |
| build | build | astro build ? produit dist/ |
| e2e | e2e-tests | Playwright sur le build?? |
| deploy | deploy | rsync dist/ vers nginx (Unraid) |
Le point cl� : les tests e2e tournent sur le build?? (dist/), pas sur le serveur dev. �a reproduit exactement ce qui sera d�ploy�.
La config Playwright
Voici mon playwright.config.ts :
import { defineConfig, devices } from '@playwright/test';
const isCI = !!process.env.CI;
export default defineConfig({
testDir: './src/tests/e2e',
outputDir: './test-results',
retries: isCI ? 1 : 0,
use: {
baseURL: 'http://localhost:4321',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: isCI
? {
command: 'python3 -m http.server 4321 --directory dist',
url: 'http://localhost:4321',
timeout: 30000,
}
: {
command: 'npm run dev',
url: 'http://localhost:4321',
reuseExistingServer: true,
timeout: 60000,
},
});
Les choix importants :
retries: 1en CI - si un test est flaky (race condition), il est relanc� une foiswebServerdiff�rent selon l’environnement - en CI on sert ledist/avec python, en local on lance le serveur dev Astrotimeout: 30000pour le webServer en CI - augment� apr�s un bugtrace: 'on-first-retry'- ne capture les traces que si le test �choue (�conomie d’espace)
Le pi�ge des race conditions
Le probl�me que j’ai rencontr� : le premier test �chouait syst�matiquement avec un timeout de 30 secondes sur page.goto('/blog/'). Les 24 autres tests passaient parfaitement.
Sympt�me :
page.goto: Test timeout of 30000ms exceeded.
navigating to "http://localhost:4321/blog/", waiting until "load"
Cause : Le serveur python (python3 -m http.server 4321 --directory dist) mettait trop de temps � d�marrer. Le webServer de Playwright attendait que le port soit ouvert (timeout 15s), mais le serveur n’�tait pas encore pr�t � servir les fichiers.
La solution :
- Augmenter le timeout du
webServerde 15s � 30s - Ajouter
retries: 1pour tol�rer les �checs transitoires
webServer: isCI
? {
command: 'python3 -m http.server 4321 --directory dist',
url: 'http://localhost:4321',
timeout: 30000, // ? augment� de 15000
}
: { /* ... */ },
retries: isCI ? 1 : 0, // ? ajout�
Depuis, le pipeline passe � chaque fois.
Structure des tests
Mes 25 tests couvrent toutes les pages du site :
// src/tests/e2e/blog.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Page Blog', () => {
test('se charge avec le bon titre', async ({ page }) => {
await page.goto('/blog/');
await expect(page).toHaveTitle(/[Bb]log/);
await expect(page.locator('h1')).toContainText('Blog');
});
test('liste des articles', async ({ page }) => {
await page.goto('/blog/');
const cards = page.locator('.post-card');
await expect(cards.first()).toBeVisible();
await expect(await cards.count()).toBeGreaterThan(0);
});
test('les filtres de tags sont pr�sents', async ({ page }) => {
await page.goto('/blog/');
await expect(page.locator('.tag-btn').first()).toBeVisible();
});
test('filtrer par tag cache les articles sans ce tag', async ({ page }) => {
await page.goto('/blog/');
const tagBtn = page.locator('.tag-btn').nth(1);
await tagBtn.click();
await expect(tagBtn).toHaveClass(/active/);
});
});
Les tests v�rifient :
- Le chargement de chaque page (titre, contenu)
- La navigation entre les pages
- Les filtres par tag
- Les liens externes
- La FAQ interactive
- Les stats hero
Le job e2e dans GitLab CI
e2e-tests:
stage: e2e
image: node:22-bookworm
needs: [build]
script:
- npm ci --no-fund
- npx playwright install --with-deps chromium
- npx playwright test --reporter=line
artifacts:
when: on_failure
paths:
- test-results/
expire_in: 3 days
rules:
- if: $CI_COMMIT_BRANCH == "master"
Points importants :
needs: [build]- t�l�charge ledist/g�n�r� par le stage build--with-deps chromium- installe les d�pendances syst�me de Chrome (fonts, libs graphiques)artifacts: when: on_failure- ne garde les screenshots/traces qu’en cas d’�chec--reporter=line- output minimal en CI (pas de HTML report)
Le deploy automatique
Si tous les tests passent, le deploy se fait en automatique :
deploy:
stage: deploy
image: alpine:latest
needs: [build, e2e-tests]
before_script:
- apk add --no-cache openssh-client rsync
- mkdir -p ~/.ssh
- echo "$SSH_DEPLOY_KEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
script:
- rsync -avz --delete
--exclude='projects/'
--exclude='fring/'
-e "ssh -p 2222 -o StrictHostKeyChecking=no"
dist/
root@$DEPLOY_HOST:$DEPLOY_PATH/
Le deploy utilise rsync over SSH pour pousser le dist/ sur le serveur Unraid. La cl� SSH est stock�e dans les variables CI/CD de GitLab.
Les le�ons apprises
| Pi�ge | Solution |
|---|---|
| Serveur pas pr�t au premier test | Augmenter timeout du webServer |
| Tests flaky en CI | retries: 1 |
| chromium ne d�marre pas | --with-deps chromium pour les libs syst�me |
| Artifacts trop gros | artifacts: when: on_failure |
| Deploy sans test | needs: [build, e2e-tests] |
Conclusion
Mettre en place des tests e2e sur un site statique peut sembler overkill, mais �a a chang� ma fa�on de travailler. Avant : je pushais, je v�rifiais manuellement, je corrigeais les bugs en prod. Maintenant : je push, le pipeline tourne, et si �a passe c’est bon.
Le vrai gain n’est pas tant la couverture de tests que la confiance. Quand je modifie un composant, un fichier de config, ou un article de blog, je sais que le pipeline v�rifiera que tout fonctionne encore.
Et avec l’IA (OpenCode dans mon cas), �crire des tests devient quasi gratuit - je lui demande de tester une fonctionnalit�, elle �crit le test, je review, et c’est push�.
Mon workflow est maintenant : �crire ? tester ? push ? deploy. Sans passer par la case “bug en prod � 23h”.
Cet article a �t� �crit avec l’aide d’OpenCode, un assistant IA de d�veloppement. Le site sevan.zone est construit avec Astro 4, test� avec Playwright, et d�ploy� via GitLab CI sur un serveur Unraid self-host�.