Кэширование Page Objects

В тестировании с использованием Protractor Page Objects выполняют роль абстракции над элементами интерфейса, инкапсулируя взаимодействия с ними. Кэширование этих объектов позволяет повысить производительность тестов, снизить количество повторяющихся поисков элементов в DOM и обеспечить более стабильное поведение при асинхронных действиях.


Зачем нужно кэширование

При работе с Protractor каждый вызов метода element(locator) инициирует поиск элемента в DOM. Если один и тот же элемент используется многократно, это приводит к:

  • избыточным обращениям к DOM;
  • замедлению тестового сценария;
  • увеличению риска нестабильности теста, если DOM динамически обновляется.

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


Подходы к кэшированию Page Objects

1. Кэширование одиночных элементов

Одиночные элементы обычно кэшируются как свойства класса Page Object. Например:

class LoginPage {
    constructor() {
        this.usernameInput = element(by.id('username'));
        this.passwordInput = element(by.id('password'));
        this.loginButton = element(by.css('.login-btn'));
    }

    async login(username, password) {
        await this.usernameInput.sendKeys(username);
        await this.passwordInput.sendKeys(password);
        await this.loginButton.click();
    }
}

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

  • Элемент кэшируется один раз при создании объекта Page Object.
  • Если DOM обновляется (например, при полной перезагрузке страницы), кэш становится недействительным.
  • Подходит для статичных элементов.

2. Ленивое кэширование (Lazy Initialization)

Иногда элемент может появляться не сразу. В этом случае кэширование лучше делать ленивым, с отложенным поиском элемента:

class DashboardPage {
    get welcomeMessage() {
        if (!this._welcomeMessage) {
            this._welcomeMessage = element(by.css('.welcome-msg'));
        }
        return this._welcomeMessage;
    }
}

Плюсы ленивого подхода:

  • Элемент ищется только при первом обращении.
  • Снижает время инициализации Page Object.
  • Обеспечивает актуальность элемента, если Page Object создается до появления элементов на странице.

Минусы:

  • Кэш все равно может устареть при перезагрузке или динамическом обновлении DOM.

3. Кэширование коллекций элементов

Если тест взаимодействует с набором элементов, например списком товаров, кэшировать лучше не сами элементы, а локатор, а получение элементов делать через метод с кэшированием:

class ProductsPage {
    constructor() {
        this._productItems = null;
    }

    async getProductItems() {
        if (!this._productItems) {
            this._productItems = element.all(by.css('.product-item'));
        }
        return this._productItems;
    }
}

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

  • element.all() возвращает объект коллекции, а не массив.
  • Методы коллекции (get, first, last) выполняют поиск при каждом обращении.
  • Можно комбинировать с ленивым кэшированием, чтобы снизить количество обращений к DOM.

Важные аспекты кэширования

  1. Актуальность элементов Protractor работает с динамическим Angular-приложением, где DOM может обновляться после действий пользователя. Закэшированный элемент может потерять актуальность. Рекомендуется использовать ExpectedConditions или переинициализировать элемент при необходимости.

  2. Использование асинхронности Все взаимодействия с элементами через Protractor возвращают Promise. При кэшировании стоит избегать хранения результата промиса, а хранить сам локатор:

    // Плохо:
    this.cachedText = element(by.css('.title')).getText(); // сохраняет промис, а не элемент
    
    // Хорошо:
    this.titleElement = element(by.css('.title')); // сохраняет элемент, промис вызывается при обращении
  3. Обновление кэша При изменении страницы или переходе между вкладками стоит пересоздавать Page Object или очищать кэш:

    class SomePage {
        resetCache() {
            this._someElement = null;
            this._someCollection = null;
        }
    }
  4. Сбалансированное использование кэша Кэширование ускоряет тесты, но чрезмерное хранение элементов может привести к проблемам с памятью или устаревшим ссылкам на DOM. Для динамических элементов предпочтительно ленивое получение и частичная очистка кэша.


Практические рекомендации

  • Статичные элементы страницы кэшировать напрямую в конструкторе.
  • Динамические элементы — использовать ленивое кэширование.
  • Коллекции элементов — хранить локатор и обращаться через методы Page Object.
  • При навигации между страницами сбрасывать кэш.
  • Использовать ExpectedConditions для проверки наличия элементов перед взаимодействием, чтобы избежать ошибок stale element.

Пример комбинированного подхода

class UserProfilePage {
    constructor() {
        this.header = element(by.css('.profile-header')); // статичный элемент
        this._notifications = null; // ленивое кэширование
    }

    get notifications() {
        if (!this._notifications) {
            this._notifications = element.all(by.css('.notification-item'));
        }
        return this._notifications;
    }

    async waitForHeader() {
        const EC = protractor.ExpectedConditions;
        await browser.wait(EC.visibilityOf(this.header), 5000);
    }

    resetCache() {
        this._notifications = null;
    }
}

Такой подход обеспечивает оптимизацию производительности, устойчивость тестов и гибкость при работе с динамическим интерфейсом.