Сортировка таблиц

Основные подходы к работе с таблицами

Таблицы на веб-страницах часто используются для отображения данных в структурированном виде. В автоматизированном тестировании критически важно проверять корректность их сортировки. В Protractor работа с таблицами строится на взаимодействии с элементами DOM, используя CSS-селекторы, XPath и методы element и element.all.

Для начала необходимо выделить следующие компоненты таблицы:

  • Заголовки колонок (<th>), на которые кликают для сортировки.
  • Строки таблицы (<tr>), содержащие ячейки данных (<td>).

Определение элементов таблицы

let table = element(by.css('table#myTable'));
let headers = table.all(by.css('th'));
let rows = table.all(by.css('tbody tr'));
  • element(by.css('table#myTable')) — основной контейнер таблицы.
  • table.all(by.css('th')) — список всех заголовков, доступных для сортировки.
  • table.all(by.css('tbody tr')) — строки данных, по которым будет проверяться порядок.

Получение данных из таблицы

Для проверки сортировки необходимо извлечь значения из ячеек:

async function getColumnValues(columnIndex) {
    return await rows.map(async (row) => {
        let cell = row.all(by.css('td')).get(columnIndex);
        return cell.getText();
    });
}
  • columnIndex указывает номер колонки, которую нужно проверять.
  • Метод map возвращает массив всех значений ячеек в указанной колонке.

Сортировка по клику на заголовок

Для взаимодействия с заголовком таблицы используют метод click():

async function sortByColumn(columnIndex) {
    let header = headers.get(columnIndex);
    await header.click();
    await browser.sleep(500); // ожидание завершения сортировки
}
  • После клика часто необходимо добавить небольшую задержку или использовать ExpectedConditions, чтобы дождаться обновления DOM.
  • Если таблица использует асинхронную загрузку данных (например, AJAX), рекомендуется использовать:
let EC = protractor.ExpectedConditions;
await browser.wait(EC.stalenessOf(rows.first()), 5000);
rows = table.all(by.css('tbody tr')); // обновляем строки после сортировки

Проверка корректности сортировки

Чтобы проверить, что данные отсортированы корректно, извлечённый массив значений сравнивают с отсортированной версией:

async function isSortedAscending(columnIndex) {
    let values = await getColumnValues(columnIndex);
    let sorted = [...values].sort((a, b) => a.localeCompare(b));
    return JSON.stringify(values) === JSON.stringify(sorted);
}

async function isSortedDescending(columnIndex) {
    let values = await getColumnValues(columnIndex);
    let sorted = [...values].sort((a, b) => b.localeCompare(a));
    return JSON.stringify(values) === JSON.stringify(sorted);
}
  • localeCompare учитывает локализацию текста, что важно при работе с кириллицей или другими национальными алфавитами.
  • Сравнение через JSON.stringify позволяет убедиться, что порядок элементов точный, а не просто отсортирован по содержимому.

Работа с числовыми и датированными колонками

Текстовое сравнение подходит не всегда. Для чисел и дат лучше приводить значения к нужному типу:

async function getNumericColumnValues(columnIndex) {
    let values = await getColumnValues(columnIndex);
    return values.map(value => parseFloat(value.replace(',', '.')));
}

async function getDateColumnValues(columnIndex) {
    let values = await getColumnValues(columnIndex);
    return values.map(value => new Date(value));
}
  • Использование parseFloat с заменой запятой на точку важно для корректной работы с числовыми форматами в разных локалях.
  • Даты преобразуются в объекты Date для удобного сравнения.

Сортировка с учётом нескольких кликов

Многие таблицы меняют порядок сортировки по повторным кликам на один и тот же заголовок (возрастание → убывание → сброс). Пример универсальной функции:

async function toggleSort(columnIndex, order = 'asc') {
    let header = headers.get(columnIndex);
    let attempts = 0;
    let sorted = false;

    while (!sorted && attempts < 3) {
        await header.click();
        await browser.sleep(500);

        if (order === 'asc') {
            sorted = await isSortedAscending(columnIndex);
        } else {
            sorted = await isSortedDescending(columnIndex);
        }

        attempts++;
    }

    if (!sorted) {
        throw new Error(`Колонка ${columnIndex} не отсортирована в порядке ${order}`);
    }
}
  • Функция кликает до трёх раз, проверяя, достигнут ли нужный порядок.
  • Если сортировка не выполнена, генерируется ошибка, что удобно для автоматизированных тестов.

Рекомендации по оптимизации

  • Использовать ExpectedConditions для ожидания изменения DOM вместо browser.sleep, чтобы тесты были стабильными.
  • Сохранять селекторы заголовков и строк в переменные для повторного использования.
  • Проверять сортировку не только по тексту, но и по типу данных, если колонка числовая или содержит даты.
  • Для больших таблиц использовать методы map и reduce для обработки массивов, чтобы избежать излишней нагрузки на браузер.

Обработка динамических таблиц

Если таблица подгружается по мере прокрутки (infinite scroll), необходимо реализовать ленивую выборку строк:

async function getAllRows() {
    let allRows = [];
    let currentCount = 0;
    let newRows;

    do {
        newRows = await table.all(by.css('tbody tr')).count();
        if (newRows > currentCount) {
            allRows = await table.all(by.css('tbody tr')).getText();
            currentCount = newRows;
            await browser.executeScript('window.scrollTo(0, document.body.scrollHeight);');
            await browser.sleep(500);
        }
    } while (newRows > currentCount);

    return allRows;
}
  • Использование executeScript позволяет прокрутить страницу для подгрузки данных.
  • Итеративная проверка гарантирует, что все строки будут включены в проверку сортировки.

Интеграция с Jasmine или Mocha

Для проверки сортировки удобно интегрировать функции в тестовые фреймворки:

it('Должен сортировать таблицу по возрастанию', async () => {
    await toggleSort(2, 'asc'); // сортировка по третьей колонке
    expect(await isSortedAscending(2)).toBe(true);
});

it('Должен сортировать таблицу по убыванию', async () => {
    await toggleSort(2, 'desc'); // сортировка по третьей колонке
    expect(await isSortedDescending(2)).toBe(true);
});
  • Тесты становятся читаемыми и легко масштабируемыми при добавлении новых колонок.
  • Использование асинхронных функций (async/await) обеспечивает стабильность и последовательность операций.