Un locator, c'est la façon dont vous indiquez à Playwright quel élément de la page utiliser. Mal choisi, vos tests cassent à chaque fois qu'un développeur change une classe CSS. Bien choisi, ils survivent aux refactorings, aux redesigns et aux migrations de framework. La différence tient au bon type de locator.

Pourquoi les locators comptent

La cause la plus courante des tests flaky n'est pas le timing. Ce sont les locators fragiles. Un test qui trouve un bouton par sa classe CSS (button.btn-primary-v2) casse dès qu'un développeur renomme cette classe. Un test qui trouve un bouton par son rôle et son label (getByRole('button', { name: 'Valider' })) survit à tout changement CSS. Il cherche le bouton comme un utilisateur : par ce qu'il dit et ce qu'il fait.

Playwright propose six types de locators, du plus au moins recommandé.

getByRole : commencez par là

getByRole trouve les éléments par leur rôle ARIA et leur nom accessible. C'est le locator que Playwright recommande par défaut, pour une bonne raison : c'est ainsi que les utilisateurs et les lecteurs d'écran identifient les éléments. Si le nom accessible change, le test doit casser, parce que c'est un vrai changement UX.

// Boutons
await page.getByRole('button', { name: 'Valider' }).click();
await page.getByRole('button', { name: 'Se connecter' }).click();
await page.getByRole('button', { name: 'Supprimer l\'élément' }).click();

// Liens
await page.getByRole('link', { name: 'Accueil' }).click();
await page.getByRole('link', { name: 'Voir les détails' }).click();

// Titres
await expect(page.getByRole('heading', { name: 'My Travel Items' })).toBeVisible();
await expect(page.getByRole('heading', { level: 1 })).toContainText('Dashboard');

// Éléments de formulaire
await page.getByRole('textbox', { name: 'Rechercher' }).fill('Tokyo');
await page.getByRole('checkbox', { name: 'Se souvenir de moi' }).check();
await page.getByRole('combobox', { name: 'Statut' }).selectOption('active');

// Tableaux
const rows = page.getByRole('row');
await expect(rows).toHaveCount(6); // 1 en-tête + 5 lignes de données

Rôles ARIA courants : button, link, heading, textbox, checkbox, radio, combobox (dropdown), listitem, row, cell, dialog, table, navigation, main.

L'option name correspond au nom accessible de l'élément. Pour les boutons et liens, c'est le texte visible. Pour les inputs, c'est le label associé. Insensible à la casse par défaut.

// exact: false (par défaut) — correspondance partielle
page.getByRole('button', { name: 'val' }) // correspond à "Valider", "Valeur"

// exact: true — correspondance complète uniquement
page.getByRole('button', { name: 'Valider', exact: true }) // seulement "Valider"

getByLabel : pour les champs de formulaire

getByLabel trouve un input, select ou textarea par son élément associé. C'est le bon locator pour les formulaires de connexion, les barres de recherche et tout champ qui a un label visible.

await page.getByLabel('Nom d\'utilisateur').fill('admin@becomeqa.com');
await page.getByLabel('Mot de passe').fill('testpass123');
await page.getByLabel('Adresse e-mail').fill('user@example.com');
await page.getByLabel('Date de naissance').fill('1990-01-15');

Il fonctionne que le label utilise for/id, aria-label ou enveloppe l'input. Vous n'avez pas besoin de savoir comment le label est implémenté. Playwright s'en charge.

<!-- Ces trois variantes sont toutes trouvées par getByLabel('Email') -->
<label for="email">Email</label><input id="email" />
<label><span>Email</span><input /></label>
<input aria-label="Email" />

getByPlaceholder : quand il n'y a pas de label

Certains inputs ont du texte de placeholder plutôt qu'un label visible. getByPlaceholder gère ce cas.

await page.getByPlaceholder('Rechercher des destinations...').fill('Tokyo');
await page.getByPlaceholder('Entrez votre email').fill('test@example.com');

Préférez getByLabel quand un label existe. getByPlaceholder est pour les inputs qui n'ont que du texte de placeholder.

getByText : pour les éléments non interactifs

getByText trouve les éléments par leur contenu textuel visible. Utilisez-le pour vérifier que du texte est présent sur la page, pas pour les éléments que vous voulez cliquer (utilisez getByRole pour ça).

// Vérifier que le texte est présent
await expect(page.getByText('My Travel Items')).toBeVisible();
await expect(page.getByText('Identifiants invalides')).toBeVisible();

// Correspondance exacte vs partielle
page.getByText('Travel')          // correspond à "My Travel Items", "Travel guide"
page.getByText('Travel', { exact: true })  // seulement "Travel" exact

// Limité à un type d'élément spécifique
page.locator('p').getByText('Erreur survenue')  // seulement les éléments <p>

getByText trouve tous les éléments contenant ce texte, y compris les conteneurs parents. Si "Valider" apparaît dans un paragraphe et un bouton, getByText('Valider') retourne plusieurs éléments. Pour les éléments interactifs, utilisez getByRole à la place.

getByTestId : le contrat explicite

getByTestId trouve les éléments par leur attribut data-testid (configurable). C'est le locator à utiliser quand les développeurs ajoutent explicitement des crochets de test dans le DOM.

<button data-testid="submit-payment">Payer maintenant</button>
<div data-testid="success-message">Paiement effectué</div>

await page.getByTestId('submit-payment').click();
await expect(page.getByTestId('success-message')).toBeVisible();

L'avantage : les attributs data-testid sont invisibles pour les utilisateurs et n'ont pas de signification fonctionnelle, donc les développeurs ne les renommeront pas accidentellement. En revanche, quelqu'un doit les ajouter au code. Pour les applications que vous contrôlez, c'est parfait.

Si votre équipe n'utilise pas encore data-testid, proposez-le. Demandez aux développeurs d'ajouter des attributs data-testid aux éléments interactifs clés. Cela prend quelques minutes par composant et rend les locators trivialement stables.

getByAltText et getByTitle

Deux locators moins courants mais occasionnellement utiles :

// Images avec texte alt
await page.getByAltText('Photo de profil utilisateur').click();
await expect(page.getByAltText('Logo de l\'entreprise')).toBeVisible();

// Éléments avec attributs title
await page.getByTitle('Fermer la boîte de dialogue').click();

Vous les utiliserez rarement. La plupart des éléments interactifs sont accessibles via getByRole.

Enchaîner les locators

Quand vous devez affiner la recherche à un élément spécifique dans un conteneur, enchaînez les locators :

// Trouver une ligne contenant "Tokyo", puis cliquer sur son bouton Supprimer
const tokyoRow = page.getByRole('row').filter({ hasText: 'Tokyo' });
await tokyoRow.getByRole('button', { name: 'Supprimer' }).click();

// Trouver une section de formulaire, puis interagir avec son input
const addressSection = page.locator('.address-section');
await addressSection.getByLabel('Ville').fill('Paris');

filter({ hasText: '...' }) restreint un locator aux éléments contenant du texte spécifique. Combiné avec nth() pour la sélection par index :

// Première ligne du tableau (index 0)
const firstRow = page.getByRole('row').nth(1); // nth(0) est l'en-tête, nth(1) est la première ligne de données
await firstRow.getByRole('button', { name: 'Modifier' }).click();

Ce qu'il faut éviter

Sélecteurs CSS. Fragiles, spécifiques à l'implémentation, cassent lors des refactorings :

// Mauvais
page.locator('.btn.btn-primary')
page.locator('#submit-button')
page.locator('div > ul > li:nth-child(3)')

XPath. Verbeux, fragile, difficile à lire :

// Mauvais
page.locator('//button[@class="btn btn-primary" and text()="Valider"]')

Sélecteurs de texte sans contexte. Ambigus quand le texte apparaît à plusieurs endroits :

// Risqué — et si "Modifier" apparaît à plusieurs endroits ?
page.getByText('Modifier')
// Mieux — limité à la ligne qui vous intéresse
page.getByRole('row').filter({ hasText: 'Tokyo' }).getByRole('button', { name: 'Modifier' })

Exercice pratique sur lab.becomeqa.com

Ouvrez https://lab.becomeqa.com et essayez d'écrire des locators pour :

1. Le bouton Login dans la navigation

2. Les inputs Nom d'utilisateur et Mot de passe dans la modale de connexion

3. Le bouton Valider dans la modale de connexion

4. La ligne du tableau contenant une destination spécifique après connexion

5. Le bouton Ajouter un élément sur le tableau de bord

import { test, expect } from '@playwright/test';

test('pratique des locators', async ({ page }) => {
  await page.goto('https://lab.becomeqa.com');

  // 1. Bouton de connexion dans la navigation
  await page.getByRole('button', { name: 'Login' }).click();

  // 2 & 3. Formulaire de connexion
  await page.getByLabel('Username').fill('admin@becomeqa.com');
  await page.getByLabel('Password').fill('testpass123');
  await page.getByRole('button', { name: 'Submit' }).click();

  // 4. Ligne spécifique dans le tableau
  const parisRow = page.getByRole('row').filter({ hasText: 'Paris' });
  await expect(parisRow).toBeVisible();

  // 5. Bouton d'ajout d'élément
  await expect(page.getByRole('button', { name: 'Add Item' })).toBeVisible();
});

FAQ

Quand utiliser locator() directement plutôt que les méthodes getBy* ?

Quand vous avez besoin de sélecteurs CSS ou d'attributs que les méthodes getBy* ne couvrent pas. Par exemple, page.locator('[data-status="active"]') trouve tous les éléments avec une valeur d'attribut de données spécifique. Utilisez-le en dernier recours, pas en premier choix.

Peut-on combiner plusieurs locators ?

Oui. locator.and(otherLocator) trouve les éléments correspondant aux deux :

// Un bouton qui est à la fois visible et a le texte "Valider"
page.getByRole('button').and(page.getByText('Valider'))

Et si deux éléments correspondent à mon locator ?

Playwright lève une strict mode violation si un locator correspond à plus d'un élément lors d'une interaction. Corrigez en rendant le locator plus spécifique : ajoutez un filter, utilisez nth(), ou limitez-le à un conteneur parent.

Comment déboguer un locator qui ne trouve rien ?

Utilisez le mode highlight dans Playwright Inspector : PWDEBUG=1 npx playwright test. Ou appelez await locator.highlight() dans votre test pour marquer visuellement l'élément trouvé lors d'une exécution en mode visible.

→ See also: Assertions dans Playwright: Le Guide Complet | Débuter avec Playwright: Vos Premiers Tests en 30 Minutes | Playwright Codegen: Enregistrez des Tests Sans Écrire de Code | Comment Lire les Messages d'Erreur de Playwright