Перейти к основному содержимому

От 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();
};

Вот и всё. Вы можете увидеть полный коммит со всеми примерами переписывания здесь:

Коммиты:

информация

Этот переход не зависит от того, используете ли вы 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 отлично справляется с обработкой синхронного и асинхронного выполнения в рамках одного фреймворка.

Welcome! How can I help?

WebdriverIO AI Copilot