跳到主要內容

定位器

簡介

定位器是 Playwright 自動等待和重試能力的中心。簡而言之,定位器代表一種在任何時刻在頁面上尋找元素的方式。

快速指南

以下是建議的內建定位器。

page.getByLabel("User Name").fill("John");

page.getByLabel("Password").fill("secret-password");

page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
.click();

assertThat(page.getByText("Welcome, John!")).isVisible();

定位元素

Playwright 隨附多個內建定位器。為了使測試具有彈性,我們建議優先考慮面向使用者的屬性和明確的契約,例如 Page.getByRole()

例如,考慮以下 DOM 結構。

https://127.0.0.1:3000
<button>Sign in</button>

透過角色為 button 且名稱為「登入」的元素來定位。

page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
.click();
注意

使用 程式碼產生器來產生定位器,然後根據您的需求進行編輯。

每次將定位器用於動作時,都會在頁面中定位最新的 DOM 元素。在下面的程式碼片段中,底層 DOM 元素將被定位兩次,每次動作之前一次。這表示如果 DOM 在呼叫之間由於重新渲染而發生變更,則將使用對應於定位器的新元素。

Locator locator = page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in"));

locator.hover();
locator.click();

請注意,所有建立定位器的方法,例如 Page.getByLabel(),在 LocatorFrameLocator 類別中也可用,因此您可以將它們串連起來並迭代地縮小您的定位器範圍。

Locator locator = page
.frameLocator("#my-frame")
.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"));

locator.click();

依角色定位

Page.getByRole() 定位器反映使用者和輔助技術如何感知頁面,例如,某個元素是否為按鈕或核取方塊。當依角色定位時,您通常也應該傳遞可存取名稱,以便定位器精確地指出元素。

例如,考慮以下 DOM 結構。

https://127.0.0.1:3000

註冊

<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>

您可以依其隱含角色定位每個元素

assertThat(page
.getByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Sign up")))
.isVisible();

page.getByRole(AriaRole.CHECKBOX,
new Page.GetByRoleOptions().setName("Subscribe"))
.check();

page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName(
Pattern.compile("submit", Pattern.CASE_INSENSITIVE)))
.click();

角色定位器包括 按鈕、核取方塊、標題、連結、清單、表格以及更多,並遵循 W3C 規格 ARIA 角色ARIA 屬性可存取名稱。請注意,許多 html 元素,例如 <button>,都具有 隱含定義的角色,角色定位器可以識別它。

請注意,角色定位器無法取代協助工具稽核和一致性測試,而是提供關於 ARIA 指南的早期回饋。

何時使用角色定位器

我們建議優先使用角色定位器來定位元素,因為這是最接近使用者和輔助技術感知頁面的方式。

依標籤定位

大多數表單控制項通常都有專用標籤,可以方便地用於與表單互動。在這種情況下,您可以使用 Page.getByLabel() 透過其相關聯標籤來定位控制項。

例如,考慮以下 DOM 結構。

https://127.0.0.1:3000
<label>Password <input type="password" /></label>

您可以在依標籤文字定位輸入框後填寫內容

page.getByLabel("Password").fill("secret");
何時使用標籤定位器

在定位表單欄位時使用此定位器。

依預留位置定位

輸入框可能具有預留位置屬性,以提示使用者應輸入的值。您可以使用 Page.getByPlaceholder() 定位此類輸入框。

例如,考慮以下 DOM 結構。

https://127.0.0.1:3000
<input type="email" placeholder="name@example.com" />

您可以在依預留位置文字定位輸入框後填寫內容

page.getByPlaceholder("name@example.com").fill("playwright@microsoft.com");
何時使用預留位置定位器

在定位沒有標籤但有預留位置文字的表單元素時,請使用此定位器。

依文字定位

依元素包含的文字尋找元素。當使用 Page.getByText() 時,您可以比對子字串、確切字串或規則運算式。

例如,考慮以下 DOM 結構。

https://127.0.0.1:3000
歡迎,John
<span>Welcome, John</span>

您可以依元素包含的文字來定位元素

assertThat(page.getByText("Welcome, John")).isVisible();

設定完全比對

assertThat(page
.getByText("Welcome, John", new Page.GetByTextOptions().setExact(true)))
.isVisible();

使用規則運算式比對

assertThat(page
.getByText(Pattern.compile("welcome, john$", Pattern.CASE_INSENSITIVE)))
.isVisible();
注意

依文字比對始終會正規化空格,即使是完全比對也是如此。例如,它會將多個空格轉換為一個空格,將換行符號轉換為空格,並忽略開頭和結尾的空格。

何時使用文字定位器

我們建議使用文字定位器來尋找非互動式元素,例如 divspanp 等。對於互動式元素,例如 buttonainput 等,請使用 角色定位器

您也可以 依文字篩選,這在嘗試在清單中尋找特定項目時非常有用。

依替代文字定位

所有圖片都應該具有描述圖片的 alt 屬性。您可以使用 Page.getByAltText() 根據替代文字來定位圖片。

例如,考慮以下 DOM 結構。

https://127.0.0.1:3000
playwright logo
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />

您可以在依替代文字定位圖片後點擊它

page.getByAltText("playwright logo").click();
何時使用替代文字定位器

當您的元素支援替代文字時,例如 imgarea 元素,請使用此定位器。

依標題定位

使用 Page.getByTitle() 定位具有相符標題屬性的元素。

例如,考慮以下 DOM 結構。

https://127.0.0.1:3000
25 個問題
<span title='Issues count'>25 issues</span>

您可以在依標題文字定位問題計數後檢查它

assertThat(page.getByTitle("Issues count")).hasText("25 issues");
何時使用標題定位器

當您的元素具有 title 屬性時,請使用此定位器。

依測試 ID 定位

依測試 ID 進行測試是最具彈性的測試方式,即使您的文字或屬性的角色發生變更,測試仍然會通過。QA 和開發人員應定義明確的測試 ID,並使用 Page.getByTestId() 查詢它們。但是,依測試 ID 進行測試並非面向使用者。如果角色或文字值對您很重要,請考慮使用面向使用者的定位器,例如 角色文字定位器

例如,考慮以下 DOM 結構。

https://127.0.0.1:3000
<button data-testid="directions">Itinéraire</button>

您可以依其測試 ID 定位元素

page.getByTestId("directions").click();
何時使用測試 ID 定位器

當您選擇使用測試 ID 方法或當您無法依 角色文字 定位時,您也可以使用測試 ID。

設定自訂測試 ID 屬性

預設情況下,Page.getByTestId() 將根據 data-testid 屬性定位元素,但您可以在測試設定中或透過呼叫 Selectors.setTestIdAttribute() 來設定它。

設定測試 ID 以使用自訂資料屬性進行測試。

playwright.selectors().setTestIdAttribute("data-pw");

在您的 html 中,您現在可以使用 data-pw 作為您的測試 ID,而不是預設的 data-testid

https://127.0.0.1:3000
<button data-pw="directions">Itinéraire</button>

然後像平常一樣定位元素

page.getByTestId("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();

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();
何時使用此方法

不建議使用 CSS 和 XPath,因為 DOM 通常會變更,從而導致測試不具彈性。相反,請嘗試提出更接近使用者感知頁面的定位器,例如 角色定位器 或使用測試 ID 定義明確的測試契約

在 Shadow DOM 中定位

Playwright 中的所有定位器預設都適用於 Shadow DOM 中的元素。例外情況是

考慮以下具有自訂 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>詳細資訊</div>

page.getByText("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", new Page.LocatorOptions().setHasText("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> 包含文字「詳細資訊」

assertThat(page.locator("x-details")).containsText("Details");

篩選定位器

考慮以下 DOM 結構,我們想要點擊第二個產品卡的購買按鈕。我們有幾個選項可以篩選定位器以取得正確的定位器。

https://127.0.0.1:3000
  • 產品 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.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasText("Product 2"))
.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Add to cart"))
.click();

使用規則運算式

page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHasText(Pattern.compile("Product 2")))
.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Add to cart"))
.click();

依沒有文字篩選

或者,依沒有文字篩選

// 5 in-stock items
assertThat(page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasNotText("Out of stock")))
.hasCount(5);

依子元素/後代元素篩選

定位器支援一個選項,僅選取具有或沒有與另一個定位器相符的後代元素的元素。因此,您可以依任何其他定位器進行篩選,例如 Locator.getByRole()Locator.getByTestId()Locator.getByText() 等。

https://127.0.0.1:3000
  • 產品 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.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHas(page.GetByRole(AriaRole.HEADING, new Page.GetByRoleOptions()
.setName("Product 2"))))
.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Add to cart"))
.click();

我們也可以斷言產品卡以確保只有一張

assertThat(page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHas(page.GetByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Product 2")))))
.hasCount(1);

篩選定位器必須相對於原始定位器,並且從原始定位器比對開始查詢,而不是文件根目錄。因此,以下程式碼將無法運作,因為篩選定位器從 <ul> 清單元素開始比對,該元素位於原始定位器比對的 <li> 清單項目之外

// ✖ WRONG
assertThat(page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHas(page.GetByRole(AriaRole.LIST)
.GetByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Product 2")))))
.hasCount(1);

依沒有子元素/後代元素篩選

我們也可以依沒有內部相符元素進行篩選。

assertThat(page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasNot(page.getByText("Product 2"))))
.hasCount(1);

請注意,內部定位器是從外部定位器開始比對,而不是從文件根目錄開始比對。

定位器運算子

在定位器內部比對

您可以串連建立定位器的方法,例如 Page.getByText()Locator.getByRole(),以將搜尋範圍縮小到頁面的特定部分。

在此範例中,我們首先建立一個名為 product 的定位器,方法是定位其角色 listitem。然後我們依文字篩選。我們可以再次使用 product 定位器來依按鈕角色取得並點擊它,然後使用斷言來確保只有一個文字為「產品 2」的產品。

Locator product = page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasText("Product 2"));

product
.getByRole(AriaRole.BUTTON,
new Locator.GetByRoleOptions().setName("Add to cart"))
.click();

您也可以將兩個定位器串連在一起,例如在特定對話方塊中尋找「儲存」按鈕

Locator saveButton = page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Save"));
// ...
Locator dialog = page.getByTestId("settings-dialog");
dialog.locator(saveButton).click();

同時比對兩個定位器

Locator.and() 方法透過比對額外的定位器來縮小現有定位器的範圍。例如,您可以結合 Page.getByRole()Page.getByTitle() 以同時依角色和標題比對。

Locator button = page.getByRole(AriaRole.BUTTON).and(page.getByTitle("Subscribe"));

比對兩個替代定位器之一

如果您想要以兩個或多個元素之一為目標,並且您不知道會是哪一個,請使用 Locator.or() 來建立一個定位器,該定位器比對任何一個或兩個替代方案。

例如,考慮這樣一種情況,您想要點擊「新電子郵件」按鈕,但有時會改為顯示安全性設定對話方塊。在這種情況下,您可以等待「新電子郵件」按鈕或對話方塊,並據此採取行動。

注意

如果「新電子郵件」按鈕和安全性對話方塊都出現在螢幕上,「or」定位器將比對它們兩個,可能會擲回 「嚴格模式違規」錯誤。在這種情況下,您可以使用 Locator.first() 僅比對其中一個。

Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New"));
Locator dialog = page.getByText("Confirm security settings");
assertThat(newEmail.or(dialog).first()).isVisible();
if (dialog.isVisible())
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click();
newEmail.click();

僅比對可見元素

注意

通常最好找到一種 更可靠的方式 來唯一識別元素,而不是檢查可見性。

考慮一個頁面,其中包含兩個按鈕,第一個按鈕不可見,第二個按鈕可見

<button style='display: none'>Invisible</button>
<button>Visible</button>
  • 這將找到兩個按鈕並擲回 嚴格模式 違規錯誤

    page.locator("button").click();
  • 這將只找到第二個按鈕,因為它是可見的,然後點擊它。

    page.locator("button").locator("visible=true").click();

清單

計算清單中的項目數

您可以斷言定位器以計算清單中的項目數。

例如,考慮以下 DOM 結構

https://127.0.0.1:3000
  • 蘋果
  • 香蕉
  • 橘子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

使用計數斷言來確保清單有 3 個項目。

assertThat(page.getByRole(AriaRole.LISTITEM)).hasCount(3);

斷言清單中的所有文字

您可以斷言定位器以尋找清單中的所有文字。

例如,考慮以下 DOM 結構

https://127.0.0.1:3000
  • 蘋果
  • 香蕉
  • 橘子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

使用 assertThat(locator).hasText() 以確保清單具有文字「蘋果」、「香蕉」和「橘子」。

assertThat(page
.getByRole(AriaRole.LISTITEM))
.hasText(new String[] { "apple", "banana", "orange" });

取得特定項目

有很多方法可以取得清單中的特定項目。

依文字取得

使用 Page.getByText() 方法依其文字內容定位清單中的元素,然後點擊它。

例如,考慮以下 DOM 結構

https://127.0.0.1:3000
  • 蘋果
  • 香蕉
  • 橘子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

依其文字內容定位項目並點擊它。

page.getByText("orange").click();

依文字篩選

使用 Locator.filter() 定位清單中的特定項目。

例如,考慮以下 DOM 結構

https://127.0.0.1:3000
  • 蘋果
  • 香蕉
  • 橘子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

依「listitem」角色定位項目,然後依「橘子」文字篩選,然後點擊它。

page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasText("orange"))
.click();

依測試 ID 取得

使用 Page.getByTestId() 方法定位清單中的元素。如果尚未有測試 ID,您可能需要修改 html 並新增測試 ID。

例如,考慮以下 DOM 結構

https://127.0.0.1:3000
  • 蘋果
  • 香蕉
  • 橘子
<ul>
<li data-testid='apple'>apple</li>
<li data-testid='banana'>banana</li>
<li data-testid='orange'>orange</li>
</ul>

依其「橘子」測試 ID 定位項目,然後點擊它。

page.getByTestId("orange").click();

依第 n 個項目取得

如果您有一個相同元素的清單,並且區分它們的唯一方法是順序,則可以使用 Locator.first()Locator.last()Locator.nth() 從清單中選擇特定元素。

Locator banana = page.getByRole(AriaRole.LISTITEM).nth(1);

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

串連篩選器

當您有各種相似之處的元素時,可以使用 Locator.filter() 方法來選取正確的元素。您也可以串連多個篩選器以縮小選取範圍。

例如,考慮以下 DOM 結構

https://127.0.0.1:3000
  • 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」和「說再見」的列的螢幕截圖

Locator rowLocator = page.getByRole(AriaRole.LISTITEM);

rowLocator
.filter(new Locator.FilterOptions().setHasText("Mary"))
.filter(new Locator.FilterOptions()
.setHas(page.getByRole(
AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Say goodbye"))))
.screenshot(new Page.ScreenshotOptions().setPath("screenshot.png"));

您現在應該在專案的根目錄中找到一個「screenshot.png」檔案。

罕見的使用案例

對清單中的每個元素執行某些操作

迭代元素

for (Locator row : page.getByRole(AriaRole.LISTITEM).all())
System.out.println(row.textContent());

使用常規 for 迴圈迭代

Locator rows = page.getByRole(AriaRole.LISTITEM);
int count = rows.count();
for (int i = 0; i < count; ++i)
System.out.println(rows.nth(i).textContent());

在頁面中評估

Locator.evaluateAll() 內部的程式碼在頁面中執行,您可以在其中呼叫任何 DOM API。

Locator rows = page.getByRole(AriaRole.LISTITEM);
Object texts = rows.evaluateAll(
"list => list.map(element => element.textContent)");

嚴格模式

定位器是嚴格的。這表示如果有多個元素符合條件,則對暗示某些目標 DOM 元素的定位器執行的所有操作都將擲回例外狀況。例如,如果 DOM 中有多個按鈕,則以下呼叫會擲回例外狀況

如果有多個則擲回錯誤

page.getByRole(AriaRole.BUTTON).click();

另一方面,Playwright 了解您何時執行多元素操作,因此當定位器解析為多個元素時,以下呼叫可以完美運作。

適用於多個元素

page.getByRole(AriaRole.BUTTON).count();

您可以透過 Locator.first()Locator.last()Locator.nth() 告訴 Playwright 在有多個元素符合條件時要使用哪個元素,從而明確選擇退出嚴格模式檢查。不建議使用這些方法,因為當您的頁面變更時,Playwright 可能會點擊您不打算點擊的元素。相反,請遵循上述最佳實務,建立唯一識別目標元素的定位器。

更多定位器

對於較不常用的定位器,請查看其他定位器指南。