Tipps, Tool-Vergleiche, How-To’s und Tutorials gezielt zu bestimmten Testwerkzeugen

Einbindung von Playwright unter JUnit

Nachdem wir in einem anderen Artikel die grundlegende Einrichtung und Verwendung von Playwright demonstriert haben, wollen wir nun Playwright einsetzen, um mit JUnit die echten Tests einer Webanwendung zu schreiben. Hierfür müssen wir entsprechend die pom.xml-Datei unseres Maven-Projekts anpassen. In diesem Tutorial verwenden wir JUnit Jupiter aka JUnit 5. Genauso wie für die Playwright API, legen wir eine neue JUnit <dependency> unter dem XML-Knoten <dependencies> an.

  <dependencies>
    <dependency>
      ...
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.7.2</version>
      <scope>test</scope>
    </dependency>
    
  </dependencies>

Möglicherweise ist bereits eine junit-Abhängigkeit beim Anlegen des Projekts erstellt worden, wir können sie aber einfach entfernen. Normalerweise benötigt JUnit Jupiter 2 Abhängigkeiten. Die junit-jupiter-api für Annotationen wie @Test, um Testmethoden zu annotieren, und die junit-jupiter-engine, damit die Tests ausgeführt werden. Intern ist jedoch die junit-jupiter-engine abhängig von der Jupiter API, weshalb es ausreicht, nur die TestEngine als Abhängigkeit einzufügen. Der scope-Tag gibt an, für welche Phasen des Builds sich die jeweiligen Abhängigkeiten eignen – in unserem Fall also für die Testkompilierung und -ausführung.

Testklasse schreiben

Wir wollen nun im Rahmen unseres ersten Testfalls überprüfen, ob der Titel der Wikipedia-Seite tatsächlich auch Wikipedia lautet. Dazu legen wir eine neue Testklasse WikipediaTest.java an, die unseren Testfall, verpackt in einer Unit-Test Methode, beinhalten wird. Im package src>test>java>de.simplytest können später beliebig viele weitere Testklassen hinzukommen.

1. Testfall: „Überprüfung des Webseitentitels“

package de.simplytest;

import com.microsoft.playwright.*;
import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class WikipediaTest {

    @Test
    public void checkWikipediaTitle() {
        Playwright playwright = Playwright.create();
        Browser browser = playwright.firefox().launch();
        Page page = browser.newPage();

        page.navigate("https://www.wikipedia.org/");

        String actualTitle = page.title();
        assertEquals("Wikipedia", actualTitle);

    }

}

Die Playwright-, Browser- und Page- Objektinstanziierung sollten bereits bekannt sein. Den Titel der Seite bekommen wir mit dem API-Aufruf page.title() und speichern diesen im String actualTitle. Der wirkliche Test erfolgt jetzt mit dem Aufruf assertEquals, der von JUnit bereitgestellt wird. Dieser Methode übergeben wir als Argumente den Titel der Webseite, den wir erwarten (also „Wikipedia“) und den eigentlichen Titel, den uns Playwright von der Webseite liefert. Intern prüft assertEquals dann die beiden Argumente auf Gleichheit. Assert-Methoden sind allgemein nützlich zur Bestimmung des Pass- oder Fail-Status eines Testfalls. Es gibt natürlich neben assertEquals auch weitere Asserts, z.B. assertTrue, um zu überprüfen, ob eine Bedingung eintritt, oder assertSame, um zu prüfen, ob 2 Objekte dasselbe Objekt referenzieren. Eine komplette Übersicht aller Asserts ist auf der Doc-Seite zu finden.

2. Testfall: „Überprüfung eines Wikipedia-Artikels“

Um sicherzustellen, dass eine Webseite funktioniert, reicht es nicht aus, nur zu prüfen, ob die Seite geladen und richtig angezeigt wurde, indem man zum Beispiel nur den Titel der Webseite prüft. Vielmehr muss sichergestellt werden, dass die Inhalte auf der Webseite korrekt sind und die Interaktionen ordnungsgemäß funktionieren. In einem zweiten Testfall testen wir daher, ob die Websuche auf der Seite beim Eintippen des Suchbegriffs Koala funktioniert und zum richtigen Ergebnis führt.

Ausschnitt der Wikipedia-Startseite

Für das Eintippen eines Suchbegriffs müssen wir jedoch zuerst das richtige Suchfeld auf der Webseite finden. Dazu sucht Palywright die Elemente einer Webseite nicht anhand ihrer visuellen gerenderten Repränsentation im Webbrowser, sondern anhand ihrer technischen Entsprechung  im sogenannten DOM-Baum mittels XPATH und CSS Selektoren. Diese Selektoren sind nichts anderes als Strings, die auf Elemente auf der Seite zeigen, und werden dafür verwendet, um Aktionen an diesen Elementen durchzuführen, wie wir weiter unten im Codebeispiel sehen werden.

Um einen geeigneten Selektor für das Suchfeld herauszufinden, klicken wir mit der rechten Maustaste über dieses Feld und wählen den Kontextmenüpunkt Element untersuchen aus. Aus dem DOM-Baum erkennen wir, dass das Suchfeld anhand der ID searchInput eindeutig identifiziert werden kann, die wir in unserem Java-Test verwenden.

Visualisierung eines HTML-Elements mit dem Inspektor

Nachdem das Suchfeld anhand der ID identifiziert wurde, können wir damit interagieren und den gewünschten Suchbegriff von der Playwright API eintippen lassen.

@Test
    public void checkWikipediaArticle() {
        Playwright playwright = Playwright.create();
        Browser browser = playwright.firefox().launch();
        Page page = browser.newPage();

        page.navigate("https://www.wikipedia.org/");
        page.fill("#searchInput", "Koala");
        page.keyboard().press("Enter");

        String actualArticleHeading = page.textContent("#firstHeading");

        assertEquals("Koala", actualArticleHeading);
    }

Wir verwenden dazu die Befehle Page.fill(), der auf das Element mit dem Selektor "#searchInput" wartet, das in diesem Beispiel das Suchfeld darstellt, und füllt dieses mit dem Suchbegriff "Koala" aus. Anschließend simulieren wir das Drücken der Eingabetaste mit dem Page.keyboard().press("Enter")-Befehl.

Dadurch haben wir die Suchanfrage automatisiert! Uns bleibt jetzt noch zu prüfen, ob die Suche funktioniert hat und der richtige Artikel gefunden wurde. Hierfür lassen wir uns von Playwright mit dem Befehl Page.textContent() den Inhalt des Elements mit dem Selektor "#firstHeading" zurückgeben und überprüfen ihn, wie im vorherigen Beispiel, mit einem assertEquals auf Gleichheit mit dem Wort Koala.

Der Selektor #firstHeading zeigt auf das rot unterstrichene Element. Der Textinhalt lautet in diesem Bild „Koala“

Code strukturieren

Wir initialisieren eine Playwright– und Browser-Instanz global, damit sie über alle Tests hinweg verwendet werden kann. Außerdem lassen wir JUnit für uns vor jeder Testmethode ein Page-Objekt erstellen, damit wir nicht in jedem Test die jeweiligen Instanzen und Objekte einzeln erstellen müssen. Für einen isolierten Browserzustand zwischen den Tests, erstellen wir auch vor jedem Test einen neuen BrowserContext. Die Annotationen @BeforeAll, @AfterAll, @BeforeEach und @AfterEach sind dabei sehr hilfreich.

Somit können wir die Tests unabhängig voneinander in einem frischen Zustand durchführen, ohne dass sich die Tests beispielsweise durch Cookies gegenseitig beeinflussen, und am Ende die Browserinstanzen ebenfalls unabhängig vom Teststatus freigeben. Wenn wir jedoch die Cookies-Funktionalität einer Webseite testen wollen, zum Beispiel für eine Authentifikation, dürfen wir auf keinen Fall neuen Instanzen von BrowserContext vor jeder Testmethode erstellen. Die Cookies müssen auf jeden Fall über die Tests hinweg erhalten bleiben!

package de.simplytest;

import com.microsoft.playwright.*;
import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class WikipediaTest
{
    //Shared between all tests in this class
    private static Playwright playwright;
    private static Browser browser;

    // New instance for each test method
    private Page page;
    private BrowserContext browserContext;

    @BeforeAll
    public static void setUpClass() {
        playwright = Playwright.create();
        browser = playwright.firefox().launch();
    }

    @AfterAll
    public static void tearDownClass() {
        browser.close();
        playwright.close();
    }

    @BeforeEach
    public void createContextAndPage() {
        browserContext = browser.newContext();
        page = browserContext.newPage();
    }

    @AfterEach
    public void destroyContext() {
        browserContext.close();
    }

    @Test
    public void checkWikipediaTitle() {
        page.navigate("https://www.wikipedia.org/");
        String actualTitle = page.title();
        assertEquals("Wikipedia", actualTitle);
    }

    @Test
    public void checkWikipediaArticle() {
        page.navigate("https://www.wikipedia.org/");
        page.fill("#searchInput", "Koala");
        page.keyboard().press("Enter");

        String actualArticleHeading = page.textContent("#firstHeading");

        assertEquals("Koala", actualArticleHeading);
    }
}

Will man nun die Tests ausführen, gibt man im Terminal den Befehl mvn test ein. Dieser führt alle Testklassen aus (in unserem Beispiel haben wir nur eine Testklasse, die WikipediaTest.java-Klasse. Um einzelne Testklassen zu starten, verwendet man mvn test -Dtest=Testklasse. Möglich ist außerdem das Starten einzelner Methoden innerhalb einer Testklasse, z.B. mit mvn test -Dtest=WikipediaTest#checkWikipediaArticle.

Maven Surefire Plugin

Für die Ausführung der Unit-Tests einer Anwendung während der Testphase ist das Surefire Plugin verantwortlich. Das Surefire Plugin sucht nach Testklassen, deren Namen den folgenden Mustern entsprechen:

  • **/Test*.java
  • **/*Test.java
  • **/*Tests.java
  • **/*TestCase.java

Da unsere Testklasse WikipediaTest mit Test endet, mussten wir uns nicht darum kümmern, dass Surefire die Klasse findet. Würden wir jedoch andere Namen für unsere Testklassen verwenden, müssten wir dies in der pom.xml-Datei für das Plugin berücksichtigen. Beispielsweise beachtet Surefire bei der Suche auch auf Testklassen, die mit dem Namen Simply anfangen.

        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>3.0.0-M5</version>
          <configuration>
            <includes>
              <include>**/Simply*.java</include>
            </includes>
          </configuration>
        </plugin>

E2E-Tests haben u.a. den Nachteil, instabil und langsam zu sein. Aus diesem Grund hat Microsoft die Open-Source Playwright-API entwickelt, die als E2E Testframework der nächsten Generation gilt. Mit Playwright verfolgt man das Ziel, die browserübergreifende Webautomatisierung schneller und zuverlässiger zu gestalten. Dabei basiert Playwright auf einer ereignisgesteuerten (sog. event-driven) Architektur, welche automatisch auf Browser-Ereignisse wie Seitennavigation, Netzwerkanfragen oder DOM-Änderungen wartet. Mit neueren Versionen kommen immer weitere Features hinzu wie beispielsweise Geolokalisierung oder Mobile Emulation, um mobile Webseiten zu testen. Playwright API wird für verschiedene Programmiersprachen bereitgestellt: Java, Python, C#, JavaScript / TypeScript. In diesem Beitrag geht es um die Einrichtung von Playwright mit Java.

1. Einrichtung der Entwicklungsumgebung

Wir benötigen zuerst eine Entwicklungsumgebung für Java, die auf der folgenden Seite heruntergeladen werden kann: https://www.jetbrains.com/de-de/idea/download/. Einfach die Community-Version auswählen, die .exe-Datei ausführen und die Installationsschritte durchführen.

2. Maven installieren

Zur schnellen und einfachen Einrichtung von Playwright verwenden wir das Build-Management-Tool Maven, mit welchem die Playwright API Bibliotheken als Abhängigkeiten in Java Projekte automatisiert eingebunden werden. Zur Installation von Maven gehen wir wie folgt vor:

  1. https://maven.apache.org/download.cgi besuchen und das Binary zip-Archiv herunterladen
  2. Die Datei in einem beliebigen Ordner entpacken, z.B. in C:/ program files /
  3. Nach dem Entpacken, den bin-Ordner zum Systempfad hinzufügen

Systemumgebungsvariablen bearbeiten

In der Suchleiste env eintippen und Systemumgebungsvariablen bearbeiten auswählen

Umgebungsvariablen auswählen

Auf Umgebungsvariablen klicken

Path bearbeiten

Path auswählen und auf Bearbeiten klicken

bin-Ordner auswählen

Neuen Eintrag erstellen, zum bin-Ordner von dem entpackten Apache-Maven Archiv navigieren und diesen selektieren

  1. cmd.exe öffnen und mit mvn -v oder mvn –version überprüfen, ob Maven richtig installiert wurde

Bei Problemen die Hinweise zur Installation beachten: https://maven.apache.org/install.html

3. Neues Playwright Projekt erstellen

Mit Maven sollen möglichst viele Schritte automatisiert werden, wie etwa die Erstellung eines Projekts, das Testen usw. Die sog. Archetypes bieten eine Art „Gerüst“ für unterschiedliche Typen von Softwareprojekten an, deren Struktur dem Standard von Maven entspricht. Der Befehl mvn -B archetype:generate -DgroupId=de.simplytest -DartifactId=mein-maven-projekt -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 erstellt das einfachste Maven-Projekt in einem Verzeichnis mit dem Namen mein-maven-projekt . Mehr über die Maven-Archetypes erfährt man hier. Den Ordner öffnen wir in Intellij und fügen Playwright unter dem XML-Knoten dependencies in der pom.xml-Datei ein. Falls eine Warnung kommt, einfach auf Trust Project klicken.

  <dependencies>
    <dependency>
      ...
    </dependency>

    <dependency>
      <groupId>com.microsoft.playwright</groupId>
      <artifactId>playwright</artifactId>
      <version>1.14.1</version>
    </dependency>
    
  </dependencies>

Außerdem sollten wir unter dem properties-Tag maven compiler source und maven compiler target zu 1.8 ändern, um die aktuellen Java 8 Features verwenden zu können.

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

4. Erste Schritte mit Playwright in Java – „Titel einer Seite ausgeben“

In mein-maven-projekt > src > main > java > de.simplytest > App.java befindet sich die main-Methode, die automatisch mit der Generierung der Archetype angelegt wird. Wir können natürlich aber ebenfalls weitere Java-Klassen erstellen und zum Projekt hinzufügen. Nehmen wir den folgenden Code als Programmbeispiel. Hier starten wir Firefox, navigieren dann zur offiziellen Playwright-Webseite und geben anschließend den Titel der Seite aus.

package de.simplytest;

import com.microsoft.playwright.*;

public class App 
{
    public static void main(String[] args )
    {
        try (Playwright playwright = Playwright.create()) {
            Browser browser = playwright.firefox().launch(new BrowserType.LaunchOptions().setHeadless(false));
            Page page = browser.newPage();
            page.navigate("http://playwright.dev");
            System.out.println(page.title());
        }
    }
}

Playwright öffnet den Browser standardmäßig ohne User Interface im sogenannten headless-Modus, also sehen wir nicht wirklich die interaktive Ausführung des Programms. Um das zu ändern, setzen wir mittels LaunchOptions den headless-Flag auf false und eventuell auch setSlowMo, um die Ausführungszeit der API zu verlangsamen.

Programm starten

Als Letztes geben wir folgenden Befehl ein, um das Programm über Terminal auszuführen:

mvn compile exec:java -Dexec.mainClass=de.simplytest.App

Ergebnis in der Konsole nach der Ausführung des Programms
Am Ende sehen wir den Titel der Webseite : Fast and reliable end-to-end testing for modern web apps

 

Ihr wollten schon immer eine mobile Anwendung mit Appium automatisieren, wisst aber nicht wie ihr am besten loslegt?

In unserem Appium Tutorial zeigen wir euch die wichtigsten Schritte auf dem Weg zum ersten automatisierten Test mit Appium.

In diesem Beitrag geht es los mit dem Teil 1: Einrichtung von Appium und Android Studio unter Windows.

 

Weiterlesen

Testautomatisierung mit Cypress vs. Selenium Cypress und Selenium sind bekannte Testwerkzeuge, die für die Automatisierung von Webapplikationen eingesetzt werden. Selenium ist ein Open-Source-Automatisierungsframework, das in unterschiedlichen Produktversionen vorhanden ist.  Die Automatisierungslösung Selenium existiert schon seit 2004 und ist ein etabliertes Produkt mit einer großen Nutzerbasis weltweit. Das End-to-End-Tool Cypress hingegen ist seit 2014 ein Newcomer auf dem Markt, welches sich wie Selenium für die Automatisierung von Webapplikationen eignet. Cypress hat in den letzten Jahren durch seine […]

Open-Source SeleniumDragDrop Nuget

Vor einigen Jahren haben wir in einem unserer Blog Beiträge für die Lösungsmöglichkeit berichtet, wie man auf HTML 5 Seiten Drag & Drop Testautomatisierung mit HTML 5 implementeiren kann.

Der Hintrgrund war, dass die offizielle Selenium Drag & Drop API aus der Actions Klasse auf HTML 5 Seiten nicht richtig funktioniert und selbst die Referenz-Implementierungen von W3C Standard nicht autoamtisieren kann. Der zugehörige Bug-Request für Selenium Web Driver wurde bereits im Jahr 2012 angelegt. Leider ist er nach inzwischen 8 Jahren immer noch ungelöst 🙁

Da die Notwenigkeit der Drag & Drop Testautomtisierung mit Selenium aufgrund von immer komplexer Geschäftsapplikationen im Web immer häufiger angefragt wird, haben wir die Drag&Drop Implementeirung auf GitHub als Open Source Projekt bereitgestellt und ein fertiges Nuget Paket für C# veröffentlicht.

Im Rahmen dieser Implementierung wird die bereits beschriebene alternative Drag & Drop Umsetzung mit einem JavaScript umgesetzt, das automatisch augerufen wird.

Bei der Verwendung von Nuget Paket für Drag&Drop kann die Testautomatisierung von Drag&Drop in nur wenigen Schritten realisiert werden:

  1. Drag & Drop Nugen Paket SeleniumDragDrop in C# Solution einbinden
  2. Quell- und Ziel-Elemente für Drag & Drop auf der Web Seite finden
  3. DragDropHelper instanzieren
  4. Drag & Drop Operation für Quell- und Ziel-Elemente aufrufen

Die Automatisierung von Drag&Drop auf der W3C Refernz-Seite für HTML 5 Drag & Drop kann damit etwa wie folgt aussehen:

    IWebDriver driver = new ChromeDriver(Environment.CurrentDirectory);
    try
    {
        driver.Navigate().GoToUrl("https://www.w3schools.com/html/html5_draganddrop.asp");

        IWebElement sourceEl = driver.FindElement(By.Id("drag1"));
        IWebElement targetEl = driver.FindElement(By.Id("div2"));

        SeleniumDragDrop.DragDropHelper dragdrop = new SeleniumDragDrop.DragDropHelper(driver);
        dragdrop.DragAndDrop(sourceEl, targetEl);

        Assert.IsNotNull(targetEl.FindElement(By.Id("drag1")));
    }
    finally
    {
        driver.Quit();
    }

Viel Spaß beim Ausprobieren.

Aktuelles Windows 10 Update sorgt für Systemabstürze bei Android Entwicklern.

 

Der schnellste und einfachste Weg mit einer Testumgebung für Appium zu starten, ist die Verwendung von einem Android Emulator. Allerdings hat sich mit dem Windows 10 Update (KB4549949) ein Bug eingeschlichen. Er macht das Benutzen des Android Emulators bzw. des AVD-Managers nahezu unmöglich. Nachdem ein Emulator z.B.: In Android Studio gestartet wird, reagiert Windows zuverlässig mit einem Bluescreen (BsoD). Auch andere Emulatoren scheinen betroffen.

 

Es gibt aber eine schnelle Abhilfe: Wird das Update deinstalliert, laufen die Emulatoren wieder einwandfrei.
Das problematische Update wurde am 14.04.2020 veröffentlicht und es gibt noch keinen offiziellen Fix dafür.
Aktuell gibt es noch keine elegantere Lösung als die Deinstallation.

Selenium Tests mit Edge

Selenium Framework bietet per sé sehr einfache Möglichkeit an die Tests mit unterschiedlichen Web Browsern durchzuführen und damit die Cross-Browser Test Strategie umzusetzen. Während die Einbindung von gängigen Browsern wie Chrome oder Firefox i.d.R. unproblematisch erfolgt, gilt es bei der Einbindung von Edge und Internet Explorer einige Besonderheiten zu beachten, die in diesem Blog zusammengefasst werden. Die Lösungen werden nachfolgend mit C# aufgezeigt. Die Vorgehensweise ist aber auch auf andere Sprachen wie Java übertragbar.

Weiterlesen

Selenium ist als Testautomatisierungstool für Webanwendungen absoluter Standard. Der Standard WebDriver an sich hat keinen integrierten Reporter und das ist auch nicht seine Kernaufgabe. Diese wird vom WebDriver an die Testframeworks (JUnit / NUnit / MSTest / Jasmine usw.) delegiert. Es kann aber trotzdem vorkommen, dass die von den Testframeworks zur Verfügung gestellten Reporter für Auswertung von den Tests nicht ausreichend sind.

Gute Testreports haben allgemein folgende Vorteile:

  • Bieten gute Übersicht über den Testlauf auf verschiedenen Ebenen an: von hoch-granaular als Testmanagement Sicht bis feingranular für die Analyse einzelner Testschritte
  • Als lesbarer strukturierter Text / HTML abgelegt, enthalten fachliche Schritte
  • Enthalten eingebetete Screenshots, was beim Nachvollziehen der Probleme hilft
  • Vereinfachen die Fehlersuche
  • Können archiviert werden
  • Können von Menschen ohne Programmier-Kenntnissen ausgewertet werden

Auf dem Markt existieren viele mehr oder weniger gute Lösungen, die das Reporting für Selenium Tests bereitstellen. Nicht alle erfüllen die davor genannten Anforderungen an gute Testberichte, aber je nach Anwendungsfall und Projekt ist es auch oft gar nicht notwendig.

Bei uns in den Projekten hat sich der Testreporter ExtentReports bewährt. Neben einer kostenpflichtigen Version, gibt es auch eine Community Edition, die für den Einsatz in den meisten Projekten absolut ausreichend ist.

In diesem Beitrag möchte ich aufzeigen, wie ExtentReports in ein .NET Projekt eingebunden und benutzt werden kann.

Weiterlesen

Angular Applikationen auf Ebene von Unit Tests und Integrationstests durchzutesten, ist eine relativ bequeme Geschichte. Gerüchten zu Folge wurde Angular bei Google von einem Test Team entwickelt und so ist die Testbarkeit der Applikation sozusagen direkt in der DNA der Architektur integriert.
Aber wie sieht es mit den E2E Tests aus? Wie werden Cross-Browser Tests im Rahmen einer Angular Applikation realisiert? Dieses Tutorial wird diese Punkte beleuchten.

Einrichtung der Umgebung

Als erstes sollten wir uns die Entwicklungsumgebung einrichten, Angular installieren, uns ein neues Angular Projekt erstellen und uns ansehen, welche Möglichkeiten zur Qualitätssicherung Angular von Haus aus mitbringt und wie speziell die E2E UI Testskripte aufgebaut sind.

Entwicklungsumgebung

Am sinnvollsten ist es, den Code der Applikation in einer IDE (Integrated Development Environment) zu öffnen. Eine IDE ist zwar nicht zwingend notwendig, um mit Angular oder automatisierten Tests zu arbeiten, bietet aber einige Vorteile im Vergleich zu einem simplen Editor (wie Notepad++), z.B. integrierte Git Anbindung, Organisation des Codes und der dazugehörigen Pakete, integrierte Autovervollständigung (Intellisense), integrierter Terminal (Batch / Powershell) und vieles mehr.  Solltest du mit Windows, Mac oder Linux arbeiten, eignet sich die Open Source Entwicklungsumgebung Visual Studio Code gut für diese Aufgabe, aber auch mit der kostenlosen Version von Visual Studio Community für Windows oder Mac, macht man nicht viel falsch. Ich verwende für dieses Tutorial Visual Studio Code.

Um Visual Studio Code zu installieren, lade es einfach  von der offiziellen Webseite runter, installiere und starte die Applikation: https://code.visualstudio.com/

Füge anschließend den Ordner „C:\tutorial\“ im Explorer zu dem Arbeitsbereich hinzu.

Angular Projekt in Visual Studio Code

Schon kannst du den Inhalt von dem Ordner und seine demnächst folgenden Source Dateien einsehen und bearbeiten. Ein weiterer Vorteil ist auch das integrierte Terminal unten, so können wir dort anschließend bequem Pakete installieren oder Tests starten:

  Tipp: Sollte Terminal nicht sichtbar sein, einfach „STRG+ö“ drücken bzw über das Menü View>Terminal auswählen.

Installation von Angular auf dem Rechner

Solltest du davor Angular noch nie auf deinem Rechner installiert haben, benötigst du zwei Sachen:

1.Intalliere Nodejs mit dem dazugehörigen NPM (Node Package Manager). Diesen findest du auf der offiziellen Webseite von NodeJS: https://nodejs.org/en/download/ – wichtig bei NodeJS ist natürlich die Installation vom npm package manager und das „Add to Path“, damit der npm Befehl aus dem Terminal bzw. der Konsole funktioniert

Node.JS Setup
Nodejs Installation

2.Installiere anschließend über NPM die Angular Client Applikation global auf deinem Rechner. Dazu kannst du nach der NodeJs Installation einfach folgende Anweisung in deiner Console bzw. in dem Visual Studio Code Terminal ausführen:

 npm install -g @angular/cli 

Erstellung des ersten Angular Projektes

Nach der Installation von Angular können wir unser erstes kleines Projekt erstellen. Dazu einfach den „ng new <projekt-name>“ aufrufen. Dieses habe ich bei mir project2 genannt.

 ng new project2 

Mit dem Projekt wird ein neuer Ordner mit der kompletten Angular Applikationsstruktur angelegt. Nach der Anlage ist das Angular Projekt sofort lauffähig. In der Ordnerstruktur wird dir vielleicht direkt unter dem Hauptverzeichnis bereits ein Ordner e2e Tests auffallen. Die Testdateien werden bei der Neuanlage eines Angular Projektes immer mit angelegt und enthalten bereits vorkonfigurierte Karma Unit Tests sowie fertig vorkonfigurierte Protractor Tests. Bequemer kann man Testautomatisierung einem Entwickler nicht näher bringen! 🙂

Du kannst auch sofort nach der Anlage aus dem Root Verzeichnis den Befehl „ng e2e“ bzw „npm run e2e“ ausführen.

 ng e2e 

Das Ergebnis sieht bei einem Erstprojekt etwa so aus:

Angular E2E Tests starten

Wie du schon mitbekommen hast, wurde auf dem Rechner die Angular Applikation gebaut, der Chrome Browser gestartet und die Applikation aufgerufen, außerdem wurde ein erster Check auf einen Titel durchgeführt. Der Aufruf ng e2e ist wirklich sehr bequem, da es uns eine Menge Arbeit abnimmt. Würden wir die gleiche Kette „manuell“ machen wollen, müssten wir folgende Aktionen unternehmen:

  1. Angular Applikation bauen und starten mit dem Befehl ng serve in einer eigenen Konsole
  2. Start vom Webdriver Manager mit webdriver-manager start in einer eigenen Konsole (evtl. sollte davor noch webdriver-manager update durchgeführt werden)
  3. Start der Testfälle mit dem Befehlt protractor e2e/protractor.conf.js in einer weiteren Konsole

Was die einzelnen Befehle bedeuten und welche weiteren Möglichkeiten existieren, Testfälle auszuführen, schauen wir uns etwas weiter unten an.

Struktur und Aufbau der E2E Tests in Angular

Der erste UI Test war ja schon erfolgreich, obwohl wir dazu auch noch überhaupt nichts beigetragen haben. Aus diesem Grund schauen wir uns jetzt die einzelnen Komponenten der E2E Testfälle an und wie diese in den Testskripten realisiert werden.

Als erstes schauen wir uns die beteiligten Komponenten an. Das Standard Testframework in Angular ist Jasmine. Aus diesem werden die Testfälle über das Testautomation Tool Protractor mit einem Selenium Webdriver im Hintergrund durchgeführt.

Jasmine

Jasmine ist ein sogenanntes Behavior-Driven Development Testing Framework, dass viele hilfreiche Funktionalitäten bereitstellt, die im Rahmen der Testdurchführung auf unterschiedlichen Schichten (Unit Tests / Integrationstests und auch E2E UI Tests) immer wieder benötigt werden, z.B. zur Verifizierung von Soll / Ist Ergebnissen.

Behavior Driven bedeutet, dass die Test Suiten und Test Cases so spezifiziert werden, dass sie das fachliche Soll-Verhalten der Applikation beschreiben, wodurch es auch für außenstehende einfacher ist, die Testinhalte und Ziele zu verstehen.

Der Aufbau einer typischen Jasmine „Test Suite“ sieht eigentlich immer so aus:

describe("A suite", function() {
it("contains spec with an expectation", function() {
expect(true).toBe(true);
});
});

Describe entspricht einer TestSuite und kann in sich mehrere it’s enthalten.
Ein it entspricht einem Testfall welches in sich eine Logik enthält, die z.B. prüft ob etwas dem erwarteten Wert entspricht
Die Struktur ist etwas gewöhnungsbedürftig, lässt sich aber nach einer Zeit tatsächlich ganz gut lesen.

Protractor

Protractor ist die offizielle UI Bibliothek für Angular UI Testfälle. Protractor ist eigentlich nichts anderes, als ein Wrapper für den Selenium Webdriver. Im Endeffekt benutzt man also Selenium zur Browserautomatisierung, mit dem Vorteil, die für Angular spezifischen Methoden von Protractor als Wrapperfunktionen nutzen zu können.

E2E Ordnerstruktur

Wenn wir uns den erstellten Ordner ansehen, finden wir darin folgende Dateien

C:\tutorial\project2\e2e\protractor.conf.js
C:\tutorial\project2\e2e\tsconfig.e2e.json
C:\tutorial\project2\e2e\src\app.e2e-spec.ts
C:\tutorial\project2\e2e\src\app.po.ts

Die tsconfig.e2e.json werden wir an dieser Stelle vorerst auslassen, da diese lediglich für die TypeScript Konfiguration zuständig ist (eine kurze Anleitung dazu kommt demnächst). Die anderen Dateien sollten wir uns aber detaillierter anschauen:

Testkonfigurationsdatei protractor.conf.js

Die Datei protractor.conf.js ist der Kern der E2E Automatisierung. Diese ist für die Konfiguration der Protractor Ausführung verantwortlich und definiert u.a. welche Testspezifikationen ausgeführt werden, z.B.:

specs: [
  './src/**/*.e2e-spec.ts'
],

Mit welchen Capabilities (Art des Browsers, Einstellungen des Browsers) z.B.:

capabilities: {
'browserName': 'chrome'
},

welche Jasmine Optionen gelten, welcher Reporter verwendet wird, welche Timeouts die Skripte enthalten und vieles vieles mehr. Eine genaue Auflistung der unterschiedlichen Möglichkeiten findest du unter: https://github.com/angular/protractor/blob/master/docs/api-overview.md#config-file

Diese Datei werden wir dann noch in weiteren Szenarien anpassen und erweitern.

Jasmine Testfallspezifikationsdatei src/app.e2e-spec.ts

Die Datei enthält die Jasmine Spezifikation der „Testsuiten“ und „Testfälle“. Der Verweis auf app.po ist interessant, weil dort die sogenannte Page Object Implementierung hinterlegt ist. Mehr dazu im nächsten Abschnitt

import { AppPage } from './app.po'; //Import von Page Object AppPage aus der datei src/app.po.ts
 
describe('workspace-project App', () => { // neue TestSuite
  let page: AppPage; //globale AppPage Variable, verfügbar für alle Its
 
  beforeEach(() => { //Jasmine "Init" Klasse, diese wird jedes mal ausgeführt, bevor ein 'it' ausgeführt wird
    page = new AppPage(); //Erstellung neuer Instanz der der Page Object Klasse AppPage
  });
 
  it('should display welcome message', async () => { //neuer Testfall
    await page.navigateTo(); //Aufruf einer Page Object Funktion, mit der zu der Startseite navigiert wird
    expect(await page.getParagraphText()).toEqual('Welcome to testapp!'); //überprüfung ob der Paragraph Text dem erwarteten Wert "Welcome to testapp!" entspricht
  });
});

Page Object Implementierung src/app.po.ts

Die Datei stellt das Page Object bzw. „funktionale Abstraktion“ der tatsächlichen Protractor Zugriffe auf die Elemente einer Webseite dar. In dieser werden also die einzelnen Elemente gesucht und über Protractorfunktionalitäten angesteuert.

import { browser, by, element } from 'protractor'; // importiert benötigte Funktionalitäten aus dem Protractor Modul

export class AppPage { // exportiert die Klasse, die von anderen Klassen konsumiert und genutzt werden kann
async navigateTo() { //Navigationsmethode
await browser.get('/'); //Ruft über protractor die "baseadress" Adresse auf, die in protractor.conf.js hinterlegt ist
}

async getParagraphText() {
await element(by.css('app-root h1')).getText(); //sucht mit Hilfe von css Identifier ein Element mit dem Tag h1 und gibt den Text von diesem Element zurück.
}
}

Aufbau des Angular Tutorial Projektes

Nachdem wir den grundsätzlich Aufbau eines E2E Tests angeschaut haben, möchten wir eine etwas komplexere Anwendung nehmen, mit der wir anschließend unsere Testfälle entwickeln und anschließend mit unterschiedlichen Konfigurationen testen werden. Dazu nehmen wir einfach das auf der Angular Hauptseite https://angular.io/tutorial/toh-pt0 aufgeführte Beispiel. Da es bei diesem Tutorial nicht um den Aufbau einer Angular Anwendung geht, laden wir einfach die fertige Angular Applikation von der Tutorialseite runter:

Links zu dem finalen Review findest du hier: https://angular.io/tutorial/toh-pt6#final-code-review
Und die Zip Datei kann hier bezogen werden: https://angular.io/generated/zips/toh-pt6/toh-pt6.zip

Solltest du die komplette Anwendung selbst aufbauen möchten, folge einfach dem sehr gut beschriebenen Tutorial auf der angular.io Webseite.

Ich habe für dieses Tutorial ein neues Verzeichnis auf C: mit dem Namen „tutorial“ angelegt, dorthin die zip Datei runtergeladen und anschließend die zip Datei in einen weiteren Unterordner mit der Bezeichnung „angular_heroes“ entpackt.

Anschließend wechseln wir in das Anwendungsverzeichnis und führen npm install aus, um die benötigten Pakete für unsere Angular Anwendung zu installieren:

 cd C:\tutorial\angular_heroes
npm install

Damit sollten sich die erforderlichen Pakete installieren lassen.  Am Ende müsste in etwa folgende  Meldung erscheinen:

 
added 1113 packages from 1279 contributors and audited 34260 packages in 70.961s
found 14 vulnerabilities (9 low, 5 high)
run `npm audit fix` to fix them, or `npm audit` for details
 

Tipp: Sollte npm install mal nicht beim ersten mal erfolgreich durchlaufen, einfach noch einmal probieren. Ab und zu gibt es da Probleme beim Download von phantomjs oder anderen Paketen.

Nun müssten wir die Applikation starten können, dazu einfach in den angular_heroes Ordner gehen und mit ng-serve starten:

ng serve --open

Mit dem –open Parameter wird die Applikation nach Start in dem Standard Browser geöffnet.

Nach diesem Befehl sollte sich dein Browser mit der URL http://localhost:4200/dashboard öffnen und du müsstest das im Angular Tutorial gezeigte „Dashboard“ sehen. Mach Dich ruhig ein paar Minuten mit der Applikation vertraut, bevor wir mit der automatisierten Qualitätssicherung dieser Applikation loslegen.

Wichtig: Da die Angular Leute ja bekannt für ihre Begeisterung für testbare Anwendungen / Testautomatisierung sind, haben sie natürlich schon etliche Testfälle für das Tutorial Projekt hinzugefügt. Du kannst dir natürlich gerne die E2E Testfälle der Entwickler anschauen und diese auch gerne mit npm run e2e ausprobieren. Ich würde allerdings kurz bitten den Ordner „src“ zu löschen bzw. außerhalb der Applikation zu verschieben und einen neuen, leeren Ordner src anzulegen, damit wir die Testfälle gleich komplett neu in der Page Objects Struktur aufbauen können.

Aufbau der UI Testautomatisierung für das Angular Projekt

Da wir nun ein funktionierendes „System Under Test“ haben, können wir uns überlegen, welche Testfälle dafür relevant sind. Am besten geht das natürlich direkt in der Jasmine BDD Notation. Daher starten wir hier einfach mit dem „Verhalten“ in dem Spec File.

Erstellung der Jasmine Spec File(s)

Wir legen uns in dem Ordner src eine Datei an. Im Visual Studio Code klicke einfach rechts auf den Ordner im Explorer und wähle „New File“ aus.

Wir betrachten einfach die beiden Views der Appliaktion und unterteilen sie in „Verhalten / Eigenschaften, die wir Testen möchten.

Beim Dashboard könnten wir z.B. prüfen ob:

  • der Titel „Tour of Heroes“ ist
  • die 4 Top Heroes angezeigt werden
  • die Navigation verfügbar ist und aus Dashboard und Heroes besteht

Bei der Hero View könnten wir prüfen ob:

  • Die Liste der Helden existiert
  • wir einen neuen Helden anlegen können
  • wir einen Helden löschen können

Die dazugehörige Jasmine Struktur könnte daher so aussehen:

describe('Heroes Dashboard', ()=> {
    it(' should have "Tour of Heroes" as title',()=> {
 
    });
    it(' should contain 4 Top Heroes',()=> {
 
    });
 
    it(' should have a navigation',()=> {
 
    });
 
});
 
describe('Heroes ', ()=&amp;gt;{
    it(' view contains a list of heroes',()=> {
 
    });
 
    it(' creation is possible',()=> {
 
    });
 
    it(' deletion is possible',()=> {
 
    });
});

Wenn wir jetzt npm run e2e ausführen, sollte die Ausgabe so aussehen:

Jasmine started
Heroes Dashboard
√  should have "Tour of Heroes" as title
√  should contain 4 Top Heroes
√  should have a navigation

Heroes
√  view contains a list of heroes
√  creation is possible
√  deletion is possible
Executed 6 of 6 specs SUCCESS in 0.023 sec.

Die Testfälle machen an der Stelle natürlich überhaupt nichts. Aber wir sehen schon mal, dass Jasmine mit unserer Struktur arbeiten und die „it“ Anweisungsblöcke problemlos aufrufen kann.

Erstellung der dazugehörigen PageObjects

Die Page Objects Dateien sollten Funktionen und Hilfsfunktionen der Businesslogik einer View abbilden. Deshalb legen wir uns nun eine weitere Datei in das Verzeichnis e2e/src an, die wir  dashboard.po.ts nennen. Ich werde die verwendeten Funktionen mit den dazugehörigen Links zu der Protractor Hilfe versehen, damit du dir gleich die offizielle Beschreibung ansehen kannst inkl. der Rückgabewerte der Funktionalität und dir natürlich auch angewöhnst, bei neuen Funktionen die zugehörige Hilfe zu verwenden.

Page Objects Datei dashboard.po.ts

Um das Verhalten der View testen zu können, könnten wir z.B. in der dazugehörigen Page Object Datei folgende Funktionen anlegen:

  1. Zu der View navigieren
  2. Liste mit Top Helden zurückgeben
  3. Title auf dem Dashboard zurückgeben
  4. Die Namen der Navigationselemente als Array zurückgeben

Rückgabe vom Titel und die Navigation zur Seite ist an sich relativ einfach, wir könnten uns eigentlich komplett den beiden Funktionen navigateTo und getTitle aus dem neuen e2e Projekt bedienen.
Die navigateTo Methode ruft lediglich browser.get(‚/‘) auf, womit der Browser angewiesen wird, die baseAddress (aus protractor.conf.js) aufzurufen.
Die getTitle Methode sieht schon etwas komplizierter aus: dort wird mit element(by.css(‚app-root h1‘)) ein Element gesucht und mit getText() anschließend der innerText von diesem Element zurückgegeben.

Auch die beiden anderen Funktionen sehen ähnlich aus, nur verwenden wir dafür dann die Suche nach mehreren Elementen mit dem Befehl element.all. Dieser Befehl liefert uns eine Art Array zurück (zur Synchronisierungt kommen wir gleich), welches 0 bis n Elemente enthalten kann, die mit den Kriterien gefunden bzw nicht gefunden wurden. Bei der Liste mit den Navigationsnamen nutzen wir die gleiche element.all Funktion, nur bekommt diese am Ende noch die Map Funktion, die eben nur die Texte aus den Daten extrahiert und uns den dazugehörigen „Texte“ zurück gibt.


import { browser, by, element, ElementFinder } from 'protractor';
  
export class DashboardPage {
 async navigateTo() {
    await browser.get('/');
  }

 async getTopHeroes(){
    return await element.all(by.css('app-root app-dashboard > div h4'));
  }
  
  async getDashboardTitle() {
    return await element(by.css('app-root h1')).getText();
  }

  async getNavigationItemNames() {
    return await element.all(by.css('app-root nav a')).map((el: ElementFinder) =><strong>;</strong> el.getText()); 
  }
}

Page Objects Datei heroes.po.ts

Um die Funktionen der Heroes Sicht abdecken zu können, würden sich folgende Methoden anbieten:

  • Liste von den Heroes zurückgeben
  • Einen neuen Hero anlegen
  • Einen Hero löschen
import { browser, by, element, ElementFinder } from 'protractor'; 
  
export class HeroesPage { 
  async navigateTo() { 
    await browser.get('/heroes'); 
  }
 
  async getHeroList(){
    return await element.all(by.xpath('//app-heroes/ul'));
  }
  
  async addHero(name:string) { 
    await element(by.xpath('//app-heroes//input')).sendKeys(name);
    await element(by.xpath('//button[text()=" add "]')).click();
    return await element(by.xpath('//app-heroes/ul//*[contains(text(),"'+name+'")]'));
  }
 
  async deleteHero(name:string) { 
   await element(by.xpath('//app-heroes/ul//*[contains(text(),"'+name+'")]/../button[@title="delete hero"]'));
    return await element.all(by.xpath('//app-heroes/ul//*[contains(text(),"'+name+'")]'));
  }
}

Implementierung der automatisierten E2E Testfälle

Nachdem die Funktionalität implementiert ist, wollen wir diese aus unseren Jasmine Spec Testfällen aufzurufen. Aber erst mal wird es etwas theoretischer:

Asynchronität in der Testautomatisierung mit JavaScript / Protractor

Solltest du bisher mit Selenium in Java oder C# gearbeitet haben, sollte dir der Code sehr bekannt vorkommen. Der vertraute Eindruck täuscht an dieser Stelle aber. Wir arbeiten mit Javascript / Typescript und natürlich auch mit Protractor asynchron. Wie die Hilfe von element(by.css(‚app-root h1‘)) schon anmerkt, gibt die Suche einen sogenannten „ElementFinder“ und nicht ein WebElement wie in C# / Java zurück. Dieses kann zwar für die meisten Funktionen wie ein WebElement verwendet werden, enthält aber auch die Besonderheit, dass die Funktionen eben nicht synchron ausgeführt werden, sondern erst dann, wenn der ElementFinder das dazugehörige Element auch gefunden hat. Wenn du dir auch erneut z.B. getText() als Funktion anschaust, wirst du feststellen, dass diese keinen String Wert zurückliefert, sondern einen Promise, der „verspricht“ irgendwann einen String Wert zu liefern. In diesem Fall heißt es, dass wenn du z.B. den Wert von getText() in die Konsole ausgeben möchtest, dieser zwingend einen await bzw. .then zur Synchronisierung benötigt, z.B.:

await element(by.tagName('h1')).getText().then(function(wert){ console.log(wert); })

Das muss man im Hinterkopf behalten, funktioniert aber problemlos, wenn man etwas javascript / typescript Erfahrung mitbringt bzw. Interesse hat, sich in dieses Thema einzuarbeiten.

Spec File mit Aufrufen der Page Objects

Aber nun kommen wir zu der Spec Datei, diese würde nun am Ende so aussehen:

import { DashboardPage } from './dashboard.po';
import { HeroesPage } from './heroes.po';
 
 
describe('Heroes Dashboard', () => {
    let dashboardPage;
 
    beforeAll(() => {
        dashboardPage = new DashboardPage();
    });
    beforeEach(async () => {
        await dashboardPage.navigateTo();
    });
 
 
    it('should have "Tour of Heroes" as title', async () => {
        expect(await dashboardPage.getDashboardTitle()).toEqual("Tour of Heroes");
    });
 
    it('should contain 4 Top Heroes', async () => {
       await dashboardPage.getTopHeroes().then(function (arr) {
            expect (arr.length).toEqual(4);
        })
    });
 
    it('should have Dashboard and Heroes in Navigation List', async () => {
       await dashboardPage.getNavigationItemNames().then((arr) => {
            expect (arr.length).toEqual(2);
            expect (arr[0]).toEqual("Dashboard");
            expect (arr[1]).toEqual("Heroes");
        })
 
    });
 
});
 
describe('Heroes ', () => {
    let heroesPage;
 
    beforeAll(() => {
        heroesPage = new HeroesPage();
    });
 
    beforeEach(async() => {
        await heroesPage.navigateTo();
    });
 
 
    it('view contains a list of ten heroes', async () => {
       await heroesPage.getHeroList().then((arr) => {
            expect(arr.length).toEqual(10);
 
        })
    });
 
    it(' creation is possible', async () => {
        //neuen Helden anlegen
        let neuerHeld = heroesPage.addHero("SuperTester");
        //prüfen ob der Held neben einer neuen ID auch den richtigen Namen enthält
        expect(await neuerHeld.getText()).toContain("SuperTester");
 
        //Bonus: Asyonchronität, hier geben wir die Liste aller Helden aus:
        heroesPage.getHeroList().then((helden) =&amp;gt; {
            for (let held of helden) {
                held.getText().then((heldenname) =&amp;gt; {
                    console.log(heldenname);
                })
            }
        });
 
        //und hier die Demonstration der Asynchronität von Protractor:
        console.log("Dieser Text wird in der Console früher ausgegeben, als die Liste der Helden ;-)");
 
    });
 
    it(' deletion is possible', async () => {
        await heroesPage.deleteHero("SuperTester").then((arr) => {
            expect(arr.length).toEqual(0);
        })
 
    });
});

Damit sind unsere ersten automatisierten E2E Testfälle der Angular Applikation fertig.

Starte die Testdruchführung mit

 ng e2e 

und überprüfe die Testergebnisse.

Der Test-Code ist natürlich noch nicht perfekt und könnte noch weiter verfeinert werden. Er demonstriert aber ganz gut, wie die Testautomatisierung mit Protractor in einem Angular Umfeld grundsätzlich realisiert werden kann. Ich habe auch eine kleine Demo der Asynchronität in die Specs eingebaut, um zu verdeutlichen wie der „Executor“ vorgeht.

Zusammenfassung

In diesem Tutorial haben wir gelernt, wie man sehr schnell eine funktionierende state-of-the-art  E2E Testautomatisierungslösung einer Angular Applikation mit Hilfe der automatisch generierten Vorlage in einem Angular Projekt aufsetzen kann. In dieser Vorlage haben wir Jasmin Framework verwendet, um eine Testspezifikation mit Hilfe der behavior-driven Notation zu definieren. Anschliessend haben wir das selenium-basierte Testautomatisierungsframework Protractor verwendet, um die konkreten Aktion dieser Testspezifikation zu automatisieren. Am Schluss haben wir die integrierte Testlaufzeitumgebung von Angular verwendet, um bequem die Testdurchführung unserer Tstsuite mit einem einzigen Befehl zu starten.

Ich werde demnächst weitere Beiträge zu dem Thema Testautomatisierung im Angular Umfeld hinzufügen, u.A.

  • Durchführung von E2E Protractor Tests in der CI Pipeline
  • Durchführung der E2E Testfälle in der Cloud (z.B. mit BrowserStack)
  • Mocken von Rest API um isoliert Oberflächen testen zu können
  • und evtl. einiges mehr.

Schaue daher regelmäßig vorbei 😉


Hat dir dieses Tutorial gefallen? Fehlt noch etwas oder du kommst an einigen Stellen nicht weiter? Bitte teile uns dein Feedback in den Kommentaren mit.

 

Im Testing Center von Microsoft Test Manager findet sich der Tab ‚Organize‘ unter dem sich vier Sub-Tabs finden.

Obwohl man sich laut den Breadcrumps in einem bestimmten Testplan befindet, werden in den vier Sub-Tabs per default alle Entitäten (Test Pläne, Test Konfigurationen, Testfälle, Shared Steps) innerhalb des TFS Team Projekts angezeigt, was einigermaßen verwirrend ist.
Weiterlesen