test(e2e): parcours borne Playwright (conteneur, stack jetable) #45
9 changed files with 238 additions and 4 deletions
|
|
@ -164,7 +164,10 @@ jobs:
|
|||
echo "JS tests skipped: no package.json + tests/js/ yet"
|
||||
exit 0
|
||||
fi
|
||||
npm ci
|
||||
# Skip le download des browsers Playwright : ce job ne fait que node:test+jsdom.
|
||||
# (@playwright/test est en devDep pour l'E2E, mais ses browsers ne servent
|
||||
# qu'a tests/e2e via le conteneur officiel.)
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm ci
|
||||
npm run test:js
|
||||
|
||||
auto-merge:
|
||||
|
|
|
|||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -75,6 +75,12 @@ node_modules/
|
|||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# === Playwright (E2E) ===
|
||||
playwright-report/
|
||||
test-results/
|
||||
/blob-report/
|
||||
.last-run.json
|
||||
|
||||
# === Docker volumes locaux ===
|
||||
/docker-data/
|
||||
|
||||
|
|
|
|||
64
package-lock.json
generated
64
package-lock.json
generated
|
|
@ -8,6 +8,7 @@
|
|||
"name": "wakdo",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.49.1",
|
||||
"jsdom": "^26.0.0"
|
||||
}
|
||||
},
|
||||
|
|
@ -140,6 +141,22 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.49.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz",
|
||||
"integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.49.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
|
|
@ -216,6 +233,21 @@
|
|||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||
|
|
@ -338,6 +370,38 @@
|
|||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.49.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz",
|
||||
"integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.49.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.49.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz",
|
||||
"integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
"private": true,
|
||||
"description": "Wakdo - tests front borne (kiosk). Back-office PHP teste via PHPUnit (phpunit.phar). NB: pas de \"type\":\"module\" a la racine -> les .js du depot (hooks .claude, _byan, bin) restent CommonJS. L'ESM est declare localement la ou il s'applique (src/public/borne/assets/js, tests/js).",
|
||||
"scripts": {
|
||||
"test:js": "node --test tests/js/"
|
||||
"test:js": "node --test tests/js/",
|
||||
"test:e2e": "playwright test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.49.1",
|
||||
"jsdom": "^26.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
playwright.config.js
Normal file
23
playwright.config.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Configuration Playwright (E2E borne). CommonJS : la racine n'est pas "type:module".
|
||||
// La stack est montee a part (tests/e2e/run.sh) ; BASE_URL pointe vers wakdo-web.
|
||||
const { defineConfig, devices } = require('@playwright/test');
|
||||
|
||||
module.exports = defineConfig({
|
||||
testDir: './tests/e2e',
|
||||
// Headless : tourne sur serveur sans ecran (dans le conteneur Playwright officiel).
|
||||
fullyParallel: false,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
workers: 1,
|
||||
reporter: [['list'], ['html', { open: 'never', outputFolder: 'playwright-report' }]],
|
||||
use: {
|
||||
// run.sh fixe BASE_URL (hostname .test, joignable via --add-host).
|
||||
baseURL: process.env.BASE_URL || 'http://kiosk.wakdo.test',
|
||||
headless: true,
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||
],
|
||||
});
|
||||
|
|
@ -40,13 +40,15 @@ function renderCart() {
|
|||
cartList.innerHTML = '';
|
||||
emptyBlock.hidden = false;
|
||||
summaryBlock.hidden = true;
|
||||
if (payBtn) payBtn.disabled = true;
|
||||
// pay-btn est un <a> : `.disabled` n'existe pas dessus, il faut piloter
|
||||
// aria-disabled (sinon le bouton reste annonce desactive panier rempli).
|
||||
if (payBtn) payBtn.setAttribute('aria-disabled', 'true');
|
||||
return;
|
||||
}
|
||||
|
||||
emptyBlock.hidden = true;
|
||||
summaryBlock.hidden = false;
|
||||
if (payBtn) payBtn.disabled = false;
|
||||
if (payBtn) payBtn.setAttribute('aria-disabled', 'false');
|
||||
|
||||
cartList.innerHTML = '';
|
||||
items.forEach((item, index) => {
|
||||
|
|
|
|||
58
tests/e2e/borne.spec.js
Normal file
58
tests/e2e/borne.spec.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// Parcours E2E borne : welcome -> categories -> produit -> ajout panier -> panier
|
||||
// -> paiement -> confirmation. La stack est montee a part (run.sh) ; le panier vit
|
||||
// dans localStorage (meme origine), donc on peut naviguer par goto sans perdre l'etat.
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
test('parcours borne : de l\'accueil a la confirmation de commande', async ({ page }) => {
|
||||
|
||||
await test.step('accueil -> categories', async () => {
|
||||
await page.goto('/index.html');
|
||||
await expect(page).toHaveTitle(/Bienvenue/);
|
||||
await expect(page.locator('#welcome-heading')).toBeVisible();
|
||||
// CTA "sur place" -> categories.html?mode=sur-place
|
||||
await page.locator('a[href*="categories.html?mode=sur-place"]').click();
|
||||
await expect(page).toHaveURL(/categories\.html/);
|
||||
});
|
||||
|
||||
await test.step('categories -> produits', async () => {
|
||||
await expect(page.locator('h1.categories-main__heading')).toBeVisible();
|
||||
// Categorie 2 = boissons : produits SIMPLES (la categorie 1 = menus, qui rendent
|
||||
// un autre gabarit a slots, sans bouton d'ajout direct).
|
||||
await page.locator('a[href="products.html?category=2"]').click();
|
||||
await expect(page).toHaveURL(/products\.html\?category=2/);
|
||||
});
|
||||
|
||||
await test.step('produits -> fiche produit', async () => {
|
||||
// Cartes rendues par JS depuis le JSON : auto-wait sur la 1re carte.
|
||||
const firstCard = page.locator('#products-grid a.product-card').first();
|
||||
await expect(firstCard).toBeVisible();
|
||||
await firstCard.click();
|
||||
await expect(page).toHaveURL(/product\.html\?id=/);
|
||||
});
|
||||
|
||||
await test.step('ajout au panier', async () => {
|
||||
const addBtn = page.locator('#add-to-cart-btn');
|
||||
await expect(addBtn).toBeVisible();
|
||||
await addBtn.click();
|
||||
// Feedback visuel "Ajoute !" (page-product.js) ; l'ecriture localStorage est synchrone.
|
||||
await expect(addBtn).toHaveText(/Ajoute/);
|
||||
});
|
||||
|
||||
await test.step('panier : recapitulatif', async () => {
|
||||
await page.goto('/cart.html');
|
||||
await expect(page.locator('#cart-summary')).toBeVisible();
|
||||
await expect(page.locator('#cart-list li')).toHaveCount(1);
|
||||
// Total calcule, plus le placeholder "—".
|
||||
await expect(page.locator('#total-ttc')).not.toHaveText('—');
|
||||
await page.locator('#pay-btn').click();
|
||||
await expect(page).toHaveURL(/payment\.html/);
|
||||
});
|
||||
|
||||
await test.step('paiement -> confirmation', async () => {
|
||||
await page.locator('#pay-card').click();
|
||||
await expect(page).toHaveURL(/confirmation\.html/);
|
||||
await expect(page.locator('.confirmation-banner__title')).toHaveText(/Commande confirmee/);
|
||||
// Numero de commande genere (plus le placeholder).
|
||||
await expect(page.locator('#order-number')).not.toHaveText('—');
|
||||
});
|
||||
});
|
||||
17
tests/e2e/docker-compose.e2e.yml
Normal file
17
tests/e2e/docker-compose.e2e.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Override JETABLE pour l'E2E : neutralise les container_name fixes du compose afin de
|
||||
# monter une stack isolee (`-p wakdoe2e`) en parallele d'une stack existante, sans
|
||||
# collision de noms. N'est PAS un compose de deploiement ; sert uniquement a run.sh.
|
||||
services:
|
||||
wakdo-db:
|
||||
container_name: wakdoe2e-db
|
||||
wakdo-migrate:
|
||||
container_name: wakdoe2e-migrate
|
||||
wakdo-app:
|
||||
container_name: wakdoe2e-app
|
||||
wakdo-web:
|
||||
container_name: wakdoe2e-web
|
||||
# Pas de port hote pour l'E2E : Playwright joint wakdo-web par le reseau interne
|
||||
# (--add-host). Evite tout conflit de port sur l'hote.
|
||||
ports: !reset []
|
||||
wakdo-cron:
|
||||
container_name: wakdoe2e-cron
|
||||
59
tests/e2e/run.sh
Executable file
59
tests/e2e/run.sh
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# E2E borne : monte une stack JETABLE isolee, lance Playwright (conteneur officiel,
|
||||
# headless) contre elle, puis demonte tout. Ne touche a aucune stack existante.
|
||||
#
|
||||
# tests/e2e/run.sh
|
||||
#
|
||||
# Pre-requis : Docker. Aucune dependance Node/Playwright sur l'hote (tout en conteneur).
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
PROJECT=wakdoe2e
|
||||
PW_VERSION=1.49.1 # doit matcher devDependencies["@playwright/test"] de package.json
|
||||
NET="${PROJECT}_wakdo_internal"
|
||||
|
||||
ENVFILE="$(mktemp)"
|
||||
cp .env.example "$ENVFILE" # template local-first : marche tel quel (valeurs dev)
|
||||
# Hostnames de TEST en .test (pas .localhost) : Chromium/curl resolvent *.localhost en
|
||||
# dur vers 127.0.0.1 (RFC 6761) et ignorent --add-host. .test n'est pas special -> joignable.
|
||||
perl -pi -e 's/^APP_HOST_KIOSK=.*/APP_HOST_KIOSK=kiosk.wakdo.test/; s/^APP_HOST_ADMIN=.*/APP_HOST_ADMIN=admin.wakdo.test/;' "$ENVFILE"
|
||||
COMPOSE="docker compose -p $PROJECT --env-file $ENVFILE -f docker-compose.yml -f tests/e2e/docker-compose.e2e.yml"
|
||||
|
||||
cleanup() { echo "[e2e] teardown"; $COMPOSE down -v >/dev/null 2>&1 || true; rm -f "$ENVFILE"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "[e2e] build + up stack jetable ($PROJECT)"
|
||||
$COMPOSE up -d --build
|
||||
|
||||
echo "[e2e] attente migrate (completion)"
|
||||
for _ in $(seq 1 40); do
|
||||
st="$(docker inspect -f '{{.State.Status}}' wakdoe2e-migrate 2>/dev/null || echo NA)"
|
||||
code="$(docker inspect -f '{{.State.ExitCode}}' wakdoe2e-migrate 2>/dev/null || echo NA)"
|
||||
[ "$st" = "exited" ] && [ "$code" = "0" ] && { echo "[e2e] migrate OK"; break; }
|
||||
[ "$st" = "exited" ] && [ "$code" != "0" ] && { echo "[e2e] migrate ECHEC ($code)"; docker logs wakdoe2e-migrate; exit 1; }
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "[e2e] attente web healthy"
|
||||
for _ in $(seq 1 40); do
|
||||
[ "$(docker inspect -f '{{.State.Health.Status}}' wakdoe2e-web 2>/dev/null || echo NA)" = "healthy" ] && break
|
||||
sleep 2
|
||||
done
|
||||
|
||||
WEB_IP="$(docker inspect -f "{{(index .NetworkSettings.Networks \"$NET\").IPAddress}}" wakdoe2e-web)"
|
||||
echo "[e2e] web @ $WEB_IP ($NET)"
|
||||
|
||||
echo "[e2e] Playwright (conteneur officiel v$PW_VERSION)"
|
||||
docker run --rm \
|
||||
--network "$NET" \
|
||||
--add-host "kiosk.wakdo.test:$WEB_IP" \
|
||||
--add-host "admin.wakdo.test:$WEB_IP" \
|
||||
-v "$ROOT":/work -w /work \
|
||||
-e BASE_URL="http://kiosk.wakdo.test" \
|
||||
-e CI=1 \
|
||||
"mcr.microsoft.com/playwright:v${PW_VERSION}-jammy" \
|
||||
bash -c "npm install --no-audit --no-fund --silent && npx playwright test"
|
||||
Loading…
Add table
Reference in a new issue