选择器
该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 属性
定位器策略“id”在 WebDriver 协议中不受支持,应改为使用 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)
视图标签策略提供了一种通过其标签方便地查找元素的方法。
const elem = await $('-android viewtag:tag_identifier')
await elem.click()
iOS UIAutomation
在自动化 iOS 应用程序时,可以使用 Apple 的UI Automation 框架来查找元素。
此 JavaScriptAPI具有访问视图及其上所有内容的方法。
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
accessibility id
定位器策略旨在读取 UI 元素的唯一标识符。这样做的好处是在本地化或任何可能更改文本的过程中都不会更改。此外,如果功能相同的元素具有相同的辅助功能 ID,它可以帮助创建跨平台测试。
- 对于 iOS,这是 Apple此处规定的
accessibility identifier
。 - 对于 Android,
accessibility id
映射到元素的content-description
,如此处所述。
对于这两个平台,通常通过其accessibility id
获取元素(或多个元素)是最好的方法。它也是优于已弃用的name
策略的首选方法。
const elem = await $('~my_accessibility_identifier')
await elem.click()
类名
class name
策略是表示当前视图上 UI 元素的string
。
- 对于 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 上,也发生在具有视网膜显示屏的 Mac 机器上,因为 DPR 大于 1)。这将导致找不到匹配项,因为提供的图像选择器可能是从原始屏幕截图中获取的。您可以通过更新 Appium 服务器设置来解决此问题,请参阅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'))
如果您想选择具有name
为WebdriverIO
的 prop 的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 />
实例,则将返回一个包含 6 个 WebdriverIO 元素的数组。
自定义选择器策略
如果您的应用程序需要一种特定的方法来获取元素,您可以自己定义一个自定义选择器策略,可以使用custom$
和custom$$
。为此,请在测试开始时(例如在before
钩子中)注册一次您的策略。
loading...
给定以下 HTML 代码片段:
loading...
然后通过调用以下命令使用它:
loading...
注意:这仅在可以运行execute
命令的 Web 环境中有效。