页面对象模式
WebdriverIO 的第5版在设计时就考虑了页面对象模式支持。通过引入"元素作为一等公民"的原则,现在可以使用此模式构建大型测试套件。
创建页面对象不需要额外的包。事实证明,简洁、现代的类提供了我们所需的所有必要功能:
- 页面对象之间的继承
- 元素的延迟加载
- 方法和动作的封装
使用页面对象的目标是将任何页面信息从实际测试中抽象出来。理想情况下,你应该将所有特定于某个页面的选择器或特定指令存储在页面对象中,这样即使在完全重新设计页面后,你仍然可以运行测试。
创建页面对象
首先,我们需要一个主页面对象,我们称之为Page.js。它将包含所有页面对象将继承的通用选择器或方法。
// Page.js
export default class Page {
constructor() {
this.title = 'My Page'
}
async open (path) {
await browser.url(path)
}
}
我们始终会export一个页面对象的实例,而不是在测试中创建该实例。由于我们正在编写端到端测试,我们始终将页面视为无状态结构——就像每个HTTP请求是无状态结构一样。
当然,浏览器可以携带会话信息,因此可以基于不同的会话显示不同的页面,但这不应该反映在页面对象中。这类状态变化应该存在于你的实际测试中。
让我们开始测试第一个页面。出于演示目的,我们使用Elemental Selenium的The Internet网站作为测试对象。让我们尝试为登录页面构建一个页面对象示例。
获取选择器
第一步是将login.page对象中所需的所有重要选择器编写为getter函数:
// login.page.js
import Page from './page'
class LoginPage extends Page {
get username () { return $('#username') }
get password () { return $('#password') }
get submitBtn () { return $('form button[type="submit"]') }
get flash () { return $('#flash') }
get headerLinks () { return $$('#header a') }
async open () {
await super.open('login')
}
async submit () {
await this.submitBtn.click()
}
}
export default new LoginPage()
在getter函数中定义选择器可能看起来有点奇怪,但它非常有用。这些函数在你访问属性时被评估,而不是在生成对象时。这样,你总是在对元素执行操作之前请求该元素。
链式命令
WebdriverIO内部会记住命令的最后结果。如果你将元素命令与动作命令链接起来,它会从前一个命令中找到元素并使用结果执行动作。这样你可以删除选择器(第一个参数),命令看起来就像:
await LoginPage.username.setValue('Max Mustermann')
这基本上与以下代码相同:
let elem = await $('#username')
await elem.setValue('Max Mustermann')
或者
await $('#username').setValue('Max Mustermann')