Работа с датапикерами и календарями

В веб-тестировании элементы интерфейса, связанные с выбором даты, часто оказываются сложными для автоматизации. Датапикеры и календари реализуются с использованием HTML, CSS и JavaScript разными способами: от стандартных <input type="date"> до сложных кастомных компонентов на Angular, React или других фреймворках. Protractor, как инструмент для E2E тестирования Angular-приложений, предоставляет гибкие возможности для работы с такими элементами, но требует внимательного подхода.


Типы датапикеров

  1. Стандартные HTML-даты Простейший вариант: <input type="date">. Эти элементы имеют встроенный календарь браузера. Основные особенности:

    • Значение хранится в формате YYYY-MM-DD.
    • Можно устанавливать значение напрямую через .sendKeys() или через element.sendKeys('2026-01-07').
  2. Кастомные календарные виджеты Часто используются библиотеки вроде Angular Material, jQuery UI Datepicker, React-Datepicker. Особенности:

    • Виджет обычно состоит из нескольких DOM-элементов: поле ввода, кнопка открытия календаря, сетка дней, переключатели месяца и года.
    • Значение поля может быть не связано напрямую с HTML-атрибутом value, а обновляться через JavaScript.
  3. Комбинированные элементы Некоторые приложения используют сочетание текстового поля и кнопки для открытия календаря. Поле может быть заблокировано для ручного ввода, что требует взаимодействия именно с календарной сеткой.


Основные подходы к взаимодействию

1. Прямое заполнение поля ввода

const dateInput = element(by.css('input[type="date"]'));
await dateInput.clear();
await dateInput.sendKeys('2026-01-07');

Особенности:

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

2. Клик по календарю

Для кастомных датапикеров нужно эмулировать действия пользователя:

const calendarButton = element(by.css('.calendar-toggle'));
await calendarButton.click();

const desiredDay = element(by.cssContainingText('.day', '7'));
await desiredDay.click();
  • Сначала открывается календарь.
  • Затем выбирается конкретный день.
  • Для выбора месяца и года могут потребоваться дополнительные клики по кнопкам “Next” или выпадающим спискам.

3. Использование функций для динамического выбора

При автоматизации календарей часто полезно создавать универсальные функции, способные выбирать любую дату:

async function selectDate(dateString) {
    const [year, month, day] = dateString.split('-').map(Number);
    
    const calendarButton = element(by.css('.calendar-toggle'));
    await calendarButton.click();

    const monthDropdown = element(by.css('.month-selector'));
    await monthDropdown.element(by.cssContainingText('option', monthNames[month - 1])).click();

    const yearDropdown = element(by.css('.year-selector'));
    await yearDropdown.element(by.cssContainingText('option', year.toString())).click();

    const dayElement = element(by.cssContainingText('.day', day.toString()));
    await dayElement.click();
}
  • Этот подход делает тесты более поддерживаемыми при изменении дат.
  • Позволяет скрыть логику выбора в отдельной функции, упрощая сценарии тестирования.

Работа с асинхронностью и ожиданиями

Календарные виджеты часто подгружаются динамически. Для стабильных тестов необходимы ожидания:

const EC = protractor.ExpectedConditions;

const calendar = element(by.css('.calendar-container'));
await calendarButton.click();
await browser.wait(EC.visibilityOf(calendar), 5000, 'Календарь не появился');

const dayToSelect = calendar.element(by.cssContainingText('.day', '7'));
await browser.wait(EC.elementToBeClickable(dayToSelect), 5000);
await dayToSelect.click();
  • ExpectedConditions предотвращает ошибки при попытке взаимодействия с ещё не загруженными элементами.
  • Рекомендуется использовать явные ожидания вместо browser.sleep(), чтобы тесты оставались быстрыми и стабильными.

Сложные сценарии

  1. Выбор диапазона дат Многие календарные компоненты позволяют выбирать период. Обычно это два клика: начало и конец диапазона.
const startDate = element(by.cssContainingText('.day', '5'));
const endDate = element(by.cssContainingText('.day', '10'));
await startDate.click();
await endDate.click();
  1. Даты вне текущего месяца Некоторые виджеты отображают дни соседних месяцев. Нужно правильно идентифицировать элемент и при необходимости переключить месяц.

  2. Проверка выбранной даты После выбора даты важно проверить, что значение корректно:

const inputValue = await dateInput.getAttribute('value');
expect(inputValue).toEqual('2026-01-07');

Рекомендации по устойчивости тестов

  • Избегать жёстких локаторов. Лучше использовать by.cssContainingText или by.model для Angular-элементов.
  • Оборачивать выбор даты в функции для повторного использования.
  • Использовать явные ожидания для календаря и отдельных дней.
  • Сравнивать дату через атрибуты, а не через текстовое поле, если оно кастомное и может форматировать значение по-другому.

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