Retour au Centre de connaissances
Démarrage

Playwright strict mode violation : corriger les doublons

Corrigez strict mode violation dans Playwright : inspectez les doublons, ciblez le bon composant et vérifiez le résultat avec un cas de test exécutable.

BotBrowser Team

Documentation

Vous voulez la documentation structurée pour Démarrage ?

Cet article fait partie de la bibliothèque éditoriale. Pour les étapes de configuration, la référence et les mises à jour continues, passez directement à la section docs.

Une erreur strict mode violation de Playwright signifie qu'une opération exigeant un seul élément en a trouvé plusieurs. Lorsque le message indique resolved to 2 elements, inspectez les deux correspondances et limitez le localisateur au composant voulu avant de modifier les délais. exact: true élimine les correspondances partielles de nom ; il ne distingue pas deux contrôles portant le même nom. BotBrowser permet de déclarer les paramètres d'environnement du navigateur pour reproduire les différences de mise en page ; il ne détermine pas sur quel contrôle dupliqué un test voulait cliquer.

Résumé

  • Vérifiez le nombre de correspondances du localisateur et tous les nœuds trouvés à l'étape en échec ; une capture peut masquer un doublon hors écran ou caché.
  • Délimitez la recherche par une frontière de l'application, comme un dialogue ou un enregistrement identifié de manière unique, puis utilisez un nom accessible exact ou un identifiant de test stable.
  • Distinguez getByText() de getByRole() : les localisateurs par rôle excluent les éléments cachés par défaut, alors que le texte caché du cas de test correspond toujours.
  • N'utilisez pas une pause plus longue, force: true ou .first() comme réparation générale d'une identité ambiguë. Vérifiez le résultat de l'application après le clic.

Sommaire

Lire l'erreur avant de modifier le localisateur

La règle d'unicité de Playwright s'applique aux opérations qui ciblent un seul élément. Un click() exige une cible ; count() décrit volontairement une collection. Plusieurs correspondances sont valides pour une assertion sur une liste : réduire tous les localisateurs à un élément casserait donc aussi des tests de liste légitimes. Consignez l'opération en échec ainsi que le sélecteur.

ObservationPremière vérificationPiste de correction
resolved to 2 elementsLes deux nœuds correspondants et leurs composants parentsDonner un localisateur unique au composant voulu
Save correspond aussi à Save draftLe mode de correspondance du texte ou du nom accessibleUtiliser exact: true lorsque le nom complet fait partie du contrat
Deux contrôles nommés SaveLe dialogue, le formulaire, la ligne ou l'état de l'applicationAjouter une portée ; le nom exact ne suffit pas à les distinguer
Aucune correspondance à l'étape en échecLa page actuelle, le cadre et la condition de disponibilitéLocaliser le bon document et attendre son propre signal
Une correspondance, mais le clic expireLa visibilité, la stabilité, l'état activé et la réception des événementsExaminer les conditions d'interaction une fois l'identité établie

Les alias de localisateurs du message aident à identifier les candidats ; ils ne demandent pas de coller le sélecteur généré le plus court. Si les navigations pour ordinateur et mobile contiennent chacune le même lien, choisissez le repère de navigation voulu. Si deux lignes de produits contiennent chacune Buy, choisissez la ligne par l'identité de son enregistrement. Le lancement du navigateur et la préparation de l'extraction relèvent de la configuration de scraping avec Playwright ; un navigateur correctement lancé ne peut pas établir l'intention d'un sélecteur.

Conserver le contexte de l'échec

Avant de modifier un sélecteur, conservez le message d'erreur, la révision du test, la version du paquet Playwright, la catégorie de l'URL finale et la chaîne de localisateurs utilisée par cette action. Une chaîne comme page.getByRole('dialog').getByText('Save') contient plus d'informations que son dernier fragment de texte. Copier seulement Save dans un débogueur peut produire un ensemble de correspondances différent et conduire à une correction erronée. Conservez aussi le choix du cadre : un localisateur dans frameLocator() interroge un autre document qu'un localisateur de page.

Utilisez le nombre du message comme l'observation de l'opération en échec. Un count() de diagnostic recueilli quelques secondes plus tard est une nouvelle observation ; si les deux nombres diffèrent, cela révèle un DOM changeant et ne prouve pas que l'erreur était fausse. Capturez la trace et la transition concernée de l'application au lieu de recharger sans cesse jusqu'à ce qu'un seul nœud demeure. Une erreur constante avec deux contrôles permanents exige une correction d'identité ; des doublons intermittents exigent aussi une explication de la transition d'état.

Reproduire deux correspondances sur une page locale

Dans un projet Node vide, installez playwright, installez son Chromium avec npx playwright install chromium, puis enregistrez le code suivant dans strict.mjs. Le cas de test ne contacte aucun site externe. Il intercepte l'erreur d'unicité attendue, puis prouve qu'un clic à portée délimitée a modifié le bon dialogue.

import assert from 'node:assert/strict';
import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const page = await browser.newPage();
  await page.setContent(`
    <button style="display:none">Save</button>
    <div role="dialog" aria-label="Edit profile">
      <button onclick="this.parentElement.dataset.saved='yes'">Save</button>
    </div>
  `);
  const ambiguous = page.getByText('Save', { exact: true });
  assert.equal(await ambiguous.count(), 2);
  await assert.rejects(() => ambiguous.click({ timeout: 1500 }), /strict mode violation/);
  const dialog = page.getByRole('dialog', {
    name: 'Edit profile',
    exact: true,
  });
  const save = dialog.getByRole('button', { name: 'Save', exact: true });
  assert.equal(await save.count(), 1);
  await save.click();
  assert.equal(await dialog.getAttribute('data-saved'), 'yes');
  console.log('textMatches=2 scopedMatches=1 saved=yes');
} finally {
  await browser.close();
}

Exécutez node strict.mjs. La dernière ligne attendue est textMatches=2 scopedMatches=1 saved=yes ; le premier clic est rejeté et n'est pas effectué. Retirez assert.rejects() et attendez directement le clic ambigu pour voir l'erreur strict mode violation non interceptée. Le bouton caché rend volontairement le DOM différent de ce que suggère une capture d'écran.

Le cas de test étendu du dépôt a été exécuté le 2026-11-11 avec Playwright 1.58.0, Chrome standard 157.0.8103.51 et macOS arm64. Sept assertions ont réussi, notamment le filtrage des rôles cachés, les dialogues dupliqués et une union or(). Ces éléments prouvent un test synthétique de localisateurs, pas une exécution de BotBrowser ni le comportement temporel d'une application. Le lancement portable ci-dessus utilise le Chromium installé par Playwright ; l'exécution consignée utilisait explicitement un exécutable de Chrome standard.

Deux correspondances de texte Save provoquent une erreur d'unicité ; la portée unique du dialogue Edit profile donne un bouton Save et un état enregistré vérifié.

Inspecter les candidats et corriger leur portée

Placez le diagnostic juste avant l'opération en échec. Capturez uniquement les attributs nécessaires pour distinguer les candidats, car un export complet du DOM peut inclure des données de compte. Cet instantané constitue une preuve utile à cet instant, sans garantir que le DOM restera inchangé.

const target = page.getByText('Save', { exact: true });
console.log(await target.count());
console.table(
  await target.evaluateAll(nodes =>
    nodes.map(node => ({
      tag: node.tagName,
      id: node.id,
      testId: node.getAttribute('data-testid'),
      dialog: node.closest('[role="dialog"]')?.getAttribute('aria-label'),
      rects: node.getClientRects().length,
    }))
  )
);

rects est un indice de diagnostic, pas un calcul complet de visibilité ou d'accessibilité de Playwright. Un nœud peut avoir une géométrie tout en étant recouvert, et sa visibilité CSS peut différer de sa présence dans l'arbre d'accessibilité. La référence du localisateur par rôle précise includeHidden ; exact contrôle le nom accessible. Dans le cas de test, un localisateur par rôle par défaut trouve un bouton Save, tandis que includeHidden: true en trouve deux. Ne généralisez pas le comportement du localisateur de texte à tous les moteurs de localisation.

Préférez une portée unique de dialogue, de formulaire ou d'enregistrement qui exprime pourquoi la cible est correcte. Un data-testid stable convient lorsque l'application n'expose aucune distinction sémantique utile. getByRole('dialog', { name: 'Edit profile', exact: true }) et son descendant Save doivent tous deux identifier le composant voulu. Si l'application affiche deux dialogues portant le même nom, le descendant à portée délimitée peut encore correspondre deux fois ; corrigez l'état de l'application ou ajoutez une véritable identité plutôt qu'un autre sélecteur de position.

Pour Save et Save draft, la correspondance exacte suffit si le libellé voulu est réellement Save. Pour deux libellés Save identiques, elle ne change rien. Un filtre de visibilité peut être utile lorsque la visibilité est la distinction voulue et que la version fixée de Playwright accepte ce filtre ; il échouera encore si les deux copies sont visibles. Validez cette condition sur les mises en page adaptatives au lieu de considérer visible comme synonyme de correct.

Les enregistrements répétés exigent une identité

Pour un tableau, identifiez l'enregistrement avant son action. Si l'application expose volontairement un identifiant de test pour la ligne, le localisateur peut exprimer ce contrat sans dépendre d'un prix, d'un indice de ligne ou d'une cellule sans rapport contenant fortuitement les mêmes mots. Le bloc suivant illustre un test sur un tableau de commandes que vous contrôlez ; ce n'est pas une autre sortie du cas local Save. Il exige @playwright/test et une application qui affiche la ligne et la confirmation nommées.

const row = page.getByTestId('order-1042');
await expect(row).toHaveCount(1);
const archive = row.getByRole('button', { name: 'Archive', exact: true });
await expect(archive).toHaveCount(1);
await archive.click();
await expect(page.getByTestId('archived-order-1042')).toBeVisible();

L'invariant important est l'identité de la commande, pas le fait que la cible occupe la première ligne. Un tri, un filtre ou un nouvel enregistrement peut déplacer la même commande. Si un tableau virtualisé retire du DOM les lignes hors écran, une ligne absente relève d'un problème distinct de chargement ou de navigation. Ne remplacez pas .getByTestId('order-1042') par .first() pour faire disparaître cette absence ; amenez l'enregistrement voulu dans la collection affichée avec les contrôles habituels de l'application.

En l'absence d'identifiant de test, filter({ has: ... }) peut exprimer une relation avec un descendant identifié de manière unique. Le localisateur interne est évalué relativement à l'élément candidat ; ce n'est pas une requête autonome sur la page cherchant toute occurrence de cet enregistrement. Vérifiez le nombre de lignes filtrées, car un numéro d'enregistrement imprimé dans un pied de page ou une ligne de détail imbriquée peut créer une autre correspondance. Préférez un libellé accessible ou un contrat de test délibéré à une chaîne d'ancêtres fragile liée aux conteneurs CSS actuels.

Distinguer les cadres des doublons dans la page

Un iframe ne fait pas partie du document parent simplement parce qu'il apparaît sur la capture. Un localisateur de page sans correspondance peut donc être correctement délimité, mais viser le mauvais document. Sélectionnez d'abord le cadre voulu, par exemple avec page.frameLocator('iframe[title="Checkout"]'), puis localisez son contrôle. Inversement, deux éléments de cadre correspondant à ce sélecteur constituent une autre ambiguïté ; un nom de bouton unique à l'intérieur de chacun ne rend pas le choix du cadre unique.

Consignez l'identité stable du cadre dans l'application au lieu de prendre le premier cadre renvoyé par une énumération. Les publicités, les outils d'analyse et les aperçus intégrés peuvent modifier leur ordre entre deux exécutions. Pour une intégration de test entre origines que vous contrôlez, utilisez les localisateurs de cadres pris en charge par Playwright ; ne supposez pas qu'une requête DOM dans le parent traverse le document intégré. Cela distingue une cible absente à cause de la frontière documentaire de deux cibles présentes dans le même document.

Vérifier le nom accessible plutôt que le seul texte visible

Le name d'un localisateur par rôle est le nom accessible calculé, qui peut provenir de aria-label, de aria-labelledby ou d'un étiquetage natif plutôt que du texte dessiné dans le bouton. Deux icônes visibles peuvent donc partager le nom Save malgré des symboles différents. À l'inverse, un bouton affichant Save peut avoir un nom accessible comme Save profile. Inspectez les informations d'accessibilité du contrôle dans les outils du navigateur avant de modifier exact ; élargir le motif de nom peut ajouter des candidats au lieu de retrouver celui voulu.

Si un aria-labelledby incorrect rattache plusieurs contrôles au même libellé, corrigez l'étiquetage du composant lorsque vous contrôlez l'application. Un sélecteur réservé au test peut débloquer le diagnostic, mais ne corrige pas les noms confus pour les utilisateurs de technologies d'assistance. Conservez une assertion d'accessibilité distincte si cet étiquetage fait partie du contrat du produit.

Séparer unicité, disponibilité et courses

Playwright vérifie les conditions d'interaction avant de cliquer, notamment l'unicité de la cible, sa visibilité, sa stabilité, la réception des événements et son état activé. L'attente automatique ne détermine pas lequel de deux contrôles valides correspond à une tâche. Des délais plus longs et des pauses fixes ne peuvent exprimer ce choix. force: true assouplit certaines vérifications d'interaction ; il ne résout pas l'ambiguïté sémantique de deux éléments correspondants.

Pendant une transition de composant, l'ancienne et la nouvelle copie peuvent brièvement coexister. Si l'application possède un signal de fin, utilisez une assertion avec réessais automatiques telle que await expect(dialog).toHaveCount(1) de @playwright/test, puis cliquez sur le descendant voulu et vérifiez le résultat métier. Un count() isolé est un instantané et ne réessaie pas comme cette assertion. Même une assertion suivie d'un clic n'est pas un verrou atomique : les localisateurs sont résolus de nouveau, et un nouveau doublon peut donc légitimement faire échouer l'action suivante.

L'analyse des traces Playwright fournit des instantanés d'actions et une chronologie pour déterminer si les doublons étaient permanents ou transitoires. Conservez la trace de l'exécution réellement en échec ; ouvrir le DOM actuel après le problème peut ne plus montrer aucun doublon. La disparition d'un indicateur de chargement n'est utile que si l'application garantit qu'elle signifie que le composant concerné est stabilisé.

a.or(b) combine des alternatives ; ce n'est pas un opérateur de priorité. Si les deux branches correspondent, une action visant un seul élément peut provoquer une erreur d'unicité. Lorsque deux états différents peuvent apparaître, attendez une condition adaptée, inspectez l'état réel de l'application et agissez sur son localisateur spécifique. .first() ou .nth() ne conviennent que lorsque l'ordre constitue lui-même l'exigence testée, comme pour sélectionner le premier résultat classé. Avec des boutons dupliqués sans lien, ils peuvent cliquer silencieusement sur le mauvais contrôle après un changement de mise en page.

Attendre une transition identifiée, puis vérifier son résultat

Supposons que fermer un éditeur déclenche une animation et qu'un autre s'ouvre avant le retrait de l'ancien du DOM. Un localisateur de dialogue nommé peut brièvement correspondre aux deux copies. Le contrat d'application le plus solide est un état explicite, par exemple un seul éditeur actif pour un enregistrement précis. Si le produit garantit plutôt que l'ancien dialogue est retiré avant que le nouveau permette l'interaction, attendez la disparition de l'ancien dialogue propre à cet enregistrement et vérifiez l'identité du nouveau. Attendre la disparition de tous les dialogues serait incorrect si la destination en garde volontairement un ouvert.

Les réessais doivent préserver ces distinctions. Un réessai qui clique deux fois peut soumettre un formulaire deux fois si le premier clic a réussi, mais que l'assertion de résultat a été exécutée trop tôt. Gardez un identifiant d'opération unique ou un marqueur de fin fourni par l'application lorsque le processus l'exige. N'ignorez pas une exception d'unicité pour poursuivre jusqu'à une bannière de succès sans rapport : elle peut appartenir à une action antérieure. Le cas Save vérifie précisément un attribut local data-saved du dialogue délimité pour qu'un nœud sans rapport ne puisse satisfaire sa condition de succès.

Après correction d'un sélecteur, testez un contre-exemple en plus du parcours attendu : affichez un second enregistrement portant le même libellé de bouton, réordonnez les enregistrements et conservez un composant inactif dans le DOM. Le localisateur corrigé doit continuer à choisir l'enregistrement voulu ou échouer clairement si cette identité est dupliquée. Ce sont des vérifications propres à l'application, pas des affirmations qu'un cas synthétique réussi couvre tous les frameworks ou toutes les transitions. Les deux dialogues identiquement nommés du cas étendu démontrent volontairement cette dernière limite d'échec.

Garder les différences d'environnement dans leur couche

Un point de rupture peut produire à la fois le balisage pour ordinateur et mobile, et un changement de police ou de fenêtre d'affichage peut modifier la disposition du dialogue. Reproduisez le problème avec la même version du navigateur, la même fenêtre d'affichage, les mêmes paramètres régionaux et le même état d'application avant d'attribuer les différents candidats au navigateur. Un nouveau profil ne corrige pas un sélecteur qui correspond délibérément à deux lignes de comptes.

BotBrowser permet de déclarer les paramètres d'écran, de police et d'environnement du navigateur décrits dans la référence de configuration des profils. Il ne fournit pas l'identité des composants d'une application, ne supprime pas un doublon caché et ne garantit pas qu'un clic aboutit au résultat métier. Placez cette comparaison d'environnement après le diagnostic du localisateur et de l'état de l'application ; les sept vérifications locales n'établissent aucun résultat d'empreinte ni de service distant.

Questions fréquentes

Est-ce le mode strict de JavaScript ?

Non. Une directive 'use strict' contrôle le comportement du langage JavaScript. L'unicité des localisateurs Playwright contrôle combien d'éléments une opération à cible unique accepte. Désactiver le mode strict de JavaScript ne change pas l'ensemble des correspondances du localisateur.

Le même diagnostic vaut-il pour Playwright en Python ?

Oui ; le choix du localisateur reste le même, bien que Python utilise des noms tels que get_by_role() et exact=True. Préservez la portée du composant lors de la traduction du code. Les exceptions d'éléments de Java Selenium relèvent d'une autre API et ne doivent pas être classées à partir d'un message d'erreur Playwright.

Pourquoi count réussit-il alors que click échoue ?

count() décrit une collection et accepte plusieurs résultats. Un clic exige un seul élément voulu. Un nombre de deux constitue donc une observation réussie de l'ambiguïté et ne prouve pas que l'action suivante sera acceptée.

Sources

Conclusion pratique

Face à une erreur strict mode violation de Playwright, conservez le localisateur en échec, inspectez toutes ses correspondances et exprimez le composant voulu dans le sélecteur. Exécutez d'abord le cas de test local, puis remplacez sa frontière de dialogue par l'identité réelle de l'application et vérifiez le résultat du clic. Examinez la disponibilité ou l'environnement du navigateur une fois seulement que la cible voulue est sans ambiguïté.

#Playwright#Strict Mode Violation#Localisateurs#Débogage De Tests

Cet article fait partie du guide : Débuter avec l'automatisation du navigateur : carte initiale

Exécutez la même vérification dans BotBrowser

Chargez un profil, gardez dans le navigateur les signaux que vous venez de lire, puis confirmez le résultat sur votre propre machine avant le déploiement.