Lorsque j'ai été onboardé sur le projet, la culture du test n'était pas encore présente. J'ai construit le framework de zéro et instauré les bonnes pratiques : conventions de nommage, isolation des données, couverture par type de comportement, intégration dans la CI.
Ce case study raconte comment j'ai abordé le projet : les décisions que j'ai prises, pourquoi, et ce que ça a donné.
Le produit
S37 Studio est une agence digitale AI-native. Le problème de toutes les agences, c'est que le scaling est limité par le temps des personnes qui travaillent là. S37 a construit S37 Portal pour changer ça : une plateforme interne qui centralise toutes les données de l'agence et fait tourner des agents IA sur les missions.
Concrètement, Portal remplace un tas d'outils dispersés (CRM, gestion de projet, docs clients, facturation) par une seule base. Companies, contacts, deals, missions, tâches, documents, factures : tout au même endroit. Les clients ont accès à leur espace pour suivre leurs missions et leurs livrables.
S37 a travaillé avec des boîtes comme Swan, HappyPal ou Flex AI. Ce niveau de clientèle impose un niveau d'exigence sur la fiabilité du système. Un bug sur la gestion des proposals ou des missions, c'est pas anodin.
Ce qui change par rapport à un CRM classique, c'est que les agents IA peuvent exécuter des missions entières. Un agent reçoit tout le contexte d'un coup (historique de la company, tâches en cours, documents, outils disponibles), fait le travail, et l'équipe valide. Ça peut être une campagne outbound, un composant à implémenter, un rapport à produire. L'idée c'est que l'équipe pilote et les agents exécutent.
C'est ce système que j'ai eu à tester.
Lire la spec avant de coder
La première chose que j'ai faite, c'est lire la spec OpenAPI de bout en bout avant d'écrire quoi que ce soit. Modèle de données, relations entre ressources, codes de statut attendus, règles de validation. C'est là que j'ai commencé à voir où les risques étaient, ce qui méritait d'être testé en priorité, et comment organiser le framework avant de toucher au code.
Mais lire la spec ne suffisait pas. Pour bien tester un produit, il faut comprendre comment il est utilisé. Je me suis construit des scénarios de bout en bout : un lead contacte l'agence, une company est créée, un deal est ouvert, une proposal est envoyée, le client l'accepte, une mission démarre. Comprendre ces enchaînements m'a permis d'identifier les ressources critiques et les endroits où un bug aurait le plus d'impact.
Un repo séparé
J'ai créé un repo à part, s37-tests, plutôt que d'intégrer les tests dans le repo principal de Portal. La raison principale : Portal est en TypeScript, et mélanger Pytest dans la même codebase aurait compliqué la gestion des dépendances et de la CI sans vraiment apporter quelque chose.
Avec un repo séparé, le framework de test a son propre cycle de vie. Si quelque chose casse côté Portal, les tests continuent de tourner de leur côté. Et ça force aussi une bonne discipline : les tests passent uniquement par l'API publique, pas par le code source de l'application. On teste ce que l'API expose, pas comment elle est construite en interne.
Structure de la codebase
Le repo est découpé en deux parties. tests/ pour les tests API, avec un dossier par ressource et un fichier par type de comportement. e2e/ pour les tests Playwright sur les interfaces publiques, avec les pages organisées en Page Object Model. Les deux partagent un conftest.py racine pour les fixtures communes :
Le découpage par ressource, c'est surtout pour la lisibilité des rapports. Quand un test échoue, on sait tout de suite dans quel dossier regarder. Et quand une nouvelle ressource est ajoutée à l'API, on ajoute un dossier. C'est simple à suivre.
Le conftest.py gère trois niveaux de fixtures. La première, portal_url, lit l'URL depuis les variables d'environnement et plante proprement si elle est absente :
Ensuite admin_client, qui ouvre une session HTTP avec requests.Session() pour garder le cookie d'auth entre les requêtes, sans avoir à se reconnecter à chaque test :
Et les fixtures de données, qui créent une ressource avant le test et la suppriment après via yield, même si le test échoue. C'est ce qui évite de laisser des données de test traîner en base :
Couverture API
Sur chaque ressource (Companies, Contacts, Deals, Proposals, Missions), je couvre quatre cas : le happy path, les erreurs de validation, les requêtes non authentifiées, et les règles métier. Un test, un comportement. C'est ce qui rend les rapports lisibles quand quelque chose casse :
Documenter les anomalies
En écrivant les tests, j'ai trouvé des endroits où l'API se comportait différemment de ce que la spec décrivait. Des cas où la ressource était bien créée, mais le statut retourné ne correspondait pas au contrat OpenAPI. Fonctionnellement ça marchait, mais le contrat n'était pas respecté.
Plutôt que de supprimer ces tests ou de les ignorer, j'ai utilisé @pytest.mark.xfail(strict=True) pour les documenter. Le test tourne dans la CI, il est censé échouer pour l'instant, et si l'API est corrigée un jour il passera en XPASS, ce qui remonte une alerte :
J'ai remonté chaque anomalie au CTO avec le contexte et la référence à la spec. Elles sont toutes tracées dans GitHub Issues.
Pipeline CI/CD
Une fois la couverture API en place, j'ai branché GitHub Actions pour déclencher les tests automatiquement sur chaque PR. Le rapport HTML part en artifact à chaque run, et si un test échoue le merge est bloqué :
Ce que ça change concrètement : le CTO n'a plus besoin de lancer les tests en local pour savoir si quelque chose est cassé. C'est visible directement sur la PR.
Tests E2E avec Playwright
Après les tests API, j'ai couvert les interfaces publiques avec Playwright : le site S37 Studio et les pages de proposal client. J'ai organisé les tests avec le Page Object Model, où chaque page est une classe qui expose ses éléments. Si le DOM change, on modifie la classe, pas chaque test individuellement :
AI-Driven development
J'ai aussi construit un Cursor Skill generate-tests pour accélérer la génération de tests à partir de la spec OpenAPI. L'idée : on lui donne un endpoint, il génère la structure de tests correspondante en respectant les conventions du framework. C'est un fichier SKILL.md qui donne les instructions à l'agent :
Sur une API avec une vingtaine d'endpoints, ça fait gagner du temps sur les tests de base et ça laisse plus de place pour les cas qui demandent vraiment de la réflexion : les règles métier, les cas limites, les interactions entre ressources.
Bilan
Ce projet m'a permis de construire un framework de test complet de zéro, sur un système qui était déjà en production. Tests API, E2E, CI/CD. Chaque décision a été prise pour que ça reste maintenable dans le temps, pas juste pour que ça tourne aujourd'hui.
Ce que j'en retiens : quand la qualité est intégrée tôt dans le processus, elle ne ralentit pas l'équipe. Elle lui donne la confiance pour avancer sans se retourner à chaque deploy.