Від синхронного до асинхронного
Через зміни в V8 команда WebdriverIO оголосила про припинення підтримки синхронного виконання команд до квітня 2023 року. Команда наполегливо працювала, щоб зробити перехід якомога простішим. У цьому посібнику ми пояснюємо, як поступово перевести вашу тестову базу від синхронного до асинхронного виконання. Як приклад проекту ми використовуємо Cucumber Boilerplate, але підхід однаковий для всіх інших проектів.
Проміси в JavaScript
Причина, чому синхронне виконання було популярним у WebdriverIO, полягає в тому, що воно усуває складність роботи з промісами. Особливо, якщо ви прийшли з інших мов, де ця концепція не існує таким чином, спочатку це може бути заплутаним. Однак, проміси — це дуже потужний інструмент для роботи з асинхронним кодом, і сучасний JavaScript робить роботу з ними досить простою. Якщо ви ніколи не працювали з промісами, рекомендуємо ознайомитися з довідковим посібником MDN, оскільки пояснення цього поняття виходить за рамки цього документа.
Перехід до асинхронного виконання
Тестовий раннер WebdriverIO може обробляти як асинхронне, так і синхронне виконання в рамках однієї тестової бази. Це означає, що ви можете повільно мігрувати свої тести та PageObjects крок за кроком у своєму темпі. Наприклад, 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, переконайтеся, що ви врешті-решт змінили властивість types
у вашому tsconfig.json
з webdriverio/sync
на @wdio/globals/types
. Також переконайтеся, що ваша ціль компіляції встановлена щонайменше на ES2018
.
Особливі випадки
Звісно, завжди є особливі випадки, на які потрібно звернути більше уваги.
Цикли ForEach
Якщо у вас є цикл forEach
, наприклад, для ітерації по елементах, вам потрібно переконатися, що функція зворотного виклику обробляється правильно в асинхронному режимі, наприклад:
const elems = $$('div')
elems.forEach((elem) => {
elem.click()
})
Функція, яку ми передаємо в forEach
, є функцією ітератора. У синхронному світі вона б клікнула на всі елементи перед тим, як рухатися далі. Якщо ми перетворимо це в асинхронний код, ми повинні переконатися, що чекаємо завершення виконання кожної функції ітератора. Додаючи async
/await
, ці функції ітератора повертатимуть проміс, який нам потрібно розрішити. Тепер forEach
не є ідеальним для ітерації по елементах, оскільки він не повертає результат функції ітератора, проміс, на який нам потрібно чекати. Тому ми повинні замінити forEach
на map
, який повертає цей проміс. Метод map
, а також всі інші методи ітератора масивів, такі як find
, every
, reduce
та інші, реалізовані так, що вони враховують проміси в функціях ітератора і тому спрощені для використання в асинхронному контексті. Вищенаведений приклад після перетворення виглядає так:
const elems = await $$('div')
await elems.forEach((elem) => {
return elem.click()
})
Наприклад, щоб отримати всі елементи <h3 />
і отримати їх текстовий вміст, ви можете виконати:
await browser.url('https://webdriver.io')
const h3Texts = await browser.$$('h3').map((img) => img.getText())
console.log(h3Texts);
/**
* повертає:
* [
* '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
, переконайтеся, що додали await
перед кожним викликом expect
, наприклад:
expect($('input')).toHaveAttributeContaining('class', 'form')
потрібно перетворити на:
await expect($('input')).toHaveAttributeContaining('class', 'form')
Синхронні методи PageObject та асинхронні тести
Якщо ви писали PageObjects у вашій тестовій базі синхронним способом, ви не зможете використовувати їх в асинхронних тестах. Якщо вам потрібно використовувати метод PageObject як в синхронних, так і в асинхронних тестах, ми рекомендуємо дублювати метод і пропонувати їх для обох середовищ, наприклад:
class MyPageObject extends Page {
/**
* визначення елементів
*/
get btnStart () { return $('button=Start') }
get loadedPage () { return $('#finish') }
someMethod () {
// синхронний код
}
someMethodAsync () {
// асинхронна версія MyPageObject.someMethod()
}
}
Після завершення міграції ви можете видалити синхронні методи PageObject та очистити іменування.
Якщо ви не хочете підтримувати дві різні версії методу PageObject, ви також можете мігрувати весь PageObject на асинхронний і використовувати browser.call
, щоб виконати метод у синхронному середовищі, наприклад:
// до:
// MyPageObject.someMethod()
// після:
browser.call(() => MyPageObject.someMethod())
Команда call
гарантуватиме, що асинхронний метод someMethod
буде розрішений перед переходом до наступної команди.
Висновок
Як ви можете побачити в остаточному PR з переписуванням, складність цього переписування досить невелика. Пам'ятайте, що ви можете переписувати по одному визначенню кроку за раз. WebdriverIO чудово справляється з синхронним та асинхронним виконанням в одному фреймворку.