Chuyển đến nội dung chính

Bộ Chọn (Selectors)

WebDriver Protocol cung cấp một số chiến lược bộ chọn để truy vấn phần tử. WebdriverIO đơn giản hóa chúng để giữ cho việc chọn phần tử đơn giản. Lưu ý rằng mặc dù lệnh để truy vấn phần tử được gọi là $$$, chúng không liên quan gì đến jQuery hoặc Sizzle Selector Engine.

Mặc dù có rất nhiều bộ chọn khác nhau, chỉ một vài trong số chúng cung cấp cách mạnh mẽ để tìm đúng phần tử. Ví dụ, với nút sau:

<button
id="main"
class="btn btn-large"
name="submission"
role="button"
data-testid="submit"
>
Submit
</button>

Chúng tôi khuyên dùngkhông khuyên dùng các bộ chọn sau:

Bộ chọnKhuyến nghịGhi chú
$('button')🚨 Không bao giờTệ nhất - quá chung chung, không có ngữ cảnh.
$('.btn.btn-large')🚨 Không bao giờKhông tốt. Gắn liền với kiểu dáng. Rất dễ thay đổi.
$('#main')⚠️ Hạn chếTốt hơn. Nhưng vẫn gắn liền với kiểu dáng hoặc trình lắng nghe sự kiện JS.
$(() => document.queryElement('button'))⚠️ Hạn chếTruy vấn hiệu quả, nhưng phức tạp để viết.
$('button[name="submission"]')⚠️ Hạn chếGắn với thuộc tính name có ngữ nghĩa HTML.
$('button[data-testid="submit"]')✅ TốtYêu cầu thuộc tính bổ sung, không liên kết với a11y.
$('aria/Submit') hoặc $('button=Submit')✅ Luôn luônTốt nhất. Giống với cách người dùng tương tác với trang. Khuyến nghị sử dụng tệp dịch của frontend để tests không bị lỗi khi bản dịch được cập nhật

Bộ chọn truy vấn CSS

Nếu không có chỉ định khác, WebdriverIO sẽ truy vấn các phần tử bằng mẫu CSS selector, ví dụ:

selectors/example.js
loading...

Để lấy một phần tử liên kết với văn bản cụ thể, truy vấn văn bản bắt đầu bằng dấu bằng (=).

Ví dụ:

selectors/example.html
loading...

Bạn có thể truy vấn phần tử này bằng cách gọi:

selectors/example.js
loading...

Để tìm phần tử liên kết có văn bản hiển thị khớp một phần với giá trị tìm kiếm của bạn, hãy truy vấn bằng cách sử dụng *= trước chuỗi truy vấn (ví dụ: *=driver).

Bạn cũng có thể truy vấn phần tử từ ví dụ trên bằng cách gọi:

selectors/example.js
loading...

Lưu ý: Bạn không thể kết hợp nhiều chiến lược bộ chọn trong một bộ chọn. Sử dụng nhiều truy vấn phần tử được nối chuỗi để đạt được mục tiêu tương tự, ví dụ:

const elem = await $('header h1*=Welcome') // không hoạt động!!!
// sử dụng thay thế
const elem = await $('header').$('*=driver')

Phần tử với văn bản nhất định

Kỹ thuật tương tự có thể được áp dụng cho các phần tử. Ngoài ra, cũng có thể thực hiện khớp không phân biệt chữ hoa chữ thường bằng cách sử dụng .= hoặc .*= trong truy vấn.

Ví dụ, đây là một truy vấn cho tiêu đề mức 1 với văn bản "Welcome to my Page":

selectors/example.html
loading...

Bạn có thể truy vấn phần tử này bằng cách gọi:

selectors/example.js
loading...

Hoặc sử dụng truy vấn văn bản một phần:

selectors/example.js
loading...

Tương tự cho tên idclass:

selectors/example.html
loading...

Bạn có thể truy vấn phần tử này bằng cách gọi:

selectors/example.js
loading...

Lưu ý: Bạn không thể kết hợp nhiều chiến lược bộ chọn trong một bộ chọn. Sử dụng nhiều truy vấn phần tử được nối chuỗi để đạt được mục tiêu tương tự, ví dụ:

const elem = await $('header h1*=Welcome') // không hoạt động!!!
// sử dụng thay thế
const elem = await $('header').$('h1*=Welcome')

Tag Name

Để truy vấn một phần tử với tên thẻ cụ thể, sử dụng <tag> hoặc <tag />.

selectors/example.html
loading...

Bạn có thể truy vấn phần tử này bằng cách gọi:

selectors/example.js
loading...

Thuộc tính Name

Để truy vấn các phần tử với thuộc tính name cụ thể, bạn có thể sử dụng bộ chọn CSS3 thông thường hoặc chiến lược name được cung cấp từ JSONWireProtocol bằng cách truyền [name="some-name"] làm tham số bộ chọn:

selectors/example.html
loading...
selectors/example.js
loading...

Lưu ý: Chiến lược bộ chọn này đã lỗi thời và chỉ hoạt động trong các trình duyệt cũ chạy bởi giao thức JSONWireProtocol hoặc bằng cách sử dụng Appium.

xPath

Cũng có thể truy vấn các phần tử qua xPath cụ thể.

Một bộ chọn xPath có định dạng như //body/div[6]/div[1]/span[1].

selectors/xpath.html
loading...

Bạn có thể truy vấn đoạn văn thứ hai bằng cách gọi:

selectors/example.js
loading...

Bạn có thể sử dụng xPath để di chuyển lên và xuống cây DOM:

selectors/example.js
loading...

Bộ chọn tên truy cập (Accessibility Name Selector)

Truy vấn các phần tử bằng tên truy cập của chúng. Tên truy cập là những gì được công bố bởi trình đọc màn hình khi phần tử đó nhận được focus. Giá trị của tên truy cập có thể là cả nội dung trực quan hoặc văn bản thay thế ẩn.

thông tin

Bạn có thể đọc thêm về bộ chọn này trong bài đăng blog phát hành

Lấy bằng aria-label

selectors/aria.html
loading...
selectors/example.js
loading...

Lấy bằng aria-labelledby

selectors/aria.html
loading...
selectors/example.js
loading...

Lấy bằng nội dung

selectors/aria.html
loading...
selectors/example.js
loading...

Lấy bằng title

selectors/aria.html
loading...
selectors/example.js
loading...

Lấy bằng thuộc tính alt

selectors/aria.html
loading...
selectors/example.js
loading...

ARIA - Thuộc tính Role

Để truy vấn các phần tử dựa trên vai trò ARIA, bạn có thể chỉ định trực tiếp vai trò của phần tử như [role=button] làm tham số bộ chọn:

selectors/aria.html
loading...
selectors/example.js
loading...

Thuộc tính ID

Chiến lược định vị "id" không được hỗ trợ trong giao thức WebDriver, nên cần sử dụng các chiến lược bộ chọn CSS hoặc xPath thay thế để tìm phần tử sử dụng ID.

Tuy nhiên, một số trình điều khiển (ví dụ: Appium You.i Engine Driver) vẫn có thể hỗ trợ bộ chọn này.

Các cú pháp bộ chọn ID được hỗ trợ hiện tại là:

//css locator
const button = await $('#someid')
//xpath locator
const button = await $('//*[@id="someid"]')
//id strategy
// Lưu ý: chỉ hoạt động trong Appium hoặc các framework tương tự hỗ trợ chiến lược định vị "ID"
const button = await $('id=resource-id/iosname')

Hàm JS

Bạn cũng có thể sử dụng các hàm JavaScript để lấy phần tử bằng các API gốc của web. Tất nhiên, bạn chỉ có thể làm điều này trong ngữ cảnh web (ví dụ: browser hoặc ngữ cảnh web trong thiết bị di động).

Với cấu trúc HTML sau:

selectors/js.html
loading...

Bạn có thể truy vấn phần tử anh em của #elem như sau:

selectors/example.js
loading...

Bộ chọn Deep

cảnh báo

Bắt đầu từ phiên bản v9 của WebdriverIO, không cần bộ chọn đặc biệt này nữa vì WebdriverIO tự động xuyên qua Shadow DOM cho bạn. Khuyến nghị chuyển đổi khỏi bộ chọn này bằng cách xóa >>> phía trước.

Nhiều ứng dụng frontend phụ thuộc nhiều vào các phần tử với shadow DOM. Về mặt kỹ thuật, không thể truy vấn các phần tử trong shadow DOM mà không có giải pháp thay thế. Các lệnh shadow$shadow$$ là những giải pháp như vậy nhưng có giới hạn. Với bộ chọn deep, bạn có thể truy vấn tất cả các phần tử trong bất kỳ shadow DOM nào bằng lệnh truy vấn thông thường.

Giả sử chúng ta có ứng dụng với cấu trúc sau:

Chrome Example

Với bộ chọn này, bạn có thể truy vấn phần tử <button /> được lồng trong shadow DOM khác, ví dụ:

selectors/example.js
loading...

Bộ chọn Mobile

Đối với kiểm thử di động lai, điều quan trọng là máy chủ tự động hóa phải ở đúng ngữ cảnh trước khi thực thi lệnh. Để tự động hóa cử chỉ, trình điều khiển lý tưởng nên được đặt vào ngữ cảnh gốc. Nhưng để chọn các phần tử từ DOM, trình điều khiển cần được đặt vào ngữ cảnh webview của nền tảng. Chỉ sau đó các phương thức đã đề cập ở trên mới có thể được sử dụng.

Đối với kiểm thử di động gốc, không có việc chuyển đổi giữa các ngữ cảnh, vì bạn phải sử dụng các chiến lược di động và sử dụng công nghệ tự động hóa thiết bị cơ bản trực tiếp. Điều này đặc biệt hữu ích khi một bài kiểm tra cần kiểm soát chi tiết khi tìm các phần tử.

Android UiAutomator

Khung UI Automator của Android cung cấp một số cách để tìm phần tử. Bạn có thể sử dụng UI Automator API, đặc biệt là lớp UiSelector để định vị phần tử. Trong Appium, bạn gửi mã Java dưới dạng chuỗi đến máy chủ, thực thi nó trong môi trường ứng dụng, trả về phần tử hoặc các phần tử.

const selector = 'new UiSelector().text("Cancel").className("android.widget.Button")'
const button = await $(`android=${selector}`)
await button.click()

Android DataMatcher và ViewMatcher (chỉ Espresso)

Chiến lược DataMatcher của Android cung cấp cách tìm phần tử bằng Data Matcher

const menuItem = await $({
"name": "hasEntry",
"args": ["title", "ViewTitle"]
})
await menuItem.click()

Và tương tự với View Matcher

const menuItem = await $({
"name": "hasEntry",
"args": ["title", "ViewTitle"],
"class": "androidx.test.espresso.matcher.ViewMatchers"
})
await menuItem.click()

Android View Tag (chỉ Espresso)

Chiến lược view tag cung cấp cách thuận tiện để tìm phần tử bằng tag của chúng.

const elem = await $('-android viewtag:tag_identifier')
await elem.click()

iOS UIAutomation

Khi tự động hóa ứng dụng iOS, UI Automation framework của Apple có thể được sử dụng để tìm phần tử.

API JavaScript này có các phương thức truy cập giao diện và mọi thứ trên đó.

const selector = 'UIATarget.localTarget().frontMostApp().mainWindow().buttons()[0]'
const button = await $(`ios=${selector}`)
await button.click()

Bạn cũng có thể sử dụng tìm kiếm vị ngữ trong iOS UI Automation trong Appium để tinh chỉnh việc chọn phần tử. Xem tại đây để biết chi tiết.

iOS XCUITest chuỗi vị ngữ và chuỗi lớp

Với iOS 10 trở lên (sử dụng trình điều khiển XCUITest), bạn có thể sử dụng chuỗi vị ngữ:

const selector = `type == 'XCUIElementTypeSwitch' && name CONTAINS 'Allow'`
const switch = await $(`-ios predicate string:${selector}`)
await switch.click()

chuỗi lớp:

const selector = '**/XCUIElementTypeCell[`name BEGINSWITH "D"`]/**/XCUIElementTypeButton'
const button = await $(`-ios class chain:${selector}`)
await button.click()

Accessibility ID

Chiến lược định vị accessibility id được thiết kế để đọc một định danh duy nhất cho phần tử UI. Điều này có lợi là không thay đổi trong quá trình bản địa hóa hoặc bất kỳ quá trình nào khác có thể thay đổi văn bản. Ngoài ra, nó có thể hỗ trợ tạo kiểm tra đa nền tảng, nếu các phần tử có chức năng tương tự có cùng mã truy cập (accessibility id).

  • Đối với iOS, đây là accessibility identifier được đặt bởi Apple tại đây.
  • Đối với Android, accessibility id ánh xạ đến content-description cho phần tử, như mô tả tại đây.

Đối với cả hai nền tảng, việc lấy phần tử (hoặc nhiều phần tử) bằng accessibility id của chúng thường là phương pháp tốt nhất. Đây cũng là cách ưa thích thay cho chiến lược name đã lỗi thời.

const elem = await $('~my_accessibility_identifier')
await elem.click()

Class Name

Chiến lược class name là một string đại diện cho phần tử UI trên giao diện hiện tại.

  • Đối với iOS, đó là tên đầy đủ của lớp UIAutomation, và sẽ bắt đầu bằng UIA-, chẳng hạn như UIATextField cho trường văn bản. Tài liệu đầy đủ có thể được tìm thấy tại đây.
  • Đối với Android, đó là tên đầy đủ của lớp UI Automator tại đây, chẳng hạn như android.widget.EditText cho trường văn bản. Tài liệu đầy đủ có thể được tìm thấy tại đây.
  • Đối với Youi.tv, đó là tên đầy đủ của lớp Youi.tv, và sẽ bắt đầu bằng CYI-, chẳng hạn như CYIPushButtonView cho phần tử nút nhấn. Tài liệu đầy đủ có thể được tìm thấy tại trang GitHub của You.i Engine Driver
// Ví dụ iOS
await $('UIATextField').click()
// Ví dụ Android
await $('android.widget.DatePicker').click()
// Ví dụ Youi.tv
await $('CYIPushButtonView').click()

Bộ chọn chuỗi

Nếu bạn muốn cụ thể hơn trong truy vấn của mình, bạn có thể nối chuỗi các bộ chọn cho đến khi tìm được đúng phần tử. Nếu bạn gọi element trước lệnh thực tế, WebdriverIO bắt đầu truy vấn từ phần tử đó.

Ví dụ, nếu bạn có cấu trúc DOM như:

<div class="row">
<div class="entry">
<label>Product A</label>
<button>Add to cart</button>
<button>More Information</button>
</div>
<div class="entry">
<label>Product B</label>
<button>Add to cart</button>
<button>More Information</button>
</div>
<div class="entry">
<label>Product C</label>
<button>Add to cart</button>
<button>More Information</button>
</div>
</div>

Và bạn muốn thêm sản phẩm B vào giỏ hàng, sẽ khó thực hiện điều đó chỉ bằng bộ chọn CSS.

Với việc nối chuỗi bộ chọn, dễ dàng hơn nhiều. Chỉ cần thu hẹp phần tử mong muốn từng bước một:

await $('.row .entry:nth-child(2)').$('button*=Add').click()

Bộ chọn hình ảnh Appium

Sử dụng chiến lược định vị -image, có thể gửi tệp hình ảnh đại diện cho phần tử bạn muốn truy cập cho Appium.

Các định dạng tệp được hỗ trợ: jpg,png,gif,bmp,svg

Tài liệu đầy đủ có thể được tìm thấy tại đây

const elem = await $('./file/path/of/image/test.jpg')
await elem.click()

Lưu ý: Cách Appium hoạt động với bộ chọn này là nó sẽ tự tạo ảnh chụp màn hình (app) và sử dụng bộ chọn hình ảnh đã cung cấp để xác minh xem phần tử có thể được tìm thấy trong ảnh chụp màn hình (app) đó không.

Lưu ý rằng Appium có thể điều chỉnh kích thước ảnh chụp màn hình (app) để phù hợp với kích thước CSS của màn hình (app) (điều này sẽ xảy ra trên iPhone và máy Mac có màn hình Retina vì DPR lớn hơn 1). Điều này sẽ dẫn đến việc không tìm thấy kết quả khớp vì bộ chọn hình ảnh đã cung cấp có thể đã được lấy từ ảnh chụp màn hình gốc. Bạn có thể khắc phục điều này bằng cách cập nhật cài đặt Appium Server, xem tài liệu Appium để biết cài đặt và bình luận này để có giải thích chi tiết.

Bộ chọn React

WebdriverIO cung cấp cách chọn các thành phần React dựa trên tên thành phần. Để làm điều này, bạn có thể chọn một trong hai lệnh: react$react$$.

Các lệnh này cho phép bạn chọn các thành phần từ React VirtualDOM và trả về một phần tử WebdriverIO hoặc một mảng các phần tử (tùy thuộc vào hàm nào được sử dụng).

Lưu ý: Các lệnh react$react$$ có chức năng tương tự, ngoại trừ việc react$$ sẽ trả về tất cả các instance khớp dưới dạng mảng các phần tử WebdriverIO, và react$ sẽ trả về instance đầu tiên được tìm thấy.

Ví dụ cơ bản

// index.jsx
import React from 'react'
import ReactDOM from 'react-dom'

function MyComponent() {
return (
<div>
MyComponent
</div>
)
}

function App() {
return (<MyComponent />)
}

ReactDOM.render(<App />, document.querySelector('#root'))

Trong mã trên có một instance MyComponent đơn giản trong ứng dụng, mà React đang hiển thị bên trong phần tử HTML với id="root".

Với lệnh browser.react$, bạn có thể chọn một instance của MyComponent:

const myCmp = await browser.react$('MyComponent')

Bây giờ bạn đã lưu phần tử WebdriverIO trong biến myCmp, bạn có thể thực thi các lệnh phần tử với nó.

Lọc các thành phần

Thư viện mà WebdriverIO sử dụng nội bộ cho phép lọc lựa chọn của bạn bằng props và/hoặc state của thành phần. Để làm như vậy, bạn cần truyền một đối số thứ hai cho props và/hoặc đối số thứ ba cho state vào lệnh browser.

// index.jsx
import React from 'react'
import ReactDOM from 'react-dom'

function MyComponent(props) {
return (
<div>
Hello { props.name || 'World' }!
</div>
)
}

function App() {
return (
<div>
<MyComponent name="WebdriverIO" />
<MyComponent />
</div>
)
}

ReactDOM.render(<App />, document.querySelector('#root'))

Nếu bạn muốn chọn instance của MyComponent có prop nameWebdriverIO, bạn có thể thực thi lệnh như sau:

const myCmp = await browser.react$('MyComponent', {
props: { name: 'WebdriverIO' }
})

Nếu bạn muốn lọc lựa chọn theo state, lệnh browser sẽ trông như sau:

const myCmp = await browser.react$('MyComponent', {
state: { myState: 'some value' }
})

Xử lý React.Fragment

Khi sử dụng lệnh react$ để chọn fragments của React, WebdriverIO sẽ trả về phần tử con đầu tiên của thành phần đó làm node của thành phần. Nếu bạn sử dụng react$$, bạn sẽ nhận được một mảng chứa tất cả các node HTML bên trong fragments khớp với bộ chọn.

// index.jsx
import React from 'react'
import ReactDOM from 'react-dom'

function MyComponent() {
return (
<React.Fragment>
<div>
MyComponent
</div>
<div>
MyComponent
</div>
</React.Fragment>
)
}

function App() {
return (<MyComponent />)
}

ReactDOM.render(<App />, document.querySelector('#root'))

Với ví dụ trên, đây là cách các lệnh hoạt động:

await browser.react$('MyComponent') // trả về phần tử WebdriverIO cho <div /> đầu tiên
await browser.react$$('MyComponent') // trả về các phần tử WebdriverIO cho mảng [<div />, <div />]

Lưu ý: Nếu bạn có nhiều instance của MyComponent và bạn sử dụng react$$ để chọn các thành phần fragment này, bạn sẽ nhận được một mảng một chiều của tất cả các node. Nói cách khác, nếu bạn có 3 instance <MyComponent />, bạn sẽ nhận được một mảng với sáu phần tử WebdriverIO.

Chiến lược bộ chọn tùy chỉnh

Nếu ứng dụng của bạn yêu cầu cách cụ thể để lấy phần tử, bạn có thể tự định nghĩa một chiến lược bộ chọn tùy chỉnh mà bạn có thể sử dụng với custom$custom$$. Để làm điều đó, hãy đăng ký chiến lược của bạn một lần vào đầu bài kiểm tra, ví dụ trong hook before:

queryElements/customStrategy.js
loading...

Với đoạn HTML sau:

queryElements/example.html
loading...

Sau đó sử dụng nó bằng cách gọi:

queryElements/customStrategy.js
loading...

Lưu ý: điều này chỉ hoạt động trong môi trường web nơi có thể chạy lệnh execute.

Welcome! How can I help?

WebdriverIO AI Copilot