從同步到非同步
由於 V8 的變更,WebdriverIO 團隊宣布將在 2023 年 4 月停止使用同步命令執行。該團隊一直努力使過渡盡可能容易。在本指南中,我們將說明如何逐步將測試套件從同步遷移到非同步。 作為範例專案,我們使用Cucumber Boilerplate,但該方法與所有其他專案相同。
JavaScript 中的 Promise
同步執行在 WebdriverIO 中流行的原因是它消除了處理 Promise 的複雜性。 特別是如果您來自其他沒有這種概念的語言,一開始可能會感到困惑。 然而,Promise 是處理非同步程式碼的非常強大的工具,而今天的 JavaScript 實際上可以很容易地處理它。 如果您從未使用過 Promise,我們建議您查看MDN 參考指南,因為在此解釋它超出範圍。
非同步轉換
WebdriverIO 測試執行器可以在同一個測試套件中處理非同步和同步執行。 這表示您可以按照自己的步調逐步遷移測試和 PageObject。 例如,Cucumber Boilerplate 定義了大量步驟定義供您複製到專案中。 我們可以繼續一次遷移一個步驟定義或一個檔案。
WebdriverIO 提供了一個 codemod,可以幾乎完全自動地將同步程式碼轉換為非同步程式碼。 請先按照文件中的說明執行 codemod,並在需要時使用本指南進行手動遷移。
在許多情況下,需要做的所有事情是使您在其中呼叫 WebdriverIO 命令的函數成為 async
,並在每個命令前面新增 await
。 看看在範本專案中要轉換的第一個檔案 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。 因此,我們需要將 forEach
替換為會傳回該 Promise 的 map
。 map
以及所有其他陣列的迭代器方法(如 find
、every
、reduce
等)的實作方式都尊重迭代器函數中的 Promise,因此簡化了在非同步內容中使用它們的方式。 上面的範例轉換後看起來像這樣
const elems = await $$('div')
await elems.forEach((elem) => {
return elem.click()
})
例如,為了擷取所有 <h3 />
元素並取得其文字內容,您可以執行
await browser.url('https://webdriverio.dev.org.tw')
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')
同步 PageObject 方法和非同步測試
如果您以同步方式在測試套件中撰寫 PageObject,您將無法再在非同步測試中使用它們。 如果您需要在同步和非同步測試中使用 PageObject 方法,我們建議複製該方法並為兩種環境提供它們,例如
class MyPageObject extends Page {
/**
* define elements
*/
get btnStart () { return $('button=Start') }
get loadedPage () { return $('#finish') }
someMethod () {
// sync code
}
someMethodAsync () {
// async version of MyPageObject.someMethod()
}
}
完成遷移後,您可以移除同步 PageObject 方法並清理命名。
如果您不想維護 PageObject 方法的兩個不同版本,您也可以將整個 PageObject 遷移到非同步並使用browser.call
在同步環境中執行該方法,例如
// before:
// MyPageObject.someMethod()
// after:
browser.call(() => MyPageObject.someMethod())
call
命令將確保在繼續執行下一個命令之前解析非同步 someMethod
。
結論
正如您在產生的重寫 PR 中看到的,此重寫的複雜性相當簡單。 請記住,您可以一次重寫一個步驟定義。 WebdriverIO 完全能夠在單一框架中處理同步和非同步執行。