Protractor ist das Framework um Angular JS Anwendungen in einem End-To-End Test unter die Lupe zu nehmen. Zwar gibt es für Angular2 noch keine angepasste Version, sodass wir auf einige Features verzichten müssen, trotzdem ist auch Angular2 Bereit zum Test.
Die Anforderung ist klar: wir wollen unsere Anwendung im Browser laufen lassen und Benutzer-Interaktionen simulieren um dann das Ergebnis zu prüfen. Unter der Haube von Protractor setzt auch dieses Framework dafür auf die bewährte WebDriver-API von Selenium. Selenium dient hier als Bindeglied zwischen unserem Test-Code und unserer Anwendung die im Browser abläuft. Die eigentlichen Tests werden schließlich mithilfe des Test-Frameworks „Jasmine“ zum Leben erweckt.
Projektsetup
Aber machen wir einen Schritt nach dem anderen und beginnen bei der Vorbereitung unseres Projektes. Zu allererst benötigen wir natürlich Protractor das mithilfe von npm schnell installiert ist:
npm install –save-dev protractor
anschließend benötigen wir noch den bereits angesprochenen Selenium-Server der bei unseren Tests die Kommunikation zwischen dem Test-Code und den gewünschten Browsern übernimmt:
./node_modules/protractor/bin/webdriver-manager update
Für unser kleines Beispielprojekt verwenden wir Webpack als Bundler für unsere Webanwendung. Die Verwendung von Protractor ist aber nicht daran gebunden. Im Kern geht es darum eine laufende Anwendung bereit zu stellen, die protractor testen kann, ob die Anwendung mit Webpack,Browserfy oder Gulp/Grunt erzeugt wird spielt für unseren Test erst einmal keine rolle.
In unserem eigentlichen Task „test“ referenzieren wir ein Config-File das Protractor zur Initialisierung dient. In diesem legen wir fest unter welcher URL unsere Anwendung zur Verfügung steht, in welchem Ordner unsere Tests zu finden sind und welchen TestRunner (Jasmin) wir verwenden wollen
exports.config = { baseUrl: 'http://localhost:4300/', specs: [ './target/**/*.test.js' ], exclude: [], framework: 'jasmine2', allScriptsTimeout: 110000, jasmineNodeOpts: { showTiming: true, showColors: true, isVerbose: false, includeStackTrace: false, defaultTimeoutInterval: 400000 }, directConnect: true, capabilities: { 'browserName': 'chrome' }, onPrepare: function () { var SpecReporter = require('jasmine-spec-reporter'); jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: true})); browser.ignoreSynchronization = false; }, /** * Angular 2 configuration * * useAllAngular2AppRoots: tells Protractor to wait for any angular2 apps on the page instead of just the one matching * `rootEl` * */ useAllAngular2AppRoots: true };
test/protractor.conf.js
Um unsere Tests aus zu führen sorgen wir für ein clean unserer kompilierten Testsourcen, führen TypeScript aus (um aus unseren TypeScript tests JavaScript-Dateien zu erzeugen) und führen anschließen Protractor aus:
rimraf ./test/target && tsc ./test/**/*.ts -t ES5 –outDir ./test/target && protractor test/protractor.conf.js
Zum Test bitte
In unseren eigentlichen Tests interagieren wir nun über den Selenium Server mit der laufenden Anwendung. Wir rufen also die zu testende URL auf, führen Aktionen aus und prüfen die Anzeige. Für das eigentliche Auffinden der Elemente stehen uns diverse so genannte Locators zu Verfügung die Elemente über Tag-Names, CSS, Texte und IDs ermittelt werden können. Wer schon Erfahrung mit dem Testen von Angular 1 hat wird allerdings einige Selektoren vermissen, so gab es z.B. die Möglich Selektionen aufgrund von NG-Model-Bindings zu ermitteln. Diese Angular-Spezifischen Selektoren werden leider (noch?) nicht Unterstützt und stehen somit erst einmal nicht zur Verfügung.
Zum auffinden der Elemente bleiben also die nicht Anguluar-Spezifischen Selektoren:
„describe“ um eine Test-Suite zu deklarieren um unsere Test zu gruppieren
„beforeEach“ um in diesem Beispiel vor jeder Funktion eine frische Navigation auf unsere Seite durch zu führen
„it“ um einen einzelnen Test zu deklarieren
„expect“ um ein erwartetes Verhalten fest zu legen
Protractor liefert die entsprechenden Möglichkeiten Elemente auf zu finden
„element“ dient zum Finden von Elementen
„by.id“ Selector für die Suche nach ID
„getText()“ Aktion die den Text des gefunden Elementes liefert
Ein wichtiger Punkt ist das alle Aktionen die mit/auf dem DOM des Browsers ausgeführt werden asynchron sind und damit einer so genannten Promise zurück liefern. Die „expect“ Methode akzeptiert solche Promises und wartet mit der Auswertung bis das Ergebnis der asynchronen Anfrage geliefert wurde. Wollen wir jedoch selber mit dem Ergebnis einer solchen Aktion weiter arbeiten ist es notwendig eine entsprechende Callback-Methode zu implementieren, die erst dann ausgeführt wird, wenn die Aktion abgeschlossen wurde.
Wer seine Anwendung mittels TypeScript schreibt wird vermutlich auch die entsprechenden Test in dieser Form verfassen wollen. Die dafür benötigten Type Definitions liefert uns das npm – Modul „typings“ (npm install –g typings) mittels:
typings install --ambient --save selenium-webdriver typings install --ambient --save angular-protractor typings install --ambient --save jasmine
Ein entsprechender Test könnte dann so aussehen:
/// <reference path="../../typings/index.d.ts" /> class PageModel { static get messageButton() { return element.all(by.className('btn-primary')).first(); } static get errorButton() { return element.all(by.className('btn-primary')).get(1); } static get nfoPanel(){ return element(by.className('nfoPanel')); } static get error(){ return element(by.id("toasty")); } } describe("Home-Page", () => { beforeEach(() => { browser.get('http://localhost:4300/#hello'); }) it("MessageButton", () => { RootPageModel.waitUntilSpinnerFinished(); expect(PageModel.nfoPanel.isPresent()).toBeFalsy(); expect(PageModel.messageButton.isPresent()).toBeTruthy(); expect(PageModel.messageButton.getText()).toEqual("Hello from Angular-InMemoryApi"); PageModel.messageButton.click(); expect(PageModel.nfoPanel.isPresent()).toBeTruthy(); }) it("ErrorButton", () => { RootPageModel.waitUntilSpinnerFinished(); expect(PageModel.errorButton.isPresent()).toBeTruthy(); PageModel.errorButton.click() expect(PageModel.error.isPresent()).toBeTruthy(); }) })
Wie wir sehen konnten ist das Testen mittels Protractor nicht schwer, dank Jasmin, Selenium und Webpack ist ein solcher Prozess schnell aufgebaut und die ersten Tests implementiert. Neben den hier gezeigten Möglichkeiten bietet Protractor und Jasmin noch einige weitere Features wie das Generieren von Reports oder sogar das Erstellen von Screenshots nach jedem Test. Mehr dazu finden Sie auf den entsprechenden Seiten der Hersteller.