PageSourceSearch

https://playwright.dev/assets/js/f8d04cfa.097d43d8.js

js playwright.dev collected 2026-09-24 06:56:15 UTC 33,364 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkplaywright_dev=self.webpackChunkplaywright_dev||[]).push([["5026"],{84950(e,t,n){n.r(t),n.d(t,{metadata:()=>s,default:()=>h,frontMatter:()=>o,contentTitle:()=>a,toc:()=>r,assets:()=>c});var s=JSON.parse('{"id":"other-locators","title":"Other locators","description":"Introduction","source":"@site/versioned_docs/version-stable/other-locators.mdx","sourceDirName":".","slug":"/other-locators","permalink":"/docs/other-locators","draft":false,"unlisted":false,"tags":[],"version":"stable","frontMatter":{"id":"other-locators","title":"Other locators"},"sidebar":"docs","previous":{"title":"Network","permalink":"/docs/network"},"next":{"title":"Pages","permalink":"/docs/pages"}}'),i=n(74848),l=n(28453);n(13554),n(41647),n(83137);let o={id:"other-locators",title:"Other locators"},a,c={},r=[{value:"Introduction",id:"introduction",level:2},{value:"CSS locator",id:"css-locator",level:2},{value:"CSS: matching by text",id:"css-matching-by-text",level:3},{value:"CSS: matching only visible elements",id:"css-matching-only-visible-elements",level:3},{value:"CSS: elements that contain other elements",id:"css-elements-that-contain-other-elements",level:3},{value:"CSS: elements matching one of the conditions",id:"css-elements-matching-one-of-the-conditions",level:3},{value:"CSS: matching elements based on layout",id:"css-matching-elements-based-on-layout",level:3},{value:"CSS: pick n-th match from the query result",id:"css-pick-n-th-match-from-the-query-result",level:3},{value:"N-th element locator",id:"n-th-element-locator",level:2},{value:"Parent element locator",id:"parent-element-locator",level:2},{value:"XPath locator",id:"xpath-locator",level:2},{value:"XPath union",id:"xpath-union",level:3},{value:"Label to form control retargeting",id:"label-to-form-control-retargeting",level:2},{value:"Legacy text locator",id:"legacy-text-locator",level:2},{value:"id, data-testid, data-test-id, data-test selectors",id:"id-data-testid-data-test-id-data-test-selectors",level:2},{value:"Chaining selectors",id:"chaining-selectors",level:2},{value:"Intermediate matches",id:"intermediate-matches",level:3}];function d(e){let t={a:"a",admonition:"admonition",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,l.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.h2,{id:"introduction",children:"Introduction"}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsxs)(t.p,{children:["Check out the main ",(0,i.jsx)(t.a,{href:"./locators",children:"locators guide"})," for most common and recommended locators."]})}),"\n",(0,i.jsxs)(t.p,{children:["In addition to recommended locators like ",(0,i.jsx)(t.a,{href:"/docs/api/class-page#page-get-by-role",children:"page.getByRole()"})," and ",(0,i.jsx)(t.a,{href:"/docs/api/class-page#page-get-by-text",children:"page.getByText()"}),", Playwright supports a variety of other locators described in this guide."]}),"\n",(0,i.jsx)(t.h2,{id:"css-locator",children:"CSS locator"}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsxs)(t.p,{children:["We recommend prioritizing ",(0,i.jsx)(t.a,{href:"/docs/locators#quick-guide",children:"user-visible locators"})," like text or accessible role instead of using CSS that is tied to the implementation and could break when the page changes."]})}),"\n",(0,i.jsx)(t.p,{children:"Playwright can locate an element by CSS selector."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"await page.locator('css=button').click();\n"})}),"\n",(0,i.jsx)(t.p,{children:"Playwright augments standard CSS selectors in two ways:"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsx)(t.li,{children:"CSS selectors pierce open shadow DOM."}),"\n",(0,i.jsxs)(t.li,{children:["Playwright adds custom pseudo-classes like ",(0,i.jsx)(t.code,{children:":visible"}),", ",(0,i.jsx)(t.code,{children:":has-text()"}),", ",(0,i.jsx)(t.code,{children:":has()"}),", ",(0,i.jsx)(t.code,{children:":is()"}),", ",(0,i.jsx)(t.code,{children:":nth-match()"})," and more."]}),"\n"]}),"\n",(0,i.jsx)(t.h3,{id:"css-matching-by-text",children:"CSS: matching by text"}),"\n",(0,i.jsx)(t.p,{children:"Playwright include a number of CSS pseudo-classe
1s to match elements by their text content."}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:'article:has-text("Playwright")'})," - the ",(0,i.jsx)(t.code,{children:":has-text()"})," matches any element containing specified text somewhere inside, possibly in a child or a descendant element. Matching is case-insensitive, trims whitespace and searches for a substring."]}),"\n",(0,i.jsxs)(t.p,{children:["For example, ",(0,i.jsx)(t.code,{children:'article:has-text("Playwright")'})," matches ",(0,i.jsx)(t.code,{children:"<article><div>Playwright</div></article>"}),"."]}),"\n",(0,i.jsxs)(t.p,{children:["Note that ",(0,i.jsx)(t.code,{children:":has-text()"})," should be used together with other CSS specifiers, otherwise it will match all the elements containing specified text, including the ",(0,i.jsx)(t.code,{children:"<body>"}),"."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"// Wrong, will match many elements including <body>\nawait page.locator(':has-text(\"Playwright\")').click();\n// Correct, only matches the <article> element\nawait page.locator('article:has-text(\"Playwright\")').click();\n"})}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:'#nav-bar :text("Home")'})," - the ",(0,i.jsx)(t.code,{children:":text()"})," pseudo-class matches the smallest element containing specified text. Matching is case-insensitive, trims whitespace and searches for a substring."]}),"\n",(0,i.jsxs)(t.p,{children:['For example, this will find an element with text "Home" somewhere inside the ',(0,i.jsx)(t.code,{children:"#nav-bar"})," element:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"await page.locator('#nav-bar :text(\"Home\")').click();\n"})}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:'#nav-bar :text-is("Home")'})," - the ",(0,i.jsx)(t.code,{children:":text-is()"})," pseudo-class matches the smallest element with exact text. Exact matching is case-sensitive, trims whitespace and searches for the full string."]}),"\n",(0,i.jsxs)(t.p,{children:["For example, ",(0,i.jsx)(t.code,{children:':text-is("Log")'})," does not match ",(0,i.jsx)(t.code,{children:"<button>Log in</button>"})," because ",(0,i.jsx)(t.code,{children:"<button>"})," contains a single text node ",(0,i.jsx)(t.code,{children:'"Log in"'})," that is not equal to ",(0,i.jsx)(t.code,{children:'"Log"'}),". However, ",(0,i.jsx)(t.code,{children:':text-is("Log")'})," matches ",(0,i.jsx)(t.code,{children:"<button> Log <span>in</span></button>"}),", because ",(0,i.jsx)(t.code,{children:"<button>"})," contains a text node ",(0,i.jsx)(t.code,{children:'" Log "'}),"."]}),"\n",(0,i.jsxs)(t.p,{children:["Similarly, ",(0,i.jsx)(t.code,{children:':text-is("Download")'})," will not match ",(0,i.jsx)(t.code,{children:"<button>download</button>"})," because it is case-sensitive."]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:'#nav-bar :text-matches("reg?ex", "i")'})," - the ",(0,i.jsx)(t.code,{children:":text-matches()"})," pseudo-class matches the smallest element with text content matching the ",(0,i.jsx)(t.a,{href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp",children:"JavaScript-like regex"}),"."]}),"\n",(0,i.jsxs)(t.p,{children:["For example, ",(0,i.jsx)(t.code,{children:':text-matches("Log\\s*in", "i")'})," matches ",(0,i.jsx)(t.code,{children:"<button>Login</button>"})," and ",(0,i.jsx)(t.code,{children:"<button>log IN</button>"}),"."]}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsx)(t.p,{children:"Text matching always normalizes whitespace. For example, it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace."})}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsxs)(t.p,{children:["Input elements of the type ",(0,i.jsx)(t.code,{children:"button"})," and ",(0,i.jsx)(t.code,{children:"submit"})," are matched by their ",(0,i.jsx)(t.code,{children:"value"})," instead of text content. For example, ",(0,i.jsx)(t.code,{children:':text("Log in")'})," matches ",(0,i.jsx)(t.code,{children:'<input type=button value="Log in">'}),"."]})}),"\n",(0,i.jsx)(t.h3,{id:"css-matching-only-visible-elements",children:"CSS: matching only visible elements"}),"\n",(0,i.jsxs)(t.p,{children:["Playwright supports the ",(0,i.jsx)(t.code,{children:":visible"})," pseudo class in CSS selectors. For example, ",(0,i.jsx)(t.code,{children:"css=button"})," matches all the buttons on the page, while ",(0,i.jsx)(t.code,{children:"css=button:visible"})," only matches visible buttons. This is useful to distinguish elements that are very similar but differ in visibility."]}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsxs)(t.p,{children:["Prefer the ",(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-visible",children:"locator.visible()"})," shortcut over the ",(0,i.jsx)(t.code,{children:":visible"})," pseudo-class."]})}),"\n",(0,i.jsx)(t.p,{children:"Consider a page with two buttons, first invisible and second visible."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-html",children:"<button style='display: none'>Invisible</button>\n<button>Visible</button>\n"})}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:["This will find both buttons and throw a ",(0,i.jsx)(t.a,{href:"/docs/locators#strictness",children:"strictness"})," violation error:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"await page.locator('button').click();\n"})}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsx)(t.p,{children:"This will only find a second button, because it is visible, and then click it."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"await page.locator('button:visible').click();\n"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(t.h3,{id:"css-elements-that-contain-other-elements",children:"CSS: elements that contain other elements"}),"\n",(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.code,{children:":has()"})," pseudo-class is a ",(0,i.jsx)(t.a,{href:"https://developer.mozilla.org/en-US/docs/Web/CSS/:has",children:"CSS pseudo-class"}),". It returns an element if any of the selectors passed as parameters relative to the ",(0,i.jsx)(t.code,{children:":scope"})," of the given element match at least one element."]}),"\n",(0,i.jsxs)(t.p,{children:["Following snippet returns text content of an ",(0,i.jsx)(t.code,{children:"<article>"})," element that has a ",(0,i.jsx)(t.code,{children:"<div class=promo>"})," inside."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"await page.locator('article:has(div.promo)').textContent();\n"})}),"\n",(0,i.jsx)(t.h3,{id:"css-elements-matching-one-of-the-conditions",children:"CSS: elements matching one of the conditions"}),"\n",(0,i.jsx)(t.p,{children:"Comma-separated list of CSS selectors will match all elements that can be selected by one of the selectors in that list."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:'// Clicks a <button> that has either a "Log in" or "Sign in" text.\nawait page.locator(\'button:has-text("Log in"), button:has-text("Sign in")\').click();\n'})}),"\n",(0,i.jsxs)(t.p,{children:["The ",(0,i.jsx)(t.code,{children:":is()"})," pseudo-class is a ",(0,i.jsx)(t.a,{href:"https://developer.mozilla.org/en-US/docs/Web/CSS/:is",children:"CSS pseudo-class"})," that may be useful for specifying a list of extra conditions on an element."]}),"\n",(0,i.jsx)(t.h3,{id:"css-matching-elements-based-on-layout",children:"CSS: matching elements based on layout"}),"\n",(0,i.jsxs)(t.admonition,{type:"warning",children:[(0,i.jsx)(t.p,{children:"Layout selectors are deprecated and may be removed in the future. Matching based on layout may produce unexpected results. For example, a different element could be matched when layout changes by one pixel."}),(0,i.jsxs)(t.p,{children:["We recommend prioritizing ",(0,i.jsx)(t.a,{href:"/docs/locators#quick-guide",children:"user-visible locators"})," instead."]})]}),"\n",(0,i.jsx)(t.p,{children:"Sometimes, it is hard to come up with a good selector to the target element when it lacks distinctive features. In this case, using Playwright layout CSS pseudo-classe
1s could help. These can be combined with regular CSS to pinpoint one of the multiple choices."}),"\n",(0,i.jsxs)(t.p,{children:["For example, ",(0,i.jsx)(t.code,{children:'input:right-of(:text("Password"))'}),' matches an input field that is to the right of text "Password" - useful when the page has multiple inputs that are hard to distinguish between each other.']}),"\n",(0,i.jsxs)(t.p,{children:["Note that layout pseudo-classes are useful in addition to something else, like ",(0,i.jsx)(t.code,{children:"input"}),". If you use a layout pseudo-class alone, like ",(0,i.jsx)(t.code,{children:':right-of(:text("Password"))'}),", most likely you'll get not the input you are looking for, but some empty element in between the text and the target input."]}),"\n",(0,i.jsxs)(t.p,{children:["Layout pseudo-classes use ",(0,i.jsx)(t.a,{href:"https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect",children:"bounding client rect"})," to compute distance and relative position of the elements."]}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.code,{children:":right-of(div > button)"})," - Matches elements that are to the right of any element matching the inner selector, at any vertical position."]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.code,{children:":left-of(div > button)"})," - Matches elements that are to the left of any element matching the inner selector, at any vertical position."]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.code,{children:":above(div > button)"})," - Matches elements that are above any of the elements matching the inner selector, at any horizontal position."]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.code,{children:":below(div > button)"})," - Matches elements that are below any of the elements matching the inner selector, at any horizontal position."]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.code,{children:":near(div > button)"})," - Matches elements that are near (within 50 CSS pixels) any of the elements matching the inner selector."]}),"\n"]}),"\n",(0,i.jsxs)(t.p,{children:["Note that resulting matches are sorted by their distance to the anchor element, so you can use ",(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-first",children:"locator.first()"})," to pick the closest one. This is only useful if you have something like a list of similar elements, where the closest is obviously the right one. However, using ",(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-first",children:"locator.first()"})," in other cases most likely won't work as expected - it will not target the element you are searching for, but some other element that happens to be the closest like a random empty ",(0,i.jsx)(t.code,{children:"<div>"}),", or an element that is scrolled out and is not currently visible."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"// Fill an input to the right of \"Username\".\nawait page.locator('input:right-of(:text(\"Username\"))').fill('value');\n\n// Click a button near the promo card.\nawait page.locator('button:near(.promo-card)').click();\n\n// Click the radio input in the list closest to the \"Label 3\".\nawait page.locator('[type=radio]:left-of(:text(\"Label 3\"))').first().click();\n"})}),"\n",(0,i.jsxs)(t.p,{children:["All layout pseudo-classes support optional maximum pixel distance as the last argument. For example ",(0,i.jsx)(t.code,{children:'button:near(:text("Username"), 120)'}),' matches a button that is at most 120 CSS pixels away from the element with the text "Username".']}),"\n",(0,i.jsx)(t.h3,{id:"css-pick-n-th-match-from-the-query-result",children:"CSS: pick n-th match from the query result"}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsx)(t.p,{children:"It is usually possible to distinguish elements by some attribute or text content, which is more resilient to page changes."})}),"\n",(0,i.jsx)(t.p,{children:"Sometimes page contains a number of similar elements, and it is hard to select a particular one. For example:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-html",children:"<section>\n  <button>Buy</button>\n</section>\n<article>\n  <div>\n    <button>Buy</button>\n  </div>\n</article>\n<div>\n  <div>\n    <button>Buy</button>\n  </div>\n</div>\n"})}),"\n",(0,i.jsxs)(t.p,{children:["In this case, ",(0,i.jsx)(t.code,{children:':nth-match(:text("Buy"), 3)'})," will select the third button from the snippet above. Note that index is one-based."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:'// Click the third "Buy" button\nawait page.locator(\':nth-match(:text("Buy"), 3)\').
1click();\n'})}),"\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:":nth-match()"})," is also useful to wait until a specified number of elements appear, using ",(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-wait-for",children:"locator.waitFor()"}),"."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"// Wait until all three buttons are visible\nawait page.locator(':nth-match(:text(\"Buy\"), 3)').waitFor();\n"})}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsxs)(t.p,{children:["Unlike ",(0,i.jsx)(t.a,{href:"https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child",children:(0,i.jsx)(t.code,{children:":nth-child()"})}),", elements do not have to be siblings, they could be anywhere on the page. In the snippet above, all three buttons match ",(0,i.jsx)(t.code,{children:':text("Buy")'})," selector, and ",(0,i.jsx)(t.code,{children:":nth-match()"})," selects the third button."]})}),"\n",(0,i.jsx)(t.h2,{id:"n-th-element-locator",children:"N-th element locator"}),"\n",(0,i.jsxs)(t.p,{children:["You can narrow down query to the n-th match using the ",(0,i.jsx)(t.code,{children:"nth="})," locator passing a zero-based index."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"// Click first button\nawait page.locator('button').locator('nth=0').click();\n\n// Click last button\nawait page.locator('button').locator('nth=-1').click();\n"})}),"\n",(0,i.jsx)(t.h2,{id:"parent-element-locator",children:"Parent element locator"}),"\n",(0,i.jsxs)(t.p,{children:["When you need to target a parent element of some other element, most of the time you should ",(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-filter",children:"locator.filter()"})," by the child locator. For example, consider the following DOM structure:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-html",children:"<li><label>Hello</label></li>\n<li><label>World</label></li>\n"})}),"\n",(0,i.jsxs)(t.p,{children:["If you'd like to target the parent ",(0,i.jsx)(t.code,{children:"<li>"})," of a label with text ",(0,i.jsx)(t.code,{children:'"Hello"'}),", using ",(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-filter",children:"locator.filter()"})," works best:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"const child = page.getByText('Hello');\nconst parent = page.getByRole('listitem').filter({ has: child });\n"})}),"\n",(0,i.jsxs)(t.p,{children:["Alternatively, if you cannot find a suitable locator for the parent element, use ",(0,i.jsx)(t.code,{children:"xpath=.."}),". Note that this method is not as reliable, because any changes to the DOM structure will break your tests. Prefer ",(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-filter",children:"locator.filter()"})," when possible."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"const parent = page.getByText('Hello').locator('xpath=..');\n"})}),"\n",(0,i.jsx)(t.h2,{id:"xpath-locator",children:"XPath locator"}),"\n",(0,i.jsx)(t.admonition,{type:"warning",children:(0,i.jsxs)(t.p,{children:["We recommend prioritizing ",(0,i.jsx)(t.a,{href:"/docs/locators#quick-guide",children:"user-visible locators"})," like text or accessible role instead of using XPath that is tied to the implementation and easily break when the page changes."]})}),"\n",(0,i.jsxs)(t.p,{children:["XPath locators are equivalent to calling ",(0,i.jsx)(t.a,{href:"https://developer.mozilla.org/en/docs/Web/API/Document/evaluate",children:(0,i.jsx)(t.code,{children:"Document.evaluate"})}),"."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"await page.locator('xpath=//button').click();\n"})}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsxs)(t.p,{children:["Any selector string starting with ",(0,i.jsx)(t.code,{children:"//"})," or ",(0,i.jsx)(t.code,{children:".."})," are assumed to be an xpath selector. For example, Playwright converts ",(0,i.jsx)(t.code,{children:"'//html/body'"})," to ",(0,i.jsx)(t.code,{children:"'xpath=//html/body'"}),"."]})}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsx)(t.p,{children:"XPath does not pierce shadow roots."})}
1),"\n",(0,i.jsx)(t.h3,{id:"xpath-union",children:"XPath union"}),"\n",(0,i.jsxs)(t.p,{children:["Pipe operator (",(0,i.jsx)(t.code,{children:"|"}),") can be used to specify multiple selectors in XPath. It will match all elements that can be selected by one of the selectors in that list."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"// Waits for either confirmation dialog or load spinner.\nawait page.locator(\n    `//span[contains(@class, 'spinner__loading')]|//div[@id='confirmation']`\n).waitFor();\n"})}),"\n",(0,i.jsx)(t.h2,{id:"label-to-form-control-retargeting",children:"Label to form control retargeting"}),"\n",(0,i.jsx)(t.admonition,{type:"warning",children:(0,i.jsxs)(t.p,{children:["We recommend ",(0,i.jsx)(t.a,{href:"/docs/locators#locate-by-label",children:"locating by label text"})," instead of relying to label-to-control retargeting."]})}),"\n",(0,i.jsx)(t.p,{children:"Targeted input actions in Playwright automatically distinguish between labels and controls, so you can target the label to perform an action on the associated control."}),"\n",(0,i.jsxs)(t.p,{children:["For example, consider the following DOM structure: ",(0,i.jsx)(t.code,{children:'<label for="password">Password:</label><input id="password" type="password">'}),'. You can target the label by its "Password" text using ',(0,i.jsx)(t.a,{href:"/docs/api/class-page#page-get-by-text",children:"page.getByText()"}),". However, the following actions will be performed on the input instead of the label:"]}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-click",children:"locator.click()"})," will click the label and automatically focus the input field;"]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-fill",children:"locator.fill()"})," will fill the input field;"]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-input-value",children:"locator.inputValue()"})," will return the value of the input field;"]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-select-text",children:"locator.selectText()"})," will select text in the input field;"]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-set-input-files",children:"locator.setInputFiles()"})," will set files for the input field with ",(0,i.jsx)(t.code,{children:"type=file"}),";"]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"/docs/api/class-locator#locator-select-option",children:"locator.selectOption()"})," will select an option from the select box."]}),"\n"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"// Fill the input by targeting the label.\nawait page.getByText('Password').fill('secret');\n"})}),"\n",(0,i.jsxs)(t.p,{children:["However, other methods will target the label itself, for example ",(0,i.jsx)(t.a,{href:"/docs/api/class-locatorassertions#locator-assertions-to-have-text",children:"expect(locator).toHaveText()"})," will assert the text content of the label, not the input field."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"// Fill the input by targeting the label.\nawait expect(page.locator('label')).toHaveText('Password');\n"})}),"\n",(0,i.jsx)(t.h2,{id:"legacy-text-locator",children:"Legacy text locator"}),"\n",(0,i.jsx)(t.admonition,{type:"warning",children:(0,i.jsxs)(t.p,{children:["We recommend the modern ",(0,i.jsx)(t.a,{href:"/docs/locators#get-by-text",children:"text locator"})," instead."]})}),"\n",(0,i.jsx)(t.p,{children:"Legacy text locator matches elements that contain passed text."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"await page.locator('text=Log in').click();\n"})}),"\n",(0,i.jsx)(t.p,{children:"Legacy text locator has a few variations:"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:"text=Log in"})," - default matching is case-insensitive, trims whitespace and searches for a substring. For example, ",(0,i.jsx)(t.code,{children:"text=Log"})," matches ",(0,i.jsx)(t.code,{children:"<button>Log in</button>"}),"."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"await page.locator('text=Log in').click();\n"})}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:'text="Log in"'})," - text body can be escaped with single or double quotes to search for a text node with exact content after trimming whitespace."]}),"\n",(0,i.jsxs)(t.p,{children:["For example, ",(0,i.jsx)(t.code,{children:'text="Log"'})," does not match ",(0,i.jsx)(t.code,{children:"<button>Log in</button>"})," because ",(0,i.jsx)(t.code,{children:"<button>"})," contains a single text node ",(0,i.jsx)(t.code,{children:'"L
1og in"'})," that is not equal to ",(0,i.jsx)(t.code,{children:'"Log"'}),". However, ",(0,i.jsx)(t.code,{children:'text="Log"'})," matches ",(0,i.jsx)(t.code,{children:"<button> Log <span>in</span></button>"}),", because ",(0,i.jsx)(t.code,{children:"<button>"})," contains a text node ",(0,i.jsx)(t.code,{children:'" Log "'}),". This exact mode implies case-sensitive matching, so ",(0,i.jsx)(t.code,{children:'text="Download"'})," will not match ",(0,i.jsx)(t.code,{children:"<button>download</button>"}),"."]}),"\n",(0,i.jsxs)(t.p,{children:["Quoted body follows the usual escaping rules, e.g. use ",(0,i.jsx)(t.code,{children:'\\"'})," to escape double quote in a double-quoted string: ",(0,i.jsx)(t.code,{children:'text="foo\\"bar"'}),"."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"await page.locator('text=\"Log in\"').click();\n"})}),"\n"]}),"\n",(0,i.jsxs)(t.li,{children:["\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:"/Log\\s*in/i"})," - body can be a ",(0,i.jsx)(t.a,{href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp",children:"JavaScript-like regex"})," wrapped in ",(0,i.jsx)(t.code,{children:"/"})," symbols. For example, ",(0,i.jsx)(t.code,{children:"text=/Log\\s*in/i"})," matches ",(0,i.jsx)(t.code,{children:"<button>Login</button>"})," and ",(0,i.jsx)(t.code,{children:"<button>log IN</button>"}),"."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"await page.locator('text=/Log\\\\s*in/i').click();\n"})}),"\n"]}),"\n"]}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsxs)(t.p,{children:["String selectors starting and ending with a quote (either ",(0,i.jsx)(t.code,{children:'"'})," or ",(0,i.jsx)(t.code,{children:"'"}),") are assumed to be a legacy text locators. For example, ",(0,i.jsx)(t.code,{children:'"Log in"'})," is converted to ",(0,i.jsx)(t.code,{children:'text="Log in"'})," internally."]})}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsx)(t.p,{children:"Matching always normalizes whitespace. For example, it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace."})}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsxs)(t.p,{children:["Input elements of the type ",(0,i.jsx)(t.code,{children:"button"})," and ",(0,i.jsx)(t.code,{children:"submit"})," are matched by their ",(0,i.jsx)(t.code,{children:"value"})," instead of text content. For example, ",(0,i.jsx)(t.code,{children:"text=Log in"})," matches ",(0,i.jsx)(t.code,{children:'<input type=button value="Log in">'}),"."]})}),"\n",(0,i.jsx)(t.h2,{id:"id-data-testid-data-test-id-data-test-selectors",children:"id, data-testid, data-test-id, data-test selectors"}),"\n",(0,i.jsx)(t.admonition,{type:"warning",children:(0,i.jsxs)(t.p,{children:["We recommend ",(0,i.jsx)(t.a,{href:"/docs/locators#locate-by-test-id",children:"locating by test id"})," instead."]})}),"\n",(0,i.jsx)(t.p,{children:"Playwright supports shorthand for selecting elements using certain attributes. Currently, only the following attributes are supported:"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsx)(t.li,{children:(0,i.jsx)(t.code,{children:"id"})}),"\n",(0,i.jsx)(t.li,{children:(0,i.jsx)(t.code,{children:"data-testid"})}),"\n",(0,i.jsx)(t.li,{children:(0,i.jsx)(t.code,{children:"data-test-id"})}),"\n",(0,i.jsx)(t.li,{children:(0,i.jsx)(t.code,{children:"data-test"})}),"\n"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"// Fill an input with the id \"username\"\nawait page.locator('id=username').fill('value');\n\n// Click an element with data-test-id \"submit\"\nawait page.locator('data-test-id=submit').click();\n"})}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsxs)(t.p,{children:["Attribute selectors are not CSS selectors, so anything CSS-specific like ",(0,i.jsx)(t.code,{children:":enabled"})," is not supported. For more features, use a proper ",(0,i.jsx)(t.a,{href:"#css-locator",children:"css"})," selector, e.g. ",(0,i.jsx)(t.code,{children:'css=[data-test="login"]:enabled'}),"."]})}),"\n",(0,i.jsx)(t.h2,{id:"chaining-selectors",children:"Chaining selectors"}),"\n",(0,i.jsx)(t.admonition,{type:"warning",children:(0,i.jsxs)(t.p,{children:["We recommend ",(0,i.jsx)(t.a,{href:"/docs/locators#matching-inside-a-locator",children:"chaining locators"})," instead."]})}),"\n",(0,i.jsxs)(t.p,{children:["Selectors defined as ",(0,i.jsx)(t.code,{children:"engine=body"})," or in short-form can be combined with the ",(0,i.jsx)(t.code,{children:">>"})," token, e.g. ",(0,i.jsx)(t.code,{children:"selector1 >> selector2 >> selectors3"}),". When selectors are chained, the next one is queried relative to the previous one's result."]}),"\n",(0,i.jsx)(t.p,{children:"For example,"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-txt",children:"css=article >> css=.bar > .baz >> css=span[attr=value]\n"})}),"\n",(0,i.jsx)(t.p,{children:"is equivalent to"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"document\n    .querySelector('article')\n    .querySelector('.bar > .baz')\n    .querySelector('span[attr=value]');\n"})}),"\n",(0,i.jsxs)(t.p,{children:["If a selector needs to include ",(0,i.jsx)(t.code,{children:">>"})," in the body, it should be escaped inside a string to not be confused with chaining separator, e.g. ",(0,i.jsx)(t.code,{children:'text="some >> text"'}),"."]}),"\n",(0,i.jsx)(t.h3,{id:"intermediate-matches",children:"Intermediate matches"}),"\n",(0,i.jsx)(t.admonition,{type:"warning",children:(0,i.jsxs)(t.p,{children:["We recommend ",(0,i.jsx)(t.a,{href:"/docs/locators#filter-by-childdescendant",children:"filtering by another locator"})," to locate elements that contain other elements."]})}),"\n",(0,i.jsxs)(t.p,{children:["By default, chained selectors resolve to an element queried by the last selector. A selector can be prefixed with ",(0,i.jsx)(t.code,{children:"*"})," to capture elements that are queried by an intermediate selector."]}),"\n",(0,i.jsxs)(t.p,{children:["For example, ",(0,i.jsx)(t.code,{children:"css=article >> text=Hello"})," captures the element with the text ",(0,i.jsx)(t.code,{children:"Hello"}),", and ",(0,i.jsx)(t.code,{children:"*css=article >> text=Hello"})," (note the ",(0,i.jsx)(t.code,{children:"*"}),") captures the ",(0,i.jsx)(t.code,{children:"article"})," element that contains some element with the text ",(0,i.jsx)(t.code,{children:"Hello"}),"."]})]})}function h(e={}){let{wrapper:t}={...(0,l.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.