Protractor, как инструмент для end-to-end тестирования Angular-приложений, тесно интегрирован с Angular-инфраструктурой. Одним из ключевых аспектов является взаимодействие с Angular-сервисами через инъекцию зависимостей (Dependency Injection, DI) и возможность работы с сервисами непосредственно из тестов.
Angular использует инжектор, который управляет созданием и предоставлением сервисов по всему приложению. Инжектор хранит singleton-инстансы сервисов, что позволяет компонентам, директивам и другим сервисам получать доступ к одним и тем же объектам без ручного создания экземпляров.
В контексте тестирования это значит, что Protractor может обращаться к сервисам через глобальный Angular-инжектор и взаимодействовать с ними напрямую, минуя DOM. Это особенно полезно для:
Protractor предоставляет возможность взаимодействовать с
Angular-инжектором через объект
browser.executeAsyncScript(). Это позволяет выполнять
функции внутри Angular-контекста и получать доступ к сервисам:
browser.executeAsyncScript(function(callback) {
var injector = angular.element(document.body).injector();
var myService = injector.get('MyService');
myService.getData().then(function(data) {
callback(data);
});
});
Здесь ключевые моменты:
angular.element(document.body).injector() — получение
корневого инжектора приложения;injector.get('MyService') — извлечение экземпляра
нужного сервиса;then) для асинхронной работы с
сервисами, так как большинство Angular-сервисов возвращают промисы.Для более изолированного тестирования часто требуется
заменить реальный сервис на mock. Это можно сделать
через Angular module и provide:
beforeEach(() => {
browser.addMockModule('mockModule', function() {
angular.module('mockModule', []).config(function($provide) {
$provide.service('MyService', function() {
this.getData = function() {
return Promise.resolve(['mocked', 'data']);
};
});
});
});
});
Особенности подхода:
browser.addMockModule() добавляет Angular-модуль перед
загрузкой страницы;$provide.service или $provide.value
позволяет полностью подменить оригинальный сервис;Protractor автоматически ожидает завершения Angular-зон
($digest циклов), но внутри
executeAsyncScript необходимо явно использовать
callback для передачи результата в тест.
Пример получения данных с асинхронного сервиса:
it('должен получить данные из сервиса', async () => {
const data = await browser.executeAsyncScript(function(callback) {
const injector = angular.element(document.body).injector();
const myService = injector.get('MyService');
myService.getData().then(callback);
});
expect(data).toEqual(['expected', 'values']);
});
Использование async/await в тестах делает код более
читаемым и упрощает обработку промисов.
Иногда нужно синхронизировать состояние сервиса с DOM. Например, после изменения данных через сервис необходимо дождаться обновления интерфейса:
await browser.executeAsyncScript(function(callback) {
const injector = angular.element(document.body).injector();
const myService = injector.get('MyService');
myService.addItem('newItem').then(callback);
});
const listItems = element.all(by.css('.item'));
expect(await listItems.count()).toBe(1);
expect(await listItems.get(0).getText()).toBe('newItem');
Важные моменты:
$digest и обновление
Angular-приложения, поэтому element.all возвращает
актуальные элементы;Для Angular 2+ подход аналогичен, но с учётом новой архитектуры:
angular используется
window.getAllAngularTestabilities();ng.probe;Promise или
Observable, что требует правильной обработки через
await или .then().Пример с Observable:
await browser.executeAsyncScript(function(callback) {
const injector = window.ng.probe(document.body).injector;
const myService = injector.get('MyService');
myService.getObservableData().subscribe(data => callback(data));
});
await, иначе тест завершится до получения
данных.Работа с Angular-сервисами через Protractor позволяет обходить пользовательский интерфейс для проверки бизнес-логики, ускорять тесты и создавать гибкие сценарии с mock-данными, сохраняя при этом полное взаимодействие с Angular-инфраструктурой.