Callbacks и Promise

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


Асинхронность в Protractor

В отличие от синхронного кода, где команды выполняются последовательно, в Protractor большинство операций с браузером выполняются асинхронно. Это значит, что результат операции может быть доступен не сразу, а через некоторое время. Например, методы element(by.css(...)).getText() или browser.get(url) возвращают не сам результат, а объект Promise, который будет разрешён в будущем.

Использование асинхронного подхода позволяет:

  • Не блокировать поток выполнения.
  • Выполнять несколько операций параллельно.
  • Эффективно ожидать завершения действий в браузере.

Callbacks в Protractor

Callback — это функция, которая передается в другую функцию и вызывается после завершения асинхронной операции. В старых версиях Protractor (до внедрения async/await) активно использовались callbacks для обработки результатов:

element(by.id('username')).getText().then(function(text) {
    console.log('Текст элемента:', text);
});

Здесь функция then принимает callback, который будет вызван после того, как Promise вернётся с результатом getText(). Основные моменты:

  • Callback выполняется после завершения асинхронной операции.
  • Можно вкладывать несколько callbacks для последовательных действий.
  • Легко получить “адский коллбек-лабиринт”, если вкладывать их глубоко.

Проблема глубокой вложенности:

element(by.id('username')).getText().then(function(text) {
    element(by.id('password')).getText().then(function(pass) {
        element(by.id('login')).click().then(function() {
            console.log('Все действия завершены');
        });
    });
});

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


Promise в Protractor

Promise — это объект, который представляет результат асинхронной операции и позволяет управлять последовательностью выполнения без глубокой вложенности.

Простейший синтаксис с использованием Promise:

let usernamePromise = element(by.id('username')).getText();

usernamePromise.then(function(text) {
    console.log('Текст пользователя:', text);
});

Основные возможности Promise:

  • then: обработка успешного завершения.
  • catch: обработка ошибок.
  • finally: выполнение кода независимо от результата.

Promise могут быть цепочкой:

element(by.id('username')).getText()
    .then(function(text) {
        console.log('Пользователь:', text);
        return element(by.id('password')).getText();
    })
    .then(function(password) {
        console.log('Пароль:', password);
        return element(by.id('login')).click();
    })
    .then(function() {
        console.log('Вход выполнен');
    })
    .catch(function(err) {
        console.error('Ошибка при выполнении действий:', err);
    });

Такой подход позволяет писать асинхронный код линейно, избегая глубокой вложенности callbacks.


Управление асинхронностью в современных тестах

С выходом Node.js с поддержкой async/await и новых версий Protractor появилась возможность использовать синтаксис, имитирующий синхронный код, что повышает читаемость тестов:

it('должен корректно войти в систему', async function() {
    await browser.get('https://example.com/login');

    let username = await element(by.id('username')).getText();
    console.log('Пользователь:', username);

    let password = await element(by.id('password')).getText();
    console.log('Пароль:', password);

    await element(by.id('login')).click();
    console.log('Вход выполнен');
});

Здесь await приостанавливает выполнение функции до разрешения Promise, что устраняет необходимость использования цепочек then.


Особенности работы с Promise в Protractor

  1. Встроенные ожидания: методы Protractor автоматически обрабатывают ожидание элементов, но для сложных условий можно использовать browser.wait() с пользовательским условием.

  2. Несколько промисов одновременно: Promise.all([promise1, promise2]) позволяет ждать завершения нескольких операций параллельно.

let usernamePromise = element(by.id('username')).getText();
let passwordPromise = element(by.id('password')).getText();

Promise.all([usernamePromise, passwordPromise]).then(([user, pass]) => {
    console.log('Пользователь:', user, 'Пароль:', pass);
});
  1. Обработка ошибок: catch и try/catch (при async/await) позволяют безопасно отлавливать проблемы с асинхронными операциями.

Важные практики

  • Избегать глубокой вложенности callback — использовать цепочки Promise или async/await.
  • Явно обрабатывать ошибки — всегда добавлять catch или использовать try/catch внутри async функций.
  • Использовать await с каждым Promise — упрощает чтение тестов и делает их более предсказуемыми.
  • Не смешивать старые callbacks и async/await в одном тесте — это может приводить к неожиданным результатам.

Асинхронная природа Protractor требует грамотного обращения с callbacks и Promise. Понимание этих инструментов позволяет создавать надежные, читаемые и масштабируемые тесты, минимизируя риски ошибок и упрощая поддержку кода.