从同步到异步
由于 V8 中的更改,WebdriverIO 团队宣布将于 2023 年 4 月弃用同步命令执行。团队一直在努力使过渡尽可能轻松。在本指南中,我们将解释如何逐步将您的测试套件从同步迁移到异步。作为示例项目,我们使用Cucumber Boilerplate,但所有其他项目的方法都相同。
JavaScript 中的 Promise
WebdriverIO 中同步执行流行的原因是它消除了处理 Promise 的复杂性。特别是如果您来自其他没有这种概念的语言,一开始可能会令人困惑。但是 Promise 是处理异步代码的非常强大的工具,而当今的 JavaScript 实际上可以轻松地处理它。如果您从未使用过 Promise,我们建议您查看MDN 参考指南,因为在这里解释它超出了范围。
异步转换
WebdriverIO 测试运行器可以在同一测试套件中处理异步和同步执行。这意味着您可以根据自己的节奏逐步迁移您的测试和页面对象。例如,Cucumber Boilerplate 为您定义了大量步骤定义,您可以将其复制到您的项目中。我们可以继续一次迁移一个步骤定义或一个文件。
WebdriverIO 提供了一个codemod,它可以将您的同步代码几乎完全自动地转换为异步代码。首先按照文档中的说明运行 codemod,并在需要时使用本指南进行手动迁移。
在许多情况下,您需要做的所有事情就是将调用 WebdriverIO 命令的函数设为async
并在每个命令前面添加await
。查看 boilerplate 项目中要转换的第一个文件clearInputField.ts
,我们从以下内容转换
export default (selector: Selector) => {
$(selector).clearValue();
};
到
export default async (selector: Selector) => {
await $(selector).clearValue();
};
就是这样。您可以在此处查看包含所有重写示例的完整提交
提交:
- 转换所有步骤定义 [af6625f]
此转换与您是否使用 TypeScript 无关。如果您使用 TypeScript,只需确保最终将tsconfig.json
中的types
属性从webdriverio/sync
更改为@wdio/globals/types
。还要确保您的编译目标至少设置为ES2018
。
特殊情况
当然,总有一些特殊情况需要您多加注意。
ForEach 循环
如果您有forEach
循环(例如,迭代元素),则需要确保以异步方式正确处理迭代器回调,例如
const elems = $$('div')
elems.forEach((elem) => {
elem.click()
})
我们传递给forEach
的函数是迭代器函数。在同步世界中,它会在继续之前单击所有元素。如果我们将此转换为异步代码,则必须确保等待每个迭代器函数完成执行。通过添加async
/await
,这些迭代器函数将返回我们需要解析的 Promise。现在,forEach
不再是迭代元素的理想选择,因为它不返回迭代器函数的结果,即我们需要等待的 Promise。因此,我们需要用map
替换forEach
,它返回该 Promise。map
以及数组的所有其他迭代器方法(如find
、every
、reduce
等)都已实现,以便它们尊重迭代器函数中的 Promise,因此使用它们在异步上下文中变得更加简单。上面的示例转换如下
const elems = await $$('div')
await elems.forEach((elem) => {
return elem.click()
})
例如,为了获取所有<h3 />
元素并获取其文本内容,您可以运行
await browser.url('https://webdriverio.node.org.cn')
const h3Texts = await browser.$$('h3').map((img) => img.getText())
console.log(h3Texts);
/**
* returns:
* [
* 'Extendable',
* 'Compatible',
* 'Feature Rich',
* 'Who is using WebdriverIO?',
* 'Support for Modern Web and Mobile Frameworks',
* 'Google Lighthouse Integration',
* 'Watch Talks about WebdriverIO',
* 'Get Started With WebdriverIO within Minutes'
* ]
*/
如果这看起来太复杂,您可能需要考虑使用简单的 for 循环,例如
const elems = await $$('div')
for (const elem of elems) {
await elem.click()
}
WebdriverIO 断言
如果您使用 WebdriverIO 断言帮助程序expect-webdriverio
,请确保在每个expect
调用之前设置await
,例如
expect($('input')).toHaveAttributeContaining('class', 'form')
需要转换为
await expect($('input')).toHaveAttributeContaining('class', 'form')
同步页面对象方法和异步测试
如果您以同步方式在测试套件中编写页面对象,则将无法再在异步测试中使用它们。如果您需要在同步和异步测试中都使用页面对象方法,我们建议复制该方法并为这两个环境提供它们,例如
class MyPageObject extends Page {
/**
* define elements
*/
get btnStart () { return $('button=Start') }
get loadedPage () { return $('#finish') }
someMethod () {
// sync code
}
someMethodAsync () {
// async version of MyPageObject.someMethod()
}
}
迁移完成后,您可以删除同步页面对象方法并清理命名。
如果您不想维护页面对象方法的两个不同版本,您也可以将整个页面对象迁移到异步,并使用browser.call
在同步环境中执行该方法,例如
// before:
// MyPageObject.someMethod()
// after:
browser.call(() => MyPageObject.someMethod())
call
命令将确保在继续执行下一个命令之前解析异步someMethod
。
结论
正如您在生成的重写 PR中看到的,此重写的复杂性相当简单。请记住,您可以一次重写一个步骤定义。WebdriverIO 能够在单个框架中处理同步和异步执行。