Phase 1 de la refonte #4433. Ce notebook est le poste de pilotage du parcours : il porte la narration de la mission, inventorie et orchestre la suite de tests E2E (Playwright) depuis ses cellules, apprend à interpréter les résultats, et propose des exercices exécutables. Le cadrage complet (fil rouge, carte de mission, capstone, acquis d’apprentissage) est dans 00-Parcours-QA-OWUI.md.
Comment lire ce notebook. Les cellules de code sont pur-Python (bibliothèque standard) : elles s’exécutent sans navigateur ni instance Open WebUI en ligne, et restent reproductibles partout. La seule cellule qui appelle l’outillage Node (npx playwright test --list) le fait hors-ligne — elle liste les tests sans les exécuter — et se rabat proprement sur un inventaire de fichiers si les dépendances ne sont pas installées.
Pas de claim de version. Lister les tests n’est pas les exécuter. La revalidation réelle de la suite contre Open WebUI v0.10.2 est l’objet de la Phase 3 et du projet de certification final (capstone). Tant qu’elle n’est pas faite, ce notebook ne porte aucune affirmation de compatibilité v0.10.2.
1. Le fil rouge — « QA Engineer d’une flotte GenAI multi-tenant »
Tu rejoins l’équipe SRE/QA d’un hébergeur de plateformes d’IA générative. La flotte compte plusieurs instances Open WebUI en production, partagées par des écoles différentes (multi-tenant). Ta mission, au fil des cinq modules : valider de bout en bout cette flotte, depuis la prise en main de l’outillage jusqu’à la certification d’une montée de version critique.
À la fin, tu sais répondre à la seule question qui compte un jour de mise en production : « Peut-on livrer, oui ou non ? » — c’est le projet de certification (capstone) qui clôt le parcours.
Chaque module est une étape de la mission, pas un chapitre de manuel. Ce notebook chapeau te donne la vue d’ensemble ; chaque module a ensuite sa propre matière (tests .spec.ts réels + README).
# Cellule 1 — Mise en place (pur-Python, bibliothèque standard uniquement).from pathlib import Pathimport os, re, json, shutil, subprocess# La série Playwright-OWUI = le dossier qui contient playwright.config.ts.# Le notebook est exécuté depuis son propre dossier (cwd = dossier du notebook).def trouver_racine_serie(depart):for d in [depart, *depart.parents]:if (d /"playwright.config.ts").exists():return dreturn departSERIE = trouver_racine_serie(Path.cwd())MODULES =sorted(p for p in SERIE.iterdir() if p.is_dir() and re.match(r"^\d\d-", p.name))print("Poste de pilotage QA-OWUI")print("Serie detectee :", SERIE.name) # nom seul, jamais de chemin absolu (anti-fuite)print("Modules :", ", ".join(m.name for m in MODULES))
Poste de pilotage QA-OWUI
Serie detectee : Playwright-OWUI
Modules : 01-decouverte, 02-navigation-authentification, 03-chat-streaming, 04-rag-tools-avances, 05-multi-tenant-ci
LECTURE ANCRÉE — Détection de l’environnement de test.
La sortie montre que le script a détecté la série Playwright-OWUI dans l’arborescence courante, et a identifié les 5 modules de test (01-decouverte, 02-navigation-authentification, 03-chat-streaming, 04-rag-tools-avances, 05-multi-tenant-ci). Ceci confirme que le notebook est correctement positionné dans le dossier de la série.
2. Carte de la mission
Cinq étapes, de l’interface visible vers l’industrialisation API-first — exactement le chemin d’un ingénieur QA qui passe du test exploratoire manuel à une suite qui tourne sans navigateur en intégration continue.
Module
Étape de la mission
Focus Playwright
01 — Découverte
Prends en main la plateforme et ton outillage
sélecteurs, auto-wait, screenshots
02 — Navigation & Auth
Sécurise ton accès et explore l’admin
storageState, RBAC, multilingue
03 — Chat & Streaming
Valide le cœur métier : le chat IA en streaming
non-déterminisme, polling, skip gracieux
04 — RAG, outils & canaux
Certifie les fonctions avancées
flux complexes, tests conditionnels
05 — Multi-tenant & CI/CD
Certifie l’isolation et industrialise en CI
API-first, isolation, GitHub Actions
La cellule suivante interroge l’outillage pour dresser l’inventaire réel des tests, étape par étape.
# Cellule 2 — Orchestration : inventorier la suite depuis le notebook.# On lance `npx playwright test --list` : cela *liste* les tests collectes par# Playwright SANS les executer (aucune instance Open WebUI requise, hors-ligne).# Si l'outillage Node n'est pas installe, on se rabat sur un inventaire de fichiers.def lister_tests_via_playwright(serie):if shutil.which("npx") isNoneornot (serie /"node_modules").exists():returnNone# dependances absentes -> fallbacktry: proc = subprocess.run("npx playwright test --list", cwd=str(serie), capture_output=True, text=True, timeout=180, shell=True, )exceptExceptionas e:print("Orchestration indisponible :", type(e).__name__)returnNone sortie = (proc.stdout or"") + (proc.stderr or"")# Anti-fuite : neutraliser tout chemin absolu eventuel (racine maison / checkout).for absolu in {str(serie), str(Path.home())}: sortie = sortie.replace(absolu, ".")return sortie if proc.returncode ==0elseNonelisting = lister_tests_via_playwright(SERIE)if listing:print(listing.strip())else:print("Dependances Node absentes — installe-les pour l'inventaire detaille :")print(" npm install && npx playwright install chromium")print("(Le reste du notebook fonctionne sans Node : on se rabat sur les fichiers .spec.ts.)")
Listing tests:
[owui-setup] › fixtures\auth.setup.ts:18:1 › authenticate
[owui] › 01-decouverte\01-decouverte.spec.ts:46:3 › 01 — Decouverte : Premiers pas avec Playwright › connexion reussie — la page principale se charge
[owui] › 01-decouverte\01-decouverte.spec.ts:68:3 › 01 — Decouverte : Premiers pas avec Playwright › le selecteur de modeles liste les modeles disponibles
[owui] › 01-decouverte\01-decouverte.spec.ts:101:3 › 01 — Decouverte : Premiers pas avec Playwright › le menu utilisateur est accessible
[owui] › 01-decouverte\01-decouverte.spec.ts:133:3 › 01 — Decouverte : Premiers pas avec Playwright › capturer un screenshot de la page principale
[owui] › 02-navigation-authentification\02-navigation-auth.spec.ts:34:3 › 02 — Navigation & Authentification › la session authentifiee est valide et persistante
[owui] › 02-navigation-authentification\02-navigation-auth.spec.ts:56:3 › 02 — Navigation & Authentification › acceder au panneau admin — liste des utilisateurs
[owui] › 02-navigation-authentification\02-navigation-auth.spec.ts:77:3 › 02 — Navigation & Authentification › acceder aux reglages admin
[owui] › 02-navigation-authentification\02-navigation-auth.spec.ts:96:3 › 02 — Navigation & Authentification › workspace — lister les bases de connaissances
[owui] › 02-navigation-authentification\02-navigation-auth.spec.ts:112:3 › 02 — Navigation & Authentification › workspace — lister les modeles personnalises
[owui] › 02-navigation-authentification\02-navigation-auth.spec.ts:125:3 › 02 — Navigation & Authentification › workspace — lister les prompts
[owui] › 02-navigation-authentification\02-navigation-auth.spec.ts:139:3 › 02 — Navigation & Authentification › workspace — lister les fonctions
[owui] › 02-navigation-authentification\02-navigation-auth.spec.ts:153:3 › 02 — Navigation & Authentification › acceder a la section Channels
[owui] › 03-chat-streaming\03-chat-streaming.spec.ts:45:3 › 03 — Chat & Streaming LLM › chat avec modele cloud — reponse basique
[owui] › 03-chat-streaming\03-chat-streaming.spec.ts:63:3 › 03 — Chat & Streaming LLM › chat avec modele local (skip si indisponible)
[owui] › 03-chat-streaming\03-chat-streaming.spec.ts:114:3 › 03 — Chat & Streaming LLM › streaming — le message assistant apparait avant la fin
[owui] › 03-chat-streaming\03-chat-streaming.spec.ts:135:3 › 03 — Chat & Streaming LLM › rendu markdown — blocs de code formates
[owui] › 03-chat-streaming\03-chat-streaming.spec.ts:162:3 › 03 — Chat & Streaming LLM › regenerer une reponse
[owui] › 03-chat-streaming\03-chat-streaming.spec.ts:188:3 › 03 — Chat & Streaming LLM › editer un message utilisateur
[owui] › 03-chat-streaming\03-chat-streaming.spec.ts:221:3 › 03 — Chat & Streaming LLM › persona — reponse structuree dans le role configure
[owui] › 04-rag-tools-avances\04-rag-tools.spec.ts:49:3 › 04a — Knowledge Bases (RAG) › attacher une knowledge base via le menu +
[owui] › 04-rag-tools-avances\04-rag-tools.spec.ts:83:3 › 04a — Knowledge Bases (RAG) › chat avec KB attachee — reponse enrichie par les documents
[owui] › 04-rag-tools-avances\04-rag-tools.spec.ts:172:3 › 04b — Outils MCP (Model Context Protocol) › menu outils MCP accessible (si configure)
[owui] › 04-rag-tools-avances\04-rag-tools.spec.ts:198:3 › 04b — Outils MCP (Model Context Protocol) › ouvrir le selecteur d outils MCP
[owui] › 04-rag-tools-avances\04-rag-tools.spec.ts:228:3 › 04b — Outils MCP (Model Context Protocol) › recherche web via outils (si disponible)
[owui] › 04-rag-tools-avances\04-rag-tools.spec.ts:280:3 › 04c — Channels (canaux de discussion) › acceder aux canaux de discussion
[owui] › 04-rag-tools-avances\04-rag-tools.spec.ts:318:3 › 04c — Channels (canaux de discussion) › poster un message dans un canal
[owui] › 05-multi-tenant-ci\05-api-multi-tenant.spec.ts:70:3 › 05a — Tests API (tenant principal) › login via API — token JWT valide
[owui] › 05-multi-tenant-ci\05-api-multi-tenant.spec.ts:88:3 › 05a — Tests API (tenant principal) › lister les modeles disponibles via API
[owui] › 05-multi-tenant-ci\05-api-multi-tenant.spec.ts:104:3 › 05a — Tests API (tenant principal) › lister les knowledge bases via API
[owui] › 05-multi-tenant-ci\05-api-multi-tenant.spec.ts:122:3 › 05a — Tests API (tenant principal) › lister les fonctions installees via API
[owui] › 05-multi-tenant-ci\05-api-multi-tenant.spec.ts:171:3 › 05b — Multi-tenant : Isolation & Partage › les deux tenants sont accessibles
[owui] › 05-multi-tenant-ci\05-api-multi-tenant.spec.ts:187:3 › 05b — Multi-tenant : Isolation & Partage › knowledge bases partagees entre tenants
[owui] › 05-multi-tenant-ci\05-api-multi-tenant.spec.ts:212:3 › 05b — Multi-tenant : Isolation & Partage › modeles custom identiques sur les deux tenants
[owui] › 05-multi-tenant-ci\05-api-multi-tenant.spec.ts:237:3 › 05b — Multi-tenant : Isolation & Partage › isolation des donnees — bases utilisateurs separees
Total: 35 tests in 6 files
LECTURE ANCRÉE — Inventaire des tests Playwright.
La sortie affiche la liste complète des 35 tests collectés par Playwright, dont 34 répartis dans 5 fichiers de spécification (.spec.ts), plus le test d’authentification owui-setup dans fixtures/auth.setup.ts. On voit notamment le test d’authentification (owui-setup) et les tests organisés par module (01 à 05), chacun avec des descriptions précises comme connexion reussie — la page principale se charge ou acceder au panneau admin.
# Cellule 3 — Construire la carte tests <-> etapes de mission.ETAPES = {"01": "Prise en main de l'outillage","02": "Acces securise & admin","03": "Coeur metier : chat streaming","04": "Fonctions avancees (RAG/MCP/canaux)","05": "Multi-tenant & CI/CD (API-first)",}def catalogue_depuis_listing(listing):# Parse la sortie `--list` : compte les tests par module (prefixe NN-). par_module = {}for ligne in listing.splitlines(): m = re.search(r"(\d\d)-[^\\/]*[\\/][^\s]*\.spec\.ts", ligne)if m: par_module[m.group(1)] = par_module.get(m.group(1), 0) +1return par_moduleif listing: counts = catalogue_depuis_listing(listing) source ="npx playwright test --list (tests reellement collectes)"else: counts = {m.name[:2]: len(list(m.glob("*.spec.ts"))) for m in MODULES} source ="inventaire de fichiers .spec.ts (mode hors-ligne)"print("Source :", source, "\n")print(f"{'Module':<8}{'Etape de la mission':<40}{'Tests'}")print("-"*56)total =0for code insorted(ETAPES): n = counts.get(code, 0); total += nprint(f"{code:<8}{ETAPES[code]:<40}{n}")print("-"*56)print(f"{'Total':<48}{total}")setup_n =sum(1for l in (listing or"").splitlines() if"owui-setup"in l)if setup_n:print(f"(+ {setup_n} test d'authentification 'owui-setup', execute en prealable)")
Source : npx playwright test --list (tests reellement collectes)
Module Etape de la mission Tests
--------------------------------------------------------
01 Prise en main de l'outillage 4
02 Acces securise & admin 8
03 Coeur metier : chat streaming 7
04 Fonctions avancees (RAG/MCP/canaux) 7
05 Multi-tenant & CI/CD (API-first) 8
--------------------------------------------------------
Total 34
(+ 1 test d'authentification 'owui-setup', execute en prealable)
LECTURE ANCRÉE — Cartographie tests ↔︎ étapes de mission.
La sortie produit un tableau aligné qui mappe chaque module (01 à 05) à son étape de mission correspondante, avec le nombre de tests associés : 01 (Prise en main de l’outillage) = 4 tests, 02 (Accès sécurisé & admin) = 8 tests, 03 (Cœur métier : chat streaming) = 7 tests, 04 (Fonctions avancées) = 7 tests, 05 (Multi-tenant & CI/CD) = 8 tests. Le total de 34 tests est complété par 1 test d’authentification ‘owui-setup’.
3. Lancer la suite contre une vraie instance
L’inventaire ci-dessus liste les tests. Pour les exécuter contre une instance Open WebUI, il faut :
Configurer l’accès — copier .env.example vers .env et y mettre l’URL + les identifiants de ton instance de cours. Le fichier .env est ignoré par Git : aucun secret n’entre dans le dépôt.
Installer l’outillage — npm install puis npx playwright install chromium.
Exécuter — toute la suite ou un module ciblé :
npx playwright test # toute la suitenpx playwright test --grep'03'# un module (ici 03 — Chat & Streaming)npx playwright test --headed# avec navigateur visible (débogage)npx playwright show-report # rapport HTML interactif
Ce notebook chapeau ne déclenche pas l’exécution réelle : elle requiert une instance accessible et des identifiants, et son résultat dépend de la version déployée. Le vrai run de bout en bout — et son interprétation go/no-go — est le projet de certification final. Ici, on apprend d’abord à lire ce qu’un run produit.
4. Lire et interpréter les résultats — le cœur du métier
Un run de tests ne se résume pas à « vert / rouge ». Le travail de QA, c’est de qualifier chaque résultat :
Succès — le comportement est conforme.
Échec réel — une régression : l’application ne fait plus ce qu’elle devrait.
Skip légitime — un service optionnel n’est pas configuré (ex. modèle local, second tenant). Ce n’est pas une régression : la raison du skip doit être lisible.
Flake — un test instable (réponse LLM lente, surcharge passagère) qui passe au retry. À distinguer d’un échec réel.
La cellule suivante illustre cette qualification sur un exemple de rapport (format simplifié du rapport JSON de Playwright). C’est exactement le raisonnement attendu dans le rapport de certification.
# Cellule 4 — Qualifier un rapport de run (exemple illustratif).# Format simplifie du rapport JSON Playwright (`--reporter=json`), reduit aux# champs utiles pour la decision. AUCUN run reel n'est lance ici : c'est un# echantillon didactique pour apprendre a interpreter.rapport_exemple = [ {"test": "01 > connexion reussie", "status": "passed", "retries": 0}, {"test": "01 > selecteur de modeles", "status": "passed", "retries": 0}, {"test": "03 > chat cloud reponse basique", "status": "passed", "retries": 1}, # passe au 2e essai {"test": "03 > chat modele local", "status": "skipped", "reason": "OWUI_LOCAL_MODEL non defini"}, {"test": "04 > recherche web via outils", "status": "skipped", "reason": "outils MCP non configures"}, {"test": "05 > isolation des donnees", "status": "passed", "retries": 0}, {"test": "02 > acceder au panneau admin", "status": "failed", "reason": "bouton 'menu' introuvable (drift DOM ?)"},]def qualifier(r):if r["status"] =="failed":return"echec reel"if r["status"] =="skipped":return"skip legitime"if r["status"] =="passed"and r.get("retries", 0) >0:return"flake a investiguer"return"succes"resume = {"succes": 0, "flake a investiguer": 0, "skip legitime": 0, "echec reel": 0}for r in rapport_exemple: resume[qualifier(r)] +=1print("Qualification du run (exemple) :")for k, v in resume.items():print(f" {k:<22}{v}")echecs = [r for r in rapport_exemple if qualifier(r) =="echec reel"]skips = [r for r in rapport_exemple if r["status"] =="skipped"]print("\nA investiguer (echecs reels) :")for r in echecs:print(f" x {r['test']} -- {r.get('reason', '?')}")print("\nSkips (a justifier dans le rapport) :")for r in skips:print(f" ~ {r['test']} -- {r.get('reason', '?')}")
Qualification du run (exemple) :
succes 3
flake a investiguer 1
skip legitime 2
echec reel 1
A investiguer (echecs reels) :
x 02 > acceder au panneau admin -- bouton 'menu' introuvable (drift DOM ?)
Skips (a justifier dans le rapport) :
~ 03 > chat modele local -- OWUI_LOCAL_MODEL non defini
~ 04 > recherche web via outils -- outils MCP non configures
LECTURE ANCRÉE — Qualification automatisée d’un rapport de test.
La sortie classe les 7 résultats de test de l’exemple en 4 catégories : 3 succès (tests passés sans retry), 1 flake à investiguer (indicateur d’instabilité), 2 skip légitimes (tests sautés pour des raisons attendues : modèle local non défini, outils MCP non configurés), et 1 échec réel (problème de drift DOM sur le bouton ‘menu’). Les échecs réels et les flakes sont explicitement listés pour investigation.
Exercices
Les exercices ci-dessous sont pur-Python : tu les complètes puis tu réexécutes la cellule. Chaque cellule fournie s’exécute déjà sans erreur (elle renvoie une valeur « à compléter ») — à toi de remplacer le TODO pour obtenir le bon résultat. Les vérifications affichent un diagnostic ; elles ne lèvent pas d’exception.
Exercice 1 — Compter les tests d’un module
Complète tests_du_module(code) pour qu’elle renvoie le nombre de tests du module donné ("01".."05"), en réutilisant l’inventaire counts construit plus haut. Pense au cas d’un module absent.
def tests_du_module(code):# TODO : renvoie le nombre de tests du module `code` a partir de `counts`.# Indice : counts est un dict { "01": n, ... }. Gere le cas absent.return-1# valeur "a completer" (placeholder neutre)for code in ["01", "03", "05"]: n = tests_du_module(code) etat ="a completer"if n <0elsef"{n} tests"print(f"Module {code} : {etat}")
Module 01 : a completer
Module 03 : a completer
Module 05 : a completer
Exercice 2 — Qualifier un résultat de test
Complète classer(status, retries, reason) pour renvoyer l’une des quatre catégories du §4 : "succes", "echec reel", "skip legitime", "flake a investiguer". Inspire-toi de la fonction qualifier ci-dessus, mais à partir des champs bruts.
passed retries=0 -> a determiner
passed retries=2 -> a determiner
skipped retries=0 -> a determiner
failed retries=0 -> a determiner
Exercice 3 — Trancher : go / no-go
Le capstone te demande un verdict. Complète verdict(echecs_reels, skips, total) selon une règle simple et défendable, par exemple : NO-GO s’il y a au moins un échec réel ; GO conditionnel s’il n’y a pas d’échec mais des skips à justifier ; GO sinon. Tu peux raffiner la règle — l’important est de pouvoir l’expliquer.
def verdict(echecs_reels, skips, total):# TODO : renvoie "GO", "GO conditionnel" ou "NO-GO" selon ta regle.return"a trancher"scenarios = [ (0, 0, 34), # tout vert (0, 5, 34), # des skips, pas d'echec (2, 3, 34), # des echecs reels]for e, s, t in scenarios:print(f"echecs={e} skips={s} total={t} -> {verdict(e, s, t)}")
echecs=0 skips=0 total=34 -> a trancher
echecs=0 skips=5 total=34 -> a trancher
echecs=2 skips=3 total=34 -> a trancher
Et après ? — du parcours au capstone
Tu as maintenant la carte de la mission, l’inventaire réel de la suite, et la grille de lecture des résultats. La suite :
Modules 01 → 05 — chaque étape a sa matière : des tests .spec.ts réels (le moteur) et un README qui guide. Ouvre-les dans l’ordre de la mission.
Capstone — le rapport de certification go/no-go d’une montée de version, contre deux tenants. Le gabarit détaillé est dans le cadrage de la mission.
Reproductibilité (C.3). Toutes les cellules de ce notebook se réexécutent à l’identique. La cellule d’orchestration affiche l’inventaire réel si l’outillage Node est installé (npm install), sinon un inventaire de fichiers — dans les deux cas sans erreur et sans instance en ligne. La revalidation contre Open WebUI v0.10.2 reste l’objet de la Phase 3.