Pourquoi un bug Cursor n'est pas un bug ordinaire

Une personne qui écrit la même modification porte avec elle un contexte que l'éditeur ne voit jamais. Elle se souvient que le panneau de réglages lit un cache qu'il faut invalider, que le bouton d'envoi reste désactivé tant qu'aucun fichier n'est sélectionné, qu'un endpoint précis renvoie un tableau vide plutôt qu'un 404 lorsque rien ne correspond. Cursor, lui, travaille à partir des fichiers que vous avez ouverts et du texte que vous avez tapé. Tout ce qui se trouve en dehors de cette fenêtre relève de la supposition.

Le mode de défaillance n'est donc pas une erreur de syntaxe. C'est une modification localement correcte et globalement fausse. La fonction fait exactement ce qu'elle annonce. Mais elle est appelée au mauvais moment, ou elle laisse derrière elle un morceau d'état, ou elle table sur une structure que l'API a cessé de renvoyer il y a deux sprints.

Les tests unitaires ne détectent rien de tout cela, parce qu'ils reposent sur les mêmes hypothèses que le code. Si l'agent a écrit les deux, ils s'accordent entre eux et se trompent de concert sur votre produit.

D'où viennent réellement les bugs Cursor

Trois endroits en concentrent l'essentiel.

L'état de l'interface

  • Un formulaire est soumis mais la liste derrière lui ne se rafraîchit pas.

  • Une modale se ferme en laissant un blocage du défilement sur le body.

  • Un bouton reste actif alors qu'une requête est déjà en cours : un double clic crée deux enregistrements.

Les flux asynchrones

  • L'écran s'affiche avant l'arrivée des données et ne se redessine jamais.

  • Une tâche de fond est lancée mais rien ne l'attend : l'étape suivante lit des valeurs périmées.

  • Un chemin d'erreur se résout silencieusement et l'utilisateur voit un message de succès pour une opération qui a échoué.

Les frontières d'intégration

  • Le payload correspond à la définition de type, mais pas à ce que le service accepte réellement.

  • L'authentification est supposée présente parce qu'elle l'était dans la session que l'agent a vue.

  • La pagination, les états vides et les limites de débit sont gérés dans le système de types, et nulle part ailleurs.

Leur point commun : on ne peut les voir qu'en exécutant l'application et en s'en servant. Lire le diff n'en révélera aucun, et une suite de tests qui n'ouvre jamais de navigateur non plus.

Comment détecter les bugs Cursor avant le merge

La vérification doit porter sur l'application déployée, pilotée comme une personne la piloterait, et le résultat doit revenir sous une forme exploitable par l'agent. Sinon, vous avez trouvé le bug, mais c'est toujours vous qui le corrigez.

Trois conditions doivent être réunies. Aucune n'est difficile ; c'est en en omettant une que la boucle se met à fuir.

L'agent doit savoir vérifier

L'installation ajoute un skill de vérification directement dans l'agent de codage, pour qu'il sache créer, exécuter et trier les tests au lieu de deviner à partir d'un README. Elle écrit un fichier d'instructions là où votre éditeur en cherche déjà un, si bien que Cursor le récupère depuis .cursor/rules/ de la même manière que Claude Code lit son propre répertoire de skills. Huit éditeurs sont pris en charge, ce qui compte dans une équipe où tout le monde n'utilise pas le même.

Terminal

npm install -g @testsprite/testsprite-cli
testsprite setup

Vous pouvez faire la même chose depuis le tableau de bord TestSprite si vous préférez ne rien installer en local. Tout le reste de ce que permet la CLI se trouve dans le dépôt de la CLI.

La vérification doit viser l'application déployée, pas un mock

Dirigez une exécution vers l'environnement où la modification est en ligne. L'agent ouvre l'application, déroule le comportement comme un utilisateur et rapporte ce qui s'est réellement passé, au lieu d'indiquer si une assertion dans un sandbox a été satisfaite. Les mocks ne peuvent pas produire ce signal, et c'est bien pour cela qu'une suite unitaire au vert et une fonctionnalité cassée cohabitent si facilement.

L'échec doit revenir sous une forme exploitable par l'agent

C'est ce point qui décide si la boucle se referme. Un échec qui arrive sous forme de capture d'écran dans un tableau de bord oblige un humain à le lire, à l'interpréter et à rédiger un prompt. Un échec qui arrive sous la forme d'un ensemble unique et cohérent — ce qui a été tenté, ce qu'a fait l'application, et où les deux ont divergé — est quelque chose que l'agent peut reprendre et traiter directement. L'itération suivante part des preuves, et non de votre description des preuves.

Faire en sorte que cela se déclenche sans que personne ait à y penser

Une vérification que vous lancez à la main est une vérification que vous sautez le jour où vous êtes pressé, c'est-à-dire le jour où vous en avez besoin. Il y a deux façons de l'automatiser, et celle qui vous convient dépend de qui possède votre pipeline.

  • La GitHub App est un webhook que vous configurez depuis le tableau de bord TestSprite. Elle écoute l'événement de déploiement que votre pipeline produit déjà, si bien que rien ne change dans votre dépôt.

  • GitHub Actions place l'étape à l'intérieur de votre propre workflow, que vous configurez depuis le terminal.

Le modèle mérite d'être compris, même si vous ne touchez jamais à la configuration. L'intégration ne construit ni ne déploie votre application, et elle n'ajoute ni ne modifie de fichiers de workflow. Elle écoute l'événement de déploiement que votre pipeline produit déjà et traite « le nouveau build est en ligne à cette URL » comme le signal de départ d'une exécution. Les résultats reviennent là où le travail se passe : en commentaire sur la pull request ou en check sur le commit.

Deux décisions déterminent son utilité, et toutes deux relèvent du jugement plutôt que de la configuration.

  • Quel moment compte comme « prêt ». Choisissez l'événement émis une fois le déploiement en ligne, pas celui qui part au démarrage du build. Un événement qui arrive trop tôt dirige l'exécution vers une URL qui n'est pas encore accessible, et tous les tests échouent pour une raison qui n'a rien à voir avec votre code. C'est de loin la façon la plus courante dont cette mise en place échoue.

  • Si la vérification est indicative ou bloquante. Un commentaire sur la pull request informe. Un check obligatoire bloque le merge. Commencez en mode indicatif pendant une semaine pour voir ce qu'il attrape, puis décidez. La rendre bloquante dès le premier jour, avant que quiconque lui fasse confiance, c'est le meilleur moyen de faire désactiver une vérification utile.

Une remarque pratique si vos previews vivent sur une plateforme comme Vercel ou Netlify : chaque hébergeur nomme les URL de preview à sa façon. L'intégration prend donc un motif plutôt qu'une adresse fixe, et y insère la branche ou la pull request à chaque exécution.

Comment cela s'articule avec vos tests existants

Il s'agit d'une vérification comportementale sur le produit en fonctionnement : elle complète vos tests unitaires locaux rapides plutôt qu'elle ne les remplace. Gardez les tests unitaires pour la logique, et laissez celle-ci couvrir ce qu'ils ne peuvent structurellement pas voir : si la fonctionnalité marche quand un vrai utilisateur s'en sert.

Un mot sur le schéma plus général

Rien de tout cela n'est propre à Cursor. Le même écart apparaît avec tout assistant qui écrit du code plus vite qu'une personne ne peut le lire, et c'est pourquoi l'étape de vérification mérite d'être construite une fois puis réutilisée d'un éditeur à l'autre. Sur un classement ouvert où des agents construisaient la même application, le modèle le moins cher du lot a livré l'application la plus correcte lorsque cette boucle de vérification était en place, pour la moitié du coût du concurrent le plus onéreux. La leçon n'était pas qu'un modèle est meilleur qu'un autre. C'était qu'un modèle doté d'un signal de retour qui fonctionne bat un modèle plus puissant qui en est dépourvu.

Le rôle de TestSprite dans la boucle Cursor

TestSprite occupe la moitié « vérification ». Cursor écrit la modification ; TestSprite ouvre l'application déployée, déroule le comportement comme le ferait un utilisateur et rapporte ce qui s'est réellement passé. Aucun des deux ne cherche à faire le travail de l'autre, et c'est tout l'intérêt de cette séparation : l'auteur d'une modification est mal placé pour la valider.

Il en découle trois choses pour une équipe qui livre du code écrit avec Cursor. La couverture ne dépend plus de quelqu'un qui trouve le temps d'écrire des tests, puisque les cas sont générés à partir de votre produit et affinés en langage courant. Les refontes cosmétiques ne font plus passer la suite au rouge, puisqu'une étape est une intention plutôt qu'un chemin dans le DOM. Et un échec revient sous forme d'un seul ensemble exploitable par l'agent, si bien que l'itération suivante part des preuves plutôt que de votre description de celles-ci.

Ce que vous pouvez attendre dès le premier jour, ce sont les parcours que vous seriez gêné de casser, exécutés sur chaque pull request, avec un résultat qui nomme la divergence. Ce qu'il ne fait pas, c'est relire votre code ou remplacer des tests unitaires locaux rapides : il fonctionne en complément des deux.

Pourquoi les tests écrits par Cursor passent-ils alors que la fonctionnalité est cassée ?

Parce que les tests et le code viennent des mêmes hypothèses. Si l'agent a cru qu'un endpoint renvoyait un tableau vide et a écrit aussi bien le handler que le test autour de cette croyance, les deux s'accordent. Seule l'exécution de la vraie application contre le vrai service permet de trancher.

Faut-il une équipe QA pour mettre cela en place ?

Non. L'installation tient en une commande CLI et l'ajout de la GitHub App. C'est pensé pour les équipes où les développeurs sont les seuls à regarder un jour un résultat de test.

A-t-il besoin d'accéder à mon code source ?

La vérification s'exécute sur votre application déployée, via son interface. La permission d'écriture GitHub sert à publier les résultats sur les pull requests et les commits, pas à pousser du code ni à modifier des fichiers de workflow.

Que deviennent les tests quand l'interface change volontairement ?

Un test lié à un parcours métier plutôt qu'à un élément précis survit à une refonte qui ne change pas le parcours. Quand le parcours lui-même change, il s'agit d'un nouveau comportement : il faut un cas nouveau ou mis à jour, que l'agent peut générer à partir de la modification.

Puis-je lancer cela sur une branche sans affecter la suite principale ?

Les tests appartiennent à l'application et non à une branche git : il n'existe donc qu'une seule suite de référence. Choisissez le déclencheur pull request si vous voulez des vérifications par branche, et le déclencheur push si vous voulez qu'un unique environnement partagé soit vérifié après chaque merge.

En résumé

Arrêtez de lire le diff. Lancez l'application.

Les bugs Cursor se cachent dans l'état, le timing et les frontières d'intégration, précisément là où une lecture statique et un test mocké ne peuvent pas aller. Mettez la vérification entre les mains de l'agent, dirigez-la vers le build déployé, et laissez la pull request vous dire si la modification fonctionne avant que quiconque ne la merge.