Apprendre / Aide-mémoire
Aide-mémoire des sélecteurs XPath et CSS
XPath et CSS côte à côte, pour Selenium, Playwright et les DevTools du navigateur. Utilisez CSS quand il suffit à exprimer ce dont vous avez besoin : c’est plus court et c’est la syntaxe que les développeurs connaissent. Passez à XPath quand vous devez cibler un texte visible ou remonter l’arbre vers un parent ou un ancêtre.
Vérifié avec XPath 1.0 (celui qu’implémentent les navigateurs), CSS Selectors Level 4 tel que pris en charge par les versions actuelles de Chrome, Firefox et Safari
Bases
| Objectif | XPath | CSS |
|---|---|---|
| Élément par balise (n’importe où) | //button | button |
| Par id | //*[@id='email'] | #email |
| Par classe (parmi plusieurs) | //*[contains(concat(' ', normalize-space(@class), ' '), ' active ')] | .active |
| Enfant direct | //ul/li | ul > li |
| Descendant quelconque | //form//input | form input |
| Plusieurs sélecteurs (OU) | //h1 | //h2 | h1, h2 |
| N’importe quel élément | //* | * |
Attributs
| Objectif | XPath | CSS |
|---|---|---|
| Possède l’attribut | //button[@disabled] | button[disabled] |
| Égal à | //input[@name='q'] | input[name='q'] |
| Contient | //a[contains(@href, 'orders')] | a[href*='orders'] |
| Commence par | //a[starts-with(@href, 'https')] | a[href^='https'] |
| Se termine par | //a[substring(@href, string-length(@href) - 3) = '.pdf'] | a[href$='.pdf'] |
| Deux conditions (ET) | //input[@type='text' and @required] | input[type='text'][required] |
| L’une ou l’autre condition (OU) | //input[@type='email' or @type='tel'] | input[type='email'], input[type='tel'] |
| Négation | //input[not(@disabled)] | input:not([disabled]) |
| Test id | //*[@data-testid='submit'] | [data-testid='submit'] |
XPath 1.0 n’a pas de ends-with() : la forme avec substring() est le contournement portable.
Texte (XPath uniquement)
| Objectif | XPath | CSS |
|---|---|---|
| Texte exact, espaces normalisés | //button[normalize-space()='Sign in'] | impossible |
| Le texte contient | //p[contains(., 'expired')] | impossible |
| Contient, sans tenir compte de la casse | //p[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'expired')] | impossible |
| Uniquement le nœud texte propre | //label[text()='Email'] | impossible |
. (la valeur textuelle) inclut le texte des éléments enfants ; text() ne correspond qu’aux nœuds texte propres de l’élément. Préférez normalize-space() ou .
Relations et axes
| Objectif | XPath | CSS |
|---|---|---|
| Parent | //input[@id='email']/.. | :has(> #email) |
| Ligne ancêtre d’une cellule | //td[normalize-space()='#1043']/ancestor::tr | tr:has(td[data-id='1043']) |
| Frère suivant | //label[.='Password']/following-sibling::input[1] | label + input |
| Tous les frères suivants | //h2/following-sibling::p | h2 ~ p |
| Frère précédent | //button/preceding-sibling::input[1] | impossible |
| Cellule voisine d’une cellule libellé | //td[.='Diego Ramos']/following-sibling::td[1] | impossible (part du texte) |
| Élément contenant un enfant | //li[.//button[@aria-label='Remove']] | li:has(button[aria-label='Remove']) |
:has() fonctionne dans les versions actuelles de Chrome, Edge, Safari et Firefox (121+). Les navigateurs plus anciens et certains outils le rejettent.
Position
| Objectif | XPath | CSS |
|---|---|---|
| Première ligne enfant (indexé à partir de 1) | //tbody/tr[1] | tbody tr:first-child |
| N-ième ligne | //tbody/tr[3] | tbody tr:nth-child(3) |
| Dernière ligne | //tbody/tr[last()] | tbody tr:last-child |
| Avant-dernière | //tbody/tr[last()-1] | tbody tr:nth-last-child(2) |
| Une ligne paire sur deux | //tbody/tr[position() mod 2 = 0] | tbody tr:nth-child(even) |
| Premier résultat dans toute la page | (//button)[1] | impossible : utilisez first() de votre outil |
//tr[1] signifie « chaque tr qui est le premier tr de son parent », et non « le premier tr de la page ». Pour cela, utilisez (//tr)[1].
Pseudo-classes d’état (CSS)
| Objectif | XPath | CSS |
|---|---|---|
| Case ou bouton radio coché | //input[@checked] (initial state only) | input:checked |
| Désactivé | //button[@disabled] | button:disabled |
| Champ obligatoire | //input[@required] | input:required |
| Élément vide | //td[not(node())] | td:empty |
XPath lit les attributs tels qu’écrits dans le HTML : @checked ne change donc pas quand l’utilisateur coche la case. Le :checked de CSS suit l’état réel.
Essayez-les dans les DevTools
| Tâche | Code |
|---|---|
| CSS, toutes les correspondances (Console) | $$('tbody tr') |
| XPath, toutes les correspondances (Console) | $x("//td[.='Mei Lin']") |
| Rechercher dans le panneau Elements | Ctrl+F / Cmd+F, then type a selector or XPath |
| Compter les correspondances | $$('tbody tr').length |