選擇器
WebDriver 協定提供了多種選擇器策略來查詢元素。WebdriverIO 簡化了它們,以保持選擇元素的簡單性。請注意,即使查詢元素的命令稱為 $
和 $$
,它們也與 jQuery 或 Sizzle 選擇器引擎無關。
雖然有這麼多不同的選擇器可用,但只有少數能提供一種彈性的方式來找到正確的元素。例如,給定以下按鈕
<button
id="main"
class="btn btn-large"
name="submission"
role="button"
data-testid="submit"
>
Submit
</button>
我們建議和不建議以下選擇器
選擇器 | 建議 | 註解 |
---|---|---|
$('button') | 🚨 絕對不要 | 最糟 - 太過通用,沒有上下文。 |
$('.btn.btn-large') | 🚨 絕對不要 | 差。與樣式耦合。極易變更。 |
$('#main') | ⚠️ 謹慎使用 | 較佳。但仍與樣式或 JS 事件監聽器耦合。 |
$(() => document.queryElement('button')) | ⚠️ 謹慎使用 | 有效的查詢,但撰寫複雜。 |
$('button[name="submission"]') | ⚠️ 謹慎使用 | 與具有 HTML 語義的 name 屬性耦合。 |
$('button[data-testid="submit"]') | ✅ 良好 | 需要額外的屬性,與 a11y 沒有關聯。 |
$('aria/Submit') 或 $('button=Submit') | ✅ 總是 | 最佳。類似於使用者與頁面互動的方式。建議使用前端的翻譯檔案,這樣當翻譯更新時,您的測試永遠不會失敗 |
CSS 查詢選擇器
如果沒有另外指示,WebdriverIO 將使用 CSS 選擇器模式查詢元素,例如
loading...
連結文字
若要取得其中具有特定文字的錨點元素,請查詢以等號 (=
) 符號開頭的文字。
例如
loading...
您可以使用以下方式查詢此元素
loading...
部分連結文字
若要尋找其可見文字部分符合您搜尋值的錨點元素,請在查詢字串前面使用 *=
來查詢 (例如,*=driver
)。
您也可以使用以下方式查詢以上範例中的元素
loading...
注意:您無法在一個選擇器中混合多個選擇器策略。使用多個鏈接的元素查詢來達到相同的目標,例如
const elem = await $('header h1*=Welcome') // doesn't work!!!
// use instead
const elem = await $('header').$('*=driver')
具有特定文字的元素
相同的技術也可以應用於元素。此外,也可以在查詢中使用 .=
或 .*=
進行不區分大小寫的匹配。
例如,以下是查詢標題為「歡迎來到我的頁面」的 1 級標題
loading...
您可以使用以下方式查詢此元素
loading...
或使用查詢部分文字
loading...
對於 id
和 class
名稱也是如此
loading...
您可以使用以下方式查詢此元素
loading...
注意:您無法在一個選擇器中混合多個選擇器策略。使用多個鏈接的元素查詢來達到相同的目標,例如
const elem = await $('header h1*=Welcome') // doesn't work!!!
// use instead
const elem = await $('header').$('h1*=Welcome')
標籤名稱
若要查詢具有特定標籤名稱的元素,請使用 <tag>
或 <tag />
。
loading...
您可以使用以下方式查詢此元素
loading...
名稱屬性
若要查詢具有特定名稱屬性的元素,您可以使用一般的 CSS3 選擇器,或透過將類似 [name="some-name"] 作為選擇器參數傳遞,來使用來自 JSONWireProtocol 的提供的名稱策略
loading...
loading...
注意:此選擇器策略已棄用,僅在由 JSONWireProtocol 協定執行的舊瀏覽器或使用 Appium 時才有效。
xPath
也可以透過特定的 xPath 來查詢元素。
xPath 選擇器的格式類似 //body/div[6]/div[1]/span[1]
。
loading...
您可以使用以下方式查詢第二個段落
loading...
您可以使用 xPath 來上下遍歷 DOM 樹
loading...
協助工具名稱選擇器
依其可存取名稱查詢元素。可存取名稱是當該元素獲得焦點時螢幕閱讀器所宣告的內容。可存取名稱的值可以是視覺內容或隱藏的文字替代方案。
您可以在我們的發佈部落格文章中閱讀更多關於此選擇器的資訊
依 aria-label
擷取
loading...
loading...
依 aria-labelledby
擷取
loading...
loading...
依內容擷取
loading...
loading...
依標題擷取
loading...
loading...
依 alt
屬性擷取
loading...
loading...
ARIA - 角色屬性
若要根據 ARIA 角色查詢元素,您可以直接將元素的角色指定為選擇器參數,例如 [role=button]
loading...
loading...
ID 屬性
WebDriver 協定不支援定位器策略「id」,應使用 CSS 或 xPath 選擇器策略來尋找使用 ID 的元素。
但是,某些驅動程式 (例如 Appium You.i Engine Driver) 可能仍然支援此選擇器。
目前支援的 ID 選擇器語法為
//css locator
const button = await $('#someid')
//xpath locator
const button = await $('//*[@id="someid"]')
//id strategy
// Note: works only in Appium or similar frameworks which supports locator strategy "ID"
const button = await $('id=resource-id/iosname')
JS 函數
您也可以使用 JavaScript 函數,透過 Web 原生 API 來擷取元素。當然,您只能在 Web 環境中執行此操作 (例如,browser
或行動裝置中的 Web 環境)。
給定以下 HTML 結構
loading...
您可以使用以下方式查詢 #elem
的兄弟元素
loading...
深層選擇器
從 WebdriverIO 的 v9
版本開始,不再需要這個特殊的選擇器,因為 WebdriverIO 會自動穿透 Shadow DOM。建議移除前面的 >>>
來遷移這個選擇器。
許多前端應用程式大量依賴具有Shadow DOM的元素。在沒有任何變通方法的情況下,技術上不可能查詢 Shadow DOM 內的元素。shadow$
和 shadow$$
就是這樣的變通方法,但有其限制。有了深層選擇器,您現在可以使用常見的查詢命令查詢任何 Shadow DOM 內的所有元素。
假設我們有一個應用程式,其結構如下
使用此選擇器,您可以查詢巢狀於另一個 Shadow DOM 內的 <button />
元素,例如:
loading...
行動裝置選擇器
對於混合式行動裝置測試,在執行命令之前,自動化伺服器必須處於正確的情境中。為了自動化手勢,驅動程式最好設定為原生情境。但是,要從 DOM 選擇元素,驅動程式需要設定為平台的 Webview 情境。只有在那之後才能使用上述方法。
對於原生行動裝置測試,不需要在情境之間切換,因為您必須使用行動裝置策略並直接使用底層裝置自動化技術。當測試需要對尋找元素進行一些精細的控制時,這特別有用。
Android UiAutomator
Android 的 UI Automator 架構提供了多種尋找元素的方法。您可以使用 UI Automator API,尤其是 UiSelector 類別來定位元素。在 Appium 中,您將 Java 程式碼以字串形式傳送至伺服器,伺服器會在應用程式的環境中執行它,並傳回元素或多個元素。
const selector = 'new UiSelector().text("Cancel").className("android.widget.Button")'
const button = await $(`android=${selector}`)
await button.click()
Android DataMatcher 和 ViewMatcher(僅限 Espresso)
Android 的 DataMatcher 策略提供了一種透過 Data Matcher 來尋找元素的方法
const menuItem = await $({
"name": "hasEntry",
"args": ["title", "ViewTitle"]
})
await menuItem.click()
類似地,View Matcher
const menuItem = await $({
"name": "hasEntry",
"args": ["title", "ViewTitle"],
"class": "androidx.test.espresso.matcher.ViewMatchers"
})
await menuItem.click()
Android View Tag(僅限 Espresso)
View tag 策略提供了一種方便的方法,可以透過其標籤來尋找元素。
const elem = await $('-android viewtag:tag_identifier')
await elem.click()
iOS UIAutomation
當自動化 iOS 應用程式時,可以使用 Apple 的 UI Automation 架構來尋找元素。
這個 JavaScript API 具有存取視圖及其所有內容的方法。
const selector = 'UIATarget.localTarget().frontMostApp().mainWindow().buttons()[0]'
const button = await $(`ios=${selector}`)
await button.click()
您也可以在 Appium 的 iOS UI Automation 中使用述詞搜尋來進一步精確元素選擇。有關詳細資訊,請參閱此處。
iOS XCUITest 述詞字串和類別鏈
對於 iOS 10 及更高版本(使用 XCUITest
驅動程式),您可以使用述詞字串
const selector = `type == 'XCUIElementTypeSwitch' && name CONTAINS 'Allow'`
const switch = await $(`-ios predicate string:${selector}`)
await switch.click()
以及類別鏈
const selector = '**/XCUIElementTypeCell[`name BEGINSWITH "D"`]/**/XCUIElementTypeButton'
const button = await $(`-ios class chain:${selector}`)
await button.click()
輔助功能 ID
輔助功能 ID
定位器策略旨在讀取 UI 元素的唯一識別碼。這有不會在本地化或任何其他可能更改文字的過程中變更的好處。此外,如果功能相同的元素具有相同的輔助功能 ID,則有助於建立跨平台測試。
- 對於 iOS,這是 Apple 佈局的
accessibility identifier
此處。 - 對於 Android,
輔助功能 ID
會對應到元素的content-description
,如 此處所述。
對於這兩個平台,通常最好透過其 輔助功能 ID
取得元素(或多個元素)。它也是優先於已棄用的 name
策略的方法。
const elem = await $('~my_accessibility_identifier')
await elem.click()
類別名稱
類別名稱
策略是一個 字串
,代表目前檢視上的 UI 元素。
- 對於 iOS,它是UIAutomation 類別的完整名稱,並將以
UIA-
開頭,例如文字欄位的UIATextField
。完整的參考資料可以在此處找到。 - 對於 Android,它是 UI Automator 類別的完整限定名稱,例如文字欄位的
android.widget.EditText
。完整的參考資料可以在此處找到。 - 對於 Youi.tv,它是 Youi.tv 類別的完整名稱,並將以
CYI-
開頭,例如按鈕元素的CYIPushButtonView
。完整的參考資料可以在 You.i Engine Driver 的 GitHub 頁面找到。
// iOS example
await $('UIATextField').click()
// Android example
await $('android.widget.DatePicker').click()
// Youi.tv example
await $('CYIPushButtonView').click()
鏈式選擇器
如果您想在查詢中更精確,您可以鏈接選擇器,直到找到正確的元素。如果您在實際命令之前呼叫 element
,WebdriverIO 會從該元素開始查詢。
例如,如果您有類似以下的 DOM 結構
<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>
而且您想將產品 B 新增至購物車,僅使用 CSS 選擇器會很困難。
使用選擇器鏈接,會更容易。只需逐步縮小所需的元素範圍
await $('.row .entry:nth-child(2)').$('button*=Add').click()
Appium 影像選擇器
使用 -image
定位器策略,可以將 Appium 傳送一個代表您要存取之元素的影像檔案。
支援的檔案格式 jpg、png、gif、bmp、svg
完整的參考資料可以在此處找到
const elem = await $('./file/path/of/image/test.jpg')
await elem.click()
注意:Appium 使用此選擇器的方式是它會在內部製作一個(應用程式)螢幕快照,並使用提供的影像選擇器來驗證是否可以在該(應用程式)螢幕快照中找到該元素。
請注意,Appium 可能會調整擷取的(應用程式)螢幕快照大小,使其與您的(應用程式)螢幕的 CSS 大小相符(這將發生在 iPhone 上,以及具有 Retina 顯示器的 Mac 電腦上,因為 DPR 大於 1)。這會導致找不到相符項目,因為提供的影像選擇器可能是從原始螢幕快照中擷取的。您可以透過更新 Appium Server 設定來修正此問題,請參閱 Appium 文件以取得設定,以及 此評論以取得詳細說明。
React 選擇器
WebdriverIO 提供了一種基於元件名稱選擇 React 元件的方法。為此,您可以選擇兩個命令:react$
和 react$$
。
這些命令允許您從 React VirtualDOM 選擇元件,並傳回單個 WebdriverIO 元素或元素陣列(取決於使用的函式)。
注意:命令 react$
和 react$$
在功能上類似,只是 react$$
會傳回所有相符的實例作為 WebdriverIO 元素的陣列,而 react$
會傳回找到的第一個實例。
基本範例
// 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'))
在上面的程式碼中,應用程式內部有一個簡單的 MyComponent
實例,React 會將其呈現在 id="root"
的 HTML 元素內。
使用 browser.react$
命令,您可以選擇 MyComponent
的實例
const myCmp = await browser.react$('MyComponent')
現在,您已將 WebdriverIO 元素儲存在 myCmp
變數中,您可以對其執行元素命令。
篩選元件
WebdriverIO 在內部使用的程式庫允許您依據元件的 props 和/或狀態篩選您的選擇。若要執行此操作,您需要為 props 傳遞第二個引數,以及為狀態傳遞第三個引數至瀏覽器命令。
// 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'))
如果您想選擇具有 props name
作為 WebdriverIO
的 MyComponent
實例,您可以像這樣執行命令
const myCmp = await browser.react$('MyComponent', {
props: { name: 'WebdriverIO' }
})
如果您想依據狀態篩選我們的選擇,browser
命令會如下所示
const myCmp = await browser.react$('MyComponent', {
state: { myState: 'some value' }
})
處理 React.Fragment
當使用 react$
命令選擇 React 片段時,WebdriverIO 會傳回該元件的第一個子元素作為元件的節點。如果您使用 react$$
,您將收到一個陣列,其中包含片段中所有符合選擇器的 HTML 節點。
// 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'))
以上述範例來說,這些指令的運作方式如下:
await browser.react$('MyComponent') // returns the WebdriverIO Element for the first <div />
await browser.react$$('MyComponent') // returns the WebdriverIO Elements for the array [<div />, <div />]
注意:如果您有多個 MyComponent
實例,並且您使用 react$$
來選取這些片段元件,則會回傳一個包含所有節點的一維陣列。換句話說,如果您有 3 個 <MyComponent />
實例,則會回傳一個包含六個 WebdriverIO 元素的陣列。
自訂選取器策略
如果您的應用程式需要以特定方式擷取元素,您可以定義自己的自訂選取器策略,並與 custom$
和 custom$$
一起使用。為此,請在測試開始時(例如在 before
hook 中)註冊您的策略。
loading...
假設有以下 HTML 片段:
loading...
然後透過呼叫來使用它:
loading...
注意:這僅在可以執行 execute
命令的 Web 環境中有效。