Обработка динамического содержимого

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

Асинхронное ожидание элементов

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

Методы ожидания:

  1. browser.wait() Метод используется для явного ожидания условий.

    const EC = protractor.ExpectedConditions;
    const dynamicButton = element(by.id('dynamic-button'));
    
    browser.wait(EC.visibilityOf(dynamicButton), 5000, 'Кнопка не появилась на странице');
    dynamicButton.click();
  2. ExpectedConditions (EC) Protractor включает набор встроенных условий для удобного использования:

    • visibilityOf(element) — элемент видим на странице
    • presenceOf(element) — элемент присутствует в DOM
    • elementToBeClickable(element) — элемент кликабелен
    • textToBePresentInElement(element, text) — текст появился в элементе
  3. Применение async/await Современный подход с использованием async/await повышает читаемость кода:

    it('ожидает появления текста', async () => {
        const message = element(by.css('.message'));
        await browser.wait(EC.textToBePresentInElement(message, 'Завершено'), 7000);
        const text = await message.getText();
        expect(text).toEqual('Завершено');
    });

Работа с изменяющимися элементами

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

Пример фильтрации элементов:

const items = element.all(by.css('.item'));
const visibleItems = items.filter(async (elem) => {
    return await elem.isDisplayed();
});
expect(await visibleItems.count()).toBeGreaterThan(0);

Получение текста из динамического списка:

const itemTexts = await element.all(by.css('.item')).map(async (elem) => {
    return await elem.getText();
});
console.log(itemTexts);

Синхронизация с Angular

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

Ожидание завершения Angular:

await browser.waitForAngular();

Пример с нестандартным AJAX-запросом:

await browser.wait(async () => {
    const status = await element(by.id('ajax-status')).getText();
    return status === 'Готово';
}, 10000, 'AJAX-запрос не завершился вовремя');

Обработка элементов, появляющихся после событий

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

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

const loadButton = element(by.id('load-data'));
const table = element(by.id('data-table'));

await loadButton.click();
await browser.wait(EC.presenceOf(table), 5000);
const rows = await table.all(by.css('tr'));
expect(rows.length).toBeGreaterThan(0);

Таймауты и устойчивость тестов

Работа с динамическими элементами требует аккуратного управления таймаутами. Стандартное ожидание можно настроить глобально:

browser.manage().timeouts().implicitlyWait(5000);

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

Динамические локаторы

Иногда элементы создаются с изменяющимися атрибутами, что делает фиксированные локаторы ненадежными. В таких случаях используют функции-локаторы или регулярные выражения:

const dynamicElement = element(by.cssContainingText('div.item', /Позиция \d+/));
await browser.wait(EC.presenceOf(dynamicElement), 5000);

Также часто применяются xpath-выражения для поиска элементов с частично совпадающими атрибутами:

const button = element(by.xpath("//button[contains(@id,'submit-')]"));
await button.click();

Работа с анимациями и скрытыми элементами

Dynamically changing elements могут быть видимыми только после завершения CSS-анимации. Для корректного взаимодействия стоит использовать проверку кликабельности:

const animatedButton = element(by.id('animated-btn'));
await browser.wait(EC.elementToBeClickable(animatedButton), 5000);
await animatedButton.click();

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

Для надежного тестирования динамических страниц рекомендуется сочетать:

  • ExpectedConditions для явных проверок
  • element.all() для работы с массивами
  • async/await для удобочитаемого асинхронного кода
  • динамические локаторы для нестандартных элементов
  • контроль таймаутов для устойчивости тестов

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