1.簡(jiǎn)介
上一篇主要是講解我們?nèi)粘9ぷ髦性谑褂肞laywright進(jìn)行元素定位的一些比較常用的定位方法的理論基礎(chǔ)知識(shí)以及在什么情況下推薦使用。今天這一篇講解和分享一下,在日常中很少用到或者很少見的定位,但是遇到了我們也要會(huì),俗話說(shuō):手里有糧心里不慌。
2.陰影定位-Shadow DOM
在做web自動(dòng)化的時(shí)候,一些元素在shadow-root的節(jié)點(diǎn)下,使得playwright中無(wú)法通過(guò)xpath來(lái)定位

上面所看到的shadow-root標(biāo)簽其實(shí)就是一個(gè)shadowDOM,那么什么是shadowDOM呢?
他是前端的一種頁(yè)面封裝技術(shù),可以將shadowDOM視為“DOM中的DOM”(可以看成一個(gè)隱藏的DOM)
他是一個(gè)獨(dú)立的DOM樹,具有自己的元素和樣式,與原始文檔DOM完全隔離。
shadowDOM必須附在一個(gè)HTML元素中,存放shadowDOM的元素,我們可以把它稱為宿主元素。在HTML5中有很多的標(biāo)簽樣式都是通過(guò)shadowDOM來(lái)實(shí)現(xiàn)的。
比如:日期選擇框,音頻播放標(biāo)簽,視頻播放標(biāo)簽都自帶了樣式;(這種封裝對(duì)于前端開發(fā)來(lái)說(shuō)雖好,但是我們測(cè)試人員在做web自動(dòng)給的時(shí)候就會(huì)遇到一些問(wèn)題,shadowDOM中的標(biāo)簽無(wú)法定位。)
默認(rèn)情況下,Playwright 中的所有定位器都使用 Shadow DOM 中的元素。例外情況是:
- 通過(guò) XPath 定位不會(huì)刺穿陰影根部。
- 不支持閉合模式影子根。
例如:以下示例和自定義 Web 組件:
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
您可以采用與影子根根本不存在相同的方式進(jìn)行定位。
要單擊 :<div>Details</div>
page.get_by_text("Details").click()
要單擊 :<x-details>
page.locator("x-details", has_text="Details" ).click()
要確保包含文本“詳細(xì)信息”,請(qǐng)執(zhí)行以下操作:<x-details>
expect(page.locator("x-details")).to_contain_text("Details")
3.過(guò)濾器定位-Filtering
例如以下 DOM 結(jié)構(gòu),我們要在其中單擊第二個(gè)產(chǎn)品卡的購(gòu)買按鈕。我們有幾個(gè)選項(xiàng)來(lái)過(guò)濾定位器以獲得正確的定位器。

3.1文本過(guò)濾
定位器可以使用 locator.filter()方法按文本進(jìn)行過(guò)濾。它將搜索元素內(nèi)某處的特定字符串,可能在后代元素中,不區(qū)分大小寫。您還可以傳遞正則表達(dá)式。
1.使用文本
page.get_by_role("listitem").filter(has_text="Product 2").get_by_role(
"button", name="Add to cart"
).click()
2.使用正則表達(dá)式
page.get_by_role("listitem").filter(has_text=re.compile("Product 2")).get_by_role(
"button", name="Add to cart"
).click()
3.通過(guò)沒(méi)有文本進(jìn)行篩選
# 5 in-stock items
expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)
3.2子項(xiàng)/后代過(guò)濾
定位器支持僅選擇具有或沒(méi)有與其他定位器匹配的后代的元素的選項(xiàng)。因此,您可以按任何其他定位器進(jìn)行過(guò)濾,例如 locator.get_by_role()、locator.get_by_test_id()、locator.get_by_text() 等。
1.使用子項(xiàng)
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
).get_by_role("button", name="Add to cart").click()
2.使用產(chǎn)品卡斷言,確保只有一個(gè)
expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
3.通過(guò)內(nèi)部沒(méi)有匹配的元素進(jìn)行過(guò)濾
expect(
page.get_by_role("listitem").filter(
has_not=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
敲黑板!?。?!注意:內(nèi)部定位器從外部定位器開始匹配,而不是從文檔根目錄匹配。
3.3匹配其他定位進(jìn)行過(guò)濾
方法 locator.and_() 通過(guò)匹配其他定位器來(lái)縮小現(xiàn)有定位器的范圍。例如,您可以組合 page.get_by_role() 和 page.get_by_title() 以按角色和頭銜進(jìn)行匹配。
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
4.鏈接定位器
您可以鏈接創(chuàng)建定位器的方法(如 page.get_by_text() 或 locator.get_by_role()),以將搜索范圍縮小到頁(yè)面的特定部分。
在此示例中,我們首先通過(guò)定位其角色:listitem 來(lái)創(chuàng)建一個(gè)名為 product 的定位器。然后我們按文本過(guò)濾。我們可以再次使用產(chǎn)品定位器按按鈕的角色獲取并單擊它,然后使用斷言來(lái)確保只有一個(gè)帶有文本“產(chǎn)品 2”的產(chǎn)品。
product = page.get_by_role("listitem").filter(has_text="Product 2")
product.get_by_role("button", name="Add to cart").click()
您還可以將兩個(gè)定位器鏈接在一起,例如在特定對(duì)話框中查找“保存”按鈕:
save_button = page.get_by_role("button", name="Save")
# ...
dialog = page.get_by_test_id("settings-dialog")
dialog.locator(save_button).click()
5.列表
5.1對(duì)列表中的項(xiàng)目進(jìn)行計(jì)數(shù)
可以斷言定位器以對(duì)列表中的項(xiàng)目進(jìn)行計(jì)數(shù)。例如:一下DOM結(jié)構(gòu)

使用計(jì)數(shù)斷言確保列表包含 3 個(gè)項(xiàng)目。
expect(page.get_by_role("listitem")).to_have_count(3)
5.2斷言列表中所有文本
可以斷言定位器以查找列表中的所有文本。使用 expect(定位器).to_have_text() 確保列表包含文本“蘋果”、“香蕉”和“橙色”。
expect(page.get_by_role("listitem")).to_have_text(["apple", "banana", "orange"])
5.3定位特定項(xiàng)目
有許多方法可以在列表中定位特定項(xiàng)目。
5.3.1通過(guò)文本定位
使用 page.get_by_text() 方法通過(guò)文本內(nèi)容在列表中查找元素,然后單擊它。

通過(guò)文本內(nèi)容找到項(xiàng)目并單擊它。
page.get_by_text("orange").click()
5.3.2通過(guò)文本過(guò)濾定位
使用 locator.filter() 在列表中查找特定項(xiàng)目。按“列表項(xiàng)”的角色找到一個(gè)項(xiàng)目,然后按“橙色”的文本進(jìn)行篩選,然后單擊它。
page.get_by_role("listitem").filter(has_text="orange").click()
5.3.3通過(guò)測(cè)試id定位
使用 page.get_by_test_id() 方法在列表中查找元素。如果您還沒(méi)有測(cè)試 ID,則可能需要修改 html 并添加測(cè)試 ID。

通過(guò)測(cè)試ID“橙色”找到項(xiàng)目,然后單擊它。
page.get_by_test_id("orange").click()
5.3.4通過(guò)第n項(xiàng)定位
如果你有一個(gè)相同元素的列表,并且區(qū)分它們的唯一方法是順序,你可以從帶有 locator.first、locator.last 或 locator.nth() 的列表中選擇一個(gè)特定的元素。
banana = page.get_by_role("listitem").nth(1)
但是,請(qǐng)謹(jǐn)慎使用此方法。通常,頁(yè)面可能會(huì)更改,并且定位器將指向與預(yù)期完全不同的元素。相反,嘗試提出一個(gè)通過(guò)嚴(yán)格標(biāo)準(zhǔn)的獨(dú)特定位器。
5.4鏈接過(guò)濾器
當(dāng)您有各種相似性的元素時(shí),可以使用 locator.filter()方法選擇正確的元素。您還可以鏈接多個(gè)篩選器以縮小選擇范圍。

要截取帶有“Mary”和“Say goodbye”的行的屏幕截圖:
row_locator = page.get_by_role("listitem")
row_locator
.filter(has_text="Mary")
.filter(has=page.get_by_role("button", name="Say goodbye"))
.screenshot(path="screenshot.png")
5.5罕見例子
5.5.1對(duì)列表中每個(gè)元素執(zhí)行操作
迭代元素
for row in page.get_by_role("listitem").all():
print(row.text_content())
使用常規(guī) for 循環(huán)進(jìn)行迭代:
rows = page.get_by_role("listitem")
count = rows.count()
for i in range(count):
print(rows.nth(i).text_content())
5.5.2在頁(yè)面中評(píng)估
locator.evaluate_all()中的代碼在頁(yè)面中運(yùn)行,您可以在那里調(diào)用任何 DOM API。
rows = page.get_by_role("listitem")
texts = rows.evaluate_all("list => list.map(element => element.textContent)")
6.小結(jié)
定位器是非常嚴(yán)格。這意味著,如果多個(gè)元素匹配,則對(duì)定位器執(zhí)行暗示某些目標(biāo) DOM 元素的所有操作都將引發(fā)異常。例如,如果 DOM 中有多個(gè)按鈕,則會(huì)引發(fā)以下調(diào)用:
6.1如果有多個(gè)button,則引發(fā)錯(cuò)誤
page.get_by_role("button").click()
另一方面,Playwright 了解何時(shí)執(zhí)行多元素操作,因此當(dāng)定位器解析為多個(gè)元素時(shí),以下調(diào)用工作正常。
6.2適用于多個(gè)元素
page.get_by_role("button").count()
您可以通過(guò) locator.first、locator.last 和 locator.nth() 告訴 Playwright 在多個(gè)元素匹配時(shí)使用哪個(gè)元素來(lái)明確選擇退出嚴(yán)格性檢查。不建議使用這些方法,因?yàn)楫?dāng)您的頁(yè)面更改時(shí),Playwright 可能會(huì)單擊您不想要的元素。相反,請(qǐng)按照上述最佳實(shí)踐創(chuàng)建唯一標(biāo)識(shí)目標(biāo)元素的定位器。
6.3其他定位器
對(duì)于不太常用的定位器,請(qǐng)查看官網(wǎng)的其他定位器指南。由于時(shí)間關(guān)系,宏哥就不在這里對(duì)其進(jìn)行展開介紹和講解了。好了時(shí)間不早了,關(guān)于元素定位大法今天就分享到這里!??!僅供大家學(xué)習(xí)參考,感謝您耐心的閱讀。