Асинхронность и обещания в WebdriverIO

WebdriverIO — это современный инструмент для автоматизированного тестирования веб-приложений на JavaScript, который использует протокол WebDriver для управления браузерами. Одной из ключевых особенностей работы с WebdriverIO является асинхронный характер операций, что напрямую связано с использованием Promise и async/await.

Асинхронная природа WebdriverIO

Большинство команд WebdriverIO возвращают обещания (Promise), поскольку взаимодействие с браузером требует времени: выполнение JavaScript в DOM, загрузка элементов страницы, сетевые запросы и рендеринг. Например, вызов:

const title = browser.getTitle();

в современном асинхронном режиме фактически возвращает промис, который необходимо разрешить:

const title = await browser.getTitle();
console.log(title);

Ключевой момент: без await значение переменной будет Promise, а не строка заголовка страницы.

Режимы работы WebdriverIO: синхронный и асинхронный

WebdriverIO поддерживает два режима исполнения:

  1. Асинхронный режим (async/await) — современный стандарт, рекомендованный для новых проектов.
  2. Синхронный режим — используется с @wdio/sync, позволяет писать код без явного await, но скрыто использует Fibers для синхронизации.

Пример асинхронного теста:

describe('Страница логина', () => {
    it('должна открыться и показать заголовок', async () => {
        await browser.url('https://example.com/login');
        const title = await browser.getTitle();
        console.log(title);
    });
});

В синхронном режиме тот же тест будет выглядеть так:

describe('Страница логина', () => {
    it('должна открыться и показать заголовок', () => {
        browser.url('https://example.com/login');
        const title = browser.getTitle();
        console.log(title);
    });
});

Синхронный режим удобен для коротких скриптов и быстрой миграции из Selenium, однако асинхронный является более гибким и совместим с современным JavaScript.

Работа с обещаниями в WebdriverIO

WebdriverIO поддерживает полный стек методов для работы с Promise:

  • await browser.url('https://example.com') — навигация к URL
  • await $('selector').click() — клик по элементу
  • await $('selector').getText() — получение текста элемента

Все команды, возвращающие данные из браузера, должны использовать await в асинхронном режиме. Попытка напрямую работать с возвращаемым промисом без await приведёт к неожиданным результатам.

const element = $('#username');
const textPromise = element.getText(); // Promise
const text = await element.getText(); // строка

Цепочки команд и асинхронность

WebdriverIO позволяет строить цепочки команд, но важно помнить: каждая команда асинхронна, и её результат нужно корректно обрабатывать.

const input = await $('#username');
await input.setValue('admin');
const value = await input.getValue();
console.log(value);

Попытка писать цепочки без await может привести к тому, что value будет Promise, а не фактическое значение.

Ошибки и обработка исключений

Асинхронные команды могут выбросить ошибки, например, если элемент не найден. Для их обработки используется try/catch:

try {
    const button = await $('#submit');
    await button.click();
} catch (error) {
    console.error('Не удалось кликнуть по кнопке:', error);
}

Это обеспечивает корректное завершение теста и логирование ошибок.

Асинхронные хуки

WebdriverIO предоставляет хуки, которые также могут быть асинхронными:

exports.config = {
    before: async function (capabilities, specs) {
        await browser.url('https://example.com');
    },
    afterTest: async function(test, context, { error, result, duration, passed, retries }) {
        if (error) {
            await browser.saveScreenshot(`./screenshots/${test.title}.png`);
        }
    }
};

Любая асинхронная операция в хуках должна использовать await, иначе последовательность выполнения тестов нарушится.

Параллельное выполнение и промисы

WebdriverIO поддерживает параллельное выполнение тестов, однако параллельные промисы нужно контролировать. Например:

const selectors = ['#username', '#password', '#submit'];
await Promise.all(selectors.map(selector => $(selector).click()));

Использование Promise.all позволяет одновременно выполнять команды, но нужно учитывать, что порядок выполнения не гарантирован.

Практические рекомендации

  • Всегда использовать await для асинхронных команд.
  • Для цепочек команд не создавать промежуточные промисы без await.
  • Оборачивать критические операции в try/catch для надежного логирования ошибок.
  • Хуки и вспомогательные функции должны быть асинхронными, если внутри них используются команды WebdriverIO.
  • При параллельном выполнении использовать Promise.all осторожно, учитывая зависимость действий.

Асинхронность и работа с промисами в WebdriverIO — фундаментальная часть стабильного тестирования. Понимание того, когда и как использовать await, а также корректное управление цепочками команд и обработка ошибок, позволяет создавать надежные, читаемые и масштабируемые тестовые сценарии.