跳至主要内容

自定义命令

如果你想用你自己的命令集扩展browser实例,浏览器方法addCommand可以帮助你。你可以像在规范中一样,以异步的方式编写你的命令。

参数

命令名称

定义命令的名称,并将附加到浏览器或元素作用域。

类型:String

自定义函数

调用命令时执行的函数。this作用域是WebdriverIO.BrowserWebdriverIO.Element,具体取决于命令是附加到浏览器作用域还是元素作用域。

类型:Function

目标作用域

决定是否将命令附加到浏览器或元素作用域的标志。如果设置为true,则命令将是元素命令。

类型:Boolean
默认值:false

示例

此示例演示如何添加一个新的命令,该命令将当前 URL 和标题作为单个结果返回。作用域 (this) 是一个WebdriverIO.Browser对象。

browser.addCommand('getUrlAndTitle', async function (customVar) {
// `this` refers to the `browser` scope
return {
url: await this.getUrl(),
title: await this.getTitle(),
customVar: customVar
}
})

此外,你可以通过将true作为最后一个参数传递,用你自己的命令集扩展元素实例。在这种情况下,作用域 (this) 是一个WebdriverIO.Element对象。

browser.addCommand("waitAndClick", async function () {
// `this` is return value of $(selector)
await this.waitForDisplayed()
await this.click()
}, true)

自定义命令使你可以将经常使用的特定命令序列捆绑为单个调用。你可以在测试套件中的任何位置定义自定义命令;只需确保在第一次使用之前定义了该命令。(wdio.conf.js中的before钩子是创建它们的一个好地方。)

定义后,你可以按如下方式使用它们

it('should use my custom command', async () => {
await browser.url('http://www.github.com')
const result = await browser.getUrlAndTitle('foobar')

assert.strictEqual(result.url, 'https://github.com/')
assert.strictEqual(result.title, 'GitHub · Where software is built')
assert.strictEqual(result.customVar, 'foobar')
})

注意:如果你向browser作用域注册了一个自定义命令,则该命令将无法用于元素。同样,如果你向元素作用域注册了一个命令,则它将无法在browser作用域中使用。

browser.addCommand("myCustomBrowserCommand", () => { return 1 })
const elem = await $('body')
console.log(typeof browser.myCustomBrowserCommand) // outputs "function"
console.log(typeof elem.myCustomBrowserCommand()) // outputs "undefined"

browser.addCommand("myCustomElementCommand", () => { return 1 }, true)
const elem2 = await $('body')
console.log(typeof browser.myCustomElementCommand) // outputs "undefined"
console.log(await elem2.myCustomElementCommand('foobar')) // outputs "1"

const elem3 = await $('body')
elem3.addCommand("myCustomElementCommand2", () => { return 2 })
console.log(typeof browser.myCustomElementCommand2) // outputs "undefined"
console.log(await elem3.myCustomElementCommand2('foobar')) // outputs "2"

注意:如果需要链接自定义命令,则命令应以$结尾。

browser.addCommand("user$", (locator) => { return ele })
browser.addCommand("user$", (locator) => { return ele }, true)
await browser.user$('foo').user$('bar').click()

小心不要用太多自定义命令来重载browser作用域。

我们建议在页面对象中定义自定义逻辑,以便它们绑定到特定页面。

扩展类型定义

使用 TypeScript,扩展 WebdriverIO 接口非常容易。像这样为你的自定义命令添加类型

  1. 创建一个类型定义文件(例如,./src/types/wdio.d.ts

  2. a. 如果使用模块样式的类型定义文件(使用 import/export 和类型定义文件中的declare global WebdriverIO),请确保在tsconfig.jsoninclude属性中包含文件路径。

    b. 如果使用环境样式的类型定义文件(类型定义文件中没有 import/export,并且自定义命令使用declare namespace WebdriverIO),请确保tsconfig.json包含任何include部分,因为这会导致 TypeScript 未识别include部分中未列出的所有类型定义文件。

tsconfig.json
{
"compilerOptions": { ... },
"include": [
"./test/**/*.ts",
"./src/types/**/*.ts"
]
}
  1. 根据你的执行模式添加命令的定义。
declare global {
namespace WebdriverIO {
interface Browser {
browserCustomCommand: (arg: any) => Promise<void>
}

interface MultiRemoteBrowser {
browserCustomCommand: (arg: any) => Promise<void>
}

interface Element {
elementCustomCommand: (arg: any) => Promise<number>
}
}
}

集成第三方库

如果你使用支持 Promise 的外部库(例如,执行数据库调用),则集成它们的一个好方法是用自定义命令包装某些 API 方法。

返回 Promise 时,WebdriverIO 会确保在 Promise 解析之前不继续执行下一个命令。如果 Promise 被拒绝,则命令将抛出错误。

browser.addCommand('makeRequest', async (url) => {
const response = await fetch(url)
return await response.json()
})

然后,只需在你的 WDIO 测试规范中使用它即可

it('execute external library in a sync way', async () => {
await browser.url('...')
const body = await browser.makeRequest('http://...')
console.log(body) // returns response body
})

注意:自定义命令的结果是你返回的 Promise 的结果。

覆盖命令

你还可以使用overwriteCommand覆盖本机命令。

不建议这样做,因为它可能导致框架出现不可预测的行为!

总体方法类似于addCommand,唯一的区别是命令函数中的第一个参数是要覆盖的原始函数。请参阅以下一些示例。

覆盖浏览器命令

/**
* print milliseconds before pause and return its value.
*/
// 'pause' - name of command to be overwritten
// origPauseFunction - original pause function
browser.overwriteCommand('pause', async (origPauseFunction, ms) => {
console.log(`sleeping for ${ms}`)
await origPauseFunction(ms)
return ms
})

// then use it as before
console.log(`was sleeping for ${await browser.pause(1000)}`)

覆盖元素命令

在元素级别覆盖命令几乎相同。只需将true作为第三个参数传递给overwriteCommand即可

/**
* Attempt to scroll to element if it is not clickable.
* Pass { force: true } to click with JS even if element is not visible or clickable.
*/
// 'click' - name of command to be overwritten
// origClickFunction - original click function
browser.overwriteCommand('click', async function (origClickFunction, { force = false } = {}) {
if (!force) {
try {
// attempt to click
await origClickFunction()
return null
} catch (err) {
if (err.message.includes('not clickable at point')) {
console.warn('WARN: Element', this.selector, 'is not clickable.',
'Scrolling to it before clicking again.')

// scroll to element and click again
await this.scrollIntoView()
return origClickFunction()
}
throw err
}
}

// clicking with js
console.warn('WARN: Using force click for', this.selector)
await browser.execute((el) => {
el.click()
}, this)
}, true) // don't forget to pass `true` as 3rd argument

// then use it as before
const elem = await $('body')
await elem.click()

// or pass params
await elem.click({ force: true })

添加更多 WebDriver 命令

如果你正在使用 WebDriver 协议并在支持任何协议定义中未定义的其他命令的平台上运行测试@wdio/protocols,你可以通过addCommand接口手动添加它们。webdriver包提供了一个命令包装器,允许以与其他命令相同的方式注册这些新端点,提供相同的参数检查和错误处理。要注册此新端点,请导入命令包装器并使用它注册一个新命令,如下所示

import { command } from 'webdriver'

browser.addCommand('myNewCommand', command('POST', '/session/:sessionId/foobar/:someId', {
command: 'myNewCommand',
description: 'a new WebDriver command',
ref: 'https://vendor.com/commands/#myNewCommand',
variables: [{
name: 'someId',
description: 'some id to something'
}],
parameters: [{
name: 'foo',
type: 'string',
description: 'a valid parameter',
required: true
}]
}))

使用无效参数调用此命令会导致与预定义协议命令相同的错误处理,例如:

// call command without required url parameter and payload
await browser.myNewCommand()

/**
* results in the following error:
* Error: Wrong parameters applied for myNewCommand
* Usage: myNewCommand(someId, foo)
*
* Property Description:
* "someId" (string): some id to something
* "foo" (string): a valid parameter
*
* For more info see https://my-api.com
* at Browser.protocolCommand (...)
* ...
*/

正确调用命令,例如browser.myNewCommand('foo', 'bar'),会正确地向例如https://127.0.0.1:4444/session/7bae3c4c55c3bf82f54894ddc83c5f31/foobar/foo发出 WebDriver 请求,并使用类似于{ foo: 'bar' }的有效负载。

注意

:sessionId url 参数将自动替换为 WebDriver 会话的会话 ID。可以应用其他 url 参数,但需要在variables中定义。

请参阅@wdio/protocols包中如何定义协议命令的示例。

欢迎!我怎样才能帮到你?

WebdriverIO AI Copilot