От Sync к Async
В связи с изменениями в 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();
};
Вот и всё. Вы можете увидеть полный коммит со всеми примерами переписывания здесь:
Коммиты:
- transform all step definitions [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);
/**
* 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
, убедитесь, что вы добавили await
перед каждым вызовом expect
, например:
expect($('input')).toHaveAttributeContaining('class', 'form')
нужно преобразовать в:
await expect($('input')).toHaveAttributeContaining('class', 'form')
Синхронные методы PageObject и асинхронные тесты
Если вы писали PageObjects в вашем наборе тестов синхронным способом, вы больше не сможете использовать их в асинхронных тестах. Если вам нужно использовать метод 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 отлично справляется с обработкой синхронного и асинхронного выполнения в рамках одного фреймворка.