by.model() — специфичный для Angular

by.model() — это локатор в Protractor, который предназначен исключительно для приложений на Angular. Он используется для поиска элементов, связанных с Angular-моделью данных, указанной в атрибуте ng-model. Такой подход позволяет напрямую взаимодействовать с данными модели, минуя сложные селекторы по CSS или XPath, что делает тесты более стабильными и читаемыми.

Основы использования by.model()

Атрибут ng-model в Angular связывает элемент формы с конкретной переменной в $scope. Например:

<input type="text" ng-model="user.name">

Чтобы получить доступ к этому элементу в Protractor, используется:

let nameInput = element(by.model('user.name'));

После этого можно применять стандартные методы взаимодействия с элементами:

nameInput.sendKeys('Иван');
nameInput.clear();
expect(nameInput.getAttribute('value')).toEqual('Иван');

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

Особенности поиска элементов

  • Поддержка вложенных моделей: если модель является объектом с вложенными полями, можно указывать путь через точку:
element(by.model('user.address.street')).sendKeys('Ленина, 10');
  • Множественные совпадения: если несколько элементов используют один и тот же ng-model, element(by.model(...)) вернет первый элемент, а для получения всех подходящих элементов применяется element.all(by.model(...)):
let checkboxes = element.all(by.model('task.completed'));
checkboxes.each(cb => cb.click());
  • Работа с различными типами элементов: by.model() применим к <input>, <textarea>, <select>, <checkbox>, <radio> — везде, где используется ng-model.

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

Protractor автоматически синхронизируется с Angular и ожидает завершения асинхронных операций перед взаимодействием с элементами. При использовании by.model() это позволяет безопасно вводить данные или получать их значение без явного ожидания:

element(by.model('user.email')).sendKeys('test@example.com');
expect(element(by.model('user.email')).getAttribute('value')).toEqual('test@example.com');

В редких случаях, если тесты выполняются в приложении с внешними AJAX-запросами вне Angular, может потребоваться использование browser.wait для явного ожидания.

Преимущества использования by.model()

  1. Прямое связывание с моделью: тесты остаются стабильными даже при изменении структуры HTML, пока сохраняется имя модели.
  2. Повышение читаемости: код теста явно показывает, с какой моделью взаимодействует тест.
  3. Совместимость с Angular формами: автоматическая синхронизация с жизненным циклом Angular делает тесты надежными.

Примеры сложных сценариев

  • Ввод и проверка нескольких полей формы:
element(by.model('user.name')).sendKeys('Иван');
element(by.model('user.email')).sendKeys('ivan@example.com');
expect(element(by.model('user.name')).getAttribute('value')).toEqual('Иван');
expect(element(by.model('user.email')).getAttribute('value')).toEqual('ivan@example.com');
  • Работа с чекбоксами и радиокнопками:
element(by.model('user.subscribe')).click(); // чекбокс
element(by.model('user.gender')).element(by.css('option[value="male"]')).click(); // select
  • Множественные элементы с одной моделью:
element.all(by.model('task.completed')).each(function(checkbox) {
    checkbox.isSelected().then(function(selected) {
        if (!selected) {
            checkbox.click();
        }
    });
});

Ограничения и нюансы

  • Работает только с AngularJS и Angular 2+ приложениями, где используется ng-model.
  • Не подходит для элементов без привязки к Angular-модели. В таких случаях необходимо использовать by.css или by.xpath.
  • Не гарантирует уникальность: при одинаковых ng-model для нескольких элементов лучше применять element.all с фильтрацией.

Использование by.model() в тестах Protractor позволяет создавать стабильные, читаемые и поддерживаемые автотесты для Angular-приложений, минимизируя зависимость от структуры DOM и упрощая взаимодействие с формами и данными модели.