Модель ожидания и синхронизация

Protractor — это фреймворк для end-to-end тестирования Angular и AngularJS приложений. Основной особенностью Protractor является встроенная поддержка асинхронной природы веб-приложений, что требует особого подхода к синхронизации тестов. В основе лежит концепция модели ожиданий (Expected Conditions) и управление асинхронными операциями через Protractor Control Flow или современные async/await.


Асинхронность и необходимость синхронизации

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

  • Implicit wait — автоматическое ожидание элементов (не рекомендуется, так как плохо контролируется).
  • Explicit wait — ожидание конкретных условий с помощью ExpectedConditions.
  • Protractor’s synchronization with Angular — автоматическое ожидание завершения всех $http и $timeout операций в Angular-приложении.

Protractor по умолчанию интегрирован с Angular и умеет дожидаться завершения асинхронных операций, что существенно упрощает написание тестов. В AngularJS это реализуется через browser.waitForAngular(), который вызывается автоматически перед выполнением каждого действия.


Expected Conditions (EC)

ExpectedConditions — набор предопределённых условий, которые можно использовать для explicit wait. Они позволяют дожидаться состояния элемента или страницы перед выполнением действий.

Примеры наиболее часто используемых условий:

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

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

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

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

Здесь browser.wait ждёт максимум 5 секунд до выполнения клика. Если условие не выполнено, тест выбрасывает ошибку с указанным сообщением.


Встроенные методы ожидания

Помимо ExpectedConditions, Protractor предоставляет дополнительные возможности для контроля асинхронности:

  • browser.waitForAngularEnabled(flag) — позволяет временно отключить автоматическое ожидание Angular.
  • browser.sleep(ms) — пауза на указанное время, не рекомендуется использовать как основное средство синхронизации.
  • elementFinder.isPresent() / isDisplayed() — проверка состояния элемента с последующим условным действием.

Пример комбинированного подхода:

const EC = protractor.ExpectedConditions;
const alert = element(by.css('.alert-success'));

// дождаться появления элемента
browser.wait(EC.presenceOf(alert), 7000);

// проверить видимость и текст
expect(alert.isDisplayed()).toBe(true);
expect(alert.getText()).toContain('Успешно');

Использование async/await

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

Пример с async/await:

it('должен дождаться кликабельного элемента и нажать на него', async () => {
    const EC = protractor.ExpectedConditions;
    const loginButton = element(by.id('login'));

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

await автоматически приостанавливает выполнение до завершения ожидания, что делает код линейным и более предсказуемым.


Синхронизация с Angular и не-Angular приложениями

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

browser.waitForAngularEnabled(false);
browser.get('https://example.com/non-angular-page');

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

browser.waitForAngularEnabled(true);

Это предотвращает потенциальные ошибки в последующих тестах.


Комбинирование условий и пользовательские ожидания

Protractor позволяет создавать комплексные условия, комбинируя стандартные EC или создавая собственные:

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

// ожидание, пока поле не будет пустым и кнопка кликабельна
const customCondition = EC.and(
    EC.textToBePresentInElementValue(inputField, 'admin'),
    EC.elementToBeClickable(submitButton)
);

browser.wait(customCondition, 5000, 'Условие для поля и кнопки не выполнено');

Пользовательские условия оформляются как функции, возвращающие Promise<boolean>, что позволяет максимально гибко управлять синхронизацией.


Рекомендации по синхронизации

  • Использовать ExpectedConditions для всех операций с элементами, особенно кликов и ввода текста.
  • Минимизировать использование browser.sleep(), применять его только для отладки или крайних случаев.
  • Для non-Angular страниц отключать автоматическое ожидание Angular.
  • При работе с динамическим контентом всегда проверять наличие и видимость элементов перед взаимодействием.
  • Применять async/await для более линейного и читаемого кода.

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