页面对象模式
WebdriverIO 的第 5 版在设计时就考虑了页面对象模式的支持。通过引入“元素作为一等公民”的原则,现在可以使用此模式构建大型测试套件。
创建页面对象不需要额外的包。事实证明,干净、现代的类提供了我们所需的所有必要功能。
- 页面对象之间的继承
- 元素的延迟加载
- 方法和操作的封装
使用页面对象的目的是将任何页面信息从实际测试中抽象出来。理想情况下,您应该将特定于某个页面的所有选择器或特定指令存储在页面对象中,以便在您完全重新设计页面后仍然可以运行测试。
创建页面对象
首先,我们需要一个名为 Page.js
的主页面对象。它将包含所有页面对象将继承的通用选择器或方法。
// Page.js
export default class Page {
constructor() {
this.title = 'My Page'
}
async open (path) {
await browser.url(path)
}
}
我们始终会 导出
页面对象的实例,并且永远不会在测试中创建该实例。由于我们正在编写端到端测试,因此我们始终将页面视为无状态构造——就像每个 HTTP 请求都是无状态构造一样。
当然,浏览器可以携带会话信息,因此可以根据不同的会话显示不同的页面,但这不应该反映在页面对象中。这些类型的状态更改应该存在于您的实际测试中。
让我们开始测试第一个页面。出于演示目的,我们使用The Internet 网站(由Elemental Selenium 提供)作为试验对象。让我们尝试为登录页面构建一个页面对象示例。
获取选择器
第一步是在我们的 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')
在测试中使用页面对象
定义完页面所需的元素和方法后,您可以开始为其编写测试。要使用页面对象,您只需 导入
(或 require
)它即可。就是这样!
由于您导出了已创建的页面对象实例,因此导入它可以让您立即开始使用它。
如果您使用断言框架,您的测试可以更具表现力。
// login.spec.js
import LoginPage from '../pageobjects/login.page'
describe('login form', () => {
it('should deny access with wrong creds', async () => {
await LoginPage.open()
await LoginPage.username.setValue('foo')
await LoginPage.password.setValue('bar')
await LoginPage.submit()
await expect(LoginPage.flash).toHaveText('Your username is invalid!')
})
it('should allow access with correct creds', async () => {
await LoginPage.open()
await LoginPage.username.setValue('tomsmith')
await LoginPage.password.setValue('SuperSecretPassword!')
await LoginPage.submit()
await expect(LoginPage.flash).toHaveText('You logged into a secure area!')
})
})
从结构方面来看,将规范文件和页面对象分离到不同的目录中是有意义的。此外,您可以为每个页面对象提供后缀:.page.js
。这使得您导入页面对象的目的更加清晰。
更进一步
这是使用 WebdriverIO 编写页面对象的基本原理。但是,您可以构建比这更复杂的页面对象结构!例如,您可能拥有模态窗口的特定页面对象,或者将一个巨大的页面对象拆分为不同的类(每个类代表整个网页的不同部分),这些类继承自主页面对象。该模式确实提供了很多机会将页面信息与测试分离,这对于在项目和测试数量不断增长的时期保持测试套件结构化和清晰非常重要。
您可以在 GitHub 上的example
文件夹中找到此示例(甚至更多页面对象示例)。