Асинхронное выполнение в Protractor

Protractor — это инструмент для автоматизированного тестирования веб-приложений на основе Angular и AngularJS, построенный поверх WebDriverJS. Ключевой особенностью Protractor является асинхронное взаимодействие с браузером, что требует понимания особенностей Promise и управления асинхронным кодом.

Принципы асинхронности

В основе Protractor лежит WebDriverJS, который полностью асинхронен. Любая операция с элементами страницы, такими как element(by.css('.selector')).click(), возвращает объект Promise. Promise представляет собой отложенное значение, которое будет доступно после завершения асинхронной операции.

Асинхронный код позволяет:

  • Не блокировать выполнение тестов во время ожидания ответа от браузера.
  • Управлять последовательностью действий через цепочки then.
  • Легко обрабатывать ошибки асинхронных операций.

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

При взаимодействии с элементами необходимо помнить, что методы click(), sendKeys(), getText() и аналогичные возвращают Promise. Для правильной работы с ними используются конструкции:

let elem = element(by.id('submitButton'));
elem.click().then(function() {
    console.log('Кнопка нажата');
});

С использованием современного синтаксиса async/await код становится более линейным и читаемым:

let elem = element(by.id('submitButton'));
await elem.click();
console.log('Кнопка нажата');

Управление ожиданиями

Protractor предоставляет встроенный механизм ExpectedConditions, который позволяет явно ожидать определённого состояния элементов перед выполнением действия. Это особенно важно для динамических Angular-приложений, где элементы могут подгружаться асинхронно.

Пример ожидания видимости элемента:

let EC = protractor.ExpectedConditions;
let elem = element(by.css('.dynamic-element'));

await browser.wait(EC.visibilityOf(elem), 5000, 'Элемент не появился на странице');
await elem.click();

Основные методы ExpectedConditions:

  • visibilityOf(element) — ожидание видимости элемента.
  • presenceOf(element) — ожидание присутствия в DOM.
  • elementToBeClickable(element) — ожидание возможности клика.
  • textToBePresentInElement(element, text) — ожидание появления текста в элементе.

Асинхронные цепочки

До появления async/await Protractor использовал control flow, который автоматически управлял цепочкой промисов. В новых версиях control flow устарел, поэтому рекомендуется использовать явное управление асинхронностью через async/await.

Пример сложной последовательности действий:

let input = element(by.model('user.name'));
let button = element(by.id('saveButton'));
let message = element(by.css('.alert-success'));

await input.sendKeys('Тестовое имя');
await button.click();
await browser.wait(EC.visibilityOf(message), 5000);
let text = await message.getText();
console.log(text);

Асинхронные проверки (Assertions)

При написании тестов важно помнить, что значения элементов нужно извлекать через await, иначе проверки будут выполняться не над фактическим содержимым:

let title = await element(by.css('h1')).getText();
expect(title).toEqual('Добро пожаловать');

Без await title будет Promise, и проверка завершится некорректно.

Управление асинхронными сценариями с несколькими элементами

Для работы с коллекциями элементов (element.all) также используется асинхронный подход:

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

for (let i = 0; i < count; i++) {
    let text = await items.get(i).getText();
    console.log(`Элемент ${i}: ${text}`);
}

Методы map, filter и each также возвращают промисы, поэтому в асинхронном коде необходимо использовать await внутри функций обратного вызова.

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

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

try {
    let elem = element(by.css('.nonexistent'));
    await browser.wait(EC.visibilityOf(elem), 3000);
} catch (error) {
    console.error('Элемент не найден или не стал видимым:', error.message);
}

Это предотвращает аварийное завершение теста и позволяет выполнять дополнительные действия, например, делать скриншоты при ошибке.

Асинхронные вызовы браузера

Protractor предоставляет глобальный объект browser для управления браузером. Методы browser.get(), browser.refresh(), browser.executeScript() также возвращают промисы и требуют асинхронного ожидания:

await browser.get('https://example.com');
let title = await browser.getTitle();
console.log(`Заголовок страницы: ${title}`);

browser.executeScript() позволяет выполнять JavaScript в контексте страницы и возвращает результат промиса:

let innerText = await browser.executeScript('return document.querySelector("h1").innerText;');
console.log(innerText);

Асинхронные таймауты и интервалы

В Protractor избегается использование setTimeout и других явных таймеров для ожидания элементов, так как это нарушает синхронизацию с Angular. Вместо этого применяется:

  • browser.wait() с ExpectedConditions.
  • Асинхронные функции, возвращающие промисы.
  • Методы коллекций элементов с await.

Лучшие практики асинхронности

  • Всегда использовать async/await вместо старого control flow.
  • Не выполнять действия с элементами без ожидания их готовности.
  • Проверять наличие элементов перед взаимодействием.
  • Оборачивать потенциально проблемные операции в try/catch.
  • Для сложных сценариев использовать цепочки ожиданий через ExpectedConditions.

Асинхронное выполнение в Protractor обеспечивает гибкость и точность тестов, но требует строгого соблюдения правил работы с промисами и правильного управления ожиданиями. Такой подход гарантирует стабильность тестов даже для динамически изменяющихся Angular-приложений.