Синглтоны для общих компонентов

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


Принципы построения Singleton

1. Единственный экземпляр класса

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

Пример базового Singleton для компонента:

class HeaderComponent {
  constructor() {
    if (HeaderComponent.instance) {
      return HeaderComponent.instance;
    }
    this.menuButton = element(by.css('.menu-button'));
    this.searchInput = element(by.css('.search-input'));
    HeaderComponent.instance = this;
  }

  async clickMenu() {
    await this.menuButton.click();
  }

  async enterSearch(query) {
    await this.searchInput.sendKeys(query);
  }
}

module.exports = new HeaderComponent();

Ключевые моменты:

  • HeaderComponent.instance хранит единственный экземпляр.
  • Конструктор возвращает существующий объект, если он уже был создан.
  • Методы компонента обеспечивают прямое взаимодействие с элементами страницы.

Применение Singleton для страниц и компонентов

2. Комбинирование с Page Object Model

Синглтон удобно использовать вместе с Page Object Model (POM). Каждый компонент, который присутствует на нескольких страницах, создается как отдельный Singleton, а страницы просто используют его методы.

Пример использования:

const HeaderComponent = require('./HeaderComponent');
const LoginPage = require('./LoginPage');

describe('Авторизация', () => {
  it('должна позволять искать товары', async () => {
    await browser.get('/login');
    await LoginPage.login('user', 'password');
    await HeaderComponent.enterSearch('Protractor');
    await HeaderComponent.clickMenu();
  });
});

Преимущества такого подхода:

  • Отсутствие повторного создания компонентов.
  • Легкая поддержка изменений локаторов.
  • Возможность централизованного обновления функциональности.

Интеграция с Protractor и асинхронностью

3. Асинхронные операции

Protractor использует WebDriver, который оперирует промисами и асинхронными вызовами. Методы Singleton-компонентов должны корректно обрабатывать асинхронность через async/await.

Пример корректного метода:

async clickMenu() {
  await browser.wait(protractor.ExpectedConditions.elementToBeClickable(this.menuButton), 5000);
  await this.menuButton.click();
}

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

  • browser.wait предотвращает ошибки при взаимодействии с еще не отрендеренными элементами.
  • Методы компонента не зависят от порядка вызова в тестах, благодаря единственному экземпляру.

Расширение и наследование

4. Создание базовых и специализированных компонентов

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

Пример базового класса:

class BaseModal {
  constructor(selector) {
    if (BaseModal.instance) {
      return BaseModal.instance;
    }
    this.modalRoot = element(by.css(selector));
    BaseModal.instance = this;
  }

  async open() {
    await this.modalRoot.isDisplayed();
  }

  async close() {
    const closeButton = this.modalRoot.element(by.css('.close'));
    await closeButton.click();
  }
}

class LoginModal extends BaseModal {
  constructor() {
    super('.login-modal');
  }

  async login(username, password) {
    await this.modalRoot.element(by.css('input[name="username"]')).sendKeys(username);
    await this.modalRoot.element(by.css('input[name="password"]')).sendKeys(password);
    await this.modalRoot.element(by.css('button[type="submit"]')).click();
  }
}

module.exports = new LoginModal();

Преимущества наследования:

  • Единая обработка общих действий (открытие/закрытие модального окна).
  • Специализированные методы добавляются только там, где это нужно.
  • Синглтон гарантирует, что один экземпляр модалки используется во всех тестах.

Рекомендации по использованию

  • Создавать Singleton только для часто используемых компонентов — не стоит применять на все страницы, иначе код станет слишком жестким.
  • Минимизировать состояние в Singleton — любые тестовые данные должны передаваться методам, а не храниться в экземпляре, чтобы избежать конфликтов между тестами.
  • Совмещать с POM — компоненты не должны дублировать логику страниц, они лишь предоставляют интерфейс для взаимодействия с элементами.
  • Использовать асинхронные методы для всех действий с элементами, чтобы исключить ошибки взаимодействия с DOM.

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