Dinoer

Le clic a porté, mais pas sur l'élément voulu

Trois boutons identiques, une liste qui a pris quatre lignes, un sélecteur qui marchait hier. Chaque cible est un sélecteur CSS : chaque erreur ici est une erreur de sélecteur.

Le symptôme. Le clic fonctionne et quelque chose se passe, mais pas ce que vous vouliez : la mauvaise ligne supprimée, la mauvaise boîte de dialogue ouverte, l’action appliquée au mauvais élément.

Aucune erreur n’est signalée, car rien n’a échoué mécaniquement : Dinoer a cliqué sur ce que vous avez désigné. Chaque cible est un sélecteur CSS ; l’erreur est donc dans le sélecteur.

Cause 1 : plusieurs éléments correspondent au sélecteur

Une page avec trois boutons « Clone », un par ligne. Ce sélecteur prend le premier dans l’ordre du document :

{"type": "cliquer", "selecteur": "button:has-text('Clone')"}

L’action s’applique alors à une autre ligne que celle visée, et rien dans la réponse ne le signale.

La solution : désigner, pas décrire. Précisez le sélecteur avec ce qui n’appartient qu’à cette ligne :

{"type": "cliquer", "selecteur": "button[title='Clone example-two']"}

Si rien dans le balisage n’est unique, evaluer peut retrouver le bon élément par son contenu avant que vous n’agissiez :

{"type": "evaluer",
 "script": "Array.from(document.querySelectorAll('tr')).findIndex(tr => tr.textContent.includes('example-two'))"}

Cause 2 : un sélecteur fondé sur la position, et la position a changé

:nth-child() et :nth-of-type() désignent une ligne par sa place dans la liste, pas par ce qu’elle est. Ajoutez quatre lignes au-dessus de votre cible entre l’écriture du scénario et son exécution, et la cible se décale de quatre. Un scénario testé sur une page d’une ligne échoue sur la même page à cinq lignes : même sélecteur, autre résultat, aucune erreur.

La solution. N’encodez jamais une position là où le contenu suffit :

{"type": "cliquer", "selecteur": "tr:nth-child(3) button.delete"}
{"type": "cliquer", "selecteur": "tr:has-text('example-two') button.delete"}

La seconde version continue de fonctionner quand la liste change, parce qu’elle désigne la ligne par ce qu’elle contient, pas par l’endroit où elle se trouve.

Identifier par le contenu, pas par la position

« La troisième ligne est la plus récente » reste vrai jusqu’à ce que quelqu’un en ajoute une quatrième.

Quand la cible porte quelque chose qui l’identifie (un horodatage, un slug, un identifiant), construisez le sélecteur à partir de lui, ou lisez-le d’abord avec evaluer et décidez d’après ce qui revient :

{"type": "evaluer",
 "script": "Array.from(document.querySelectorAll('input.chk-item')).map(i => i.value)"}

En bref

  • Un sélecteur qui correspond à plusieurs éléments prend le premier, sans le dire.
  • :nth-child() et :nth-of-type() cassent quand la liste change ; préférez un sélecteur fondé sur le contenu chaque fois qu’il en existe un.
  • evaluer peut localiser un élément par son contenu avant que vous ne choisissiez un sélecteur.
  • Identifiez les cibles par ce qu’elles contiennent, jamais par leur place dans une liste.