Работа с кастомными компонентами

Особенности кастомных компонентов

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

Основные сложности при тестировании кастомных компонентов:

  • Вложенность элементов: компонент может состоять из нескольких слоёв, каждый из которых требует отдельного поиска.
  • Скрытые или динамические элементы: некоторые элементы отображаются только после определённых действий, таких как hover или клик.
  • Кастомные события Angular: стандартные методы .click() или .sendKeys() могут не всегда корректно инициировать Angular-события.

Выбор селекторов

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

  • CSS-селекторы по классам и атрибутам:
const customButton = element(by.css('.my-custom-button[data-role="submit"]'));
  • По ID (если компонент имеет уникальный идентификатор):
const modalWindow = element(by.id('customModal'));
  • По Angular-specific атрибутам (для Angular-приложений):
const componentInput = element(by.model('user.name'));
  • Комбинированные селекторы для вложенных элементов:
const dropdownOption = element(by.css('.dropdown .option[data-value="1"]'));

Использование CSS-селекторов предпочтительнее XPath, так как они быстрее и стабильнее при рендеринге Angular.

Ожидания и синхронизация

Кастомные компоненты часто создают асинхронные ситуации, поэтому важно использовать ожидания. Protractor интегрирован с механизмом ожиданий Angular, но для нестандартных компонентов необходимо применять Explicit Waits:

const EC = protractor.ExpectedConditions;
const button = element(by.css('.custom-button'));
browser.wait(EC.elementToBeClickable(button), 5000);
button.click();

Важные методы ExpectedConditions:

  • elementToBeClickable(element) — ожидание кликабельного состояния.
  • visibilityOf(element) — ожидание появления элемента в DOM.
  • presenceOf(element) — ожидание наличия элемента в DOM, даже если он невидим.

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

Кастомные селекты

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

const selectContainer = element(by.css('.custom-select'));
const option = element(by.cssContainingText('.custom-select .option', 'Выбор 1'));

selectContainer.click();
browser.wait(EC.visibilityOf(option), 3000);
option.click();
Переключатели и чекбоксы

Для кастомных чекбоксов стандартный .click() может не сработать. Рекомендуется проверять текущее состояние и использовать JavaScript-клик при необходимости:

const checkbox = element(by.css('.custom-checkbox input'));
checkbox.isSelected().then(selected => {
    if (!selected) {
        browser.executeScript("arguments[0].click();", checkbox.getWebElement());
    }
});
Модальные окна и диалоги

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

  1. Ожидать появления окна.
  2. Выполнять действия внутри окна.
  3. Проверять закрытие после действия.
const modal = element(by.css('.custom-modal'));
browser.wait(EC.visibilityOf(modal), 5000);

const confirmButton = modal.element(by.css('.confirm'));
confirmButton.click();

browser.wait(EC.invisibilityOf(modal), 5000);

Взаимодействие с Shadow DOM

Некоторые кастомные компоненты используют Shadow DOM. Protractor не имеет прямого метода для работы с shadowRoot, поэтому используется executeScript:

const shadowHost = element(by.css('custom-component'));
browser.executeScript('return arguments[0].shadowRoot.querySelector("button")', shadowHost.getWebElement())
    .then(button => button.click());

Советы по написанию тестов

  • Создавать функции-обёртки для компонентов: оборачивать действия в методы класса, чтобы скрыть детали селекторов.
  • Использовать Page Object Pattern: каждый кастомный компонент описывать как отдельный объект с методами взаимодействия.
  • Минимизировать использование sleep(): вместо этого применять ExpectedConditions.
  • Логировать шаги: для сложных кастомных элементов важно понимать, на каком этапе тест падает.

Пример Page Object для кастомного селекта

class CustomSelect {
    constructor(rootSelector) {
        this.root = element(by.css(rootSelector));
    }

    open() {
        return this.root.click();
    }

    selectOption(optionText) {
        const option = this.root.element(by.cssContainingText('.option', optionText));
        browser.wait(protractor.ExpectedConditions.visibilityOf(option), 3000);
        return option.click();
    }

    getSelectedText() {
        return this.root.element(by.css('.selected')).getText();
    }
}

// Использование
const countrySelect = new CustomSelect('.country-select');
countrySelect.open();
countrySelect.selectOption('Россия');
countrySelect.getSelectedText().then(text => console.log(text));

Этот подход повышает читаемость тестов и облегчает поддержку, особенно при работе с динамическими кастомными компонентами.