Protractor является инструментом для автоматизированного тестирования веб-приложений на основе Angular и других современных фреймворков. Одной из ключевых задач при разработке стабильных и масштабируемых тестов является переиспользование компонентов. Этот подход позволяет уменьшить дублирование кода, облегчает поддержку и делает тестовую архитектуру более гибкой.
Page Object Model (POM) — это паттерн проектирования, который позволяет абстрагировать элементы страницы и действия с ними в отдельные объекты. В Protractor это реализуется через создание классов или модулей для каждой страницы приложения.
Пример структуры POM:
// loginPage.js
class LoginPage {
constructor() {
this.usernameInput = element(by.id('username'));
this.passwordInput = element(by.id('password'));
this.loginButton = element(by.buttonText('Login'));
}
async enterUsername(username) {
await this.usernameInput.sendKeys(username);
}
async enterPassword(password) {
await this.passwordInput.sendKeys(password);
}
async clickLogin() {
await this.loginButton.click();
}
}
module.exports = new LoginPage();
В тесте этот объект используется следующим образом:
const loginPage = require('./loginPage');
describe('Авторизация', () => {
it('должна успешно авторизовать пользователя', async () => {
await browser.get('https://example.com/login');
await loginPage.enterUsername('testuser');
await loginPage.enterPassword('password123');
await loginPage.clickLogin();
expect(await browser.getCurrentUrl()).toContain('/dashboard');
});
});
Преимущества POM:
Помимо Page Object, часто создаются утилитарные функции и компоненты для повторяющихся действий. Это особенно важно для сложных элементов, таких как таблицы, выпадающие списки, модальные окна.
Пример компонента для работы с таблицей:
class TableComponent {
constructor(tableElement) {
this.table = tableElement;
}
async getRowCount() {
return await this.table.all(by.css('tr')).count();
}
async getCellText(rowIndex, colIndex) {
const cell = this.table.all(by.css('tr')).get(rowIndex).all(by.css('td')).get(colIndex);
return await cell.getText();
}
}
module.exports = TableComponent;
Использование:
const TableComponent = require('./tableComponent');
const usersTable = new TableComponent(element(by.id('users-table')));
const rowCount = await usersTable.getRowCount();
const firstUserName = await usersTable.getCellText(0, 1);
Такая структура позволяет работать с таблицами на любой странице без дублирования логики.
Protractor работает с асинхронными действиями
браузера, поэтому переиспользуемые методы должны корректно
обрабатывать ожидания. Для этого используются async/await и
встроенные ожидания Protractor (ExpectedConditions).
Пример универсального метода для клика по элементу с ожиданием:
const EC = protractor.ExpectedConditions;
async function clickWhenClickable(element, timeout = 5000) {
await browser.wait(EC.elementToBeClickable(element), timeout, 'Элемент не кликабелен');
await element.click();
}
module.exports = { clickWhenClickable };
Файл protractor.conf.js можно структурировать таким
образом, чтобы переиспользовать общие настройки, такие
как базовый URL, тайм-ауты, репортеры, браузеры. Это позволяет запускать
тесты в разных средах без дублирования кода.
Пример конфигурации:
exports.config = {
framework: 'jasmine',
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['tests/**/*.spec.js'],
capabilities: {
browserName: 'chrome'
},
baseUrl: 'https://example.com',
jasmineNodeOpts: {
defaultTimeoutInterval: 30000
},
onPrepare: async () => {
await browser.manage().window().maximize();
}
};
При переиспользовании компонентов тесты становятся независимыми от конкретного URL или настроек браузера.
Для сложных интерфейсных элементов, таких как модальные окна или формы, удобен паттерн Component Object, аналогичный Page Object, но на уровне отдельных элементов.
Пример:
class Modal {
constructor(modalElement) {
this.modal = modalElement;
this.closeButton = this.modal.element(by.css('.close'));
}
async close() {
await this.closeButton.click();
}
async getText() {
return await this.modal.getText();
}
}
module.exports = Modal;
Использование:
const Modal = require('./modal');
const successModal = new Modal(element(by.id('successModal')));
await successModal.close();
Для масштабных проектов структура файлов имеет большое значение. Рекомендуется разделять:
pages/ — Page Object для страниц.components/ — Component Object для повторяющихся
элементов.utils/ — вспомогательные функции и утилиты.tests/ — сами тестовые сценарии.Такое разделение способствует максимальному переиспользованию кода и ускоряет внедрение новых тестов.
Переиспользуемые компоненты хорошо сочетаются с параметризацией тестов. Вместо дублирования сценариев можно использовать массивы данных и функции генерации тестов:
const users = [
{ username: 'user1', password: 'pass1' },
{ username: 'user2', password: 'pass2' }
];
users.forEach(user => {
it(`должна авторизовать ${user.username}`, async () => {
await browser.get('/login');
await loginPage.enterUsername(user.username);
await loginPage.enterPassword(user.password);
await loginPage.clickLogin();
expect(await browser.getCurrentUrl()).toContain('/dashboard');
});
});
Это позволяет создавать десятки тестов без дублирования шагов.
Эффективное переиспользование компонентов в Protractor основывается на правильной архитектуре, разделении логики, обработке асинхронности и использовании POM и Component Object. Такой подход повышает надежность тестов, упрощает их поддержку и обеспечивает масштабируемость при увеличении функциональности веб-приложения.