Локаторы являются основой взаимодействия с элементами веб-страницы в Protractor. Они позволяют тестам находить элементы и выполнять действия над ними. Несмотря на кажущуюся простоту, неправильный выбор локаторов часто приводит к нестабильным и трудно поддерживаемым тестам. В этой части рассматриваются основные проблемы, с которыми сталкиваются при работе с локаторами, и подходы к их решению.
Ключевой критерий локатора — его стабильность. Надежный локатор должен сохранять корректность даже при изменениях интерфейса, таких как редизайн или изменение структуры DOM. Проблемы возникают, когда:
element.all(by.css('div')).get(3). Изменение количества
элементов в контейнере ломает тест.id="item_12345"), которые генерируются при каждой загрузке
страницы.by.buttonText('Отправить')), особенно если текст
локализован.Решение: Использовать уникальные и стабильные
атрибуты (data-*, ng-model для Angular) и
избегать привязки к позициям или тексту, который может измениться.
Пример надежного локатора:
element(by.css('[data-test-id="login-button"]')).click();
Protractor работает с Angular и умеет ждать завершения асинхронных операций. Однако проблемы с локаторами часто возникают при динамическом изменении DOM:
NoSuchElementError.infinite scroll), и сразу получить нужный элемент
невозможно.Решение: Использовать ожидания
(ExpectedConditions) для проверки доступности элемента:
const EC = protractor.ExpectedConditions;
const loginButton = element(by.css('[data-test-id="login-button"]'));
browser.wait(EC.visibilityOf(loginButton), 5000);
loginButton.click();
Такой подход гарантирует, что тест не упадет из-за временной недоступности элемента.
Часто тестировщики создают локаторы типа:
element(by.css('body > app-root > app-login > div.container > form > button.btn-primary'))
Проблема заключается в:
Решение: Использовать короткие и семантические локаторы с уникальными атрибутами или классами:
element(by.css('.login-form button.submit'));
или Angular-специфичные локаторы:
element(by.model('user.password'));
XPath позволяет создавать гибкие и точные выражения, однако их использование имеет подводные камни:
// для поиска по всему дереву.element(by.xpath('//div[@class="container"]/form//button[contains(text(), "Войти")]'))
может быть менее наглядным, чем CSS-селектор с уникальным атрибутом.
Решение: XPath использовать только там, где CSS
невозможен или неэффективен. Для динамических текстов можно использовать
функции contains() или starts-with(), но при
этом сохранять минимальную глубину поиска:
element(by.xpath('//button[contains(@class,"submit") and text()="Войти"]'));
Частая ошибка — дублирование одинаковых локаторов по всему тестовому проекту. Это усложняет поддержку при изменении интерфейса.
Решение: Создавать отдельные Page Objects, где все локаторы определяются один раз:
class LoginPage {
constructor() {
this.username = element(by.model('user.username'));
this.password = element(by.model('user.password'));
this.loginButton = element(by.css('[data-test-id="login-button"]'));
}
login(user, pass) {
this.username.sendKeys(user);
this.password.sendKeys(pass);
this.loginButton.click();
}
}
Такой подход упрощает изменение локаторов и повышает читаемость тестов.
Списки, таблицы и повторяющиеся компоненты создают дополнительные сложности:
element.all(by.css(...)).get(index) может
быть ненадежным.Решение: Использовать фильтры и условия для поиска конкретного элемента:
const rows = element.all(by.css('table tr'));
const userRow = rows.filter(async row => {
const text = await row.getText();
return text.includes('Иван Иванов');
}).first();
userRow.element(by.buttonText('Удалить')).click();
Такой метод позволяет не зависеть от индекса и правильно работать с динамическими списками.
Проблемы с локаторами чаще всего связаны с нестабильностью, зависимостью от структуры DOM или динамическим поведением страницы. Правильный подход включает:
data-*, ng-model).ExpectedConditions.Эти методы значительно повышают устойчивость тестов и упрощают их сопровождение в больших проектах.