Работа с Angular моделями

Protractor — это фреймворк для автоматизированного тестирования веб-приложений, построенных на Angular и AngularJS. Основная сила Protractor заключается в его глубокой интеграции с Angular, что позволяет взаимодействовать с элементами страницы через Angular модели и привязки (bindings), обходя сложные манипуляции с DOM.

Основные концепции

Angular использует концепцию двусторонней привязки данных. Каждое поле ввода может быть связано с моделью в контроллере через ng-model. Protractor предоставляет удобные методы для работы с такими моделями:

element(by.model('user.name')).sendKeys('Иван');
element(by.model('user.email')).getAttribute('value').then(function(value) {
    console.log(value);
});
  • by.model('user.name') — локатор, который находит элемент по модели Angular.
  • sendKeys() — ввод текста в поле.
  • getAttribute('value') — получение текущего значения поля.

Использование by.model позволяет не зависеть от структуры DOM, что делает тесты более устойчивыми к изменениям разметки.

Работа с формами

Angular модели часто используются в формах. Protractor позволяет:

  1. Вводить значения.
  2. Проверять значения.
  3. Отслеживать изменение состояния формы (valid/invalid, touched, dirty).

Пример проверки валидности формы:

var usernameInput = element(by.model('user.username'));
usernameInput.sendKeys('testUser');

var form = element(by.css('form[name="userForm"]'));
form.getAttribute('class').then(function(classes) {
    if (classes.includes('ng-valid')) {
        console.log('Форма валидна');
    } else {
        console.log('Форма невалидна');
    }
});

Здесь важно понимать, что Angular автоматически добавляет CSS-классы ng-valid, ng-invalid, ng-touched к элементам и формам, что позволяет легко проверять состояние формы.

Привязки (bindings)

Angular позволяет отображать данные на странице через {{expression}}. Protractor может работать с такими binding-выражениями через локатор by.binding.

Пример:

element(by.binding('user.name')).getText().then(function(text) {
    console.log('Имя пользователя:', text);
});
  • by.binding('user.name') находит элемент, который отображает значение user.name.
  • getText() возвращает текст, отображаемый на странице.

Для повторяющихся элементов используется by.exactBinding и by.binding, что позволяет точно выбрать нужный binding.

Работа с массивами и повторяющимися элементами

Angular использует директиву ng-repeat для рендеринга списков. Protractor предоставляет локатор by.repeater для работы с такими коллекциями.

Пример работы с таблицей пользователей:

var users = element.all(by.repeater('user in users'));

users.count().then(function(count) {
    console.log('Количество пользователей:', count);
});

users.get(0).element(by.binding('user.name')).getText().then(function(name) {
    console.log('Первый пользователь:', name);
});
  • element.all(by.repeater('user in users')) возвращает массив элементов.
  • Методы count(), get(index), first(), last() позволяют удобно обращаться к конкретным элементам списка.

Можно комбинировать by.repeater с row(index) и column(binding) для точного извлечения данных из таблиц:

var firstUserEmail = element(by.repeater('user in users').row(0).column('user.email')).getText();

Асинхронность и ожидания

Protractor автоматически синхронизируется с Angular, но иногда необходимо использовать явные ожидания. Особенно важно для динамических моделей и асинхронных операций:

var usernameInput = element(by.model('user.username'));
var EC = protractor.ExpectedConditions;

browser.wait(EC.presenceOf(usernameInput), 5000);
usernameInput.sendKeys('testUser');

Использование ExpectedConditions позволяет безопасно взаимодействовать с элементами, которые могут появляться с задержкой.

Дополнительные возможности

  • Проверка состояния чекбоксов и радиокнопок:
var subscribeCheckbox = element(by.model('user.subscribe'));
subscribeCheckbox.isSelected().then(function(selected) {
    if (!selected) subscribeCheckbox.click();
});
  • Работа с select (выпадающими списками):
var countrySelect = element(by.model('user.country'));
countrySelect.element(by.cssContainingText('option', 'Россия')).click();
  • Чтение и установка значений в Angular компонентах с ng-model:
var ageInput = element(by.model('user.age'));
ageInput.clear();
ageInput.sendKeys('30');

clear() — необходим для удаления старого значения перед вводом нового.

Рекомендации по стабильности тестов

  • Использовать Angular локаторы (by.model, by.binding, by.repeater) вместо чистого CSS или XPath.
  • Минимизировать использование sleep(). Вместо этого применять ExpectedConditions.
  • Проверять состояние формы через CSS-классы Angular (ng-valid, ng-invalid).
  • Для повторяющихся элементов работать с element.all() и методами first(), last(), get(index).

Взаимодействие с сложными формами

Для сложных форм с вложенными объектами и массивами можно комбинировать by.model и by.repeater. Пример: редактирование адресов пользователя:

var addressRows = element.all(by.repeater('address in user.addresses'));
addressRows.get(0).element(by.model('address.city')).sendKeys('Москва');
addressRows.get(1).element(by.model('address.city')).sendKeys('Санкт-Петербург');

Такой подход позволяет работать с глубоко вложенными моделями без прямого обращения к DOM-структуре.