Skip to main content

定位器

簡介

Locator 是 Playwright 自動等待和重試功能的核心部分。簡而言之,locators 代表了一種在任何時刻在頁面上找到元素的方法。

快速指南

這些是推薦的內建定位器。

await page.GetByLabel("User Name").FillAsync("John");

await page.GetByLabel("Password").FillAsync("secret-password");

await page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();

await Expect(Page.GetByText("Welcome, John!")).ToBeVisibleAsync();

定位元素

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

例如,考慮以下的 DOM 結構。

http://localhost:3000
<button>Sign in</button>

找到角色為 button 且名稱為 "Sign in" 的元素。

await page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
note

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

每次使用定位器執行動作時,會在頁面中定位最新的 DOM 元素。在下面的程式碼片段中,基礎的 DOM 元素將被定位兩次,每次動作前各一次。這意味著如果在呼叫之間由於重新渲染而導致 DOM 發生變化,將使用與定位器對應的新元素。

var locator = page.GetByRole(AriaRole.Button, new() { Name = "Sign in" });

await locator.HoverAsync();
await locator.ClickAsync();

請注意,所有建立定位器的方法,例如 Page.GetByLabel(),也可在 LocatorFrameLocator 類別上使用,因此您可以鏈接它們並逐步縮小定位器的範圍。

var locator = page
.FrameLocator("#my-frame")
.GetByRole(AriaRole.Button, new() { Name = "Sign in" });

await locator.ClickAsync();

根據角色定位

Page.GetByRole() 定位器反映了使用者和輔助技術如何感知頁面,例如某個元素是按鈕還是複選框。當按角色定位時,通常應該同時傳遞可訪問名稱,以便定位器精確定位到具體元素。

例如,考慮以下的 DOM 結構。

http://localhost:3000

註冊


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

您可以通過其隱含角色定位每個元素:

await Expect(Page
.GetByRole(AriaRole.Heading, new() { Name = "Sign up" }))
.ToBeVisibleAsync();

await page
.GetByRole(AriaRole.Checkbox, new() { Name = "Subscribe" })
.CheckAsync();

await page
.GetByRole(AriaRole.Button, new() {
NameRegex = new Regex("submit", RegexOptions.IgnoreCase)
})
.ClickAsync();

角色定位器包括按鈕、複選框、標題、連結、清單、表格等,並遵循 W3C 的 ARIA 角色ARIA 屬性可訪問名稱規範。請注意,許多 html 元素如 <button> 具有隱式定義的角色,這些角色被角色定位器識別。

請注意,角色定位器不會取代可及性審計和符合性測試,而是提供有關 ARIA 指南的早期反饋。

何時使用角色定位器

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

Locate by label

大多數表單控制項通常都有專用的標籤,可以方便地用來與表單互動。在這種情況下,你可以使用 Page.GetByLabel() 根據其相關標籤定位控制項。

例如,考慮以下的 DOM 結構。

http://localhost:3000
<label>Password <input type="password" /></label>

您可以在找到標籤文字後填寫輸入:

await page.GetByLabel("Password").FillAsync("secret");
何時使用標籤定位器

當定位表單欄位時,使用此定位器。

Locate by placeholder

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

例如,考慮以下的 DOM 結構。

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

您可以在找到佔位符文字後填寫輸入:

await page
.GetByPlaceholder("name@example.com")
.FillAsync("playwright@microsoft.com");
何時使用佔位符定位器

當定位沒有標籤但有佔位符文本的表單元素時,使用此定位器。

根據文字定位

找到包含特定文字的元素。你可以使用子字串、精確字串或正則表達式來匹配 Page.GetByText()

例如,考慮以下的 DOM 結構。

http://localhost:3000
歡迎, John
<span>Welcome, John</span>

您可以通過其包含的文本來定位該元素:

await Expect(Page.GetByText("Welcome, John")).ToBeVisibleAsync();

設置精確匹配:

await Expect(Page
.GetByText("Welcome, John", new() { Exact = true }))
.ToBeVisibleAsync();

與正則表達式匹配:

await Expect(Page
.GetByText(new Regex("welcome, john", RegexOptions.IgnoreCase)))
.ToBeVisibleAsync();
note

匹配文本時總是會正規化空白,即使是精確匹配。例如,它會將多個空格變成一個,將換行符變成空格,並忽略前後的空白。

何時使用文字定位器

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

你也可以 filter by text 這在嘗試在列表中找到特定項目時很有用。

Locate by alt text

所有圖片應該有一個 alt 屬性來描述圖片。你可以使用 Page.GetByAltText() 根據文字替代項定位圖片。

例如,考慮以下的 DOM 結構。

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

您可以在找到文字替代後點擊圖片:

await page.GetByAltText("playwright logo").ClickAsync();
何時使用 alt 定位器

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

根據標題定位

找到具有匹配標題屬性的元素,使用 Page.GetByTitle()

例如,考慮以下的 DOM 結構。

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

您可以在通過標題文字定位後檢查問題數量:

await Expect(Page.GetByTitle("Issues count")).toHaveText("25 issues");
何時使用標題定位器

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

依據測試 ID 定位

測試 id 是最具彈性的測試方式,即使您的文字或屬性角色發生變化,測試仍然會通過。QA 和開發人員應該定義明確的測試 id 並使用 Page.GetByTestId() 進行查詢。然而,通過測試 id 進行測試並不是面向使用者的。如果角色或文字值對您很重要,那麼考慮使用面向使用者的定位器,例如 roletext locators

例如,考慮以下的 DOM 結構。

http://localhost:3000
<button data-testid="directions">Itinéraire</button>

你可以通過測試 ID 定位該元素:

await page.GetByTestId("directions").ClickAsync();
何時使用 testid 定位器

當你選擇使用 test id 方法或無法通過role文字定位時,你也可以使用 test id。

設定自訂測試 id 屬性

根據預設,Page.GetByTestId() 將根據 data-testid 屬性定位元素,但你可以在測試配置中進行配置,或通過呼叫 Selectors.SetTestIdAttribute() 來配置。

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

playwright.Selectors.SetTestIdAttribute("data-pw");

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

http://localhost:3000
<button data-pw="directions">Itinéraire</button>

然後像平常一樣定位該元素:

await page.GetByTestId("directions").ClickAsync();

使用 CSS 或 XPath 定位

如果你絕對必須使用 CSS 或 XPath 定位器,你可以使用 Page.Locator() 建立一個定位器,該定位器採用描述如何在頁面中找到元素的選擇器。Playwright 支援 CSS 和 XPath 選擇器,如果你省略 css=xpath= 前綴,它會自動檢測它們。

await page.Locator("css=button").ClickAsync();
await page.Locator("xpath=//button").ClickAsync();

await page.Locator("button").ClickAsync();
await page.Locator("//button").ClickAsync();

XPath 和 CSS 選擇器可以與 DOM 結構或實現綁定。當 DOM 結構改變時,這些選擇器可能會失效。下面的長 CSS 或 XPath 鏈是一個導致測試不穩定的不良實踐範例:

await page.Locator("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input").ClickAsync();

await page.Locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").ClickAsync();
When to use this

CSS 和 XPath 不建議使用,因為 DOM 經常變動,導致測試不穩定。相反地,嘗試使用接近使用者感知頁面的定位器,例如角色定位器或使用測試 ID 定義明確的測試合約

在 Shadow DOM 中定位

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

考慮以下帶有自定義網頁元件的範例:

<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>:

await page.GetByText("Details").ClickAsync();
<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>:

await page
.Locator("x-details", new() { HasText = "Details" })
.ClickAsync();
<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":

await Expect(Page.Locator("x-details")).ToContainTextAsync("Details");

過濾定位器

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

http://localhost: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() 方法按文本過濾。它將在元素內的某個地方(可能是在後代元素中)搜尋特定字串,不區分大小寫。你也可以傳遞正則表達式。

await page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasText = "Product 2" })
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();

使用正規表示式:

await page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasTextRegex = new Regex("Product 2") })
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();

篩選沒有文字的

或者,篩選 不包含 文字:

// 5 in-stock items
await Expect(Page.getByRole(AriaRole.Listitem).Filter(new() { HasNotText = "Out of stock" }))
.ToHaveCountAsync(5);

根據子項/後代篩選

定位器支持一個選項,只選擇具有或不具有匹配另一個定位器的後代的元素。因此,您可以通過任何其他定位器過濾,例如 Locator.GetByRoleLocator.GetByTestIdLocator.GetByText 等。

http://localhost: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>
await page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
Has = page.GetByRole(AriaRole.Heading, new() {
Name = "Product 2"
})
})
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();

我們也可以斷言產品卡片以確保只有一個:

await Expect(Page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
Has = page.GetByRole(AriaRole.Heading, new() { Name = "Product 2" })
}))
.ToHaveCountAsync(1);

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

// ✖ WRONG
await Expect(Page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
Has = page.GetByRole(AriaRole.List).GetByRole(AriaRole.Heading, new() { Name = "Product 2" })
}))
.ToHaveCountAsync(1);

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

我們也可以過濾掉沒有匹配元素的內容。

await Expect(Page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
HasNot = page.GetByRole(AriaRole.Heading, new() { Name = "Product 2" })
}))
.ToHaveCountAsync(1);

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

定位器運算子

在定位器內匹配

你可以鏈接建立定位器的方法,如 Page.GetByText()Locator.GetByRole(),以縮小頁面中特定部分的搜索範圍。

在這個範例中,我們首先透過定位其角色為 listitem 來建立一個名為 product 的定位器。然後我們透過文字進行篩選。我們可以再次使用 product 定位器來透過角色取得按鈕並點擊它,然後使用斷言來確保只有一個產品的文字為 "Product 2"。

var product = page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasText = "Product 2" });

await product
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();

您也可以將兩個定位器連接在一起,例如在特定對話框中找到“Save”按鈕:

var saveButton = page.GetByRole(AriaRole.Button, new() { Name = "Save" });
// ...
var dialog = page.GetByTestId("settings-dialog");
await dialog.Locator(saveButton).ClickAsync();

同時匹配兩個定位器

方法 Locator.And() 透過匹配額外的定位器來縮小現有定位器的範圍。例如,你可以結合 Page.GetByRole()Page.GetByTitle() 來同時匹配角色和標題。

var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));

匹配兩個替代定位器之一

如果你想要針對兩個或更多元素中的一個,而你不知道會是哪一個,請使用 Locator.Or() 來建立一個符合所有替代方案的定位器。

例如,考慮一個情境,你想點擊 "New email" 按鈕,但有時會出現一個安全設定對話框。在這種情況下,你可以等待 "New email" 按鈕或對話框並據此行動。

note

如果螢幕上同時出現 "New email" 按鈕和安全對話框,"or" 定位器將匹配它們兩者,可能會拋出"strict mode violation" 錯誤。在這種情況下,你可以使用Locator.First來只匹配其中一個。

var newEmail = page.GetByRole(AriaRole.Button, new() { Name = "New" });
var dialog = page.GetByText("Confirm security settings");
await Expect(newEmail.Or(dialog).First).ToBeVisibleAsync();
if (await dialog.IsVisibleAsync())
await page.GetByRole(AriaRole.Button, new() { Name = "Dismiss" }).ClickAsync();
await newEmail.ClickAsync();

僅匹配可見元素

note

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

考慮一個有兩個按鈕的頁面,第一個是不可見的,第二個是可見的

<button style='display: none'>Invisible</button>
<button>Visible</button>
  • 這將會找到兩個按鈕並拋出一個嚴格性違規錯誤:

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

    await page.Locator("button").Locator("visible=true").ClickAsync();

列表

計算清單中的項目數量

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

例如,考慮以下的 DOM 結構:

http://localhost:3000
  • 蘋果
  • 香蕉
  • 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

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

await Expect(Page.GetByRole(AriaRole.Listitem)).ToHaveCountAsync(3);

斷言清單中的所有文字

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

例如,考慮以下的 DOM 結構:

http://localhost:3000
  • 蘋果
  • 香蕉
  • 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

使用 Expect(Locator).ToHaveTextAsync() 確保列表中有 "apple"、"banana" 和 "orange"。

await Expect(Page
.GetByRole(AriaRole.Listitem))
.ToHaveTextAsync(new string[] {"apple", "banana", "orange"});

取得特定項目

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

取得文字

使用 Page.GetByText() 方法來根據其文本內容在列表中定位元素,然後點擊它。

例如,考慮以下的 DOM 結構:

http://localhost:3000
  • 蘋果
  • 香蕉
  • 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

根據其文本內容找到一個項目並點擊它。

await page.GetByText("orange").ClickAsync();

根據文字篩選

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

例如,考慮以下的 DOM 結構:

http://localhost:3000
  • 蘋果
  • 香蕉
  • 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

找到角色為 "listitem" 的項目,然後按 "orange" 的文字過濾,然後點擊它。

await page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasText = "orange" })
.ClickAsync();

Get by test id

使用 Page.GetByTestId() 方法來定位清單中的元素。 如果你還沒有測試 id,你可能需要修改 html 並新增一個測試 id。

例如,考慮以下的 DOM 結構:

http://localhost:3000
  • 蘋果
  • 香蕉
  • 橙子
<ul>
<li data-testid='apple'>apple</li>
<li data-testid='banana'>banana</li>
<li data-testid='orange'>orange</li>
</ul>

找到測試 ID 為 "orange" 的項目,然後點擊它。

await page.GetByTestId("orange").ClickAsync();

取得第 n 個物件

如果你有一個相同元素的列表,並且區分它們的唯一方法是順序,你可以從列表中選擇一個特定的元素,使用 Locator.FirstLocator.LastLocator.Nth()

var banana = await page.GetByRole(AriaRole.Listitem).Nth(1);

然而,請謹慎使用此方法。通常情況下,頁面可能會變更,而定位器將指向與您預期完全不同的元素。相反,請嘗試提出一個獨特的定位器,以通過嚴格標準

鏈接過濾器

當你有具有各種相似性的元素時,你可以使用 Locator.Filter() 方法來選擇合適的元素。你也可以串聯多個過濾器來縮小選擇範圍。

例如,考慮以下的 DOM 結構:

http://localhost: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" 和 "Say goodbye" 的行的螢幕截圖:

var rowLocator = page.GetByRole(AriaRole.Listitem);

await rowLocator
.Filter(new() { HasText = "Mary" })
.Filter(new() {
Has = page.GetByRole(AriaRole.Button, new() { Name = "Say goodbye" })
})
.ScreenshotAsync(new() { Path = "screenshot.png" });

你現在應該在你的專案根目錄中有一個 "screenshot.png" 文件。

罕見的使用案例

對清單中的每個元素做些事情

迭代元素:

foreach (var row in await page.GetByRole(AriaRole.Listitem).AllAsync())
Console.WriteLine(await row.TextContentAsync());

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

var rows = page.GetByRole(AriaRole.Listitem);
var count = await rows.CountAsync();
for (int i = 0; i < count; ++i)
Console.WriteLine(await rows.Nth(i).TextContentAsync());

在頁面中評估

程式碼在 Locator.EvaluateAllAsync() 內 執行於頁面中,你可以在那裡呼叫任意 DOM apis。

var rows = page.GetByRole(AriaRole.Listitem);
var texts = await rows.EvaluateAllAsync(
"list => list.map(element => element.textContent)");

嚴格性

定位器是嚴格的。這意味著所有對定位器的操作,如果匹配多個目標 DOM 元素,將拋出異常。例如,如果 DOM 中有多個按鈕,則以下呼叫將拋出異常:

如果超過一個則拋出錯誤

await page.GetByRole(AriaRole.Button).ClickAsync();

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

多個元素運作良好

await page.GetByRole(AriaRole.Button).CountAsync();

您可以通過 Locator.First, Locator.LastLocator.Nth() 告訴 Playwright 當多個元素匹配時使用哪個元素,從而明確選擇退出嚴格性檢查。這些方法 不建議使用,因為當您的頁面發生變化時,Playwright 可能會點擊您未打算點擊的元素。相反,請遵循上述最佳實踐來建立唯一標識目標元素的定位器。

更多定位器

不常用的定位器請參閱其他定位器指南。