🔥《最新出爐》系列初窺篇-Python+Playwright 自動化測試-6-元素定位大法 - 下篇

北京-宏哥發表於2024-11-07

1.簡介

上一篇主要是講解我們日常工作中在使用 Playwright 進行元素定位的一些比較常用的定位方法的理論基礎知識以及在什麼情況下推薦使用。今天這一篇講解和分享一下,在日常中很少用到或者很少見的定位,但是遇到了我們也要會,俗話說:手裡有糧心裡不慌。

2.陰影定位-Shadow DOM

在做 web 自動化的時候,一些元素在 shadow-root 的節點下,使得 playwright 中無法透過 xpath 來定位

上面所看到的 shadow-root 標籤其實就是一個 shadowDOM,那麼什麼是 shadowDOM 呢?

他是前端的一種頁面封裝技術,可以將 shadowDOM 視為 “DOM 中的 DOM”(可以看成一個隱藏的 DOM)

他是一個獨立的 DOM 樹,具有自己的元素和樣式,與原始文件 DOM 完全隔離。

shadowDOM 必須附在一個 HTML 元素中,存放 shadowDOM 的元素,我們可以把它稱為宿主元素。在 HTML5 中有很多的標籤樣式都是透過 shadowDOM 來實現的。

比如:日期選擇框,音訊播放標籤,影片播放標籤都自帶了樣式;(這種封裝對於前端開發來說雖好,但是我們測試人員在做 web 自動給的時候就會遇到一些問題,shadowDOM 中的標籤無法定位。)

預設情況下,Playwright 中的所有定位器都使用 Shadow DOM 中的元素。例外情況是:

  • 透過 XPath 定位不會刺穿陰影根部。
  • 不支援閉合模式影子根。

例如:以下示例和自定義 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>

您可以採用與影子根根本不存在相同的方式進行定位。

要單擊 :<div>Details</div>

page.get_by_text("Details").click()

要單擊 :<x-details>

page.locator("x-details", has_text="Details" ).click()

要確保包含文字 “詳細資訊”,請執行以下操作:<x-details>

expect(page.locator("x-details")).to_contain_text("Details")

3.過濾器定位-Filtering

例如以下 DOM 結構,我們要在其中單擊第二個產品卡的購買按鈕。我們有幾個選項來過濾定位器以獲得正確的定位器。

3.1 文字過濾

定位器可以使用 locator.filter()方法按文字進行過濾。它將搜尋元素內某處的特定字串,可能在後代元素中,不區分大小寫。您還可以傳遞正規表示式。

1.使用文字

page.get_by_role("listitem").filter(has_text="Product 2").get_by_role(
    "button", name="Add to cart"
).click()

2.使用正規表示式

page.get_by_role("listitem").filter(has_text=re.compile("Product 2")).get_by_role(
    "button", name="Add to cart"
).click()

3.透過沒有文字進行篩選

# 5 in-stock items
expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)

3.2 子項/後代過濾

定位器支援僅選擇具有或沒有與其他定位器匹配的後代的元素的選項。因此,您可以按任何其他定位器進行過濾,例如 locator.get_by_role()、locator.get_by_test_id()、locator.get_by_text()等。

1.使用子項

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.使用產品卡斷言,確保只有一個

expect(
    page.get_by_role("listitem").filter(
        has=page.get_by_role("heading", name="Product 2")
    )
).to_have_count(1)

3.透過內部沒有匹配的元素進行過濾

expect(
    page.get_by_role("listitem").filter(
        has_not=page.get_by_role("heading", name="Product 2")
    )
).to_have_count(1)

敲黑板!!!!注意:內部定位器從外部定位器開始匹配,而不是從文件根目錄匹配。

3.3 匹配其他定位進行過濾

方法 locator.and_()透過匹配其他定位器來縮小現有定位器的範圍。例如,您可以組合 page.get_by_role()和 page.get_by_title()以按角色和頭銜進行匹配。

button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))

4.連結定位器

您可以連結建立定位器的方法(如 page.get_by_text()或 locator.get_by_role()),以將搜尋範圍縮小到頁面的特定部分。

在此示例中,我們首先透過定位其角色:listitem 來建立一個名為 product 的定位器。然後我們按文字過濾。我們可以再次使用產品定位器按按鈕的角色獲取並單擊它,然後使用斷言來確保只有一個帶有文字 “產品 2” 的產品。

product = page.get_by_role("listitem").filter(has_text="Product 2")

product.get_by_role("button", name="Add to cart").click()

您還可以將兩個定位器連結在一起,例如在特定對話方塊中查詢 “儲存” 按鈕:

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 對列表中的專案進行計數

可以斷言定位器以對列表中的專案進行計數。例如:一下 DOM 結構

使用計數斷言確保列表包含 3 個專案。

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 定位特定專案

有許多方法可以在列表中定位特定專案。

5.3.1 透過文字定位

使用 page.get_by_text()方法透過文字內容在列表中查詢元素,然後單擊它。

透過文字內容找到專案並單擊它。

page.get_by_text("orange").click()
5.3.2 透過文字過濾定位

使用 locator.filter()在列表中查詢特定專案。按 “列表項” 的角色找到一個專案,然後按 “橙色” 的文字進行篩選,然後單擊它。

page.get_by_role("listitem").filter(has_text="orange").click()
5.3.3 透過測試 id 定位

使用 page.get_by_test_id()方法在列表中查詢元素。如果您還沒有測試 ID,則可能需要修改 html 並新增測試 ID。

透過測試 ID“橙色” 找到專案,然後單擊它。

page.get_by_test_id("orange").click()
5.3.4 透過第 n 項定位

如果你有一個相同元素的列表,並且區分它們的唯一方法是順序,你可以從帶有 locator.first、locator.last 或 locator.nth()的列表中選擇一個特定的元素。

banana = page.get_by_role("listitem").nth(1)

但是,請謹慎使用此方法。通常,頁面可能會更改,並且定位器將指向與預期完全不同的元素。相反,嘗試提出一個透過嚴格標準的獨特定位器。

5.4 連結過濾器

當您有各種相似性的元素時,可以使用 locator.filter()方法選擇正確的元素。您還可以連結多個篩選器以縮小選擇範圍。

要擷取帶有 “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 對列表中每個元素執行操作

迭代元素

for row in page.get_by_role("listitem").all():
    print(row.text_content())

使用常規 for 迴圈進行迭代:

rows = page.get_by_role("listitem")
count = rows.count()
for i in range(count):
    print(rows.nth(i).text_content())
5.5.2 在頁面中評估

locator.evaluate_all()中的程式碼在頁面中執行,您可以在那裡呼叫任何 DOM API。

rows = page.get_by_role("listitem")
texts = rows.evaluate_all("list => list.map(element => element.textContent)")

6.小結

定位器是非常嚴格。這意味著,如果多個元素匹配,則對定位器執行暗示某些目標 DOM 元素的所有操作都將引發異常。例如,如果 DOM 中有多個按鈕,則會引發以下呼叫:

如果有多個 button,則引發錯誤

page.get_by_role("button").click()

另一方面,Playwright 瞭解何時執行多元素操作,因此當定位器解析為多個元素時,以下呼叫工作正常。

適用於多個元素

page.get_by_role("button").count()

您可以透過 locator.first、locator.last 和 locator.nth()告訴 Playwright 在多個元素匹配時使用哪個元素來明確選擇退出嚴格性檢查。不建議使用這些方法,因為當您的頁面更改時,Playwright 可能會單擊您不想要的元素。相反,請按照上述最佳實踐建立唯一標識目標元素的定位器。

6.1 其他定位器

對於不太常用的定位器,請檢視官網的其他定位器指南。由於時間關係,宏哥就不在這裡對其進行展開介紹和講解了。好了時間不早了,關於元素定位大法今天就分享到這裡!!!僅供大家學習參考,感謝您耐心的閱讀。

相關文章