Học Playwright tiếng Việt, Cộng đồng Playwright cho người Việt

Khóa học

[Vọc Playwright] – Other locators

https://playwright.dev/docs/other-locators

Introduction

  • Ngoài các hàm lấy locators được đề xuất như page.getByRole()page.getByText(), Playwright còn supports nhiều công cụ khác để lấy locator sẽ được nói tới trong bài này.

CSS locator

  • Note: Playwright khuyên nên ưu tiên sử dụng user-visible locators như text hoặc accessible thay vì sử dụng CSS trong quá trình triển khai vì có thể fail khi page thay đổi.
  • Playwright có thể xác định vị trí element bằng CSS selector.
await page.locator('css=button').click();
  • Playwright mở rộng CSS selector tiêu chuẩn theo 2 cách:
    • CSS selectors thông qua shadow DOM.
    • Playwright thêm các pseudo-classes tuỳ chỉnh như: :visible, :has-text(), :has(), :is(), :nth-match() và thêm nữa.

CSS: matching by text

  • Playwright bao gồm 1 số CSS pseudo-classes để match với các elements theo text content của chúng.

    • article:has-text("Playwright") – the :has-text() match với bất kỳ element nào chứa text được chỉ định ở đâu đó bên trong, có thể ở 1 element child hoặc 1 element descendant. So sánh không phân biệt chữ hoa chữ thường, cắt khoảng trắng và tìm kiếm chuỗi con.
    • Ví dụ: article:has-text("Playwright") matches <article><div>Playwright</div></article>.
    • Lưu ý rằng: has-text() nên được sử dụng cùng với các CSS khác, nếu không nó sẽ match với tất cả các elements chứa text được chỉ định, bao gồm cả <body>
    // Wrong, will match many elements including <body>
    await page.locator(':has-text("Playwright")').click();
    // Correct, only matches the <article> element
    await page.locator('article:has-text("Playwright")').click();
    
    • nav-bar :text("Home"):text matches với element nhỏ nhất chứa text được chỉ định. So sánh không phân biệt chữ hoa chữ thường, cắt khoảng trắng và tìm kiếm chuỗi con.
      • Ví dụ dưới đây sẽ tìm ra được element với text “Home” ở đâu đó bên trong element #nav-bar:
      await page.locator('#nav-bar :text("Home")').click();
      
    • #nav-bar :text-is("Home"):text-is() matches với element nhỏ nhất với text chính xác. So sánh có phân biệt chữ hoa, chữ thường, cắt khoảng trắng và tìm kiếm chuỗi đầy đủ.
      • Ví dụ: :text-is("Log") không match với <button>Log in</button> bởi vì button chứa node text "Log in" và nó không bằng với "Log". Tuy nhiên :text-is("Log") matches với <button> Log <span>in</span></button>, bởi vì <button> chứa node text " Log ".
      • Tương tự :text-is("Download") sẽ không match với <button>download</button> bởi vì nó phân biệt chữ hoa chữ thường.
    • #nav-bar :text-matches("reg?ex", "i"):text-matches() matches với element nhỏ nhất với text content matching với JavaScript-like regex.
      • Ví dụ :text-matches("Log\s*in", "i") matches với <button>Login</button><button>log IN</button>.
  • Note:

    • So sánh text luôn normalizes khoảng trắng. Ví dụ, nó biến nhiều khoảng trắng (spaces) thành một, biến các ngắt dòng thành khoảng trắng và trim khoảng trắng ở đầu và cuối.
    • Các element dạng buttonsubmit match được với value của chúng thay vì text content. Ví dụ: :text("Log in") matches với <input type=button value="Log in">.

CSS: matching only visible elements

  • Playwright hỗ trợ :visible trong CSS selector. Ví dụ css=button matches với tất cả các button trên page, trong khi đó css=button:visible chỉ matches với các buttons được hiển thị. Điều này rất hữu ích để phân biệt các elements giống nhau những khác nhau về mặt hiển thị.

  • Cân nhắc về 1 page có 2 buttons, button đầu tiên ẩn và button thứ 2 hiển thị.

    <button style='display: none'>Invisible</button>
    <button>Visible</button>
    
    • Điều nay sẽ tìm thấy cả 2 buttons và đưa ra lỗi strictness:
        await page.locator('button').click();
    
    • Thao tác này sẽ chỉ tìm thấy button thứ hai vì nó hiển thị và sau đó click vào button đó.
        await page.locator('button:visible').click();
    

CSS: elements that contain other elements

  • :has() là một CSS thử nghiệm . Nó trả về 1 element nếu bất kỳ selectors nào được truyền dưới dạng tham số liên quan đến :scope: của element đã match với ít nhất 1 element.

  • Đoạn code sau trả về text content của element <article><div class=promo> bên trong.

    await page.locator('article:has(div.promo)').textContent();

CSS: elements matching one of the conditions

  • Danh sách các CSS selector được phân tách bằng dấu phẩy sẽ match với tất cả các elements có thể được chọn bởi một trong các selector trong danh sách đó.
// Clicks a <button> that has either a "Log in" or "Sign in" text.
await page.locator('button:has-text("Log in"), button:has-text("Sign in")').click();
  • :is() là một CSS thử nghiệm . Nó hữu ích trong việc chỉ định danh sách các điều kiện bổ sung trên 1 element.

CSS: matching elements based on layout

  • Note: Matching dựa trên layout có thể tạo ra những kết quả không như mong muốn. Ví dụ, một element khác có thể match khi layout thay đổi 1 pixel.
  • Đôi khi, thật khó để tìm ra 1 selector tốt cho element nhắm tới khi nó thiếu các tính năng đặc biệt. Trong trường hợp này, Playwright layout CSS có thể giúp. Chúng có thể được kết hợp với CSS thông thường để xác định 1 trong nhiều lựa chọn.
  • Ví dụ, input:right-of(:text("Password")) matches với 1 field input ở bên phải text “Password” – hưu ích khi trang có nhiều fields input khó phân biệt với nhau.
  • Lưu ý rằng layout pseudo-classes rất hữu ích ngoài những thứ khác, chẳng hạn như input. Nếu bạn chỉ sử dụng layout pseudo-class, như :right-of(:text("Password")) rất có thể bạn sẽ không nhận được input mà bạn đang tìm kiếm mà là 1 element trống nào đó giữa đoạn text và input muốn nhắm tới.
  • Layout pseudo-classes sử dụng bounding client rect để tính toán khoảng cách và vị trí tương đối của các elements.
    • :right-of(div > button) – Match với các elements nằm ở bên phải của bất kỳ element nào match với inner selector, ở bất kỳ vị trí dọc nào.
    • :left-of(div > button) – Match với các element nằm ở bên trái của bất kỳ element nào match với inner selector, ở bất kỳ vị trí dọc nào.
    • :above(div > button) – Match với các element nằm ở trên của bất kỳ element nào match với inner selector, bất kỳ vị trí nằm ngang nào.
    • :below(div > button) – Match với các element nằm ở dưới của bất kỳ element nào match với inner selector, bất kỳ vị trí nằm ngang nào.
    • :near(div > button) – Match với các element gần (trong phạm vi 50 pexels CSS) bất kỳ element match với inner selector.
  • Lưu ý rằng các kết quả match được sắp xếp theo khoảng cách của chúng với anchor element, vì vậy bạn có thể sử dụng locator.first() để lấy cái gần nhất. Nó chỉ hữu dụng nếu bạn có danh sách các elements giống nhau, trong đó element gần nhất là element phù hợp. Tuy nhiên sử dụng locator.first() trong các cases khác không work như mong đợi – nó sẽ không nhắm tới element mà bạn tìm kiếm, mà chọn 1 element khác gần nhất như 1 <div> trống ngẫu nhiên, hoặc 1 element đã bị scroll ra ngoài và không hiển thị trên màn hình nữa.
// Fill an input to the right of "Username".
await page.locator('input:right-of(:text("Username"))').fill('value');

// Click a button near the promo card.
await page.locator('button:near(.promo-card)').click();

// Click the radio input in the list closest to the "Label 3".
await page.locator('[type=radio]:left-of(:text("Label 3"))').first().click();
  • Tất cả các layout pseudo-class đều hỗ trợ 1 khoảng cách pixel tối đa tuỳ chọn làm đối số cuối cùng. Ví dụ: button:near(:text("Username"), 120) sẽ match với một node cách element có text “Username” tối đa 120 pixel CSS.

CSS: pick n-th match from the query result

  • Note: Thông thường có thể phân biệt được các elements bởi thuộc tính hay nội dung text của nó, điều này cps khả năng ổn định hơn trước các thay đổi của trang.
  • Đôi khi trang chứa mốt số element tương tự nhau và rất khó để lấy được thành phần cụ thể. Ví dụ:
<section> <button>Buy</button> </section>
<article><div> <button>Buy</button> </div></article>
<div><div> <button>Buy</button> </div></div>
  • Trong trường hợp này, :nth-match(:text("Buy"), 3) sẽ chọn button thứ ba từ đoạn code trên. Lưu ý rằng index bắt đầu từ 1.
// Click the third "Buy" button
await page.locator(':nth-match(:text("Buy"), 3)').click();
  • :nth-match() cũng hữu ích khi đợi cho đến khi một số elements được chỉ định xuất hiện, sử dụng locator.waitFor().
// Wait until all three buttons are visible
await page.locator(':nth-match(:text("Buy"), 3)').waitFor();
  • Note: Không giống như :nth-child(), các element không nhất thiết phải là anh em (siblings), chúng có thể ở bất kỳ đâu trên trang. Trong đoạn code trên, cả ba button đều matches với bộ chọn :text("Buy"):nth-match() chọn button thứ ba.

N-th element locator

  • Bạn có thể thu hẹp truy vấn đến kết quả match n-th bằng cách sử dụng locator nth= truyền index bắt đầu từ số 0.
// Click first button
await page.locator('button').locator('nth=0').click();

// Click last button
await page.locator('button').locator('nth=-1').click();

Parent element locator

  • Khi bạn muốn nhắm tới 1 element cha của 1 vài elements khác, bạn nên dùng locator.filter() bởi element con. Ví dụ, hãy xem cấu trúc DOM sau:
<li><label>Hello</label></li>
<li><label>World</label></li>
  • Nếu bạn muốn nhắm tới element cha <li>> của label với text "Hello", sử dụng locator.filter() là hợp lý nhất:
const child = page.getByText('Hello');
const parent = page.getByRole('listitem').filter({ has: child });
  • Ngoài ra, nếu bạn không tìm được locator phù hợp với element cha đó, sử dụng xpath=... Lưu ý rằng phương thức này không đáng tin cậy, bởi vì nếu có bất kỳ thay dổi ở cấu trúc DOM test của bạn sẽ bị fail. Ưu tiên locator.filter() khi có thể.
const parent = page.getByText('Hello').locator('xpath=..');

React locator

  • Note: React locator là thực nghiệm và bắt đầu với dấu _. Function có thể thay đổi trong tương lai.
  • React locator cho phép tìm kiếm elements bởi tên component của nó và các giá trị của property. Cú pháp rất giống với CSS attribute selectors và hỗ trợ tất cả CSS attribute.
  • Trong React locator, tên component được phiên âm với kiểu CamelCase.
await page.locator('_react=BookItem').click();
  • Một số ví dụ:

    • match theo component: _react=BookItem
    • match theo component và giá trị property chính xác, phân biệt chữ hoa chữ thường: _react=BookItem[author = "Steven King"]
    • match theo giá trị property duy nhất, phân biệt chữ hoa chữ thường: _react=[author = "steven king" i]
    • match theo component and giá trị property đúng: _react=MyButton[enabled]
    • match theo component và giá trị boolean: _react=MyButton[enabled = false]
    • match theo property giá trị substring: _react=[author *= "King"]
    • match theo component và nhiều properties: _react=BookItem[author *= "king" i][year = 1990]
    • match theo giá trị property lồng nhau: _react=[some.nested.value = 12]
    • match theo component và tiền tố giá trị property: _react=BookItem[author ^= "Steven"]
    • match theo component và hậu tố giá trị property: _react=BookItem[author $= "Steven"]
    • match theo component và key: _react=BookItem[key = '2']
    • match theo giá trị biểu thức property: _react=[author = /Steven(\\s+King)?/i]
  • Để tìm tên React element trong cây sử dụng React DevTools.

  • Note:

    • React locator hỗ trợ React 15 trở xuống.
    • React locator, cũng như React DevTools, chỉ hoạt động đối với các app dựng chưa được tối ưu hoá.

Vue locator

  • Note: Vue locator là thực nghiệm và bắt đầu với dấu _. Function có thể thay đổi trong tương lai.
  • Vue locator cho phép tìm các elements bởi tên component và giá trị property của nó. Cú pháp rất giống với CSS attribute selectors và hỗ trợ tất cả CSS attribute.
  • Trong React locator, tên component được phiên âm với kiểu kebab-case.
await page.locator('_vue=book-item').click();
  • Một số ví dụ:
    • match theo component: _vue=book-item
    • match theo component và giá trị property chính xác, phân biệt chữ hoa chữ thường: _vue=book-item[author = "Steven King"]
    • match theo giá trị property duy nhất, phân biệt chữ hoa chữ thường: _vue=[author = "steven king" i]
    • match theo component and giá trị property đúng: _vue=my-button[enabled]
    • match theo component và giá trị boolean: _vue=my-button[enabled = false]
    • match theo property giá trị substring: _vue=[author *= "King"]
    • match theo component và nhiều properties: _vue=book-item[author *= "king" i][year = 1990]
    • match theo giá trị property lồng nhau: _vue=[some.nested.value = 12]
    • match theo component và tiền tố giá trị property: _vue=book-item[author ^= "Steven"]
    • match theo component và hậu tố giá trị property: _vue=book-item[author $= "Steven"]
    • match theo giá trị biểu thức property: _vue=[author = /Steven(\\s+King)?/i]
  • Để tìm tên Vue element trong cây sử dụng Vue DevTools.
  • Note:
    • Vue locator hỗ trợ Vue2 trở xuống.
    • Vue locator, cũng như Vue DevTools, chỉ hoạt động đối với các app dựng chưa được tối ưu hoá.

Xpath locator

  • Warrning: Playwright ưu tiên user-visible locators như text hoặc accessible thay vì sử dụng Xpath trong quá trình triển khai vì có thể fail khi page thay đổi.
  • Xpath locators còn được gọi là Document.evaluate.
await page.locator('xpath=//button').click();
  • Note:
    • Bất kỳ selector string nào bắt đầu bằng // hoặc .. đều được coi là xpath selector. Ví dụ, Playwright chuyển đổi '//html/body' sang xpath=//html/body.
    • XPath không thể truy xuất vào bên trong shadow roots.

XPath union

  • Toán tử pipe (|) có thể được sử dụng để chỉ định nhiều selector trong XPath. Nó sẽ match với tất cả các elements có thể được chọn bởi một trong các selectors trong danh sách đó.
// Waits for either confirmation dialog or load spinner.
await page.locator(
    `//span[contains(@class, 'spinner__loading')]|//div[@id='confirmation']`
).waitFor();

Label to form control retargeting

  • Warrning: Playwright khuyên nên xác định bằng label text thay vì dựa vào việc chuyển từ label sang control.
  • Mục tiêu các hành động input trong Playwright tự dộng phân biệt labels và controls, vì vậy bạn cần nhắm đến label để thực hiện hành động associated control.
  • Ví dụ, theo cấu trúc DOM sau: <label for="password">Password:</label><input id="password" type="password">. Bạn có thể nhắm tới label bởi text “Password” của nó bằng page.getByText(). Tuy nhiên, các hành động sau sẽ được thực hiện trên element input thay vì label:
    • locator.click() sẽ click vào label và tự động đưa focus vào field input;
    • locator.fill() sẽ điền vào field input;
    • locator.inputValue() sẽ trả về giá trị của field input;
    • locator.selectText() sẽ chọn text trong field input;
    • locator.setInputFiles() sẽ set files cho field input với kiểu là file;
    • locator.selectOption() sẽ chọn một tùy chọn từ hộp chọn (select box).
// Fill the input by targeting the label.
await page.getByText('Password').fill('secret');
  • Tuy nhiên, các phương thức khác sẽ nhắm vào chính label đó, ví dụ expect(locator).toHaveText() sẽ xác nhận nội dung text của label chứ không phải field input.
// Fill the input by targeting the label.
await expect(page.locator('label')).toHaveText('Password');

Legacy text locator

  • Warning: Playwirght khuyên sử dụng text locator hiện đại.
  • Legacy text locator match với các phần tử có chứa text được truyền.
await page.locator('text=Log in').click();
  • Legacy text locator có 1 vài biến thể:
    • text=Log in – mặc định match không phân biệt chữ hoa chữ thường, cắt bớt khoảng trắng và tìm kiếm chuỗi con. Ví dụ, text=Log matches <button>Log in</button>.
    await page.locator('text=Log in').click();
    
    • text="Log in" – nội dung text có thể được thoát bằng dấu nháy đơn hoặc dấu nháy kép để tìm kiếm node text với nội dung chính xác sau khi cắt khoảng trắng.
      • Ví dụ, text="Log" không match với <button>Log in</button> bởi vì <button> chứa một node text duy nhất là ‘Log in’, không khớp chính xác với ‘Log’. Tuy nhiên, text="Log" sẽ match với <button> Log <span>in</span></button>, vì <button> chứa một node text là ‘Log’. Chế độ này yêu cầu match chính xác phân biệt chữ hoa chữ thường, do đó text="Download" sẽ không match với <button>download</button>.
      • Nội dung được trích dẫn tuân theo các quy tắc thoát thông thường, ví dụ: sử dụng \" để thoát dấu nháy đơn hoặc nháy kép trong chuỗi trích dẫn text="foo\"bar".
      await page.locator('text="Log in"').click();
      
    • /Log\s*in/i – body có thể là một Javascript-like regex được bọc bởi ký hiệu /. Ví dụ, text=/Log\s*in/i sẽ match với <button>Login</button> và <button>log IN</button>.
    • Note:
      • Chuỗi selectors bắt đầu và kết thúc bằng dấu nháy đơn hoặc nháy kép (" hoặc ') được coi coi là legacy text locators. Ví dụ, “Log in” được chuyển đổi thành text="Login" nội bộ.
      • Việc matching luôn bình thường hoá khoảng trắng. Ví dụ, nó biến nhiều khoảng trắng thành một, biến các ngắt dòng thành khoảng trắng và bỏ qua khoảng trắng ở đầu và cuối.
      • Những elements input loại buttonsubmit đùe được match với value của nó thay vì nội dụng text. Ví dụ, text=Log in match với <input type=button value="Log in">.

id, data-testid, data-test-id, data-test selectors

  • Warning: Playwright khuyên sử dụng locating by test id
  • Playwright hỗ trợ cách viết tắt để chọn các elements bằng cách sử dụng các thuộc tính nhất định. Hiện tại, chỉ có các thuộc tính sau được hỗ trợ:
    • id
    • data-testid
    • data-test-id
    • data-test
// Fill an input with the id "username"
await page.locator('id=username').fill('value');

// Click an element with data-test-id "submit"
await page.locator('data-test-id=submit').click();
  • Note: Thuộc tính selector không phải là CSS selector, vì vậy bất kỳ CSS nào như :enabled sẽ không hỗ trợ. Để biết thêm tính năng mới, hãy sử dụng CSS selector phù hợp, ví dụ: css=[data-test="login"]:enabled.

Chaining selectors

  • Warning: Playwright khuyên sử dụng chaining locators.
  • Selectors được xác định là engine=body hoặc ở dạng ngắn có thể kết hợp với >> token, ví dụ selector1 >> selector2 >> selectors3. Khi selectors được xâu chuỗi, selector tiếp theo sẽ được truy vấn liên quan đến kết quả của selector trước đó.
  • Ví dụ:
css=article >> css=.bar > .baz >> css=span[attr=value]

tương đương

document
    .querySelector('article')
    .querySelector('.bar > .baz')
    .querySelector('span[attr=value]');
  • Nếu như selecotr cần bao gồm >> trong body, thì selector đó phải được thoát bên trong 1 chuỗi để không bị nhầm lẫn với dấu phân cách chuỗi, ví dụ text="some >> text".

Intermediate matches

  • Warning: Playwright ưu tiên filtering by another locator để xác định được element có chứa các element khác.
  • Theo mặc định, các selectors sẽ được phân giải thành 1 element được selector cuối cùng truy vấn. Selector có thể có tiền tố * để nắm bắt được các element được truy vấn bởi selector trung gian.
  • Ví dụ, css=article >> text=Hello ghi lại element với text Hello, and *css=article >> text=Hello (lưu ý *) ghi lại element article có chứa 1 số element với text Hello.

Trả lời