Инкапсуляция селекторов — ключевой подход при организации автоматизированных тестов на Protractor. Она позволяет отделить логику работы с элементами страницы от самой структуры тестов, делая код более читаемым, поддерживаемым и удобным для масштабирования.
Основная идея заключается в том, чтобы сохранять все селекторы в одном месте — чаще всего в Page Object или отдельном объекте с элементами страницы. Это позволяет при изменении структуры интерфейса обновлять селекторы только в одном файле, без необходимости менять каждый тест отдельно.
Ключевые моменты:
ElementFinder.Классический пример инкапсуляции в Protractor выглядит так:
class LoginPage {
constructor() {
this.usernameInput = element(by.id('username'));
this.passwordInput = element(by.css('input[type="password"]'));
this.loginButton = element(by.buttonText('Login'));
this.errorMessage = element(by.css('.error-msg'));
}
async enterUsername(username) {
await this.usernameInput.clear();
await this.usernameInput.sendKeys(username);
}
async enterPassword(password) {
await this.passwordInput.clear();
await this.passwordInput.sendKeys(password);
}
async clickLogin() {
await this.loginButton.click();
}
async getErrorMessage() {
return this.errorMessage.getText();
}
}
В этом примере тесты никогда не используют селекторы напрямую. Они вызывают методы объекта страницы, что делает тесты лаконичными:
it('should show error for invalid login', async () => {
const loginPage = new LoginPage();
await loginPage.enterUsername('wronguser');
await loginPage.enterPassword('wrongpass');
await loginPage.clickLogin();
const error = await loginPage.getErrorMessage();
expect(error).toEqual('Invalid username or password');
});
Для улучшения инкапсуляции можно сделать локаторы приватными с использованием конвенций или синтаксиса ES6:
class LoginPage {
#usernameInput = element(by.id('username'));
#passwordInput = element(by.css('input[type="password"]'));
#loginButton = element(by.buttonText('Login'));
async enterUsername(username) {
await this.#usernameInput.clear();
await this.#usernameInput.sendKeys(username);
}
async enterPassword(password) {
await this.#passwordInput.clear();
await this.#passwordInput.sendKeys(password);
}
async clickLogin() {
await this.#loginButton.click();
}
}
Использование приватных полей предотвращает прямой доступ к селекторам из тестов и строго разделяет интерфейс объекта страницы и внутренние детали.
Для крупных проектов полезно создавать централизованные файлы с селекторами. Например:
// selectors.js
module.exports = {
login: {
usernameInput: by.id('username'),
passwordInput: by.css('input[type="password"]'),
loginButton: by.buttonText('Login'),
errorMessage: by.css('.error-msg')
},
dashboard: {
profileLink: by.css('.profile'),
logoutButton: by.id('logout')
}
};
Page Object использует эти селекторы:
const selectors = require('./selectors');
class LoginPage {
usernameInput = element(selectors.login.usernameInput);
passwordInput = element(selectors.login.passwordInput);
loginButton = element(selectors.login.loginButton);
async login(username, password) {
await this.usernameInput.sendKeys(username);
await this.passwordInput.sendKeys(password);
await this.loginButton.click();
}
}
Такой подход упрощает поддержку при изменении идентификаторов на сайте и позволяет легко масштабировать проект, добавляя новые страницы и разделы интерфейса.
Часто элементы страницы формируются динамически. Для их инкапсуляции можно использовать методы с параметрами:
class ProductPage {
getProductCardByName(name) {
return element(by.xpath(`//div[@class='product-card' and .//h2[text()='${name}']]`));
}
async clickAddToCart(productName) {
const card = this.getProductCardByName(productName);
await card.element(by.buttonText('Add to Cart')).click();
}
}
Методы возвращают ElementFinder, а тесты остаются
чистыми и не зависят от внутренней структуры
страницы.
Инкапсуляция селекторов — фундаментальная практика при работе с Protractor, которая повышает качество тестов, уменьшает технический долг и облегчает работу в крупных проектах.