Aller au contenu

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 .robot et .py → Robot Framework / pytest dans perfshop-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 :

  1. Scripts UI → Forgejo — par HTTP REST (API /api/v1) en utilisant le token stocké dans /token/forgejo_token (volume partagé avec le seed Forgejo).
  2. Scripts UI → Test Runner — par docker exec via le socket Docker, pour lancer les commandes robot ou python -m pytest.
  3. 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 = 3 par défaut) pour éviter de saturer perfshop-test-runner
  • Un identifiant unique par run (runId basé 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)