Синхронизация с Angular: автоматическое ожидание

Protractor — это инструмент для end-to-end тестирования веб-приложений на базе Angular и AngularJS. Одной из ключевых особенностей Protractor является автоматическая синхронизация с Angular, что позволяет тестам выполняться только после того, как Angular завершит все свои асинхронные операции, такие как обновление модели, HTTP-запросы или отрисовка элементов.


Принцип работы автоматического ожидания

Angular использует внутренний цикл выполнения (digest cycle) для отслеживания изменений в модели и их отображения в DOM. Protractor интегрируется с этим циклом и выполняет следующие действия:

  1. Отслеживает pending HTTP-запросы через $http или HttpClient.
  2. Мониторит таймеры и асинхронные задачи через $timeout и $interval.
  3. Ждет завершения изменений модели, которые еще не были применены к DOM.

Как результат, команды WebDriver (click(), sendKeys(), getText()) выполняются только после того, как Angular закончил все внутренние операции, исключая необходимость вручную использовать sleep() или wait() в большинстве случаев.


Включение синхронизации

Protractor по умолчанию включен для приложений на Angular. Основной механизм контроля синхронизации представлен методом:

browser.waitForAngularEnabled(true);
  • true — Protractor будет ждать завершения всех Angular-операций перед взаимодействием с элементами.
  • false — синхронизация отключена, что необходимо для тестирования не-Angular страниц.

Пример использования:

it('должен дождаться загрузки данных Angular', async () => {
    await browser.waitForAngularEnabled(true);
    await browser.get('/dashboard');
    const title = await element(by.css('h1')).getText();
    expect(title).toEqual('Dashboard');
});

Сценарии отключения автоматического ожидания

Отключение автоматической синхронизации необходимо в следующих случаях:

  1. Тестирование страниц, не использующих Angular (например, сторонние виджеты или лендинги).
  2. Использование сторонних библиотек для асинхронных операций, которые Angular не отслеживает.
  3. Оптимизация тестов при проблемах с долгими $http запросами, которые не критичны для теста.

Пример отключения:

browser.waitForAngularEnabled(false);
await browser.get('https://external-site.com');

После выполнения таких действий следует вновь включить синхронизацию при переходе на Angular-страницы.


Методы явного ожидания

Хотя Protractor синхронизируется с Angular автоматически, иногда возникает необходимость использовать явные ожидания для элементов, загружаемых динамически или зависящих от пользовательских событий. Для этого применяются ExpectedConditions:

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

await browser.wait(EC.elementToBeClickable(submitButton), 5000);
await submitButton.click();

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

  • elementToBeClickable — ждет, пока элемент не станет кликабельным.
  • visibilityOf — ждет появления элемента в DOM и его видимости.
  • presenceOf — проверяет, что элемент присутствует в DOM, даже если он невидим.

Особенности AngularJS vs Angular 2+

Для AngularJS (1.x) Protractor использует глобальные объекты $browser и $injector, чтобы отслеживать pending операции.

Для Angular 2+ синхронизация строится на основе Zone.js, который перехватывает все асинхронные операции. Protractor автоматически определяет наличие Angular и корректно ждет завершения всех зон.

Важно учитывать:

  • Для AngularJS: rootEl можно указать явно, если приложение не использует <body> как корневой элемент:
browser.waitForAngularEnabled(true);
browser.rootEl = 'app-root';
  • Для Angular 2+: rootEl обычно не требуется, синхронизация работает с любым корневым компонентом.

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

Часто встречаются ошибки типа Timed out waiting for Angular. Основные причины:

  1. Синхронизация включена на странице, которая не использует Angular.
  2. Асинхронные операции в Angular не завершаются корректно.
  3. Некорректно определен корневой элемент приложения (rootEl).

Для диагностики полезны методы:

browser.waitForAngularEnabled(false); // временно отключить синхронизацию
browser.getCurrentUrl(); // проверить переходы

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


Рекомендации по использованию

  • Не смешивать sleep() с автоматическим ожиданием, так как это снижает надежность тестов.
  • Для динамически подгружаемых элементов использовать явные ожидания через ExpectedConditions.
  • При работе с не-Angular страницами всегда отключать синхронизацию, иначе тесты будут зависать.
  • Следить за корректностью $http и $timeout в приложении: незавершенные асинхронные операции блокируют выполнение тестов.

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