Доступ к Angular scope

В тестировании Angular-приложений использование Protractor предоставляет уникальные возможности для взаимодействия с внутренним состоянием компонентов через scope. Понимание того, как получить доступ к Angular scope, позволяет проверять данные модели напрямую, что особенно полезно при тестировании динамических интерфейсов.


Основные понятия Angular Scope

Scope в Angular — это объект, который связывает модель и представление. Он хранит все переменные и функции, которые доступны в шаблоне компонента через выражения Angular ({{variable}}) или директивы (ng-model, ng-repeat и т.д.). Каждый контроллер и директива создают свой собственный scope, который может наследоваться от родительского.

  • $scope — объект в контроллерах, доступный через DI.
  • $rootScope — глобальный scope приложения, общий для всех контроллеров.
  • Наследование scope — дочерние scopes получают доступ к родительским переменным через прототипное наследование.

В Protractor можно напрямую обращаться к этим объектам через браузер, используя executeScript.


Получение scope элемента

Для доступа к scope элемента используется метод browser.executeScript, который позволяет выполнить произвольный JavaScript в контексте страницы.

Пример получения scope:

const scope = await browser.executeScript(function(el) {
    return angular.element(el).scope();
}, element(by.css('.my-controller')).getWebElement());

Пояснения:

  • angular.element(el) — обертка Angular вокруг DOM-элемента.
  • .scope() — возвращает scope, связанный с этим элементом.
  • element(by.css(...)).getWebElement() — стандартный способ получения нативного DOM-элемента в Protractor.

После выполнения этого кода переменная scope содержит объект, через который можно читать и изменять свойства модели.


Чтение данных из scope

После получения scope доступны все переменные контроллера. Например, если в контроллере объявлено:

$scope.username = 'admin';

Можно прочитать значение через Protractor:

const username = await browser.executeScript(function(el) {
    return angular.element(el).scope().username;
}, element(by.css('.my-controller')).getWebElement());

console.log(username); // 'admin'

Это позволяет проверять внутреннее состояние приложения без зависимости от DOM-элементов.


Изменение данных scope

Изменение свойств scope через Protractor требует обновления Angular digest цикла для применения изменений в DOM:

await browser.executeScript(function(el) {
    const scope = angular.element(el).scope();
    scope.username = 'newUser';
    scope.$apply();
}, element(by.css('.my-controller')).getWebElement());
  • scope.$apply() запускает digest цикл, чтобы изменения в модели сразу отразились в представлении.
  • Без $apply() изменения останутся только в объекте JavaScript, а DOM не обновится.

Работа с isolate scope

Некоторые директивы создают isolate scope, который изолирован от родительского:

const isolateScope = await browser.executeScript(function(el) {
    return angular.element(el).isolateScope();
}, element(by.css('.my-directive')).getWebElement());
  • isolateScope() позволяет получать и изменять свойства, специфичные для директивы.
  • В отличие от обычного scope, isolate scope не наследует данные родителя напрямую.

Советы по использованию scope в тестах

  1. Минимизировать прямой доступ к scope, если можно обойтись через UI, чтобы тест оставался ближе к пользовательскому сценарию.
  2. Использовать await с browser.executeScript, так как Protractor полностью асинхронен.
  3. Проверять наличие angular.element(el).scope() перед использованием, чтобы избежать ошибок на элементах, не связанными с Angular.
  4. Для повторного доступа к scope сохранять результат executeScript в переменную и работать с ней в последующих проверках.
  5. Для isolate scope важно понимать, что $apply() влияет только на эту директиву и ее дочерние scopes, но не на весь родительский scope.

Пример комплексного использования

const el = element(by.css('.user-form'));
await browser.executeScript(function(el) {
    const scope = angular.element(el).scope();
    scope.username = 'testUser';
    scope.password = 'securePass';
    scope.$apply();
}, el.getWebElement());

const formValues = await browser.executeScript(function(el) {
    const scope = angular.element(el).scope();
    return {
        username: scope.username,
        password: scope.password
    };
}, el.getWebElement());

console.log(formValues); // { username: 'testUser', password: 'securePass' }

В этом примере одновременно изменяется несколько полей модели и проверяется их значение после digest цикла.


Доступ к Angular scope в Protractor открывает прямое взаимодействие с внутренней моделью приложения, что позволяет создавать более точные и быстрые тесты, особенно для сложных форм и динамического контента. Понимание различий между обычным scope и isolate scope помогает эффективно тестировать как контроллеры, так и кастомные директивы.