Поиск множества элементов

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

Методы поиска множества элементов

Protractor предоставляет несколько способов получения коллекции элементов на странице:

  1. element.all(locator) Основной метод для работы с множеством элементов. Возвращает объект ElementArrayFinder, который предоставляет набор методов для работы с коллекцией.

    let buttons = element.all(by.css('.btn'));

    В данном примере buttons будет содержать все элементы с классом btn.

  2. Локаторы для поиска множества элементов

    • by.css(selector) — поиск по CSS-селектору. Позволяет получать сразу несколько элементов.
    • by.tagName(tag) — поиск по имени тега, полезно для списков и таблиц.
    • by.className(name) — поиск по имени класса.
    • by.xpath(xpath) — гибкий способ поиска сложных структур, когда CSS-селекторы недостаточны.
    let listItems = element.all(by.tagName('li'));
    let highlightedRows = element.all(by.css('.table-row.highlighted'));

Основные операции с коллекциями

ElementArrayFinder предоставляет методы, позволяющие управлять множеством элементов как единым объектом, а также выполнять проверки и взаимодействие с каждым элементом коллекции.

  • count() — возвращает количество найденных элементов.

    let totalButtons = element.all(by.css('.btn')).count();
  • get(index) — получает элемент по индексу. Индексация начинается с 0.

    let firstButton = element.all(by.css('.btn')).get(0);
    let lastButton = element.all(by.css('.btn')).last();
  • first() и last() — удобные методы для получения первого и последнего элемента.

  • each(function(element, index) {...}) — позволяет выполнить функцию для каждого элемента коллекции.

    element.all(by.css('.item')).each(function(elem, index) {
        elem.getText().then(function(text) {
            console.log(`Элемент ${index}: ${text}`);
        });
    });
  • map(function(element, index) {...}) — преобразует коллекцию, возвращая массив результатов.

    let texts = element.all(by.css('.item')).map(function(elem) {
        return elem.getText();
    });
  • filter(function(element, index) {...}) — позволяет отфильтровать элементы по заданному условию.

    let visibleButtons = element.all(by.css('.btn')).filter(function(elem) {
        return elem.isDisplayed();
    });

Работа с ожиданиями и коллекциями

При работе с множеством элементов важно учитывать асинхронность. Методы count(), getText(), isDisplayed() возвращают промисы. Для обработки используется await в сочетании с async функциями:

let buttons = element.all(by.css('.btn'));
let total = await buttons.count();

for (let i = 0; i < total; i++) {
    let text = await buttons.get(i).getText();
    console.log(text);
}

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

let EC = protractor.ExpectedConditions;
let items = element.all(by.css('.list-item'));

await browser.wait(EC.visibilityOf(items.first()), 5000);

Сочетание с локаторами внутри элементов

Для сложных структур часто нужно искать элементы внутри родительского элемента. Protractor поддерживает метод all на объекте ElementFinder:

let table = element(by.css('.table'));
let rows = table.all(by.css('tr'));

await rows.each(async function(row) {
    let columns = row.all(by.css('td'));
    let texts = await columns.map(col => col.getText());
    console.log(texts);
});

Это позволяет работать с таблицами, списками и повторяющимися блоками интерфейса, сохраняя структуру данных и упрощая проверку содержимого.

Особенности и советы

  • Промисы и асинхронность: все методы ElementArrayFinder возвращают промисы, поэтому важно использовать await или цепочки .then().
  • Ленивая загрузка: element.all() не выполняет поиск немедленно, поиск происходит только при вызове метода, который требует реальные элементы (count(), get(), each()).
  • Комбинирование фильтров: можно последовательно фильтровать, маппить и получать текст, что позволяет строить гибкие проверки.

Примеры практического использования

  • Проверка количества элементов в списке:
expect(await element.all(by.css('.todo-item')).count()).toBe(5);
  • Получение текста всех элементов и проверка содержимого:
let itemsText = await element.all(by.css('.menu-item')).map(el => el.getText());
expect(itemsText).toContain('Настройки');
  • Взаимодействие с видимыми кнопками:
let buttons = element.all(by.css('.btn')).filter(btn => btn.isDisplayed());
await buttons.get(0).click();

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