Une convention de nommage data-testid qui scale au-delà de 10 devs

Sans convention, vos data-testid se transforment en musée des styles au bout de six mois. Voici le pattern kebab-case scope-element-role qui survit à la croissance d'équipe, avec des exemples concrets et comment l'imposer sans devenir le flic du DOM.

Une convention de nommage data-testid qui scale au-delà de 10 devs

Vous savez comment l'histoire se termine. Sprint 1, votre équipe est d'accord que data-testid est la voie. Sprint 4, quelqu'un écrit data-testid="submitBtn". Sprint 8, quelqu'un d'autre écrit data-testid="signup_form_submit". Sprint 12, quelqu'un écrit data-testid="btn-1". Sprint 26, vous avez un musée.

Votre suite de tests fonctionne toujours (à peu près). Les refactors sont douloureux (vous ne pouvez jamais grep "tous les boutons submit"). L'onboarding est plus dur (les nouveaux QAs demandent "c'est quoi la convention ?" et vous répondez "euh"). Et le pire : l'équipe est maintenant réticente à ajouter de nouveaux data-testid parce que personne ne sait à quoi ils devraient ressembler.

C'est évitable. Le fix n'est pas une page wiki de 50 pages. C'est une seule règle en trois parties.

La règle

Chaque valeur data-testid suit ce pattern :

data-testid="<scope>-<element>-<role>"

Tout en minuscules, kebab-case (tirets entre les parties). Exemples :

Page / fonctionnalité data-testid
Formulaire d'inscription, input email data-testid="signup-email-input"
Formulaire d'inscription, bouton submit data-testid="signup-submit-button"
Page paramètres, lien suppression compte data-testid="settings-delete-account-link"
Ligne utilisateur dans table admin, bouton edit data-testid="admin-user-edit-button"
Modal pricing, icône close data-testid="pricing-close-button"
Footer, lien politique de confidentialité data-testid="footer-privacy-link"

C'est tout. Trois parties. Minuscules. Kebab-case.

Pourquoi trois parties (ni deux, ni quatre)

J'ai expérimenté avec des conventions à deux parties (<element>-<role>) et à quatre parties (<page>-<section>-<element>-<role>). Les deux échouent.

Deux parties échouent à cause des collisions. Deux pages ont un "bouton submit". Sans scope, les sélecteurs entrent en collision et le QA ne peut pas dire de quel bouton parle le test sans lire le fichier de test. Pire : quand un test casse, le message d'échec est "submit-button non trouvé" et vous devez chercher de quel submit-button il s'agissait.

Quatre parties échouent parce que les devs arrêtent de les adopter. "Page X section Y élément Z role W" c'est trop à taper et trop de décisions pour une PR. Au bout de trois semaines, l'équipe commence à couper les angles (<page>-<element>-<role>, <element>-<role>), et votre "convention" devient une aspiration.

Trois parties, c'est le sweet spot. Assez de spécificité pour désambiguer. Assez court pour qu'aucun dev ne déteste l'écrire.

Ce qu'on met dans <scope>

Le scope, c'est ce qui rend l'élément non ambigu dans votre app. La règle empirique la plus rapide : c'est en général la page ou la fonctionnalité.

Pour des composants profondément imbriqués ou réutilisables, le scope peut aussi être un composant parent :

Le principe : le scope répond à la question "où dans l'app se trouve cet élément, pour que je le trouve sans scroller dans les DevTools".

Ce qu'on met dans <element>

L'élément, c'est ce que l'utilisateur voit / avec quoi il interagit. C'est le nom.

Évitez de dupliquer le rôle ici. N'écrivez pas submit-button-button. L'élément est le nom, le rôle est le type.

Ce qu'on met dans <role>

Le rôle, c'est le type d'élément HTML, généralisé. C'est ce sur quoi l'utilisateur clique / dans quoi il tape.

Pourquoi inclure le rôle ? Deux raisons. La première : la recherchabilité. Vous pouvez grep -button pour trouver tous les boutons. La seconde : votre futur vous qui debug un test appréciera que signup-submit-button soit évidemment un bouton sans avoir à ouvrir les DevTools.

Quand trois parties ne suffisent pas

Parfois trois parties ne suffisent pas. Les listes avec plusieurs items, par exemple. Deux patterns marchent ici.

Pattern A : suffixe dynamique (quand la liste est data-driven)

<button data-testid="user-row-edit-button" data-id="42">Edit</button>

Le data-testid reste générique (match toutes les lignes), et le test désambigue via un attribut séparé ou via un scope parent. C'est l'approche la plus propre quand vous avez beaucoup d'items similaires.

Côté test :

await page.locator('[data-testid="user-row-edit-button"][data-id="42"]').click()
// ou
await page.locator('[data-id="42"]').getByTestId('user-row-edit-button').click()

Pattern B : indexé (quand la liste est fixe)

<button data-testid="onboarding-step-1-next-button">Next</button>
<button data-testid="onboarding-step-2-next-button">Next</button>
<button data-testid="onboarding-step-3-next-button">Next</button>

À utiliser seulement quand la liste est vraiment fixe (un onboarding en 3 étapes, une page paramètres à 4 onglets). Ne l'utilisez pas pour des listes data-driven, parce que les indices deviennent vides de sens quand les données changent.

L'imposer sans devenir le flic du DOM

La convention meurt si son seul champion est une personne qui crie "kebab-case !" dans les reviews de PR. Trois mécanismes d'application légers marchent mieux.

1. Un linter / check CI

Ajoutez une vérification regex dans votre CI qui rejette les PRs introduisant des data-testid qui ne matchent pas le pattern. Des plugins ESLint existent (eslint-plugin-testing-library, règles custom). Pour les frameworks sans bon support de plugin, un simple grep marche :

# Trouve les data-testid qui ne sont pas en kebab-case alphanumérique minuscule
grep -rE 'data-testid="[^"]*[A-Z_][^"]*"' src/ && exit 1 || exit 0

La CI qui échoue est un enseignant beaucoup plus efficace qu'un QA senior qui écrit un commentaire en review.

2. Une doc de convention en 1 page

Pas un wiki de 50 pages. Une page. Titre : "Comment nommer les data-testid". Contenu : la règle de cet article, la table d'exemples, les deux patterns pour les listes. Pinglez-la dans le repo (docs/testids.md) et mettez le lien dans le template de PR.

3. Des exemples dans le codebase

Quand un nouveau dev arrive, il grep le codebase pour data-testid=. Si 90% de ce qu'il voit suit la convention, il la suit. Si 50% la suit et 50% est du chaos, il prend le style le plus proche de ce sur quoi il travaille, et l'entropie gagne.

Ça veut dire que le premier gros effort est de nettoyer les incohérences existantes dans un sprint focalisé. Après ça, la convention s'auto-applique par gravité : le nouveau code ressemble au code environnant.

Un outil qui pousse dans la bonne direction

C'est ici que je place le produit. TestID Hunter enregistre une session QA et, pour chaque élément qui manque un data-testid, en suggère un qui suit déjà cette convention. La suggestion utilise l'URL de la page ou l'ancêtre le plus proche avec un nom reconnaissable comme scope, le label visible le plus proche comme élément, et le tag HTML comme rôle.

Donc un dev qui reçoit un ticket TestID Hunter voit Ajouter data-testid="signup-email-input" à <input>. Il n'a pas besoin de connaître la convention. Il colle, c'est tout. Au bout de trois mois, le codebase est conventionnel par accident.

La conclusion

Les conventions ne meurent pas parce qu'elles sont mauvaises. Elles meurent parce qu'elles ne sont pas appliquées. Choisissez-en une simple, automatisez le check, documentez-la sur une page. Le pattern kebab-case scope-element-role marche pour des équipes de 5 et des équipes de 50. C'est ennuyeux. C'est exactement le but.