TP : Tests unitaires, d'intégration et E2E avec OWASP Juice Shop et GitLab CI¶
Ce TP vous fait pratiquer une stratégie de tests automatisés autour d'une application réelle : OWASP Juice Shop
L'objectif n'est pas de réaliser un audit de sécurité, mais de comprendre comment organiser des tests unitaires, des tests d'intégration et des tests end-to-end dans une pipeline GitLab CI/CD
Objectifs du TP¶
- Identifier ce que chaque niveau de test doit prouver
- Écrire des tests unitaires rapides sur du code isolé
- Écrire des tests d'intégration contre l'API HTTP de Juice Shop
- Écrire des tests E2E sur un parcours utilisateur avec Playwright
- Automatiser l'exécution dans GitLab CI
- Publier des rapports de tests au format JUnit XML dans GitLab
- Conserver les artifacts utiles au diagnostic en cas d'échec
Contexte¶
OWASP Juice Shop est une application web volontairement vulnérable utilisée pour la formation
Dans ce TP, elle sert uniquement d'application cible pour apprendre à tester :
- une fonction isolée utilisée par notre outillage de test
- des endpoints HTTP de Juice Shop
- un parcours utilisateur réel dans le navigateur
Vous allez créer un dépôt séparé contenant votre outillage de test
Structure cible :
juice-shop-testing-tp/
├── src/
│ └── juice-shop-client.js
├── tests/
│ ├── unit/
│ │ └── juice-shop-client.test.js
│ ├── integration/
│ │ └── product-search.test.js
│ └── e2e/
│ └── search.spec.js
├── .gitlab-ci.yml
├── package.json
└── playwright.config.js
Partie 1 : Préparer Juice Shop en local¶
Exercice 1 : Démarrer l'application cible¶
Lancez Juice Shop avec Docker :
Dans un navigateur, ouvrez :
Travail demandé :
- Vérifiez que la page d'accueil se charge
- Fermez les éventuelles fenêtres d'accueil ou de cookies
- Recherchez un produit depuis l'interface
- Ouvrez les outils développeur du navigateur
- Dans l'onglet réseau, repérez l'appel HTTP déclenché par la recherche
Partie 2 : Initialiser le projet de tests¶
Exercice 2 : Créer le dépôt de travail¶
Créez un nouveau projet :
mkdir juice-shop-testing-tp
cd juice-shop-testing-tp
npm init -y
npm install --save-dev jest jest-junit wait-on @playwright/test@1.58.2
npx playwright install chromium
La version de @playwright/test est volontairement alignée avec l'image Docker utilisée plus loin dans GitLab CI
Si vous changez cette version, pensez à changer aussi l'image mcr.microsoft.com/playwright
Créez les dossiers :
Ajoutez ces scripts dans package.json :
{
"scripts": {
"test:unit": "jest tests/unit --ci --reporters=default --reporters=jest-junit",
"test:integration": "jest tests/integration --ci --reporters=default --reporters=jest-junit",
"test:e2e": "playwright test",
"test": "npm run test:unit && npm run test:integration && npm run test:e2e",
"wait:juice-shop": "wait-on http://localhost:3000"
}
}
Ajoutez un .gitignore :
Travail demandé :
- Initialisez un dépôt Git
- Créez un dépôt GitLab
- Poussez votre projet vide sur GitLab
- Vérifiez que les scripts npm apparaissent avec
npm run
Partie 3 : Tests unitaires¶
Exercice 3 : Créer un petit client de test isolable¶
Dans les tests unitaires, vous ne devez pas démarrer Juice Shop
Vous allez tester une fonction pure qui prépare les requêtes de recherche envoyées ensuite à l'application
Créez src/juice-shop-client.js :
function normalizeSearchTerm(term) {
return String(term).trim().replace(/\s+/g, ' ')
}
function buildProductSearchPath(term) {
const normalizedTerm = normalizeSearchTerm(term)
return `/rest/products/search?q=${encodeURIComponent(normalizedTerm)}`
}
function productNamesFromSearchResponse(responseBody) {
if (!responseBody || !Array.isArray(responseBody.data)) {
return []
}
return responseBody.data.map((product) => product.name)
}
module.exports = {
normalizeSearchTerm,
buildProductSearchPath,
productNamesFromSearchResponse
}
Créez tests/unit/juice-shop-client.test.js
Travail demandé :
- Testez que
normalizeSearchTermsupprime les espaces au début et à la fin - Testez que plusieurs espaces internes deviennent un seul espace
- Testez que
buildProductSearchPathencode correctement une recherche contenant un espace - Testez que
productNamesFromSearchResponseretourne uniquement les noms de produits - Testez que
productNamesFromSearchResponseretourne une liste vide si la réponse est invalide
Exemple de départ :
const {
normalizeSearchTerm,
buildProductSearchPath,
productNamesFromSearchResponse
} = require('../../src/juice-shop-client')
test('normalise un terme de recherche', () => {
expect(normalizeSearchTerm(' Apple Juice ')).toBe('Apple Juice')
})
Lancez les tests :
Partie 4 : Tests d'intégration¶
Exercice 4 : Tester l'API HTTP de Juice Shop¶
Un test d'intégration accepte une dépendance réelle
Ici, votre test va appeler Juice Shop par HTTP
Assurez-vous que Juice Shop tourne localement :
Dans un second terminal, vérifiez que l'application est prête :
Créez tests/integration/product-search.test.js :
const {
buildProductSearchPath,
productNamesFromSearchResponse
} = require('../../src/juice-shop-client')
const baseUrl = process.env.JUICE_SHOP_URL || 'http://localhost:3000'
async function getStatus(path) {
const response = await fetch(`${baseUrl}${path}`)
return response.status
}
async function getJson(path) {
const response = await fetch(`${baseUrl}${path}`)
const body = await response.json()
return {
status: response.status,
body
}
}
Travail demandé :
- Testez que la page d'accueil répond avec un statut HTTP
200 - Testez que la recherche de produit sur
Applerépond avec un statut HTTP200 - Vérifiez que la réponse contient une propriété
data - Vérifiez qu'au moins un nom de produit contient
Apple - Testez qu'une recherche improbable retourne une liste vide ou ne contient pas le produit recherché
Exemple de test attendu :
test('recherche un produit via l API Juice Shop', async () => {
const result = await getJson(buildProductSearchPath('Apple'))
const productNames = productNamesFromSearchResponse(result.body)
expect(result.status).toBe(200)
expect(productNames.some((name) => name.includes('Apple'))).toBe(true)
})
Lancez les tests :
Partie 5 : Tests E2E avec Playwright¶
Exercice 5 : Configurer Playwright¶
Créez playwright.config.js :
module.exports = {
testDir: './tests/e2e',
use: {
baseURL: process.env.JUICE_SHOP_URL || 'http://localhost:3000',
trace: 'retain-on-failure',
screenshot: 'only-on-failure'
},
reporter: [
['list'],
['junit', { outputFile: process.env.PLAYWRIGHT_JUNIT_OUTPUT_NAME || 'test-results/e2e.xml' }],
['html', { open: 'never' }]
]
}
Le format JUnit XML n'est pas réservé à Java
C'est un format de rapport de tests que GitLab sait afficher dans ses merge requests et dans l'onglet des tests de pipeline
Exercice 6 : Écrire un parcours utilisateur¶
Créez tests/e2e/search.spec.js
Travail demandé :
- Ouvrez la page d'accueil de Juice Shop
- Fermez les éventuels dialogues d'accueil ou de cookies
- Recherchez
Apple - Vérifiez qu'un produit contenant
Appleest visible - Recherchez un terme improbable
- Vérifiez que le résultat précédent n'est plus affiché ou que l'application indique l'absence de résultat
Exemple de point de départ :
const { expect, test } = require('@playwright/test')
async function closeOptionalDialogs(page) {
await page.getByRole('button', { name: /dismiss/i }).click().catch(() => {})
await page.getByRole('button', { name: /close/i }).click().catch(() => {})
}
test('un utilisateur peut rechercher un produit', async ({ page }) => {
await page.goto('/')
await closeOptionalDialogs(page)
await page.getByRole('button', { name: /search/i }).click()
await page.getByPlaceholder(/search/i).fill('Apple')
await page.keyboard.press('Enter')
await expect(page.getByText(/Apple/i)).toBeVisible()
})
Selon la version de Juice Shop, certains libellés ou sélecteurs peuvent varier
Si le test ne trouve pas un élément, inspectez la page avec Playwright Inspector ou les outils développeur du navigateur, puis adaptez le sélecteur
Lancez les tests :
Partie 6 : Automatisation avec GitLab CI¶
Exercice 7 : Créer la pipeline¶
Créez .gitlab-ci.yml à la racine du dépôt
Base de travail :
stages:
- test
- e2e
default:
image: node:22-bookworm
cache:
key:
files:
- package-lock.json
paths:
- .npm/
before_script:
- npm ci --cache .npm --prefer-offline
unit_tests:
stage: test
variables:
JEST_JUNIT_OUTPUT_DIR: reports/unit
JEST_JUNIT_OUTPUT_NAME: unit.xml
script:
- mkdir -p reports/unit
- npm run test:unit
artifacts:
when: always
paths:
- reports/unit/
reports:
junit: reports/unit/unit.xml
integration_tests:
stage: test
services:
- name: bkimminich/juice-shop:latest
alias: juice-shop
variables:
JUICE_SHOP_URL: http://juice-shop:3000
JEST_JUNIT_OUTPUT_DIR: reports/integration
JEST_JUNIT_OUTPUT_NAME: integration.xml
script:
- mkdir -p reports/integration
- npx wait-on "$JUICE_SHOP_URL"
- npm run test:integration
artifacts:
when: always
paths:
- reports/integration/
reports:
junit: reports/integration/integration.xml
e2e_tests:
stage: e2e
image: mcr.microsoft.com/playwright:v1.58.2-noble
services:
- name: bkimminich/juice-shop:latest
alias: juice-shop
variables:
JUICE_SHOP_URL: http://juice-shop:3000
PLAYWRIGHT_JUNIT_OUTPUT_NAME: test-results/e2e.xml
before_script:
- npm ci --cache .npm --prefer-offline
script:
- npx wait-on "$JUICE_SHOP_URL"
- npm run test:e2e
artifacts:
when: always
paths:
- playwright-report/
- test-results/
reports:
junit: test-results/e2e.xml
needs:
- unit_tests
- integration_tests
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Travail demandé :
- Commitez le fichier
.gitlab-ci.yml - Poussez votre branche sur GitLab
- Ouvrez
Build > Pipelines - Vérifiez que
unit_testsetintegration_testss'exécutent dans le stagetest - Vérifiez que
e2e_testss'exécute ensuite - Ouvrez les artifacts de chaque job
- Ouvrez les rapports de tests affichés par GitLab
Partie 7 : Faire échouer puis diagnostiquer¶
Exercice 8 : Lire un échec de test¶
Modifiez volontairement un test unitaire pour attendre une mauvaise valeur
Exemple :
Travail demandé :
- Poussez la modification sur GitLab
- Identifiez le job en échec
- Lisez les logs du job
- Ouvrez le rapport de tests GitLab
- Corrigez le test
- Poussez la correction
- Vérifiez que la pipeline redevient verte
Exercice 9 : Diagnostiquer un échec E2E¶
Modifiez volontairement le sélecteur de recherche dans le test Playwright
Travail demandé :
- Poussez la modification
- Attendez l'échec du job
e2e_tests - Téléchargez les artifacts
playwright-reportettest-results - Ouvrez le rapport HTML Playwright
- Identifiez la ligne exacte du test en échec
- Corrigez le sélecteur
- Vérifiez que la pipeline repasse au vert
Partie 8 - Bonus¶
Bonus 1 : Ajouter la couverture¶
Ajoutez la couverture sur les tests unitaires
Objectifs :
- générer un dossier
coverage/ - produire un rapport Cobertura
- publier la couverture dans GitLab avec
coverage_report - définir un seuil minimal avec Jest
Bonus 2 : Ajouter un test d'authentification¶
Écrivez un test d'intégration qui vérifie le comportement de la route de connexion
Objectifs :
- tester une connexion invalide
- vérifier le statut HTTP attendu
- vérifier que le message d'erreur est exploitable
- ne pas tester une faille de sécurité dans ce TP
Bonus 3 : Planifier les tests E2E¶
Ajoutez une règle GitLab CI pour exécuter une suite E2E plus complète uniquement la nuit
Expliquez le compromis entre rapidité du feedback et niveau de confiance
Critères de réussite¶
- Juice Shop est utilisé comme application cible
- les tests unitaires ne démarrent pas Juice Shop
- les tests d'intégration appellent Juice Shop par HTTP
- les tests E2E valident un parcours utilisateur dans le navigateur
- la pipeline GitLab CI sépare les tests rapides et les tests E2E
- les rapports de tests au format JUnit XML sont publiés dans GitLab
- les artifacts utiles sont conservés pour diagnostiquer les échecs