跳至主要内容

从同步到异步

由于 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();
};

就是这样。您可以在此处查看包含所有重写示例的完整提交

提交:

信息

此转换与您是否使用 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以及数组的所有其他迭代器方法(如findeveryreduce等)都已实现,以便它们尊重迭代器函数中的 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 能够在单个框架中处理同步和异步执行。

欢迎!我如何帮助您?

WebdriverIO AI Copilot