Перейти до основного вмісту

Від синхронного до асинхронного

Через зміни в 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 чудово справляється з синхронним та асинхронним виконанням в одному фреймворку.

Welcome! How can I help?

WebdriverIO AI Copilot