定位器
簡介
Locator 是 Playwright 自動等待和重試功能的核心部分。簡而言之,locators 代表了一種在任何時刻在頁面上找到元素的方法。
快速指南
這些是推薦的內建定位器。
- page.get_by_role() 透過顯性和隱性無障礙屬性來定位。
- page.get_by_text() 透過文本內容來定位。
- page.get_by_label() 透過關聯標籤的文本來定位表單控制項。
- page.get_by_placeholder() 透過佔位符來定位輸入框。
- page.get_by_alt_text() 透過文本替代來定位元素,通常是圖片。
- page.get_by_title() 透過標題屬性來定位元素。
- page.get_by_test_id() 透過其
data-testid
屬性來定位元素(其他屬性可以配置)。
- Sync
- Async
page.get_by_label("User Name").fill("John")
page.get_by_label("Password").fill("secret-password")
page.get_by_role("button", name="Sign in").click()
expect(page.get_by_text("Welcome, John!")).to_be_visible()
await page.get_by_label("User Name").fill("John")
await page.get_by_label("Password").fill("secret-password")
await page.get_by_role("button", name="Sign in").click()
await expect(page.get_by_text("Welcome, John!")).to_be_visible()
定位元素
Playwright 附帶多個內建定位器。為了使測試具有彈性,我們建議優先考慮面向使用者的屬性和明確的合約,例如 page.get_by_role。
例如,考慮以下的 DOM 結構。
<button>Sign in</button>
找到角色為 button
且名稱為 "Sign in" 的元素。
- Sync
- Async
page.get_by_role("button", name="Sign in").click()
await page.get_by_role("button", name="Sign in").click()
使用 程式碼產生器 來生成定位器,然後根據需要進行編輯。
每次使用定位器執行動作時,會在頁面中定位最新的 DOM 元素。在下面的程式碼片段中,基礎的 DOM 元素將被定位兩次,每次動作前各一次。這意味著如果在呼叫之間由於重新渲染而導致 DOM 發生變化,將使用與定位器對應的新元素。
- Sync
- Async
locator = page.get_by_role("button", name="Sign in")
locator.hover()
locator.click()
locator = page.get_by_role("button", name="Sign in")
await locator.hover()
await locator.click()
請注意,所有建立定位器的方法,例如 page.get_by_label(),也可在 Locator 和 FrameLocator 類別上使用,因此您可以將它們鏈接起來並逐步縮小定位器範圍。
- Sync
- Async
locator = page.frame_locator("my-frame").get_by_role("button", name="Sign in")
locator.click()
locator = page.frame_locator("#my-frame").get_by_role("button", name="Sign in")
await locator.click()
根據角色定位
page.get_by_role() 定位器反映了使用者和輔助技術如何感知頁面,例如某些元素是按鈕還是複選框。當按角色定位時,通常應該同時傳遞可訪問名稱,以便定位器精確定位到具體元素。
例如,考慮以下的 DOM 結構。
註冊
<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>
您可以通過其隱含角色定位每個元素:
- Sync
- Async
expect(page.get_by_role("heading", name="Sign up")).to_be_visible()
page.get_by_role("checkbox", name="Subscribe").check()
page.get_by_role("button", name=re.compile("submit", re.IGNORECASE)).click()
await expect(page.get_by_role("heading", name="Sign up")).to_be_visible()
await page.get_by_role("checkbox", name="Subscribe").check()
await page.get_by_role("button", name=re.compile("submit", re.IGNORECASE)).click()
角色定位器包括按鈕、複選框、標題、連結、清單、表格等,並遵循 W3C 的 ARIA 角色、ARIA 屬性和可訪問名稱規範。請注意,許多 html 元素如 <button>
具有隱式定義的角色,這些角色被角色定位器識別。
請注意,角色定位器不會取代可及性審計和符合性測試,而是提供有關 ARIA 指南的早期反饋。
我們建議優先使用角色定位器來定位元素,因為這是最接近 用戶和輔助技術感知頁面的方式。
Locate by label
大多數表單控制項通常都有專用的標籤,可以方便地用來與表單互動。在這種情況下,你可以使用 page.get_by_label 根據其關聯的標籤來定位控制項。
例如,考慮以下的 DOM 結構。
<label>Password <input type="password" /></label>
您可以在找到標籤文字後填寫輸入:
- Sync
- Async
page.get_by_label("Password").fill("secret")
await page.get_by_label("Password").fill("secret")
當定位表單欄位時,使用此定位器。
Locate by placeholder
輸入可能有一個 placeholder 屬性來提示使用者應該輸入什麼值。你可以使用 page.get_by_placeholder 來定位這樣的輸入。
例如,考慮以下的 DOM 結構。
<input type="email" placeholder="name@example.com" />
您可以在找到佔位符文字後填寫輸入:
- Sync
- Async
page.get_by_placeholder("name@example.com").fill("playwright@microsoft.com")
await page.get_by_placeholder("name@example.com").fill("playwright@microsoft.com")
當定位沒有標籤但有佔位符文本的表單元素時,使用此定位器。
根據文字定位
根據其包含的文本尋找元素。使用 page.get_by_text() 時,您可以通過子字元串、精確字元串或正則表達式進行匹配。
例如,考慮以下的 DOM 結構。
<span>Welcome, John</span>
您可以通過其包含的文本來定位該元素:
- Sync
- Async
expect(page.get_by_text("Welcome, John")).to_be_visible()
await expect(page.get_by_text("Welcome, John")).to_be_visible()
設置精確匹配:
- Sync
- Async
expect(page.get_by_text("Welcome, John", exact=True)).to_be_visible()
await expect(page.get_by_text("Welcome, John", exact=True)).to_be_visible()
與正則表達式匹配:
- Sync
- Async
expect(page.get_by_text(re.compile("welcome, john", re.IGNORECASE))).to_be_visible()
await expect(
page.get_by_text(re.compile("welcome, john", re.IGNORECASE))
).to_be_visible()
匹配文本時總是會正規化空白,即使是精確匹配。例如,它會將多個空格變成一個,將換行符變成空格,並忽略前後的空白。
我們建議使用文字定位器來查找非互動元素,如 div
、span
、p
等。對於互動元素,如 button
、a
、input
等,請使用角色定位器。
你也可以 filter by text 這在嘗試在列表中找到特定項目時很有用。
Locate by alt text
所有圖像應具有描述圖像的 alt
屬性。你可以使用 page.get_by_alt_text 根據文字替代項定位圖像。
例如,考慮以下的 DOM 結構。
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />
您可以在找到文字替代後點擊圖片:
- Sync
- Async
page.get_by_alt_text("playwright logo").click()
await page.get_by_alt_text("playwright logo").click()
當你的元素支援 alt 文字時,例如 img
和 area
元素,請使用此定位器。
根據標題定位
使用 page.get_by_title() 定位具有匹配 title 屬性的元素。
例如,考慮以下的 DOM 結構。
<span title='Issues count'>25 issues</span>
您可以在通過標題文字定位後檢查問題數量:
- Sync
- Async
expect(page.get_by_title("Issues count")).to_have_text("25 issues")
await expect(page.get_by_title("Issues count")).to_have_text("25 issues")
當你的元素具有 title
屬性時,使用此定位器。
依據測試 ID 定位
測試 id 測試是最具彈性的測試方式,即使您的文字或屬性角色發生變化,測試仍然會通過。QA 和開發人員應該定義明確的測試 id 並使用 page.get_by_test_id() 來查詢它們。然而,通過測試 id 測試並不是面向用戶的。如果角色或文字值對您很重要,請考慮使用面向用戶的定位器,例如 role 和 text locators。
例如,考慮以下的 DOM 結構。
<button data-testid="directions">Itinéraire</button>
你可以通過測試 ID 定位該元素:
- Sync
- Async
page.get_by_test_id("directions").click()
await page.get_by_test_id("directions").click()
設定自訂測試 id 屬性
預設情況下,page.get_by_test_id() 將根據 data-testid
屬性定位元素,但您可以在測試配置中進行配置或通過呼叫 selectors.set_test_id_attribute() 來設置。
將測試 ID 設定為使用自訂資料屬性進行測試。
- Sync
- Async
playwright.selectors.set_test_id_attribute("data-pw")
playwright.selectors.set_test_id_attribute("data-pw")
在你的 html 中,你現在可以使用 data-pw
作為你的測試 id,而不是預設的 data-testid
。
<button data-pw="directions">Itinéraire</button>
然後像平常一樣定位該元素:
- Sync
- Async
page.get_by_test_id("directions").click()
await page.get_by_test_id("directions").click()
使用 CSS 或 XPath 定位
如果你絕對必須使用 CSS 或 XPath 定位器,你可以使用 page.locator() 來建立一個定位器,該定位器使用選擇器描述如何在頁面中找到一個元素。Playwright 支援 CSS 和 XPath 選擇器,如果你省略了 css=
或 xpath=
前綴,它會自動檢測它們。
- Sync
- Async
page.locator("css=button").click()
page.locator("xpath=//button").click()
page.locator("button").click()
page.locator("//button").click()
await page.locator("css=button").click()
await page.locator("xpath=//button").click()
await page.locator("button").click()
await page.locator("//button").click()
XPath 和 CSS 選擇器可以與 DOM 結構或實現綁定。當 DOM 結構改變時,這些選擇器可能會失效。下面的長 CSS 或 XPath 鏈是一個導致測試不穩定的不良實踐範例:
- Sync
- Async
page.locator(
"#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input"
).click()
page.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input').click()
await page.locator(
"#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input"
).click()
await page.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input').click()
在 Shadow DOM 中定位
所有在 Playwright 中的定位器 預設 都適用於 Shadow DOM 中的元素。例外情況如下:
- 使用 XPath 定位不會穿透 shadow roots。
- Closed-mode shadow roots 不受支持。
考慮以下帶有自定義網頁元件的範例:
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
你可以像根本沒有 shadow root 一樣定位。
要點擊 <div>Details</div>
:
- Sync
- Async
page.get_by_text("Details").click()
await page.get_by_text("Details").click()
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
要點擊 <x-details>
:
- Sync
- Async
page.locator("x-details", has_text="Details" ).click()
await page.locator("x-details", has_text="Details" ).click()
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
為了確保 <x-details>
包含文字 "Details":
- Sync
- Async
expect(page.locator("x-details")).to_contain_text("Details")
await expect(page.locator("x-details")).to_contain_text("Details")
過濾定位器
考慮以下 DOM 結構,我們想要點擊第二個產品卡的購買按鈕。我們有幾個選項來過濾定位器以獲取正確的定位器。
產品 1
產品 2
<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>
以文字篩選
定位器可以通過 locator.filter() 方法按文本過濾。它會在元素內的某處(可能在子元素中)搜尋特定字串,不區分大小寫。你也可以傳遞正則表達式。
- Sync
- Async
page.get_by_role("listitem").filter(has_text="Product 2").get_by_role(
"button", name="Add to cart"
).click()
await page.get_by_role("listitem").filter(has_text="Product 2").get_by_role(
"button", name="Add to cart"
).click()
使用正規表示式:
- Sync
- Async
page.get_by_role("listitem").filter(has_text=re.compile("Product 2")).get_by_role(
"button", name="Add to cart"
).click()
await page.get_by_role("listitem").filter(has_text=re.compile("Product 2")).get_by_role(
"button", name="Add to cart"
).click()
篩選沒有文字的
或者,篩選 不包含 文字:
- Sync
- Async
# 5 in-stock items
expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)
# 5 in-stock items
await expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)
根據子項/後代篩選
定位器支援一個選項,只選擇具有或不具有符合另一個定位器的後代元素。因此,您可以通過任何其他定位器過濾,例如 locator.get_by_role、locator.get_by_test_id、locator.get_by_text 等。
產品 1
產品 2
<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>
- Sync
- Async
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
).get_by_role("button", name="Add to cart").click()
await page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
).get_by_role("button", name="Add to cart").click()
我們也可以斷言產品卡片以確保只有一個:
- Sync
- Async
expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
await expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
篩選定位器必須是相對於原始定位器,並從原始定位器匹配開始查詢,而不是從文件根目錄開始。因此,以下將無法運作,因為篩選定位器從 <ul>
列表元素開始匹配,而該元素位於原始定位器匹配的 <li>
列表項之外:
- Sync
- Async
# ✖ WRONG
expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("list").get_by_role("heading", name="Product 2")
)
).to_have_count(1)
# ✖ WRONG
await expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("list").get_by_role("heading", name="Product 2")
)
).to_have_count(1)
篩選沒有子元素/後代的元素
我們也可以過濾掉沒有匹配元素的內容。
- Sync
- Async
expect(
page.get_by_role("listitem").filter(
has_not=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
await expect(
page.get_by_role("listitem").filter(
has_not=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
請注意,內部定位器是從外部開始匹配的,而不是從文件根目錄開始。
定位器運算子
在定位器內匹配
您可以鏈接建立定位器的方法,如 page.get_by_text() 或 locator.get_by_role(),以縮小搜尋頁面的特定部分。
在這個範例中,我們首先透過定位其角色為 listitem
來建立一個名為 product 的定位器。然後我們透過文字進行篩選。我們可以再次使用 product 定位器來透過角色取得按鈕並點擊它,然後使用斷言來確保只有一個產品的文字為 "Product 2"。
- Sync
- Async
product = page.get_by_role("listitem").filter(has_text="Product 2")
product.get_by_role("button", name="Add to cart").click()
product = page.get_by_role("listitem").filter(has_text="Product 2")
await product.get_by_role("button", name="Add to cart").click()
您也可以將兩個定位器連接在一起,例如在特定對話框中找到“Save”按鈕:
- Sync
- Async
save_button = page.get_by_role("button", name="Save")
# ...
dialog = page.get_by_test_id("settings-dialog")
dialog.locator(save_button).click()
save_button = page.get_by_role("button", name="Save")
# ...
dialog = page.get_by_test_id("settings-dialog")
await dialog.locator(save_button).click()
同時匹配兩個定位器
方法 locator.and_() 通過匹配額外的定位器來縮小現有定位器的範圍。例如,你可以結合 page.get_by_role() 和 page.get_by_title() 來同時匹配角色和標題。
- Sync
- Async
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
匹配兩個替代定位器之一
如果你想針對兩個或更多元素中的一個,而你不知道會是哪一個,請使用 locator.or_() 建立一個匹配所有替代方案的定位器。
例如,考慮一個情境,你想點擊 "New email" 按鈕,但有時會出現一個安全設定對話框。在這種情況下,你可以等待 "New email" 按鈕或對話框並據此行動。
如果螢幕上同時出現 "New email" 按鈕和安全對話框,"or" 定位器將同時匹配它們,可能會拋出"strict mode violation" 錯誤。在這種情況下,你可以使用locator.first 來只匹配其中之一。
- Sync
- Async
new_email = page.get_by_role("button", name="New")
dialog = page.get_by_text("Confirm security settings")
expect(new_email.or_(dialog).first).to_be_visible()
if (dialog.is_visible()):
page.get_by_role("button", name="Dismiss").click()
new_email.click()
new_email = page.get_by_role("button", name="New")
dialog = page.get_by_text("Confirm security settings")
await expect(new_email.or_(dialog).first).to_be_visible()
if (await dialog.is_visible()):
await page.get_by_role("button", name="Dismiss").click()
await new_email.click()
僅匹配可見元素
通常最好找到更可靠的方法來唯一識別元素,而不是檢查可見性。
考慮一個有兩個按鈕的頁面,第一個是不可見的,第二個是可見的。
<button style='display: none'>Invisible</button>
<button>Visible</button>
-
這將找到兩個按鈕並拋出一個嚴格性違規錯誤:
- Sync
- Async
page.locator("button").click()
await page.locator("button").click()
-
這將只找到第二個按鈕,因為它是可見的,然後點擊它。
- Sync
- Async
page.locator("button").locator("visible=true").click()
await page.locator("button").locator("visible=true").click()