Кастомные условия ожидания

Protractor предоставляет встроенные механизмы синхронизации с Angular-приложениями, но стандартные ожидания (как browser.wait с простым условием ExpectedConditions) часто недостаточны для сложных сценариев. Кастомные условия ожидания позволяют создавать гибкие проверки состояния элементов, страниц или данных, которые нельзя реализовать стандартными средствами.


Основы кастомных ожиданий

Кастомное ожидание в Protractor — это функция, которая возвращает Promise, резолвящийся в true или false. Пример базового синтаксиса:

const customCondition = () => {
    return element(by.css('.my-element')).isDisplayed().then(isVisible => {
        return isVisible;
    });
};

browser.wait(customCondition, 5000, 'Элемент не появился за 5 секунд');

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

  • Функция может быть асинхронной и использовать async/await.
  • В качестве результата должна возвращаться булева переменная или Promise<boolean>.
  • Таймаут задается через browser.wait, а сообщение ошибки помогает в диагностике.

Создание асинхронных условий

С ростом сложности тестов часто требуется проверять состояния, которые зависят от нескольких элементов или внешних данных. В таких случаях удобнее использовать async/await:

const allItemsLoaded = async () => {
    const items = await element.all(by.css('.item'));
    const statuses = await Promise.all(items.map(async (item) => {
        return await item.getText() !== '';
    }));
    return statuses.every(status => status === true);
};

await browser.wait(allItemsLoaded, 10000, 'Не все элементы загрузились за 10 секунд');

Пояснение:

  • Проверяется текст всех элементов на странице.
  • Используется Promise.all для параллельного получения данных.
  • Возвращается true только если все элементы удовлетворяют условию.

Комбинирование нескольких условий

Для сложных сценариев часто необходимо объединять несколько условий. Например, нужно дождаться, когда элемент видим и текст в нём изменился:

const elementVisibleAndHasText = async (locator, expectedText) => {
    const el = element(locator);
    const isVisible = await el.isDisplayed();
    const text = await el.getText();
    return isVisible && text.includes(expectedText);
};

await browser.wait(() => elementVisibleAndHasText(by.id('status'), 'Готово'), 7000);

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


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

Для повышения читаемости и поддержки тестов рекомендуется выносить кастомные условия в отдельные функции или утилиты:

class CustomConditions {
    static elementHasClass(locator, className) {
        return async () => {
            const el = element(locator);
            const classes = await el.getAttribute('class');
            return classes.split(' ').includes(className);
        };
    }

    static elementCountMoreThan(locator, count) {
        return async () => {
            const elements = await element.all(locator);
            return elements.length > count;
        };
    }
}

// Использование
await browser.wait(CustomConditions.elementHasClass(by.css('.btn'), 'active'), 5000);
await browser.wait(CustomConditions.elementCountMoreThan(by.css('.list-item'), 3), 5000);

Преимущества:

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

Встраивание кастомных ожиданий в ExpectedConditions

Protractor позволяет интегрировать кастомные условия с существующими ExpectedConditions:

const EC = protractor.ExpectedConditions;

const customCondition = async () => {
    const el = element(by.css('.notification'));
    const text = await el.getText();
    return text.includes('Успешно');
};

await browser.wait(EC.and(EC.visibilityOf(element(by.css('.notification'))), customCondition), 5000);

Таким образом можно комбинировать стандартные и кастомные проверки, что делает ожидания более гибкими и точными.


Логирование и отладка

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

const waitForText = async (locator, expectedText) => {
    const el = element(locator);
    return await browser.wait(async () => {
        const text = await el.getText();
        console.log(`Текущий текст: ${text}`);
        return text.includes(expectedText);
    }, 5000, `Текст элемента ${locator} не соответствует ожидаемому`);
};

Это позволяет:

  • Отслеживать текущие значения элементов.
  • Быстро выявлять ошибки тестов.
  • Уменьшать время на отладку сложных сценариев.

Советы по производительности

  • Избегать излишнего вызова element(locator) внутри цикла; лучше сохранять ссылку на элемент.
  • Использовать Promise.all для параллельных проверок нескольких элементов.
  • Ограничивать таймауты разумными значениями, чтобы не увеличивать время выполнения тестов.

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