[{"data":1,"prerenderedAt":2118},["ShallowReactive",2],{"SiteFooter_cLEacilcm59lTjFJjLY6mXNWLL0ETtzOIiZwIHgDpy0":3,"doc-\u002Fdocs\u002Fconcepts\u002Fselectors":17,"docs-nav":1986,"nav-compare":2060,"editor-pva9b8":2070,"editor-kauqxv":2072,"editor-1uwn4sq":2074,"editor-e8k3ca":2076,"editor-z1i6y1":2078,"editor-16ecr2k":2080,"editor-1cs3znm":2082,"editor-1c5pxim":2084,"editor-1pb2zt7":2086,"editor-pvoe9x":2088,"editor-1l59h2o":2090,"editor-nvgpe7":2092,"editor-19qk383":2094,"editor-1xd2dut":2096,"editor-1egejjs":2098,"editor-azu92u":2100,"editor-ecs78m":2102,"editor-n8acbd":2104,"editor-s1pnwd":2106,"editor-tk6j79":2108,"editor-2nkx7y":2110,"editor-fqlp2w":2112,"editor-15uvt7o":2114,"editor-ur3b65":2116},["Island",4],{"key":5,"result":6},"SiteFooter_cLEacilcm59lTjFJjLY6mXNWLL0ETtzOIiZwIHgDpy0",{"head":7},{"style":8},[9,11,13,15],{"innerHTML":10},".site-footer[data-v-8bb1d4ae]{border-top:1px solid var(--line);background:var(--chrome);padding:var(--sp-8) var(--gutter) var(--sp-10);gap:var(--sp-6);flex-direction:column;display:flex}.links[data-v-8bb1d4ae]{gap:var(--sp-4);grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));max-width:62rem;display:grid}.links a[data-v-8bb1d4ae]{align-items:flex-start;gap:var(--sp-3);padding:var(--sp-3);border-bottom:1px solid var(--line);border-radius:var(--radius-sm);background:var(--panel);color:var(--ink);border:1px solid var(--line);display:flex}.links a[data-v-8bb1d4ae]:focus-visible,.links a[data-v-8bb1d4ae]:hover{border-color:var(--line-strong)}.links[data-v-8bb1d4ae] .mark,.links[data-v-8bb1d4ae] .rf-mark{width:1.35rem;height:1.35rem;color:var(--dim);flex:none;margin-top:.1rem}.links a[data-v-8bb1d4ae]:hover .mark,.links a[data-v-8bb1d4ae]:hover .rf-mark{color:var(--red)}.links span[data-v-8bb1d4ae]{flex-direction:column;gap:2px;min-width:0;display:flex}.links b[data-v-8bb1d4ae]{font-family:var(--font-display);font-size:.85rem;font-weight:400}.links small[data-v-8bb1d4ae]{color:var(--faint);font-size:.78rem}.colophon[data-v-8bb1d4ae]{color:var(--faint);margin:0;font-size:.75rem}@supports (corner-shape:bevel){.links a[data-v-8bb1d4ae]{corner-shape:bevel}}",{"innerHTML":12},".mark[data-v-cc0d96a5]{vertical-align:-.125em;flex:none;width:1em;height:1em}",{"innerHTML":14},".mark[data-v-26f5673d]{vertical-align:-.125em;flex:none;width:1em;height:1em}",{"innerHTML":16},".rf-mark[data-v-26317b3e]{fill:currentColor;stroke:none;flex:none}",{"id":18,"title":19,"body":20,"description":1978,"extension":1979,"meta":1980,"navigation":830,"order":93,"path":1981,"section":1982,"seo":1983,"stem":1984,"__hash__":1985},"docs\u002Fdocs\u002Fconcepts\u002Fselectors.md","Finding elements",{"type":21,"value":22,"toc":1933},"minimark",[23,32,35,40,60,65,74,103,114,121,149,156,164,173,176,179,186,194,214,226,251,259,274,281,298,306,321,324,331,334,342,357,360,363,366,375,381,388,391,663,669,679,687,697,704,707,715,720,725,728,732,770,773,777,798,805,811,856,859,863,874,889,896,899,908,912,923,926,930,937,944,964,980,987,1002,1005,1045,1049,1053,1259,1277,1281,1288,1292,1321,1339,1343,1361,1372,1381,1396,1400,1403,1424,1602,1605,1612,1618,1633,1636,1654,1658,1661,1670,1673,1676,1695,1699,1710,1739,1748,1776,1780,1783,1800,1803,1807,1813,1833,1844,1851,1866,1880,1884,1929],[24,25,26,27,31],"p",{},"Every keyword that touches the page takes a ",[28,29,30],"code",{},"selector",". Which strategy you choose\ndecides how often your suite breaks for reasons that have nothing to do with the\nsoftware under test.",[24,33,34],{},"This page covers the strategies, the order I would reach for them in, and the\nsyntax for chaining, iframes and shadow DOM.",[36,37,39],"h2",{"id":38},"pick-a-strategy","Pick a strategy",[41,42,44,50,57],"doc-note",{"kind":43},"note",[24,45,46],{},[47,48,49],"strong",{},"The ranking below is opinionated!",[24,51,52,53],{},"It optimises for one thing: ",[54,55,56],"em",{},"a selector that keeps working when the page is redesigned but the feature is unchanged.",[24,58,59],{},"Every project is different, and you should base your selector strategy on other criteria as well. For example, prior knowledge, GUI framework, or other factors.",[61,62],"doc-table",{":head":63,":rows":64},"[\"\",\"Strategy\",\"Reach for it when\"]","[[\"1\",\"role=\",\"The element has a proper accessible role and name. This is the default choice.\"],[\"2\",\"data-testid=\",\"Stability matters more than testing the interface as a user meets it.\"],[\"3\",\"text=\",\"The visible text is the thing you actually mean, and the app is single-language.\"],[\"4\",\"id=\",\"You know the id is contractual, not incidental.\"],[\"5\",\"css=\",\"None of the above identify the element.\"],[\"6\",\"xpath=\",\"Genuinely nothing else can select it.\"]]",[66,67,69,70,73],"h3",{"id":68},"_1-role-how-the-user-finds-it","1. ",[28,71,72],{},"role="," — how the user finds it",[75,76,81],"pre",{"className":77,"code":78,"language":79,"meta":80,"style":80},"language-robot-repl shiki shiki-themes github-light github-dark","Click    role=button[name=\"Save\"]\nClick    role=link[name=\"Get started\"]\nFill Text    role=textbox[name=\"Email\"]    admin@example.com\n","robot-repl","",[28,82,83,91,97],{"__ignoreMap":80},[84,85,88],"span",{"class":86,"line":87},"line",1,[84,89,90],{},"Click    role=button[name=\"Save\"]\n",[84,92,94],{"class":86,"line":93},2,[84,95,96],{},"Click    role=link[name=\"Get started\"]\n",[84,98,100],{"class":86,"line":99},3,[84,101,102],{},"Fill Text    role=textbox[name=\"Email\"]    admin@example.com\n",[24,104,105,106,109,110,113],{},"A role selector matches on what the element ",[54,107,108],{},"is"," and what it is ",[54,111,112],{},"called"," —\nthe same two things a screen-reader user navigates by. It is semantic rather\nthan structural, so it survives a redesign that moves the button, restyles it,\nor rebuilds the surrounding markup.",[24,115,116,117,120],{},"The name here is the ",[47,118,119],{},"accessible name",", which the browser computes in this\norder:",[122,123,124,130,135,141,144],"ol",{},[125,126,127],"li",{},[28,128,129],{},"aria-labelledby",[125,131,132],{},[28,133,134],{},"aria-label",[125,136,137,138],{},"An associated ",[28,139,140],{},"\u003Clabel>",[125,142,143],{},"Visible text content",[125,145,146],{},[28,147,148],{},"title",[24,150,151,152,155],{},"There is a bonus that is easy to miss. If you cannot write a role selector\nbecause the element has no proper role or no accessible name, ",[47,153,154],{},"you have found\nan accessibility bug",". A screen-reader user cannot identify that control\neither. That is worth an issue, not a workaround.",[66,157,159,160,163],{"id":158},"_2-data-testid-the-one-attribute-that-belongs-to-us","2. ",[28,161,162],{},"data-testid="," — the one attribute that belongs to us",[75,165,167],{"className":77,"code":166,"language":79,"meta":80,"style":80},"Click    [data-testid=\"checkout-submit\"]\n",[28,168,169],{"__ignoreMap":80},[84,170,171],{"class":86,"line":87},[84,172,166],{},[24,174,175],{},"Every other attribute on the page belongs to someone else. Classes belong to the\ndesigners, ids to the developers, text to the copywriters — and all three of\nthem are entitled to change their minds without telling you.",[24,177,178],{},"A dedicated test id is the only hook that exists for testing, and the only one\nnobody will change by accident. If long-term stability is what you are buying,\nbuy this one.",[24,180,181,182,185],{},"Be clear-eyed about what it costs, though. A test id is invisible to the user,\nso a suite built on test ids is ",[47,183,184],{},"using the interface to test the functionality\nbehind it",", not testing the interface. That is often exactly the right trade —\njust make it deliberately rather than by default.",[66,187,189,190,193],{"id":188},"_3-text-what-is-written-on-it","3. ",[28,191,192],{},"text="," — what is written on it",[75,195,197],{"className":77,"code":196,"language":79,"meta":80,"style":80},"Click    text=Sign in\nClick    \"Sign in\"\nClick    text=\u002F^Sign in$\u002Fi\n",[28,198,199,204,209],{"__ignoreMap":80},[84,200,201],{"class":86,"line":87},[84,202,203],{},"Click    text=Sign in\n",[84,205,206],{"class":86,"line":93},[84,207,208],{},"Click    \"Sign in\"\n",[84,210,211],{"class":86,"line":99},[84,212,213],{},"Click    text=\u002F^Sign in$\u002Fi\n",[24,215,216,217,219,220,222,223,225],{},"Text selectors use a user-facing property, like ",[28,218,72],{},", which is why they rank\nabove anything structural. They are one step behind ",[28,221,72],{}," because text alone\ndoes not say what the element ",[54,224,108],{},", and because text is language-dependent: the\nmoment the app is localised, every text selector is a translation away from\nfailing.",[24,227,228,231,232,235,236,239,240,242,243,246,247,250],{},[28,229,230],{},"text=Sign in"," matches by substring, case-insensitively. Quoting the value —\n",[28,233,234],{},"\"Sign in\""," — makes it a whole-string, case-sensitive match. Both ",[47,237,238],{},"normalise\nwhitespace",": edges trimmed and internal runs collapsed, so ",[28,241,234],{}," still\nmatches ",[28,244,245],{},"\u003Cp> Sign in \u003C\u002Fp>",". The regex form is the exception — it runs\nagainst the raw text, so ",[28,248,249],{},"text=\u002F^Sign in$\u002Fi"," will not match a padded node.",[66,252,254,255,258],{"id":253},"_4-id-less-stable-than-it-looks","4. ",[28,256,257],{},"id="," — less stable than it looks",[75,260,262],{"className":77,"code":261,"language":79,"meta":80,"style":80},"Click    id=submit-button\nClick    \\#submit-button\n",[28,263,264,269],{"__ignoreMap":80},[84,265,266],{"class":86,"line":87},[84,267,268],{},"Click    id=submit-button\n",[84,270,271],{"class":86,"line":93},[84,272,273],{},"Click    \\#submit-button\n",[24,275,276,277,280],{},"An id feels like a stable, unique handle, and sometimes it is. But ids belong to\nthe developers, they are frequently generated by a framework, and nothing stops\nthem changing in a refactor that nobody thought was user-visible. The stability\nis a ",[47,278,279],{},"false sense of safety"," unless you have agreed with the developers that\nthese particular ids are contractual.",[41,282,284],{"kind":283},"warning",[24,285,286,289,290,293,294,297],{},[28,287,288],{},"#"," starts a comment in Robot Framework syntax, so a CSS id selector must be\nescaped as ",[28,291,292],{},"\\#submit-button",", or written as ",[28,295,296],{},"id=submit-button",".",[66,299,301,302,305],{"id":300},"_5-css-acceptable-not-preferable","5. ",[28,303,304],{},"css="," — acceptable, not preferable",[75,307,309],{"className":77,"code":308,"language":79,"meta":80,"style":80},"Click    css=button.primary\nClick    .checkout > button\n",[28,310,311,316],{"__ignoreMap":80},[84,312,313],{"class":86,"line":87},[84,314,315],{},"Click    css=button.primary\n",[84,317,318],{"class":86,"line":93},[84,319,320],{},"Click    .checkout > button\n",[24,322,323],{},"CSS is web-native and every web developer reads it, which is a real advantage:\na developer looking at your selector understands it immediately and can tell\nyou when a change will break it.",[24,325,326,327,330],{},"It ranks below the four above because it selects on ",[54,328,329],{},"structure and styling"," —\nexactly the things a redesign changes. A class name is a styling decision, typically not a contract with you.",[24,332,333],{},"CSS is the implicit default: a selector that is not obviously something else is treated as CSS.",[66,335,337,338,341],{"id":336},"_6-xpath-the-last-resort","6. ",[28,339,340],{},"xpath="," — the last resort",[75,343,345],{"className":77,"code":344,"language":79,"meta":80,"style":80},"Click    xpath=\u002F\u002Fbutton[@type=\"submit\"]\nClick    \u002F\u002Fdiv[@class=\"row\"]\u002F\u002Fbutton\n",[28,346,347,352],{"__ignoreMap":80},[84,348,349],{"class":86,"line":87},[84,350,351],{},"Click    xpath=\u002F\u002Fbutton[@type=\"submit\"]\n",[84,353,354],{"class":86,"line":93},[84,355,356],{},"Click    \u002F\u002Fdiv[@class=\"row\"]\u002F\u002Fbutton\n",[24,358,359],{},"XPath is CSS's powerful, unpleasant relative. It is more verbose for the same\nresult, many web developers do not read it fluently, it is not web-native, and\nit invites selecting by document position rather than function — which is the\nmost brittle thing you can possibly do.",[24,361,362],{},"It is genuinely more powerful, and occasionally something is unselectable\nwithout it. Use it then, and only then. It is the last resort, not a\ngeneral-purpose tool.",[24,364,365],{},"And if you are about to paste something like this out of your browser's\ndevtools:",[75,367,369],{"className":77,"code":368,"language":79,"meta":80,"style":80},"Click    \u002Fhtml\u002Fbody\u002Fdiv[3]\u002Fdiv\u002Fdiv[2]\u002Fbutton\n",[28,370,371],{"__ignoreMap":80},[84,372,373],{"class":86,"line":87},[84,374,368],{},[24,376,377,380],{},[47,378,379],{},"DON'T!"," That selector describes where the button sits today, not what it is.\nIt will break on the next layout change, and the failure will look like a bug in\nthe software rather than in the test.",[24,382,383,384,387],{},"A legitimate use for XPath is ",[47,385,386],{},"relative navigation",": start from an element you can identify reliably, then move through the DOM to an otherwise ambiguous element.",[24,389,390],{},"For example, imagine a form with several fields, each with the same info button:",[75,392,397],{"className":393,"code":394,"filename":395,"language":396,"meta":80,"style":80},"language-html shiki shiki-themes github-light github-dark","...\n\u003Cdiv class=\"field\">\n    \u003Clabel for=\"email\">Email\u003C\u002Flabel>\n    \u003Cdiv class=\"control\">\n        \u003Cinput id=\"email\" type=\"text\">\n        \u003Cbutton type=\"button\" aria-label=\"More information\">ⓘ\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\u003Cdiv class=\"field\">\n    \u003Clabel for=\"phone\">Phone\u003C\u002Flabel>\n    \u003Cdiv class=\"control\">\n        \u003Cinput id=\"phone\" type=\"text\">\n        \u003Cbutton type=\"button\" aria-label=\"More information\">ⓘ\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n...\n","DOM","html",[28,398,399,405,428,451,467,493,523,533,543,558,579,594,615,640,649,658],{"__ignoreMap":80},[84,400,401],{"class":86,"line":87},[84,402,404],{"class":403},"sVt8B","...\n",[84,406,407,410,414,418,421,425],{"class":86,"line":93},[84,408,409],{"class":403},"\u003C",[84,411,413],{"class":412},"s9eBZ","div",[84,415,417],{"class":416},"sScJk"," class",[84,419,420],{"class":403},"=",[84,422,424],{"class":423},"sZZnC","\"field\"",[84,426,427],{"class":403},">\n",[84,429,430,433,436,439,441,444,447,449],{"class":86,"line":99},[84,431,432],{"class":403},"    \u003C",[84,434,435],{"class":412},"label",[84,437,438],{"class":416}," for",[84,440,420],{"class":403},[84,442,443],{"class":423},"\"email\"",[84,445,446],{"class":403},">Email\u003C\u002F",[84,448,435],{"class":412},[84,450,427],{"class":403},[84,452,454,456,458,460,462,465],{"class":86,"line":453},4,[84,455,432],{"class":403},[84,457,413],{"class":412},[84,459,417],{"class":416},[84,461,420],{"class":403},[84,463,464],{"class":423},"\"control\"",[84,466,427],{"class":403},[84,468,470,473,476,479,481,483,486,488,491],{"class":86,"line":469},5,[84,471,472],{"class":403},"        \u003C",[84,474,475],{"class":412},"input",[84,477,478],{"class":416}," id",[84,480,420],{"class":403},[84,482,443],{"class":423},[84,484,485],{"class":416}," type",[84,487,420],{"class":403},[84,489,490],{"class":423},"\"text\"",[84,492,427],{"class":403},[84,494,496,498,501,503,505,508,511,513,516,519,521],{"class":86,"line":495},6,[84,497,472],{"class":403},[84,499,500],{"class":412},"button",[84,502,485],{"class":416},[84,504,420],{"class":403},[84,506,507],{"class":423},"\"button\"",[84,509,510],{"class":416}," aria-label",[84,512,420],{"class":403},[84,514,515],{"class":423},"\"More information\"",[84,517,518],{"class":403},">ⓘ\u003C\u002F",[84,520,500],{"class":412},[84,522,427],{"class":403},[84,524,526,529,531],{"class":86,"line":525},7,[84,527,528],{"class":403},"    \u003C\u002F",[84,530,413],{"class":412},[84,532,427],{"class":403},[84,534,536,539,541],{"class":86,"line":535},8,[84,537,538],{"class":403},"\u003C\u002F",[84,540,413],{"class":412},[84,542,427],{"class":403},[84,544,546,548,550,552,554,556],{"class":86,"line":545},9,[84,547,409],{"class":403},[84,549,413],{"class":412},[84,551,417],{"class":416},[84,553,420],{"class":403},[84,555,424],{"class":423},[84,557,427],{"class":403},[84,559,561,563,565,567,569,572,575,577],{"class":86,"line":560},10,[84,562,432],{"class":403},[84,564,435],{"class":412},[84,566,438],{"class":416},[84,568,420],{"class":403},[84,570,571],{"class":423},"\"phone\"",[84,573,574],{"class":403},">Phone\u003C\u002F",[84,576,435],{"class":412},[84,578,427],{"class":403},[84,580,582,584,586,588,590,592],{"class":86,"line":581},11,[84,583,432],{"class":403},[84,585,413],{"class":412},[84,587,417],{"class":416},[84,589,420],{"class":403},[84,591,464],{"class":423},[84,593,427],{"class":403},[84,595,597,599,601,603,605,607,609,611,613],{"class":86,"line":596},12,[84,598,472],{"class":403},[84,600,475],{"class":412},[84,602,478],{"class":416},[84,604,420],{"class":403},[84,606,571],{"class":423},[84,608,485],{"class":416},[84,610,420],{"class":403},[84,612,490],{"class":423},[84,614,427],{"class":403},[84,616,618,620,622,624,626,628,630,632,634,636,638],{"class":86,"line":617},13,[84,619,472],{"class":403},[84,621,500],{"class":412},[84,623,485],{"class":416},[84,625,420],{"class":403},[84,627,507],{"class":423},[84,629,510],{"class":416},[84,631,420],{"class":403},[84,633,515],{"class":423},[84,635,518],{"class":403},[84,637,500],{"class":412},[84,639,427],{"class":403},[84,641,643,645,647],{"class":86,"line":642},14,[84,644,528],{"class":403},[84,646,413],{"class":412},[84,648,427],{"class":403},[84,650,652,654,656],{"class":86,"line":651},15,[84,653,538],{"class":403},[84,655,413],{"class":412},[84,657,427],{"class":403},[84,659,661],{"class":86,"line":660},16,[84,662,404],{"class":403},[24,664,665,668],{},[28,666,667],{},"role=button[name=\"More information\"]"," alone is ambiguous: there are two of them. But the Email textbox is easy to identify. We can anchor there, move up to the common parent, and then find the button within it:",[75,670,673],{"className":77,"code":671,"filename":672,"language":79,"meta":80,"style":80},"Click    xpath=\u002F\u002Flabel[text()=\"Email\"]\u002F..\u002F\u002Fbutton[@aria-label=\"More information\"]\n","Bad Example",[28,674,675],{"__ignoreMap":80},[84,676,677],{"class":86,"line":87},[84,678,671],{},[24,680,681,682],{},"The above example solves the problem but just because you need one functionality of XPath, does not mean you have to use it all the way. See ",[683,684,686],"a",{"href":685},"#chaining-with","Cascading Selectors",[75,688,691],{"className":77,"code":689,"filename":690,"language":79,"meta":80,"style":80},"Click    role=textbox[name=\"Email\"] >> xpath=.. >> role=button[name=\"More information\"]\n","Good Example",[28,692,693],{"__ignoreMap":80},[84,694,695],{"class":86,"line":87},[84,696,689],{},[24,698,699,700,703],{},"Here XPath is doing something useful and narrowly scoped: ",[47,701,702],{},"navigating relative to a reliably identified element",". We are not describing where the element happens to sit in the entire document; we are expressing a local relationship between two elements.",[24,705,706],{},"That is a good use of XPath.",[66,708,710,711,714],{"id":709},"also-available-the-data-testid-aliases","Also available: the ",[28,712,713],{},"data-testid"," aliases",[24,716,717,719],{},[28,718,162],{}," has two siblings that do exactly the same job against a different\nattribute. Which one you use is decided by what your developers already put in\nthe markup, not by preference:",[61,721],{":head":722,":rows":723,":nowrap":724},"[\"Prefix\",\"Matches\"]","[[\"`data-testid=`\",\"`data-testid` — Playwright's own default, and the most common in the wild\"],[\"`data-test-id=`\",\"`data-test-id`\"],[\"`data-test=`\",\"`data-test`\"]]","[0]",[24,726,727],{},"All three behave identically. Pick the one your application emits and stay with\nit.",[36,729,731],{"id":730},"how-a-strategy-is-used","How a strategy is used",[24,733,734,735,738,739,741,742,744,745,747,748,741,751,754,755,758,759,762,763,765,766,769],{},"You can always be explicit with a ",[28,736,737],{},"strategy=value"," prefix. Spaces around the\nseparator are ignored by ",[28,740,304],{},", ",[28,743,340],{}," and ",[28,746,192],{},", so ",[28,749,750],{},"css=foo",[28,752,753],{},"css= foo","\nand ",[28,756,757],{},"css = foo"," are equivalent. They are ",[47,760,761],{},"not"," ignored by ",[28,764,257],{}," or the\ntest-id engines: the space becomes part of the value, so ",[28,767,768],{},"id = save"," silently\nmatches nothing.",[24,771,772],{},"Without a prefix, the strategy is inferred:",[61,774],{":head":775,":rows":776},"[\"Selector looks like\",\"Treated as\"]","[[\"Starts with `\u002F\u002F` or `..`\",\"XPath\"],[\"Starts and ends with a quote\",\"Text\"],[\"Anything else\",\"CSS\"]]",[75,778,781],{"className":77,"code":779,"filename":780,"language":79,"meta":80,"style":80},"Get Element    \u002F\u002Fhtml\u002Fbody\u002Fdiv      # xpath\nGet Element    \"foo\"                # text exact match\nGet Element    div                  # css\n","Implicit Selector Strategies",[28,782,783,788,793],{"__ignoreMap":80},[84,784,785],{"class":86,"line":87},[84,786,787],{},"Get Element    \u002F\u002Fhtml\u002Fbody\u002Fdiv      # xpath\n",[84,789,790],{"class":86,"line":93},[84,791,792],{},"Get Element    \"foo\"                # text exact match\n",[84,794,795],{"class":86,"line":99},[84,796,797],{},"Get Element    div                  # css\n",[36,799,801,802],{"id":800},"cascading-selectors-with","Cascading Selectors with ",[28,803,804],{},">>",[24,806,807,808,810],{},"This is the part that makes Browser's selectors worth learning. Strategies\ncombine in a single string, left to right, with ",[28,809,804],{},". Each step searches inside\nthe result of the previous one.",[75,812,814],{"className":77,"code":813,"language":79,"meta":80,"style":80},"# Find the element with text \"Login\", then the input beside it\nClick    \"Login\" >> xpath=..\u002Finput\n\n# Find a css element, then a button inside it by text\nClick    css=.checkout >> text=Confirm\n\n# Start with a role, narrow with css\nGet Text    role=listitem >> css=.price\n",[28,815,816,821,826,832,837,842,846,851],{"__ignoreMap":80},[84,817,818],{"class":86,"line":87},[84,819,820],{},"# Find the element with text \"Login\", then the input beside it\n",[84,822,823],{"class":86,"line":93},[84,824,825],{},"Click    \"Login\" >> xpath=..\u002Finput\n",[84,827,828],{"class":86,"line":99},[84,829,831],{"emptyLinePlaceholder":830},true,"\n",[84,833,834],{"class":86,"line":453},[84,835,836],{},"# Find a css element, then a button inside it by text\n",[84,838,839],{"class":86,"line":469},[84,840,841],{},"Click    css=.checkout >> text=Confirm\n",[84,843,844],{"class":86,"line":495},[84,845,831],{"emptyLinePlaceholder":830},[84,847,848],{"class":86,"line":525},[84,849,850],{},"# Start with a role, narrow with css\n",[84,852,853],{"class":86,"line":535},[84,854,855],{},"Get Text    role=listitem >> css=.price\n",[24,857,858],{},"That means you rarely need one clever selector. You need two obvious ones.",[66,860,862],{"id":861},"when-the-chain-returns-the-wrong-element","When the chain returns the wrong element",[24,864,865,866,869,870,873],{},"By default a chain returns what the ",[54,867,868],{},"last"," step matched. Prefix a step with ",[28,871,872],{},"*","\nto return that step's element instead, while still requiring the rest of the\nchain to match:",[75,875,877],{"className":77,"code":876,"language":79,"meta":80,"style":80},"# The article that contains \"Hello\" — not the text node inside it\nGet Element    *css=article >> text=Hello\n",[28,878,879,884],{"__ignoreMap":80},[84,880,881],{"class":86,"line":87},[84,882,883],{},"# The article that contains \"Hello\" — not the text node inside it\n",[84,885,886],{"class":86,"line":93},[84,887,888],{},"Get Element    *css=article >> text=Hello\n",[66,890,892,893,895],{"id":891},"when-appears-in-the-text-you-are-matching","When ",[28,894,804],{}," appears in the text you are matching",[24,897,898],{},"Escape it by quoting the value, or the chain splits in the wrong place:",[75,900,902],{"className":77,"code":901,"language":79,"meta":80,"style":80},"Get Text    text=\"some >> text\"\n",[28,903,904],{"__ignoreMap":80},[84,905,906],{"class":86,"line":87},[84,907,901],{},[36,909,911],{"id":910},"filter-selectors","Filter selectors",[24,913,914,915,918,919,922],{},"Some prefixes do not find elements at all. They take what the previous step\nfound and ",[47,916,917],{},"narrow it",", which is why they are only ",[54,920,921],{},"useful"," as a step in a\nchain. Used alone they apply to the whole document rather than being rejected.",[24,924,925],{},"It is worth holding the two kinds apart in your head:",[61,927],{":head":928,":rows":929,":nowrap":724},"[\"Kind\",\"Does\",\"Examples\"]","[[\"Strategy\",\"Finds elements in the page\",\"`css=`, `xpath=`, `text=`, `role=`, `id=`\"],[\"Filter\",\"Narrows what the step before it found\",\"`nth=`, `visible=`\"]]",[66,931,933,936],{"id":932},"nth-pick-one-out-of-many",[28,934,935],{},"nth="," — pick one out of many",[24,938,939,940,943],{},"Zero-based, and ",[28,941,942],{},"-1"," is the last one:",[75,945,947],{"className":77,"code":946,"language":79,"meta":80,"style":80},"Click    css=.result >> nth=0     # the first result\nClick    css=.result >> nth=2     # the third\nClick    css=.result >> nth=-1    # the last\n",[28,948,949,954,959],{"__ignoreMap":80},[84,950,951],{"class":86,"line":87},[84,952,953],{},"Click    css=.result >> nth=0     # the first result\n",[84,955,956],{"class":86,"line":93},[84,957,958],{},"Click    css=.result >> nth=2     # the third\n",[84,960,961],{"class":86,"line":99},[84,962,963],{},"Click    css=.result >> nth=-1    # the last\n",[24,965,966,967,971,972,975,976,979],{},"This is the honest escape hatch from ",[683,968,970],{"href":969},"#strict-mode","strict mode",": when a\nselector legitimately matches several elements and you want a specific one,\nsay so. It is still positional, so prefer narrowing by something meaningful\nfirst — ",[28,973,974],{},"css=.result >> text=Helsinki"," beats ",[28,977,978],{},"nth=3"," whenever it is available.",[66,981,983,986],{"id":982},"visible-keep-only-what-can-be-seen",[28,984,985],{},"visible="," — keep only what can be seen",[75,988,990],{"className":77,"code":989,"language":79,"meta":80,"style":80},"Click    css=button.save >> visible=true\nGet Element Count    css=.row >> visible=false    ==    2\n",[28,991,992,997],{"__ignoreMap":80},[84,993,994],{"class":86,"line":87},[84,995,996],{},"Click    css=button.save >> visible=true\n",[84,998,999],{"class":86,"line":93},[84,1000,1001],{},"Get Element Count    css=.row >> visible=false    ==    2\n",[24,1003,1004],{},"Useful when a page keeps hidden copies of things in the DOM — a mobile menu\nnext to a desktop one, a template, a collapsed panel.",[41,1006,1007,1013,1042],{"kind":283},[24,1008,1009,1012],{},[47,1010,1011],{},"The order of filters changes the answer."," These two are not the same\nselector:",[75,1014,1016],{"className":77,"code":1015,"language":79,"meta":80,"style":80},"# Third input among the visible ones\nClick    \u002F\u002Finput >> visible=true >> nth=2\n\n# Third input in the DOM, then check it happens to be visible\nClick    \u002F\u002Finput >> nth=2 >> visible=true\n",[28,1017,1018,1023,1028,1032,1037],{"__ignoreMap":80},[84,1019,1020],{"class":86,"line":87},[84,1021,1022],{},"# Third input among the visible ones\n",[84,1024,1025],{"class":86,"line":93},[84,1026,1027],{},"Click    \u002F\u002Finput >> visible=true >> nth=2\n",[84,1029,1030],{"class":86,"line":99},[84,1031,831],{"emptyLinePlaceholder":830},[84,1033,1034],{"class":86,"line":453},[84,1035,1036],{},"# Third input in the DOM, then check it happens to be visible\n",[84,1038,1039],{"class":86,"line":469},[84,1040,1041],{},"Click    \u002F\u002Finput >> nth=2 >> visible=true\n",[24,1043,1044],{},"Each step operates on the result of the one before it. Reading a chain left to\nright tells you exactly what it does, and reading it in any other order tells\nyou something false.",[36,1046,1048],{"id":1047},"css-basics-and-advanced","CSS Basics and Advanced",[66,1050,1052],{"id":1051},"css-basics","CSS Basics",[1054,1055,1056,1072],"table",{},[1057,1058,1059],"thead",{},[1060,1061,1062,1066,1069],"tr",{},[1063,1064,1065],"th",{},"Syntax",[1063,1067,1068],{},"Meaning",[1063,1070,1071],{},"Example",[1073,1074,1075,1090,1105,1120,1135,1150,1165,1180,1197,1214,1229,1244],"tbody",{},[1060,1076,1077,1083,1086],{},[1078,1079,1080],"td",{},[28,1081,1082],{},"tag",[1078,1084,1085],{},"Element type",[1078,1087,1088],{},[28,1089,500],{},[1060,1091,1092,1097,1100],{},[1078,1093,1094],{},[28,1095,1096],{},".class",[1078,1098,1099],{},"Class",[1078,1101,1102],{},[28,1103,1104],{},".submit-button",[1060,1106,1107,1112,1115],{},[1078,1108,1109],{},[28,1110,1111],{},"#id",[1078,1113,1114],{},"ID",[1078,1116,1117],{},[28,1118,1119],{},"#email",[1060,1121,1122,1127,1130],{},[1078,1123,1124],{},[28,1125,1126],{},"[attr]",[1078,1128,1129],{},"Has attribute",[1078,1131,1132],{},[28,1133,1134],{},"[disabled]",[1060,1136,1137,1142,1145],{},[1078,1138,1139],{},[28,1140,1141],{},"[attr=\"value\"]",[1078,1143,1144],{},"Attribute equals",[1078,1146,1147],{},[28,1148,1149],{},"[type=\"submit\"]",[1060,1151,1152,1157,1160],{},[1078,1153,1154],{},[28,1155,1156],{},"A B",[1078,1158,1159],{},"Descendant",[1078,1161,1162],{},[28,1163,1164],{},"form button",[1060,1166,1167,1172,1175],{},[1078,1168,1169],{},[28,1170,1171],{},"A > B",[1078,1173,1174],{},"Direct child",[1078,1176,1177],{},[28,1178,1179],{},"form > button",[1060,1181,1182,1187,1192],{},[1078,1183,1184],{},[28,1185,1186],{},"A + B",[1078,1188,1189],{},[47,1190,1191],{},"Next sibling",[1078,1193,1194],{},[28,1195,1196],{},"label + input",[1060,1198,1199,1204,1209],{},[1078,1200,1201],{},[28,1202,1203],{},"A ~ B",[1078,1205,1206],{},[47,1207,1208],{},"Any following sibling",[1078,1210,1211],{},[28,1212,1213],{},"label ~ button",[1060,1215,1216,1221,1224],{},[1078,1217,1218],{},[28,1219,1220],{},":nth-child(n)",[1078,1222,1223],{},"Child by position",[1078,1225,1226],{},[28,1227,1228],{},"li:nth-child(2)",[1060,1230,1231,1236,1239],{},[1078,1232,1233],{},[28,1234,1235],{},":not(...)",[1078,1237,1238],{},"Exclude matches",[1078,1240,1241],{},[28,1242,1243],{},"button:not([disabled])",[1060,1245,1246,1251,1254],{},[1078,1247,1248],{},[28,1249,1250],{},":has(...)",[1078,1252,1253],{},"Has matching descendant\u002Frelative",[1078,1255,1256],{},[28,1257,1258],{},".field:has(input[name=\"email\"])",[24,1260,1261,1262,1265,1266,744,1269,1272,1273,1276],{},"One useful distinction to xpath: CSS can select ",[47,1263,1264],{},"following"," siblings with ",[28,1267,1268],{},"+",[28,1270,1271],{},"~",", but it has no simple equivalent of XPath's ",[28,1274,1275],{},".."," for selecting a parent directly.",[66,1278,1280],{"id":1279},"filtering-inside-a-css-selector","Filtering inside a CSS selector",[24,1282,1283,1284,1287],{},"Playwright adds pseudo-classes to CSS that stay inside one step, rather than\nbecoming another link in the chain. These are strategies-with-conditions, not\nfilters, because they still describe ",[54,1285,1286],{},"which"," element you want:",[61,1289],{":head":1290,":rows":1291,":nowrap":724},"[\"Pseudo-class\",\"Matches\"]","[[\"`:has(sel)`\",\"An element that contains something matching `sel`\"],[\"`:has-text(\\\"x\\\")`\",\"An element containing that text anywhere inside it, case-insensitive\"],[\"`:text(\\\"x\\\")`\",\"The *smallest* element containing that text\"],[\"`:text-is(\\\"x\\\")`\",\"The smallest element whose text is exactly that\"],[\"`:text-matches(\\\"re\\\")`\",\"Text matching a regular expression\"],[\"`:visible`\",\"Only elements that are visible\"],[\"`:is(a, b)`\",\"An element matching *any* of the listed selectors\"],[\"`:nth-match(sel, n)`\",\"The n-th match, one-based, across the whole query\"]]",[75,1293,1295],{"className":77,"code":1294,"language":79,"meta":80,"style":80},"# The row that contains the name, then the button inside that row\nClick    css=tr:has-text(\"Ada Lovelace\") >> role=button[name=\"Edit\"]\n\n# A card that contains an image, rather than a card whose text mentions one\nGet Text    css=.card:has(img) >> css=.title\n",[28,1296,1297,1302,1307,1311,1316],{"__ignoreMap":80},[84,1298,1299],{"class":86,"line":87},[84,1300,1301],{},"# The row that contains the name, then the button inside that row\n",[84,1303,1304],{"class":86,"line":93},[84,1305,1306],{},"Click    css=tr:has-text(\"Ada Lovelace\") >> role=button[name=\"Edit\"]\n",[84,1308,1309],{"class":86,"line":99},[84,1310,831],{"emptyLinePlaceholder":830},[84,1312,1313],{"class":86,"line":453},[84,1314,1315],{},"# A card that contains an image, rather than a card whose text mentions one\n",[84,1317,1318],{"class":86,"line":469},[84,1319,1320],{},"Get Text    css=.card:has(img) >> css=.title\n",[24,1322,1323,1326,1327,1330,1331,1334,1335,1338],{},[28,1324,1325],{},":has-text()"," is the one you will reach for most. Note the difference from\n",[28,1328,1329],{},":text()",": ",[28,1332,1333],{},"tr:has-text(\"Ada\")"," is the whole row, while ",[28,1336,1337],{},"tr :text(\"Ada\")"," is the\ncell.",[66,1340,1342],{"id":1341},"layout-selectors","Layout selectors",[24,1344,1345,1346,741,1349,741,1352,741,1355,744,1358,297],{},"Playwright can also select by where an element sits relative to another:\n",[28,1347,1348],{},":right-of()",[28,1350,1351],{},":left-of()",[28,1353,1354],{},":above()",[28,1356,1357],{},":below()",[28,1359,1360],{},":near()",[24,1362,1363,1364,1367,1368,1371],{},"They return ",[47,1365,1366],{},"every"," element in that direction, sorted by distance — not the\nnearest one — so under strict mode you need a ",[28,1369,1370],{},"nth=0"," to say you meant the\nclosest.",[75,1373,1375],{"className":77,"code":1374,"language":79,"meta":80,"style":80},"Fill Text    css=input:right-of(:text(\"Postcode\")) >> nth=0    00100\n",[28,1376,1377],{"__ignoreMap":80},[84,1378,1379],{"class":86,"line":87},[84,1380,1374],{},[41,1382,1383,1393],{"kind":283},[24,1384,1385,1386,1388,1389,1392],{},"These match on rendered geometry, so they break when the layout changes — the\nsame objection as selecting by document position with XPath, and for the same\nreason. A form field is nearly always reachable through its ",[28,1387,140],{},", which is\nwhat ",[28,1390,1391],{},"role=textbox[name=\"Postcode\"]"," uses and what a screen reader uses too.",[24,1394,1395],{},"Reach for a layout selector when the markup genuinely offers nothing else, and\ntreat it as a note that the page has an accessibility problem worth reporting.",[36,1397,1399],{"id":1398},"xpath-basics","XPath Basics",[24,1401,1402],{},"XPath has tons of features and was generally designed to navigate in XML trees.\nHere are some of the more common used ones.",[24,1404,1405,1406,744,1409,1412,1413,1415,1416,1419,1420,1423],{},"As you can see, unlike in CSS, ",[28,1407,1408],{},"class",[28,1410,1411],{},"id"," attributes are not treated specially. To identify an element whose ",[28,1414,1408],{}," attribute contains ",[28,1417,1418],{},"error",", you need to use the ",[28,1421,1422],{},"contains()"," function.",[1054,1425,1426,1436],{},[1057,1427,1428],{},[1060,1429,1430,1432,1434],{},[1063,1431,1065],{},[1063,1433,1068],{},[1063,1435,1071],{},[1073,1437,1438,1453,1467,1481,1496,1511,1525,1540,1554,1572,1587],{},[1060,1439,1440,1445,1448],{},[1078,1441,1442],{},[28,1443,1444],{},"\u002F\u002F",[1078,1446,1447],{},"Descendant anywhere below",[1078,1449,1450],{},[28,1451,1452],{},"\u002F\u002Fbutton",[1060,1454,1455,1460,1462],{},[1078,1456,1457],{},[28,1458,1459],{},"\u002F",[1078,1461,1174],{},[1078,1463,1464],{},[28,1465,1466],{},"\u002F\u002Fform\u002Fbutton",[1060,1468,1469,1473,1476],{},[1078,1470,1471],{},[28,1472,1275],{},[1078,1474,1475],{},"Parent",[1078,1477,1478],{},[28,1479,1480],{},"\u002F\u002Finput\u002F..",[1060,1482,1483,1488,1491],{},[1078,1484,1485],{},[28,1486,1487],{},"@",[1078,1489,1490],{},"Attribute",[1078,1492,1493],{},[28,1494,1495],{},"\u002F\u002Finput[@name=\"email\"]",[1060,1497,1498,1503,1506],{},[1078,1499,1500],{},[28,1501,1502],{},"[...]",[1078,1504,1505],{},"Filter \u002F condition",[1078,1507,1508],{},[28,1509,1510],{},"\u002F\u002Fbutton[@type=\"submit\"]",[1060,1512,1513,1517,1520],{},[1078,1514,1515],{},[28,1516,872],{},[1078,1518,1519],{},"Any element",[1078,1521,1522],{},[28,1523,1524],{},"\u002F\u002F*[@data-id=\"123\"]",[1060,1526,1527,1532,1535],{},[1078,1528,1529],{},[28,1530,1531],{},"text()",[1078,1533,1534],{},"Element text",[1078,1536,1537],{},[28,1538,1539],{},"\u002F\u002Fbutton[text()=\"Save\"]",[1060,1541,1542,1546,1549],{},[1078,1543,1544],{},[28,1545,1422],{},[1078,1547,1548],{},"Partial match",[1078,1550,1551],{},[28,1552,1553],{},"\u002F\u002Fdiv[contains(@class,\"error\")]",[1060,1555,1556,1564,1567],{},[1078,1557,1558,741,1561],{},[28,1559,1560],{},"[1]",[28,1562,1563],{},"[2]",[1078,1565,1566],{},"Positional match",[1078,1568,1569],{},[28,1570,1571],{},"(\u002F\u002Fbutton)[1]",[1060,1573,1574,1579,1582],{},[1078,1575,1576],{},[28,1577,1578],{},"ancestor::",[1078,1580,1581],{},"Navigate upward",[1078,1583,1584],{},[28,1585,1586],{},"\u002F\u002Finput\u002Fancestor::form",[1060,1588,1589,1594,1597],{},[1078,1590,1591],{},[28,1592,1593],{},"following-sibling::",[1078,1595,1596],{},"Following sibling",[1078,1598,1599],{},[28,1600,1601],{},"\u002F\u002Flabel\u002Ffollowing-sibling::input",[24,1603,1604],{},"There are way more functionalities supported by XPath that you may learn somewhere else.",[36,1606,1608,1609],{"id":1607},"crossing-into-iframes-with","Crossing into iframes with ",[28,1610,1611],{},">>>",[24,1613,1614,1615,1617],{},"Selector chains stop at frame boundaries by default. To cross one, use ",[28,1616,1611],{},":",[75,1619,1621],{"className":77,"code":1620,"language":79,"meta":80,"style":80},"Get Text    iframe#preview >>> h1\nClick       iframe[name=\"editor\"] >>> role=button[name=\"Bold\"]\n",[28,1622,1623,1628],{"__ignoreMap":80},[84,1624,1625],{"class":86,"line":87},[84,1626,1627],{},"Get Text    iframe#preview >>> h1\n",[84,1629,1630],{"class":86,"line":93},[84,1631,1632],{},"Click       iframe[name=\"editor\"] >>> role=button[name=\"Bold\"]\n",[24,1634,1635],{},"No context switching, and no switching back afterwards — the frame boundary is\njust another step in the chain.",[24,1637,1638,1639,1641,1642,1645,1646,1649,1650,1653],{},"Two rules it is easy to trip over. ",[28,1640,1611],{}," must have ",[47,1643,1644],{},"spaces around it",": written\nas ",[28,1647,1648],{},"a>>>b"," it is parsed as ordinary CSS and you get a timeout with no\nexplanation. And the clause immediately before it must select the ",[28,1651,1652],{},"\u003Ciframe>","\nelement itself — under strict mode, exactly one of them.",[36,1655,1657],{"id":1656},"shadow-dom","Shadow DOM",[24,1659,1660],{},"Browser pierces open shadow roots automatically, so a normal chain reaches into\na web component without any special syntax:",[75,1662,1664],{"className":77,"code":1663,"language":79,"meta":80,"style":80},"Get Text    css=my-widget >> css=button\n",[28,1665,1666],{"__ignoreMap":80},[84,1667,1668],{"class":86,"line":87},[84,1669,1663],{},[24,1671,1672],{},"This is one of the places Browser is genuinely ahead of older tools: automatic\npiercing means a component-based frontend does not need a different approach\nfrom any other page.",[24,1674,1675],{},"Closed shadow roots cannot be pierced by anything, by design — if you hit one,\nthat is a conversation with the developers rather than a selector problem.",[24,1677,1678,1679,741,1682,741,1685,1688,1689,1692,1693,297],{},"Piercing is what most engines do — ",[28,1680,1681],{},"css",[28,1683,1684],{},"text",[28,1686,1687],{},"role"," and the attribute\nengines all cross open shadow roots. ",[28,1690,1691],{},"xpath"," does not. Every descendant\ncombinator, including the implicit one at the start of a selector, crosses any\nnumber of open roots. Elements are searched in the light DOM first, then inside\nopen shadow roots, in document order. No engine enters an iframe — that needs\n",[28,1694,1611],{},[66,1696,1698],{"id":1697},"turning-piercing-off","Turning piercing off",[24,1700,1701,1702,1705,1706,1709],{},"One engine stops at the shadow boundary: ",[28,1703,1704],{},"css:light=",", which behaves like\n",[28,1707,1708],{},"document.querySelector"," and follows the CSS spec exactly.",[75,1711,1713],{"className":77,"code":1712,"language":79,"meta":80,"style":80},"# Matches .label inside the component's shadow root\nGet Text    css=my-widget .label\n\n# Matches only if .label is in the light DOM\nGet Text    css:light=my-widget .label\n",[28,1714,1715,1720,1725,1729,1734],{"__ignoreMap":80},[84,1716,1717],{"class":86,"line":87},[84,1718,1719],{},"# Matches .label inside the component's shadow root\n",[84,1721,1722],{"class":86,"line":93},[84,1723,1724],{},"Get Text    css=my-widget .label\n",[84,1726,1727],{"class":86,"line":99},[84,1728,831],{"emptyLinePlaceholder":830},[84,1730,1731],{"class":86,"line":453},[84,1732,1733],{},"# Matches only if .label is in the light DOM\n",[84,1735,1736],{"class":86,"line":469},[84,1737,1738],{},"Get Text    css:light=my-widget .label\n",[24,1740,1741,1742,1744,1745,1747],{},"Reach for ",[28,1743,1704],{}," when you specifically need to assert that something is\n",[54,1746,761],{}," inside a shadow root. The rest of the time the piercing default is what\nyou want.",[41,1749,1750],{"kind":283},[24,1751,1752,1753,1756,1757,741,1760,1763,1764,741,1767,741,1770,744,1772,1775],{},"The other ",[28,1754,1755],{},":light"," engines are deprecated. ",[28,1758,1759],{},"text:light=",[28,1761,1762],{},"id:light=",",\n",[28,1765,1766],{},"xpath:light=",[28,1768,1769],{},"data-testid:light=",[28,1771,1769],{},[28,1773,1774],{},"data-test:light=","\ndo not work anymore.",[36,1777,1779],{"id":1778},"strict-mode","Strict mode",[24,1781,1782],{},"By default, a selector that matches more than one element is an error rather\nthan a silent pick of the first one.",[75,1784,1788],{"className":1785,"code":1786,"language":1787,"meta":80,"style":80},"language-robot shiki shiki-themes github-light github-dark","*** Settings ***\nLibrary    Browser    strict=False    # opt out globally\n","robot",[28,1789,1790,1795],{"__ignoreMap":80},[84,1791,1792],{"class":86,"line":87},[84,1793,1794],{},"*** Settings ***\n",[84,1796,1797],{"class":86,"line":93},[84,1798,1799],{},"Library    Browser    strict=False    # opt out globally\n",[24,1801,1802],{},"Leave it on. A selector matching three elements when you meant one is a bug in\nthe selector, and strict mode tells you immediately instead of at some later\npoint when the order changes.",[36,1804,1806],{"id":1805},"element-references","Element references",[24,1808,1809,1812],{},[28,1810,1811],{},"Get Element"," returns a reference you can pass to other keywords, so an\nexpensive lookup is done once:",[75,1814,1816],{"className":77,"code":1815,"language":79,"meta":80,"style":80},"${button} =    Get Element    role=button[name=\"Save\"]\nGet Element States    ${button}    contains    enabled\nClick    ${button}\n",[28,1817,1818,1823,1828],{"__ignoreMap":80},[84,1819,1820],{"class":86,"line":87},[84,1821,1822],{},"${button} =    Get Element    role=button[name=\"Save\"]\n",[84,1824,1825],{"class":86,"line":93},[84,1826,1827],{},"Get Element States    ${button}    contains    enabled\n",[84,1829,1830],{"class":86,"line":99},[84,1831,1832],{},"Click    ${button}\n",[24,1834,1835,1836,1839,1840,1843],{},"What you get back is a ",[47,1837,1838],{},"selector string"," — the selector Playwright resolved for\nthat element — not a snapshot of the DOM node. ",[28,1841,1842],{},"Get Elements"," returns a list of\nthem.",[24,1845,1846,1847,1850],{},"That distinction matters in both directions. It re-resolves on every use, so it\nsurvives a re-render that would invalidate a stored node. But it is only a\nselector, so it goes in the ",[54,1848,1849],{},"first"," clause of a chain and nothing more:",[75,1852,1854],{"className":77,"code":1853,"language":79,"meta":80,"style":80},"${row} =    Get Element    css=tr.selected\nClick       ${row} >> css=button.delete    # relative to the row\n",[28,1855,1856,1861],{"__ignoreMap":80},[84,1857,1858],{"class":86,"line":87},[84,1859,1860],{},"${row} =    Get Element    css=tr.selected\n",[84,1862,1863],{"class":86,"line":93},[84,1864,1865],{},"Click       ${row} >> css=button.delete    # relative to the row\n",[24,1867,1868,1869,1871,1872,1875,1876,1879],{},"A reference works like any other first clause, ",[28,1870,1611],{}," included: if it points at\nan iframe, ",[28,1873,1874],{},"${frame} >>> h1"," crosses into it. And there is no ",[28,1877,1878],{},"element="," prefix —\nthe value is already an ordinary selector, so it needs no strategy in front of it.",[36,1881,1883],{"id":1882},"in-short","In short",[1885,1886,1887,1892,1898,1903,1911,1916,1922],"ul",{},[125,1888,1741,1889,1891],{},[28,1890,72],{}," first. If you cannot, you may have found an accessibility bug.",[125,1893,1894,1895,1897],{},"Use ",[28,1896,162],{}," when stability is the priority, knowing what it trades away.",[125,1899,1900,1902],{},[28,1901,192],{}," is fine until you localise.",[125,1904,1905,1907,1908,1910],{},[28,1906,304],{}," is acceptable; ",[28,1909,257],{}," is less stable than it looks.",[125,1912,1913,1915],{},[28,1914,340],{}," last, and never by document position.",[125,1917,1918,1919,1921],{},"Two obvious selectors chained with ",[28,1920,804],{}," beat one clever one.",[125,1923,1924,744,1926,1928],{},[28,1925,935],{},[28,1927,985],{}," are filters, not strategies: they narrow the step\nbefore them, and their order in a chain changes the answer.",[1930,1931,1932],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":80,"searchDepth":93,"depth":93,"links":1934},[1935,1951,1952,1958,1964,1969,1970,1972,1975,1976,1977],{"id":38,"depth":93,"text":39,"children":1936},[1937,1939,1941,1943,1945,1947,1949],{"id":68,"depth":99,"text":1938},"1. role= — how the user finds it",{"id":158,"depth":99,"text":1940},"2. data-testid= — the one attribute that belongs to us",{"id":188,"depth":99,"text":1942},"3. text= — what is written on it",{"id":253,"depth":99,"text":1944},"4. id= — less stable than it looks",{"id":300,"depth":99,"text":1946},"5. css= — acceptable, not preferable",{"id":336,"depth":99,"text":1948},"6. xpath= — the last resort",{"id":709,"depth":99,"text":1950},"Also available: the data-testid aliases",{"id":730,"depth":93,"text":731},{"id":800,"depth":93,"text":1953,"children":1954},"Cascading Selectors with >>",[1955,1956],{"id":861,"depth":99,"text":862},{"id":891,"depth":99,"text":1957},"When >> appears in the text you are matching",{"id":910,"depth":93,"text":911,"children":1959},[1960,1962],{"id":932,"depth":99,"text":1961},"nth= — pick one out of many",{"id":982,"depth":99,"text":1963},"visible= — keep only what can be seen",{"id":1047,"depth":93,"text":1048,"children":1965},[1966,1967,1968],{"id":1051,"depth":99,"text":1052},{"id":1279,"depth":99,"text":1280},{"id":1341,"depth":99,"text":1342},{"id":1398,"depth":93,"text":1399},{"id":1607,"depth":93,"text":1971},"Crossing into iframes with >>>",{"id":1656,"depth":93,"text":1657,"children":1973},[1974],{"id":1697,"depth":99,"text":1698},{"id":1778,"depth":93,"text":1779},{"id":1805,"depth":93,"text":1806},{"id":1882,"depth":93,"text":1883},"Which selector strategy to reach for, why the order matters, and how to chain across iframes and shadow DOM.","md",{},"\u002Fdocs\u002Fconcepts\u002Fselectors","concepts",{"title":19,"description":1978},"docs\u002Fconcepts\u002Fselectors","qw4mOZigsUFf7kFTJGkUVexFU25P2rpfvkpLJvXqOnc",[1987,1991,1995,1999,2003,2004,2008,2013,2017,2021,2025,2030,2034,2038,2043,2047,2051,2055],{"path":1988,"title":1989,"description":1990,"section":1982,"order":469},"\u002Fdocs\u002Fconcepts\u002Farchitecture","How Browser works","Python, a Node process and Playwright — what runs where, and which installation to choose.",{"path":1992,"title":1993,"description":1994,"section":1982,"order":99},"\u002Fdocs\u002Fconcepts\u002Fassertions","Assertions","Almost every getter can assert, every assertion retries, and each return type allows different operators. The complete AssertionEngine reference.",{"path":1996,"title":1997,"description":1998,"section":1982,"order":87},"\u002Fdocs\u002Fconcepts\u002Fbrowser-context-page","Browser, context and page","The three layers Playwright is built on — and why a new context is the cheapest clean slate you will ever get.",{"path":2000,"title":2001,"description":2002,"section":1982,"order":495},"\u002Fdocs\u002Fconcepts\u002Flogging","Logging and debugging","Four places Browser tells you what happened — log.html, the Playwright log, traces and coverage.",{"path":1981,"title":19,"description":1978,"section":1982,"order":93},{"path":2005,"title":2006,"description":2007,"section":1982,"order":453},"\u002Fdocs\u002Fconcepts\u002Fwaiting","Waiting and promises","Most waiting is already done for you. This is the rest of it — Wait For Condition, and how to run keywords in parallel with Promise To.",{"path":2009,"title":2010,"description":2011,"section":2012,"order":93},"\u002Fdocs\u002Fextending\u002Fjavascript-basics","Just enough JavaScript","The JavaScript you need to write extensions and Evaluate JavaScript, for people who write Python.","extending",{"path":2014,"title":2015,"description":2016,"section":2012,"order":99},"\u002Fdocs\u002Fextending\u002Fjavascript-extensions","JavaScript extensions","Add keywords that run on the Node side with full Playwright access — and attach a debugger to them.",{"path":2018,"title":2019,"description":2020,"section":2012,"order":87},"\u002Fdocs\u002Fextending\u002Fpython-plugins","Python plugins","Add or replace keywords from Python, with access to the library's own API and to the Node side.",{"path":2022,"title":2023,"description":2024,"section":2012,"order":453},"\u002Fdocs\u002Fextending\u002Ftranslations","Translating keywords","Using a translation package to get keyword names and documentation in your language, and writing one for a language nobody has covered yet.",{"path":2026,"title":2027,"description":2028,"section":2029,"order":93},"\u002Fdocs\u002Fmobile\u002Fdevices","Emulating a device","Device descriptors, what the six or seven properties they set actually change, and the one that emulation cannot give you.","mobile",{"path":2031,"title":2032,"description":2033,"section":2029,"order":87},"\u002Fdocs\u002Fmobile\u002Fresponsive","Testing responsive pages","Why a browser at a phone-sized viewport tests responsiveness properly, what emulation cannot tell you, and where real devices actually belong.",{"path":2035,"title":2036,"description":2037,"section":2029,"order":99},"\u002Fdocs\u002Fmobile\u002Ftouch","Touch, permissions and location","Tap and why it needs hasTouch, granting camera or notifications without a dialog, faking where the user is, and the gestures Browser has no keyword for.",{"path":2039,"title":2040,"description":2041,"section":2042,"order":99},"\u002Fdocs\u002Foperations\u002Fdocker","Running in Docker","The published Browser image, the four docker run flags that matter, and why running as root breaks Chromium.","operations",{"path":2044,"title":2045,"description":2046,"section":2042,"order":453},"\u002Fdocs\u002Foperations\u002Fdocker-images","Building your own image","Extending the published image with your dependencies, the version lock between Browser and Playwright, and using it as a CI job container.",{"path":2048,"title":2049,"description":2050,"section":2042,"order":93},"\u002Fdocs\u002Foperations\u002Fenvironment-variables","Environment variables","The variables that change how Browser behaves, which of them are safe in production, and the import parameters that should usually be preferred.",{"path":2052,"title":2053,"description":2054,"section":2042,"order":87},"\u002Fdocs\u002Foperations\u002Fnode-process","The Node process","Browser starts a Node process to talk to Playwright. What that costs, how to share one across parallel runs, and how to pass it Node flags.",{"path":2056,"title":2057,"description":2058,"section":2059,"order":87},"\u002Fdocs\u002Fstart\u002Fgetting-started","Getting started","Install Browser, write a first test, and run it.","start",[2061,2064,2067],{"to":2062,"label":2063},"\u002Fwhy\u002Fvs-cypress","vs Cypress",{"to":2065,"label":2066},"\u002Fwhy\u002Fvs-playwright","vs Playwright Test",{"to":2068,"label":2069},"\u002Fwhy\u002Fvs-seleniumlibrary","vs SeleniumLibrary",[2071],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    role=button[name=\"Save\"]\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    role=link[name=\"Get started\"]\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Fill Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    role=textbox[name=\"Email\"]    admin@example.com\u003C\u002Fspan>\u003C\u002Fspan>",[2073],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    [data-testid=\"checkout-submit\"]\u003C\u002Fspan>\u003C\u002Fspan>",[2075],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    text=Sign in\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    \"Sign in\"\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    text=\u002F^Sign in$\u002Fi\u003C\u002Fspan>\u003C\u002Fspan>",[2077],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    id=submit-button\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    \\#submit-button\u003C\u002Fspan>\u003C\u002Fspan>",[2079],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=button.primary\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    .checkout > button\u003C\u002Fspan>\u003C\u002Fspan>",[2081],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    xpath=\u002F\u002Fbutton[@type=\"submit\"]\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    \u002F\u002Fdiv[@class=\"row\"]\u002F\u002Fbutton\u003C\u002Fspan>\u003C\u002Fspan>",[2083],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    \u002Fhtml\u002Fbody\u002Fdiv[3]\u002Fdiv\u002Fdiv[2]\u002Fbutton\u003C\u002Fspan>\u003C\u002Fspan>",[2085],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    xpath=\u002F\u002Flabel[text()=\"Email\"]\u002F..\u002F\u002Fbutton[@aria-label=\"More information\"]\u003C\u002Fspan>\u003C\u002Fspan>",[2087],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    role=textbox[name=\"Email\"] >> xpath=.. >> role=button[name=\"More information\"]\u003C\u002Fspan>\u003C\u002Fspan>",[2089],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Element\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    \u002F\u002Fhtml\u002Fbody\u002Fdiv\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA;font-style:italic\">      # xpath\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Element\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    \"foo\"\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA;font-style:italic\">                # text exact match\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Element\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    div\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA;font-style:italic\">                  # css\u003C\u002Fspan>\u003C\u002Fspan>",[2091],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># Find the element with text \"Login\", then the input beside it\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    \"Login\" >> xpath=..\u002Finput\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># Find a css element, then a button inside it by text\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=.checkout >> text=Confirm\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># Start with a role, narrow with css\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    role=listitem >> css=.price\u003C\u002Fspan>\u003C\u002Fspan>",[2093],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># The article that contains \"Hello\" — not the text node inside it\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Element\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    *css=article >> text=Hello\u003C\u002Fspan>\u003C\u002Fspan>",[2095],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    text=\"some >> text\"\u003C\u002Fspan>\u003C\u002Fspan>",[2097],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=.result >> nth=0\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA;font-style:italic\">     # the first result\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=.result >> nth=2\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA;font-style:italic\">     # the third\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=.result >> nth=-1\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA;font-style:italic\">    # the last\u003C\u002Fspan>\u003C\u002Fspan>",[2099],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=button.save >> visible=true\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Element Count\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=.row >> visible=false    ==    2\u003C\u002Fspan>\u003C\u002Fspan>",[2101],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># Third input among the visible ones\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    \u002F\u002Finput >> visible=true >> nth=2\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># Third input in the DOM, then check it happens to be visible\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    \u002F\u002Finput >> nth=2 >> visible=true\u003C\u002Fspan>\u003C\u002Fspan>",[2103],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># The row that contains the name, then the button inside that row\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=tr:has-text(\"Ada Lovelace\") >> role=button[name=\"Edit\"]\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># A card that contains an image, rather than a card whose text mentions one\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=.card:has(img) >> css=.title\u003C\u002Fspan>\u003C\u002Fspan>",[2105],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Fill Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=input:right-of(:text(\"Postcode\")) >> nth=0    00100\u003C\u002Fspan>\u003C\u002Fspan>",[2107],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    iframe#preview >>> h1\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">       iframe[name=\"editor\"] >>> role=button[name=\"Bold\"]\u003C\u002Fspan>\u003C\u002Fspan>",[2109],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=my-widget >> css=button\u003C\u002Fspan>\u003C\u002Fspan>",[2111],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># Matches .label inside the component's shadow root\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=my-widget .label\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#96A2A8;font-style:italic\"># Matches only if .label is in the light DOM\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Text\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css:light=my-widget .label\u003C\u002Fspan>\u003C\u002Fspan>",[2113],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#FF6B5E;font-weight:bold\">*** Settings ***\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#C9A6E9\">Library\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    Browser\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    strict=False\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA;font-style:italic\">    # opt out globally\u003C\u002Fspan>\u003C\u002Fspan>",[2115],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#A9B4BA\">${\u003C\u002Fspan>\u003Cspan style=\"color:#FFB86B\">button\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA\">}\u003C\u002Fspan>\u003Cspan style=\"color:#FF6B5E\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#2BD9CC\">    Get Element\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    role=button[name=\"Save\"]\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Get Element States\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA\">    ${\u003C\u002Fspan>\u003Cspan style=\"color:#FFB86B\">button\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA\">}\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    contains    enabled\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA\">    ${\u003C\u002Fspan>\u003Cspan style=\"color:#FFB86B\">button\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA\">}\u003C\u002Fspan>\u003C\u002Fspan>",[2117],"\u003Cspan class=\"line\">\u003Cspan style=\"color:#A9B4BA\">${\u003C\u002Fspan>\u003Cspan style=\"color:#FFB86B\">row\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA\">}\u003C\u002Fspan>\u003Cspan style=\"color:#FF6B5E\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#2BD9CC\">    Get Element\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\">    css=tr.selected\u003C\u002Fspan>\u003C\u002Fspan>\u003Cspan class=\"line\">\u003Cspan style=\"color:#2BD9CC\">Click\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA\">       ${\u003C\u002Fspan>\u003Cspan style=\"color:#FFB86B\">row\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA\">}\u003C\u002Fspan>\u003Cspan style=\"color:#E0D3A3\"> >> css=button.delete\u003C\u002Fspan>\u003Cspan style=\"color:#A9B4BA;font-style:italic\">    # relative to the row\u003C\u002Fspan>\u003C\u002Fspan>",1786734137761]