定位器
簡介
定位器是 Playwright 自動等待和重試能力的核心。簡而言之,定位器代表一種在任何時刻在頁面上尋找元素的方式。
快速指南
以下是建議的內建定位器。
- 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() 依據 title 屬性來定位元素。
- page.get_by_test_id() 依據元素的
data-testid
屬性 (可以設定其他屬性) 來定位元素。
- 同步
- 非同步
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
和名稱 "登入" 來定位元素。
- 同步
- 非同步
page.get_by_role("button", name="Sign in").click()
await page.get_by_role("button", name="Sign in").click()
使用程式碼產生器來產生定位器,然後依您的需求編輯。
每次定位器用於動作時,都會在頁面中定位最新的 DOM 元素。在下面的程式碼片段中,底層 DOM 元素將被定位兩次,每次動作之前定位一次。這表示如果 DOM 在呼叫之間由於重新渲染而發生變化,將會使用對應於定位器的新元素。
- 同步
- 非同步
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 類別中也可用,因此您可以將它們串聯起來並迭代地縮小定位器的範圍。
- 同步
- 非同步
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>
您可以依據每個元素的隱含角色來定位
- 同步
- 非同步
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 指南的早期回饋。
我們建議優先使用角色定位器來定位元素,因為這是最接近使用者和輔助技術感知頁面的方式。
依標籤定位
大多數表單控制項通常都有專用標籤,可以方便地用於與表單互動。在這種情況下,您可以使用 page.get_by_label() 依據其相關聯的標籤來定位控制項。
例如,考慮以下 DOM 結構。
<label>Password <input type="password" /></label>
您可以在依標籤文字定位輸入框後填寫內容
- 同步
- 非同步
page.get_by_label("Password").fill("secret")
await page.get_by_label("Password").fill("secret")
當定位表單欄位時,請使用此定位器。
依佔位符定位
輸入框可能具有佔位符屬性,以提示使用者應輸入的值。您可以使用 page.get_by_placeholder() 來定位此類輸入框。
例如,考慮以下 DOM 結構。
<input type="email" placeholder="name@example.com" />
您可以在依佔位符文字定位輸入框後填寫內容
- 同步
- 非同步
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>
您可以依據元素包含的文字來定位元素
- 同步
- 非同步
expect(page.get_by_text("Welcome, John")).to_be_visible()
await expect(page.get_by_text("Welcome, John")).to_be_visible()
設定完全比對
- 同步
- 非同步
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()
使用正則表達式比對
- 同步
- 非同步
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
等,請使用 角色定位器。
您也可以依文字篩選,這在嘗試在清單中尋找特定項目時非常有用。
依替代文字定位
所有圖片都應該有一個 alt
屬性來描述圖片。您可以使用 page.get_by_alt_text() 依據替代文字來定位圖片。
例如,考慮以下 DOM 結構。
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />
您可以在依替代文字定位圖片後點擊圖片
- 同步
- 非同步
page.get_by_alt_text("playwright logo").click()
await page.get_by_alt_text("playwright logo").click()
當您的元素支援替代文字 (例如 img
和 area
元素) 時,請使用此定位器。
依標題定位
使用 page.get_by_title() 定位具有相符 title 屬性的元素。
例如,考慮以下 DOM 結構。
<span title='Issues count'>25 issues</span>
您可以在依標題文字定位問題計數後檢查問題計數
- 同步
- 非同步
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 進行測試並非面向使用者。如果角色或文字值對您很重要,請考慮使用面向使用者的定位器,例如 角色 和 文字定位器。
例如,考慮以下 DOM 結構。
<button data-testid="directions">Itinéraire</button>
您可以依據元素的測試 ID 來定位元素
- 同步
- 非同步
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 以針對您的測試使用自訂資料屬性。
- 同步
- 非同步
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>
然後像平常一樣定位元素
- 同步
- 非同步
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=
前綴,它會自動偵測它們。
- 同步
- 非同步
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 鏈是導致測試不穩定的不良示範
- 同步
- 非同步
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 定位不會穿透陰影根。
- 封閉模式陰影根不受支援。
考慮以下使用自訂 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()
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>
- 同步
- 非同步
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"
- 同步
- 非同步
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() 方法依文字篩選定位器。它將在元素內某處 (可能在後代元素中) 以不區分大小寫的方式搜尋特定字串。您也可以傳遞正則表達式。
- 同步
- 非同步
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()
使用正則表達式
- 同步
- 非同步
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()
依沒有文字篩選
或者,依沒有文字篩選
- 同步
- 非同步
# 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>
- 同步
- 非同步
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()
我們也可以斷言產品卡,以確保只有一張
- 同步
- 非同步
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>
清單項目之外
- 同步
- 非同步
# ✖ 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)
依沒有子系/後代篩選
我們也可以依沒有內部相符元素進行篩選。
- 同步
- 非同步
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(),以將搜尋範圍縮小到頁面的特定部分。
在此範例中,我們首先建立一個名為 product 的定位器,方法是定位其 listitem
角色。然後我們依文字篩選。我們可以再次使用 product 定位器,依按鈕角色取得並點擊它,然後使用斷言來確保只有一個文字為 "產品 2" 的產品。
- 同步
- 非同步
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_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(),以依角色和標題進行比對。
- 同步
- 非同步
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
比對兩個替代定位器之一
如果您想要鎖定兩個或多個元素中的一個,並且您不知道會是哪一個,請使用 locator.or_() 來建立一個定位器,該定位器比對任一或兩個替代方案。
例如,考慮一種情況,您想要點擊 "新電子郵件" 按鈕,但有時會出現安全設定對話方塊。在這種情況下,您可以等待 "新電子郵件" 按鈕或對話方塊,並採取相應的動作。
如果 "新電子郵件" 按鈕和安全對話方塊都出現在螢幕上,則 "or" 定位器將比對它們兩者,可能會拋出 「嚴格模式違規」錯誤。在這種情況下,您可以使用 locator.first 只比對其中一個。
- 同步
- 非同步
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>
-
這將找到兩個按鈕並拋出嚴格模式違規錯誤
- 同步
- 非同步
page.locator("button").click()
await page.locator("button").click()
-
這只會找到第二個按鈕,因為它是可見的,然後點擊它。
- 同步
- 非同步
page.locator("button").locator("visible=true").click()
await page.locator("button").locator("visible=true").click()
清單
計算清單中的項目
您可以斷言定位器,以便計算清單中的項目。
例如,考慮以下 DOM 結構
- 蘋果
- 香蕉
- 橘子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
使用計數斷言來確保清單有 3 個項目。
- 同步
- 非同步
expect(page.get_by_role("listitem")).to_have_count(3)
await expect(page.get_by_role("listitem")).to_have_count(3)
斷言清單中的所有文字
您可以斷言定位器,以便在清單中尋找所有文字。
例如,考慮以下 DOM 結構
- 蘋果
- 香蕉
- 橘子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
使用 expect(locator).to_have_text() 來確保清單具有文字 "蘋果"、"香蕉" 和 "橘子"。
- 同步
- 非同步
expect(page.get_by_role("listitem")).to_have_text(["apple", "banana", "orange"])
await expect(page.get_by_role("listitem")).to_have_text(["apple", "banana", "orange"])
取得特定項目
有很多方法可以取得清單中的特定項目。
依文字取得
使用 page.get_by_text() 方法依其文字內容定位清單中的元素,然後點擊它。
例如,考慮以下 DOM 結構
- 蘋果
- 香蕉
- 橘子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
依其文字內容定位項目並點擊它。
- 同步
- 非同步
page.get_by_text("orange").click()
await page.get_by_text("orange").click()
依文字篩選
使用 locator.filter() 來定位清單中的特定項目。
例如,考慮以下 DOM 結構
- 蘋果
- 香蕉
- 橘子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
依 "listitem" 角色定位項目,然後依 "橘子" 文字篩選,然後點擊它。
- 同步
- 非同步
page.get_by_role("listitem").filter(has_text="orange").click()
await page.get_by_role("listitem").filter(has_text="orange").click()
依測試 ID 取得
使用 page.get_by_test_id() 方法來定位清單中的元素。如果沒有測試 ID,您可能需要修改 html 並新增一個測試 ID。
例如,考慮以下 DOM 結構
- 蘋果
- 香蕉
- 橘子
<ul>
<li data-testid='apple'>apple</li>
<li data-testid='banana'>banana</li>
<li data-testid='orange'>orange</li>
</ul>
依其 "橘子" 測試 ID 定位項目,然後點擊它。
- 同步
- 非同步
page.get_by_test_id("orange").click()
await page.get_by_test_id("orange").click()
依第 n 個項目取得
如果您有一個相同元素的清單,並且區分它們的唯一方法是順序,則可以使用 locator.first、locator.last 或 locator.nth() 從清單中選擇特定元素。
- 同步
- 非同步
banana = page.get_by_role("listitem").nth(1)
banana = await page.get_by_role("listitem").nth(1)
但是,請謹慎使用此方法。通常,頁面可能會變更,並且定位器將指向與您預期的元素完全不同的元素。相反地,請嘗試提出一個唯一的定位器,該定位器將通過嚴格模式標準。
鏈接篩選器
當您有各種相似之處的元素時,可以使用 locator.filter() 方法來選擇正確的元素。您也可以鏈接多個篩選器來縮小選擇範圍。
例如,考慮以下 DOM 結構
- John
- Mary
- John
- Mary
<ul>
<li>
<div>John</div>
<div><button>Say hello</button></div>
</li>
<li>
<div>Mary</div>
<div><button>Say hello</button></div>
</li>
<li>
<div>John</div>
<div><button>Say goodbye</button></div>
</li>
<li>
<div>Mary</div>
<div><button>Say goodbye</button></div>
</li>
</ul>
擷取包含 "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")
row_locator = page.get_by_role("listitem")
await row_locator.filter(has_text="Mary").filter(
has=page.get_by_role("button", name="Say goodbye")
).screenshot(path="screenshot.png")
您現在應該在專案的根目錄中找到 "screenshot.png" 檔案。
罕見的使用案例
對清單中的每個元素執行某些操作
迭代元素
- 同步
- 非同步
for row in page.get_by_role("listitem").all():
print(row.text_content())
for row in await page.get_by_role("listitem").all():
print(await row.text_content())
使用常規 for 迴圈迭代
- 同步
- 非同步
rows = page.get_by_role("listitem")
count = rows.count()
for i in range(count):
print(rows.nth(i).text_content())
rows = page.get_by_role("listitem")
count = await rows.count()
for i in range(count):
print(await rows.nth(i).text_content())
在頁面中評估
locator.evaluate_all() 內的程式碼在頁面中執行,您可以在其中呼叫任何 DOM API。
- 同步
- 非同步
rows = page.get_by_role("listitem")
texts = rows.evaluate_all("list => list.map(element => element.textContent)")
rows = page.get_by_role("listitem")
texts = await rows.evaluate_all("list => list.map(element => element.textContent)")
嚴格模式
定位器是嚴格的。這表示如果有多個元素符合條件,則對定位器執行的所有暗示某些目標 DOM 元素的操作都會拋出例外。例如,如果 DOM 中有多個按鈕,則以下呼叫會拋出例外
如果有多個則拋出錯誤
- 同步
- 非同步
page.get_by_role("button").click()
await page.get_by_role("button").click()
另一方面,當您執行多元素操作時,Playwright 會理解,因此當定位器解析為多個元素時,以下呼叫可以正常運作。
多個元素可以正常運作
- 同步
- 非同步
page.get_by_role("button").count()
await page.get_by_role("button").count()
您可以透過告知 Playwright 在多個元素符合條件時要使用哪個元素,透過 locator.first、locator.last 和 locator.nth() 來明確選擇退出嚴格模式檢查。不建議使用這些方法,因為當您的頁面變更時,Playwright 可能會點擊您不打算點擊的元素。相反地,請遵循上述最佳實務來建立唯一識別目標元素的定位器。
更多定位器
對於較少使用的定位器,請查看其他定位器指南。