async/await в тестах

Protractor изначально разрабатывался для тестирования Angular-приложений и построен на WebDriverJS, который использует промисы для работы с асинхронными действиями. До появления async/await управление асинхронностью в тестах было громоздким: приходилось строить цепочки промисов через .then(). Современный подход с async/await делает код читаемым, линейным и поддерживаемым.


Основы async/await в Protractor

Любая асинхронная операция в Protractor возвращает промис. Примеры таких операций: навигация по странице, поиск элементов, клики, ввод текста. Ключевое правило: все действия с WebElement и браузером должны ожидать завершения промиса, иначе результат будет непредсказуемым.

it('пример использования async/await', async () => {
    await browser.get('https://example.com');
    const input = element(by.css('input[name="username"]'));
    await input.sendKeys('testuser');
    const button = element(by.css('button[type="submit"]'));
    await button.click();
});

В этом примере каждая операция дожидается завершения предыдущей, что исключает состояние гонки и делает тест предсказуемым.


Работа с элементами

В Protractor элементы обозначаются через element(locator) или массив элементов element.all(locator). Асинхронная природа работы с DOM требует явного ожидания:

const items = element.all(by.css('.list-item'));
const count = await items.count();
console.log(`Количество элементов: ${count}`);

Важно: методы вроде getText(), getAttribute(), isDisplayed() также возвращают промис и требуют await.

const title = await element(by.css('h1')).getText();
if (title === 'Главная страница') {
    await element(by.css('button.next')).click();
}

Асинхронные ожидания

Для синхронизации тестов используется ExpectedConditions (EC). В связке с async/await это позволяет надежно ожидать появления элементов или изменения состояния:

const EC = protractor.ExpectedConditions;
const submitButton = element(by.id('submit'));

await browser.wait(EC.elementToBeClickable(submitButton), 5000, 'Кнопка не стала кликабельной');
await submitButton.click();

Ключевые моменты:

  • browser.wait возвращает промис и требует await.
  • Таймаут указывается в миллисекундах.
  • Второй аргумент — сообщение об ошибке при таймауте.

Циклы и асинхронные операции

Асинхронные операции в циклах требуют особого внимания. Использование обычного forEach не подходит, так как await внутри него не работает корректно. Правильный подход — использовать for...of:

const elementsList = element.all(by.css('.item'));
for (const el of await elementsList) {
    const text = await el.getText();
    console.log(text);
}

Или с map и Promise.all, если требуется параллельное выполнение:

const texts = await Promise.all(
    (await elementsList).map(el => el.getText())
);
console.log(texts);

Ошибки и отладка

При работе с async/await важно правильно обрабатывать ошибки. Непойманные промисы приведут к падению теста без информативного сообщения. Для этого используется try/catch:

try {
    await browser.get('https://example.com');
    await element(by.id('nonexistent')).click();
} catch (error) {
    console.error('Ошибка при клике:', error);
}

Также полезно использовать browser.sleep() только для отладки, так как это блокирует тест и увеличивает время выполнения.


Организация функций

Для упрощения кода рекомендуется выносить повторяющиеся действия в асинхронные функции:

async function login(username, password) {
    await element(by.name('username')).sendKeys(username);
    await element(by.name('password')).sendKeys(password);
    await element(by.css('button[type="submit"]')).click();
}

it('тест логина', async () => {
    await browser.get('https://example.com/login');
    await login('user1', 'password1');
});

Такой подход улучшает читаемость и позволяет повторно использовать логику.


Взаимодействие с Angular

Protractor автоматически ожидает завершения асинхронных операций Angular. Однако при использовании async/await важно помнить:

  • Методы browser.waitForAngularEnabled(false) отключают ожидание Angular.
  • Для не-Angular страниц необходимо управлять синхронизацией вручную.
await browser.waitForAngularEnabled(false);
await browser.get('https://non-angular-site.com');

Советы по стилю кода

  • Всегда использовать await при взаимодействии с браузером и элементами.
  • Не смешивать async/await и .then() без необходимости.
  • Использовать ExpectedConditions вместо browser.sleep() для надежных ожиданий.
  • Разбивать сложные действия на асинхронные функции для повторного использования.
  • Обрабатывать ошибки с try/catch для информативного логирования.

Async/await превращает асинхронные тесты Protractor в линейные скрипты, упрощает синхронизацию и делает код понятным и поддерживаемым. Правильное применение этих принципов минимизирует состояние гонки и повышает стабильность тестов.