В автоматизированном тестировании веб-приложений на Protractor часто возникает необходимость создавать повторно используемые компоненты, которые могут применяться в разных тестах без дублирования кода. Для этой цели используется паттерн 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 хранит единственный
экземпляр.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();
});
});
Преимущества такого подхода:
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();
Преимущества наследования:
Использование синглтонов для общих компонентов повышает читаемость и поддержку тестов, облегчает рефакторинг и снижает количество дублирующегося кода. Этот подход особенно эффективен для крупных проектов с повторяющимися элементами интерфейса и сложной структурой страниц.