Quand on fait des tests avec un outil type Playwright, Cypress, Testing Library ou même le classique Selenium, on voit parfois passer le terme "locator" parfois on voit aussi "selector", parfois on voit "query", en tout cas ça reste souvent assez succin en explications, et on passe vite à la pratique. Repartons de la définition et voyons comment être pertinent niveau locator !
Note : Une fois n'est pas coutume, je vais vous proposer des exemples écrits avec Playwright, mais modulo une histoire de syntaxe, ce que je vais dire s'applique à n'importe quel framework de test à priori.
C'est quoi un locator ?
Avant de parler locator, parlons DOM ou Document Object Model. Le DOM c'est la représentation structurée sous forme d'arbre de l'ensemble du contenu d'une page web.
Comme une page web HTML est un document type XML, on peut le représenter sous la forme d'un arbre, chaque nœud ayant un parent et ayant entre 0 et N enfants. Je pourrais encore donner beaucoup de détails, car il y a pas mal d'élément associé au DOM, mais ce n'est pas le sujet du jour.
Venons-en au locator : c'est un pointeur vers un / des éléments du DOM. Ce pointeur est construit à partir d'une requête qu'on fait sur le DOM avec une méthode disponible : attributs, class, id, arbre d'accessibilité, data test id, CSS, XPath et surement d'autres.

Les différents types de locator
Il existe un certain nombre de manière de construire un locator. Voici dans l'ordre à préférer les différents types :
getByRole()
Ce locator permet de facilement cibler un élément en fonction de son rôle dans l'arbre d'accessibilité. Comme l'arbre du DOM c'est un arbre qui représente le document. Mais à la différence du DOM qui est une représentation purement technique, l'arbre d'accessibilité est utilisé par les lecteurs d'écrans pour permettre aux personnes aveugles ou malvoyantes de naviguer sur le web.

<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>
const title = page.getByRole('heading', { name: 'Sign up' });
const subscribeCheckbox = page.getByRole('checkbox', { name: 'Subscribe' });
const submitButton = page.getByRole('button', { name: /submit/i });
Cette méthode a l'avantage de clairement mettre en avant le type d'élément (heading, checkbox, button, etc.), d'être très efficace, d'être bien intégré avec Playwright et d'être plutôt simple à utiliser.
getByText()
Ici on ne va cibler un élément via son contenu. C'est plutôt clair à l'usage car ça permet de directement voir sur la page ce qu'on cherche.
<div>Hello <span>world</span></div>
<div>Hello</div>
const spanWithWorld = page.getByText('world');
const divWithHelloWorld = page.getByText('Hello world');
const divWithOnlyHello = page.getByText('Hello', { exact: true });
getByLabel()
Particulièrement pratique pour les formulaires, on peut cibler n'importe quel élément correctement défini via son label.
<input aria-label="Username">
<label for="password-input">Password:</label>
<input id="password-input" />
<label>
Confirmation Password:
<input />
</label>
const usernameField = page.getByLabel('Username');
const passwordAndConfirmationFields = page.getByLabel('Password');
const passwordField = page.getByLabel('Password', { exact: true });
getByPlaceholder()
Le placeholder est un élément qui souvent utilisé pour aider les utilisateurs à remplir les champs. Cibler via le placeholder est plutôt pratique dans certains cas quand on ne peut pas mettre de label en place.
<input type="email" placeholder="name@example.com" />
const emailField = page.getByPlaceholder('name@example.com');
const emailField = page.getByPlaceholder('name@example.com', { exact: true });
getByAltText()
Cibler une image n'est jamais pratique, sauf via son texte alternatif.
<img alt='Playwright logo'>
const img = page.getByAltText('Playwright');
const img = page.getByAltText('Playwright logo', { exact: true });
getByTitle()
L'attribut title
est souvent oublié mais c'est pourtant une bonne façon d'afficher de l'aide aux utilisateurs. Utiliser cette aide pour cibler un élément peut aider.
<span title="Issues count">25 issues</span>
const span = page.getByTitle('Issues');
const span = page.getByTitle('Issues count', { exact: true });
getByTestId()
L'attribut data-testid
est un attribut qui a été popularisé par Testing Library. Ce n'est pas le seul framework qui a poussé ce genre d'attribut, j'ai par exemple travaillé avec Selenium et des attributs e2e-id
(que je ne recommande pas, car cet attribut n'est pas valide selon le standard HTML), je peux aussi citer data-cy
du framework Cypress.
On notera que Playwright a repris l'attribut poussé par Testing Library qui sert essentiellement dans les tests unitaires / tests de composants pour éviter de multiplier les data attributes de tests.
<button data-testid="directions">Itinéraire</button>
const button = page.getByTestId('directions');
locator()
Ici on peut faire un peu ce qu'on veut, on peut écrire une requête CSS ou XPath.
Je peux vous faire principalement trois recommandations :
- ne l'utilisez qu'en dernier recours car ce n'est pas le plus pratique à maintenir à long terme ;
- favorisez le CSS plutôt que XPath, le langage XPath étant vite peu clair ;
- même si c'est optionnel, pensez à mettre
css=
ouxpath=
devant la requête pour accélérer son traitement (contre un effort faible) ;
<button id="id-du-bouton">Hello</button>
const button = page.locator('button');
const button = page.locator('#id-du-bouton'); // sélection par id
const button = page.locator('css=button');
const button = page.locator('//button');
const button = page.locator('xpath=//button');
Cibler comme un utilisateur
Si dans la partie précédente je vous listais les locators dans l'ordre qui pour moi est l'ordre dans lequel il faut favoriser l'utilisation. Je ne vous ai pas expliqué pourquoi.
Les premiers locators permettent de cibler des éléments comme le ferait un utilisateur. En effet, projetez-vous quand vous êtes utilisateur d'un site web, vous remplissez un formulaire puis vous le validez. Est-ce que vous allez chercher dans le DOM des éléments qui portent des attributs data-testid
ou vous allez parcourir les champs du formulaire en vous basant sur le nom visible du champ puis allez cliquer sur un bouton avec "Envoyer" comme texte ?
J'espère que c'est plutôt le second cas sinon soit vous être bizarre soit vous êtres contraint d'utiliser des formulaires horribles 🙈
Avoir des locators qui simulent de manière plus réaliste le comportement utilisateur permet de plus facilement mettre en avant des erreurs qui pourraient empêcher un utilisateur d'utiliser correctement votre application.
Si vous vous concentrez sur les locators accessibilités (getByRole()
mais aussi les autres getByXXX()
à l'exception de getByTestId()
), vous allez gagner un effet secondaire : améliorer l'accessibilité de votre application, et garantir que cette accessibilité se maintient au cours du temps !
Note : l'accessibilité est quelque chose de complexe, qu'il faut travailler sur beaucoup d'aspects et utiliser des locators accessibilités ne suffit pas à avoir une bonne accessibilité, c'est simplement un élément qui peut aider à aller vers une meilleure accessibilité.
Point à ne pas négliger : vos tests seront beaucoup plus faciles à maintenir, et beaucoup plus stable si vous travaillez avec des locators qui imitent les utilisateurs, plutôt que des locators plus techniques.
Stabilité du contenu et internationalisation !
On m'a déjà rétorqué que cibler comme un utilisateur et donc en se basant sur des éléments textuels visible à l'écran pose des questions sur la stabilité des contenus et au niveau de l'internationalisation.
Pour ce qui est de la stabilité du contenu, pour moi c'est un faux problème. C'est presque une excuse pour au choix mal faire ou ne pas faire de test. Si votre contenu est très mouvant, c'est au choix : trop tôt pour écrire des tests de type E2E/IHM soit vous ne cibler pas le bon élément. Sinon c'est quoi pour vous un contenu trop mouvant ? On peut toujours mettre à jour le contenu au fur et à mesure, réserver des données de tests qui sont stables ou mettre des mocks qui ne vont pas bouger au fil du temps. Aujourd'hui il existe beaucoup de techniques / outils pour permettre de tester du contenu mouvant sans qu'il bouge réellement. Si je devais n'en citer qu'un, ce serait Wiremock qui va vous permettre de simuler une API facilement.
Pour ce qui est l'internationalisation : est-ce que vous avez vraiment besoin de tester toutes les langues ? En général, on a quelques rares spécificités par langue, sinon on a des fonctionnalités uniformes sur toutes les langues, donc autant tester tout dans une seule langue (français ou anglais) avec laquelle on est à l'aise et avec juste quelques cas dans d'autres langues pour les rares spécificités.
Si vraiment vous avez besoin de tester toutes les langues, pensez à l'option de partager les fichiers de traduction entre l'application et les tests. Ça vous donnera la possibilité d'écrire des tests réalistes sans pour autant vous poser trop de questions sur le contenu internationalisé.
Conclusion
Je ne sais pas à quel point cet article peut aider des gens, mais j'ai l'impression que c'est quelque chose qui est souvent mal compris, je l'ai particulièrement vu au fil de mes missions et aussi quand j'ai donné des formations (en particulier récemment avec Playwright).
De mon expérience, je peux vraiment dire que les locators accessibilités permettent de vraiment gagner en efficacité, en qualité des tests, en qualité d'application.
Source :
Crédit photo : Générée via Mistral AI avec le prompt suivant :
Créez une illustration colorée et chaleureuse du personnage de Père Castor, un castor sage et bienveillant, assis dans une bibliothèque remplie de livres pour enfants. Père Castor porte des lunettes et un gilet, et il tient un livre ouvert sur ses genoux. Autour de lui, des enfants de différentes origines sont assis en cercle, écoutant attentivement une histoire. La scène doit être accueillante et éducative, avec des étagères remplies de livres illustrés et des jouets éducatifs dispersés sur le sol. L'ambiance doit être douce et magique, évoquant la joie de la lecture et de l'apprentissage.