Scripts UI¶
perfshop-scripts-ui est l'atelier web pour les scripts de test pédagogiques. Il combine un éditeur de fichiers Git branché sur Forgejo, un lanceur de tests qui exécute les scripts dans perfshop-test-runner via docker exec, et une interface de consultation des runs historiques avec leurs artefacts.
Son rôle diffère de perfshop-jmeter-ui :
- JMeter UI pilote des tirs de charge (
.jmx→ JMeter dans un autre container). - Scripts UI pilote des tests fonctionnels (scripts
.robotet.py→ Robot Framework / pytest dansperfshop-test-runner), et gère en plus l'édition directe des fichiers versionnés dans Forgejo.
L'architecture est très proche de JMeter UI (même socle Node/Express, même pattern « bind socket Docker pour exec »), avec deux particularités en plus : l'intégration Forgejo via API REST et la persistance structurée des runs sous forme d'artefacts.
Source de vérité
Cette page est tirée du bloc perfshop-scripts-ui des fichiers compose, du Dockerfile du dossier scripts-ui/, et des 9 modules du dossier scripts-ui/src/ (server.js, config.js, i18n.js, validation.js, docker.js, security.js, auth.js, forgejo.js, runs.js).
Architecture globale¶
flowchart TB
subgraph browser["Navigateur"]
UI["UI Scripts<br/>http://localhost:3008"]
end
subgraph sui["perfshop-scripts-ui<br/>(Node 20 + Express, port 3008)"]
direction TB
SRV["server.js<br/>(orchestration)"]
AU["auth.js<br/>(session 8h + CSRF + rate limit)"]
FG["forgejo.js<br/>(API Forgejo, tree, CRUD)"]
RN["runs.js<br/>(lancement + artefacts)"]
DK["docker.js<br/>(socket Docker)"]
VA["validation.js<br/>(sanitisation chemins)"]
SRV --> AU
SRV --> FG
SRV --> RN
RN --> DK
FG --> VA
end
subgraph fg["perfshop-forgejo<br/>(Git auto-hébergé, port 3009)"]
REPO["Dépôt perfshop-tests<br/>(tests Robot Framework<br/>+ pytest)"]
end
subgraph tr["perfshop-test-runner<br/>(Python 3.11 + RF 7.0 + pytest)"]
SH["entrypoint SSH"]
RUNNER["exec robot|pytest"]
end
subgraph sel["perfshop-selenium"]
CHROME["standalone-chrome<br/>:4444"]
end
subgraph vols["Volumes Docker"]
FTD[/"forgejo-token-data:/token<br/>(RO - token Forgejo)"/]
LOGS[/"./test-runner/logs → /rf-logs<br/>(runs persistants partagés)"/]
end
DOCK[/"/var/run/docker.sock"/]
UI -->|HTTPS| SRV
FG -->|API REST v1| fg
DK -->|monte| DOCK
DOCK -.->|docker exec perfshop-test-runner<br/>robot ... / python -m pytest ...| RUNNER
RUNNER -->|WebDriver| CHROME
RUNNER -->|écrit logs + artefacts| LOGS
SRV -->|lit /token/forgejo_token| FTD
SRV -->|lit runs passés| LOGS
Trois chaînes de communication clés :
- Scripts UI → Forgejo — par HTTP REST (
API /api/v1) en utilisant le token stocké dans/token/forgejo_token(volume partagé avec le seed Forgejo). - Scripts UI → Test Runner — par
docker execvia le socket Docker, pour lancer les commandesrobotoupython -m pytest. - Test Runner → Scripts UI — via le bind mount partagé
./test-runner/logs → /rf-logs, qui permet à l'UI de lire les logs et artefacts générés par les runs.
Configuration Docker Compose¶
| Clé | Valeur |
|---|---|
| Build | ./scripts-ui (Dockerfile local) |
container_name |
perfshop-scripts-ui |
| Port exposé | 3008 (hôte) → 3008 (container), variable SCRIPTS_UI_PORT |
| Réseau | perfshop-network |
Bind mounts¶
| Bind mount | Point de montage | Mode | Rôle |
|---|---|---|---|
/var/run/docker.sock |
/var/run/docker.sock |
RW | Socket Docker pour docker exec perfshop-test-runner |
forgejo-token-data (volume nommé) |
/token |
RO | Token Forgejo généré par perfshop-forgejo-seed, partagé via volume nommé |
./test-runner/logs |
/rf-logs |
RW | Volume partagé avec perfshop-test-runner — lecture des logs + écriture des runs persistants |
Le partage via volume nommé pour le token
Le token Forgejo est généré une seule fois par perfshop-forgejo-seed et écrit dans /token/forgejo_token à l'intérieur d'un volume nommé Docker (forgejo-token-data). Ce volume est ensuite monté en lecture seule dans perfshop-scripts-ui. Ce pattern évite de passer le token en variable d'environnement (visible dans docker inspect) et garantit qu'il soit généré fraîchement à chaque nouveau déploiement.
Variables d'environnement¶
| Variable | Défaut | Rôle |
|---|---|---|
PORT |
3008 |
Port HTTP du serveur Express |
PUBLIC_SCRIPTS_URL |
http://localhost:3008 |
URL publique exposée |
PUBLIC_FORGEJO_URL |
http://localhost:3009 |
URL Forgejo exposée dans l'UI (liens de navigation) |
FORGEJO_INTERNAL_URL |
http://perfshop-forgejo:3000 |
URL interne pour les appels API côté serveur |
FORGEJO_CI_USER |
perfshop-ci |
Nom du compte CI Forgejo utilisé pour les commits automatiques |
FORGEJO_REPO |
perfshop-tests |
Nom du dépôt cible |
FORGEJO_TOKEN_FILE |
/token/forgejo_token |
Chemin du fichier contenant le token |
PERFSHOP_API_INTERNAL |
http://perfshop-app:8080 |
URL interne du backend PerfShop (utilisée par certains tests) |
TEST_RUNNER_CONTAINER |
perfshop-test-runner |
Nom du container cible pour docker exec |
PERFSHOP_FRONTEND_URL / _INTERNAL |
— | URLs publiques et internes du frontend (passées aux scripts de test comme variables Robot) |
SELENIUM_REMOTE_URL |
http://perfshop-selenium:4444/wd/hub |
URL du hub Selenium à utiliser dans les tests |
RUNS_DIR |
/rf-logs/runs |
Dossier de persistance des métadonnées de runs |
MAX_CONCURRENT_RUNS |
3 |
Nombre maximum de runs simultanés |
TREE_MAX_DEPTH |
4 |
Profondeur maximale de l'arborescence Forgejo affichée (sécurité contre les dépôts malformés) |
SESSION_SECRET |
perfshop-dev-secret (dev) |
Secret signature cookies — fail-fast en NODE_ENV=production si laissé à la valeur par défaut |
SESSION_COOKIE_SECURE / _SAME_SITE |
false / lax |
Politique cookie (HTTP / HTTPS) |
PERFSHOP_LANG |
fr |
Langue de l'UI |
Les 9 modules du dossier src/¶
| Module | Lignes | Rôle |
|---|---|---|
server.js |
81 | Orchestration Express, session, middlewares, montage des routes, injection window.__CONFIG__ |
config.js |
49 | Lecture des variables d'environnement et export de PUBLIC_CONFIG |
i18n.js |
51 | Chargement des bundles de traduction depuis public/i18n/{fr,en}.json |
validation.js |
33 | Sanitisation des chemins (protection contre ../ et caractères dangereux) |
docker.js |
123 | Client bas niveau pour l'API Docker via socket Unix |
security.js |
64 | Middlewares csrfProtection et rateLimit |
auth.js |
68 | Login/logout/status + middleware requireAuth |
forgejo.js |
312 | Wrapper complet de l'API Forgejo REST (tree, CRUD fichiers, commits, sync) |
runs.js |
230 | Lancement des runs, gestion des artefacts, limite de concurrence |
Fonction utilitaire clé — validation.js¶
Toute la sécurité des opérations fichier repose sur une seule fonction : la validation stricte des chemins relatifs fournis par le client. Aucune opération d'écriture n'est effectuée sans passer par ce filtre, qui rejette :
- Les chemins absolus commençant par
/ - Les séquences
..ou../ - Les caractères de contrôle et les caractères spéciaux du shell
- Les chemins plus profonds que
TREE_MAX_DEPTH(4 niveaux)
Cette fonction est appelée par forgejo.js avant tout appel à l'API Forgejo, et par runs.js avant toute commande docker exec.
Intégration Forgejo — le module forgejo.js¶
Le module forgejo.js est un wrapper complet de l'API Forgejo v1 qui expose vers le navigateur un ensemble de routes REST calquées sur les opérations Git usuelles. En interne, il traduit chaque appel en une requête HTTP Bearer <token> vers FORGEJO_INTERNAL_URL/api/v1.
Routes exposées¶
| Méthode | Route | Opération Forgejo |
|---|---|---|
GET |
/api/tree |
GET /repos/{owner}/{repo}/git/trees/main?recursive=true puis reconstruction hiérarchique |
GET |
/api/file?path=... |
GET /repos/{owner}/{repo}/contents/{path} (décodage base64) |
GET |
/api/download?path=... |
Téléchargement du contenu brut |
PUT |
/api/file |
PUT /repos/{owner}/{repo}/contents/{path} (création ou mise à jour) |
POST |
/api/upload |
Upload multipart (via multer) puis PUT Forgejo |
DELETE |
/api/file?path=... |
DELETE /repos/{owner}/{repo}/contents/{path} |
POST |
/api/mkdir |
Création d'un fichier .gitkeep dans un nouveau dossier |
POST |
/api/move |
Rename ou déplacement (= delete + create côté Forgejo) |
GET |
/api/commits |
GET /repos/{owner}/{repo}/commits |
POST |
/api/sync |
git pull côté perfshop-test-runner via docker exec |
Authentification HTTP vers Forgejo¶
Le token est lu une seule fois au démarrage depuis /token/forgejo_token. Si le fichier est absent ou vide, le module log un warning mais continue (les routes Forgejo retourneront alors 500 à chaque appel — défaillance silencieuse volontaire pour ne pas empêcher l'UI de démarrer dans un état dégradé).
sequenceDiagram
autonumber
participant UI as Navigateur
participant SRV as scripts-ui (Node)
participant FGS as perfshop-forgejo-seed
participant FTD as volume forgejo-token-data
participant FG as perfshop-forgejo
Note over FGS,FTD: Au premier démarrage (one-shot)
FGS->>FG: POST /users/admin<br/>(compte CI)
FGS->>FG: POST /users/{ci}/tokens<br/>(génération token API)
FG-->>FGS: token
FGS->>FTD: écrit /token/forgejo_token
Note over SRV,FTD: Au démarrage de scripts-ui
SRV->>FTD: fs.readFileSync('/token/forgejo_token')
FTD-->>SRV: token (en mémoire, variable module)
Note over UI,FG: Opération normale
UI->>SRV: GET /api/tree
SRV->>FG: GET /api/v1/repos/perfshop-ci/perfshop-tests/git/trees/main<br/>Authorization: Bearer <token>
FG-->>SRV: arbre JSON
SRV-->>UI: arborescence reformatée
Exécution des runs — le module runs.js¶
Le module runs.js implémente un mini-moteur d'exécution avec :
- Une limite de concurrence (
MAX_CONCURRENT_RUNS = 3par défaut) pour éviter de saturerperfshop-test-runner - Un identifiant unique par run (
runIdbasé sur timestamp + suffixe aléatoire) - Une persistance structurée dans
/rf-logs/runs/<runId>/avec trois artefacts :metadata.json,stdout.log,stderr.log - Un streaming de sortie live pendant l'exécution, accessible via le polling de
/api/run/:runId
Routes exposées¶
| Méthode | Route | Rôle |
|---|---|---|
POST |
/api/run |
Lance un script (Robot Framework ou pytest) dans perfshop-test-runner |
GET |
/api/run/:runId |
État en direct du run courant (stdout streamed, status, durée écoulée) |
GET |
/api/runs |
Liste de tous les runs passés (scan de RUNS_DIR) |
GET |
/api/runs/:id |
Métadonnées complètes d'un run passé |
GET |
/api/runs/:id/artifact?name=... |
Téléchargement d'un artefact (log.html, report.html, output.xml...) |
GET |
/api/runner-status |
Public — healthcheck du container perfshop-test-runner (UP/DOWN) |
Flux de lancement d'un run Robot Framework¶
sequenceDiagram
autonumber
actor E as Étudiant
participant UI as Navigateur
participant SRV as scripts-ui
participant DK as socket Docker
participant TR as perfshop-test-runner
participant SEL as perfshop-selenium
participant FS as /rf-logs/runs/{runId}
E->>UI: Sélectionne tests/smoke/login.robot<br/>clique « Lancer »
UI->>SRV: POST /api/run {path: "tests/smoke/login.robot", type: "robot"}
SRV->>SRV: Validation du chemin (validation.js)
SRV->>SRV: runId = Date.now() + random
SRV->>FS: mkdir /rf-logs/runs/{runId}
SRV->>FS: write metadata.json (user, start, path, type)
SRV->>DK: docker exec perfshop-test-runner<br/>robot --outputdir /rf-logs/runs/{runId}<br/>--variable SELENIUM_URL:http://perfshop-selenium:4444/wd/hub<br/>/scripts/tests/smoke/login.robot
DK->>TR: exec
TR->>SEL: WebDriver session
SEL-->>TR: Chrome session ID
TR->>TR: Exécute les keywords Robot
TR->>FS: écrit log.html, report.html, output.xml au fil de l'eau
loop Polling toutes les 2s
UI->>SRV: GET /api/run/{runId}
SRV->>FS: lit stdout.log (tail)
SRV-->>UI: {status: "running", stdout: "...", elapsed: 12.5}
end
TR->>FS: génère les artefacts finaux
TR-->>DK: exit code 0 ou 1
SRV->>FS: met à jour metadata.json (end, duration, exitCode, status)
UI->>SRV: GET /api/run/{runId}
SRV-->>UI: {status: "passed", artifacts: [log.html, report.html, output.xml]}
E->>UI: Clique sur « report.html »
UI->>SRV: GET /api/runs/{runId}/artifact?name=report.html
SRV->>FS: stream le fichier
SRV-->>UI: HTML rendu inline
Persistance structurée d'un run¶
Chaque run crée un dossier dans RUNS_DIR (par défaut /rf-logs/runs/) :
/rf-logs/runs/
├── 1712582340123-a7f2/
│ ├── metadata.json # user, path, type, start, end, duration, exitCode, status
│ ├── stdout.log # sortie standard capturée
│ ├── stderr.log # sortie erreur capturée
│ ├── log.html # log Robot Framework (si type=robot)
│ ├── report.html # rapport Robot Framework
│ └── output.xml # XML structuré pour agrégation
└── 1712581200456-bc91/
└── ...
Cette persistance est partagée avec perfshop-test-runner (même bind mount ./test-runner/logs), ce qui permet au Test Runner d'écrire directement les artefacts dans la bonne arborescence sans copie intermédiaire.
Sécurité¶
Les quatre couches sont les mêmes que pour jmeter-ui :
| Couche | Détail |
|---|---|
| CSRF | Token signé par session, requis sur POST, PUT, DELETE |
| Rate limit | rateLimit(60_000, 10) sur /api/auth/login |
| Session | express-session 8 h, cookie httpOnly, politique secure / sameSite conditionnelle |
| Auth gate | requireAuth systématique sur toutes les routes sensibles, sauf /api/runner-status (healthcheck public) |
S'y ajoute la sanitisation stricte des chemins par validation.js avant tout appel Forgejo ou docker exec — cette couche est propre à Scripts UI et se justifie par la richesse des opérations fichier exposées.
Limitations connues et pistes¶
- Pas de SSE/WebSocket pour le streaming live — le polling à 2 s a été choisi pour sa simplicité et sa robustesse. Une future itération pourrait passer en SSE pour le streaming de
stdout.log. - Pas d'édition multi-utilisateur collaborative — deux formateurs qui éditent le même fichier en parallèle risquent un conflit Git (résolu par le dernier commit). Acceptable pour l'usage pédagogique ciblé.
- Pas de gestion des branches — l'UI travaille uniquement sur
main. Les branches et les pull requests sont gérés directement dans Forgejo par les formateurs avancés.
Pour aller plus loin¶
- Vue d'ensemble QA
- Forgejo — configuration du service Git auto-hébergé et du seed
- Test Runner — container où les scripts sont réellement exécutés
- Selenium — grille Chrome utilisée par les tests web
- Squash TM — pour la gestion formelle des cas de test (usage complémentaire)