Automatische Tests des User Interface einer Web Applikation: Tipps und Tricks mit Se Builder (Teil 4)

von Roland Rickborn

Diese 6-teilige Blogpost-Reihe befasst sich mit dem Thema Test-Automatisierung für grafische Benutzeroberflächen von Webanwendungen. Im Wesentlichen kommt die Selenium Test Tool Suite zum Einsatz. Im Speziellen wird der Umgang mit dem Tool Se Builder erklärt, mit dem Tests aufgenommen und abgespielt werden können. 

Der erste Teil [1] gab eine kurze Einführung zum Tool Selenium Builder, die Installation sowie einen Vergleich des Tools mit der Selenium IDE. Im zweiten Teil [2] haben wir das Selenium JSON-Format untersucht und eine Test-Suite erstellt. In diesem Post erfolgt die Fortsetzung von Tipps und Tricks aus Teil 3 [3] der Serie.

In den weiteren Teilen der Blogpost Reihe geht es dann um das Client-Server-Szenario mit Selenium Grid und um automatisierte Tests zusammen mit dem CI Server Jenkins.

Dialog-Box Beispiele

Das Thema "Dialog-Boxen" wird in der Selenium Community immer wieder diskutiert. Hauptsächlich weil die diversen Anwender von unterschiedlichen Elementen sprechen. Im Wesentlichen gibt es zwei Arten von Dialog-Boxen:

  1. Der Dialog wird vom Betriebssystem geworfen
    Eine Dialog-Box des Betriebssystems ist außerhalb des Anwendungsbereichs von Selenium. Damit kann diese mit Selenium nicht abgefangen werden. Davon betroffen ist insbesondere der „Datei öffnen“-Dialog beim Upload einer Datei auf einen Webserver.
  2. Der Dialog wird von JavaScript geworfen
    Die Dialog-Box von JavaScript befindet sich im Kontext des Browsers und kann daher von Selenium abgefangen werden. Dies gilt insbesondere für Bestätigungen und Warnungen.

Im Se Builder gibt es folgende Methoden, um einen Alert abzufangen:

  • answerAlert
  • acceptAlert
  • dismissAlert

Außerdem gibt es folgende Methoden, um einen Alert zu behandeln:

  • AlertText 
  • AlertPresent

Diese sind jeweils für die Methoden-Gruppen Assertion, Verify, Wait und Store verfügbar. Das aufgeführte Beispiel zeigt ein Testskript mit einem Confirm-Dialog als Demo [4]:

{
  "type": "script",
  "seleniumVersion": "2",
  "formatVersion": 2,
  "steps": [
    {
      "type": "get",
      "url": "http://sislands.com/coin70/week1/dialogbox.htm#confirm"
    },
    {
      "type": "clickElement",
      "locator": {
        "type": "xpath",
        "value": "//div/center/table/tbody/tr/td/form[3]/p/input"
      }
    },
    {
      "type": "dismissAlert"
    },
    {
      "type": "acceptAlert"
    },
    {
      "type": "get",
      "url": "http://blog.exensio.de/"
    }
  ],
  "data": {
    "configs": {},
    "source": "none"
  },
  "inputs": [],
  "timeoutSeconds": 60
}

Im Beispiel wird der Dialog abgelehnt. Die Aktion wird mit einem zweiten Dialog quittiert.
Das Abfangen eines Alerts mit Selenium (z. B. mit "acceptAlert") funktioniert zuverlässig. Allerdings verliert Se Builder nach dem Bestätigen des Alerts teilweise den Fokus zum aktiven Browser-Fenster. Mein Test-Skript kann nach einem solchen Aufruf nicht mehr zum aktiven Browser-Fenster zurückkehren und bricht typischerweise ab.
Als Lösung teile ich meine Testfälle, die eine solche "Alert"-Methode enthalten, immer in mehrere kleine Testfälle auf, die ich dann als Test-Suite speichere. Dieses Vorgehen hat bisher zuverlässig funktioniert, denn beim Wechsel zu einem neuen Testfall innerhalb einer Suite, erhält der Browser den Fokus erneut.

ComboBox Beispiele

In Claretportal wird das Element ComboBox u. a. für die Auswahl der maximal angezeigten Datensätze bei der Paginierung verwendet. Möchte ein Anwender diese maximale Anzahl ändern, klickt man zunächst die ComboBox an, woraufhin sie sich öffnet. Danach klickt man den gewünschten Wert an, woraufhin sich die ComboBox wieder schließt. Aus Sicht von Selenium gibt es für die ComboBox zwar einen Locator bzw. einen XPath. Aber die einzelnen Werte der ComboBox besitzen keinen XPath. Um sie dennoch mit Selenium auswählen zu kennen, verwendet man einen passenden CSS-Selector:

option[value='<Selektierter Wert der Combobox>']<selektierter combobox="" der="" wert=""></selektierter>

Um die maximale Anzahl angezeigter Datensätze des Objekts "News" bei der Paginierung auf "10" zu setzen, sind also folgende beiden Schritte im Testskript erforderlich:

{
  "type": "clickElement",
  "locator": {
    "type": "id",
    "value": "news_max_selection"
  }
},
{
  "type": "clickElement",
  "locator": {
    "type": "css selector",
    "value": "option[value='10']"
  }
}

Dabei hat das Element ComboBox im Beispiel die ID "news_max_selection". Um den Testfall generisch zu gestalten, kann man zuvor natürlich über die Methode storeEval und die JavaScript -Funktionen .length sowie .value die Werte der ComboBox abfragen und abspeichern.

Internet Explorer 8

Eine nach wie vor gängige Anforderung in Unternehmen ist die Unterstützung der Version 8 des Internet Explorers von Microsoft. Wir verwenden dazu in unserer Testumgebung Virtuelle Maschinen (VM) von modern.IE [5] (dazu mehr im letzten Teil dieser Blogpost Reihe). In der Hauptsache traten die folgenden beiden Schwierigkeiten auf: 

  • JavaScript-Funktion .getElementsByClassName
  • Unterschiede bei RGB Angaben

Im Claretportal erhält nicht jedes Element eine eigene ID. So können manche Elemente nicht über die ID angesprochen werden, sondern z. B. über Ihre Position im Document Object Model (DOM). Dafür hatte ich anfangs die .getElementsByClassName Funktion verwendet. Die Testfälle konnten im Firefox fehlerfrei abgespielt werden. Unter Internet Explorer 8 (IE8) kam es allerdings zu JavaScript Fehlern. Wie sich zeigte ist diese Methode im IE8 nicht implementiert [6]. Als Abhilfe bietet sich die Verwendung der Methode .querySelectorAll an [7]. Der Aufruf im Skript sieht dann wie folgt aus:

{
  "type": "storeEval",
  "script": "var index = document.querySelectorAll('.pagination')[0].getElements \
       ByTagName(\"a\").length; var link = \
       document.querySelectorAll('.pagination')[0].getElements \
       ByTagName(\"a\")[index-2].innerHTML;return link;",
  "variable": "link"
}

Eine weitere Stolperfalle waren Farbangaben im RGB-Format. Im Claretportal gibt es die Möglichkeit, eigene Favoriten zu setzen. Ein neu angelegter Favorit, also ein Link, wird in schwarzer Farbe dargestellt. Sobald man den Favoriten das erste Mal geöffnet hat, wird der Link in grauer Farbe dargestellt. Ich habe mich für die Verwendung der Methode storeElementStyle entschieden. Per CSS Selector identifiziere ich den Favoriten und als propertyName gebe ich "color" an. Danach verwende ich die Methode assertEval, um den gespeicherten Rückgabewert mit meiner Vorgabe "rgba(8,117,198,1)" zu vergleichen. Als Stolperfalle erwies sich das unterschiedliche Rückgabeformat des RGB-Werts. Firefox (und andere Browser) geben den Wert "rgba( 8, 117, 198, 1)" zurück. Vom Internet Explorer 8 erhält man dagegen den Wert "rgba(8,117,198,1)". Als Abhilfe rufe ich in der Methode assertEval ein kleines Skript auf, welches bei meinem Rückgabewert zuerst alle Leerzeichen entfernt und danach die beiden Werte vergleicht. Die beiden Schritte des Skripts sehen dann wie folgt aus:

{
  "type": "storeElementStyle",
  "locator": {
  "type": "css selector",
  "value": "b"
  },
  "propertyName": "color",
  "value": "retval"
},
{
  "type": "assertEval",
  "script": "retval = '${retval}'; retval = retval.replace(/\\s/g, \"\"); \
if ( retval == 'rgba(8,117,198,1)' ) { return 1; };",
  "value": "1"
}

Damit lassen sich Farbangaben, auch bei unterschiedlichen Browsern, zuverlässig miteinander vergleichen.

Pausen einplanen

Beim Aufzeichnen von Testfällen registriert Se Builder keine Pausen. Ein nachträglich manuelles Setzen ist notwendig. Bei meiner Arbeit hatte ich immer wieder das Phänomen, dass ich aufgezeichnete Testfälle zwar erfolgreich lokal abspielen konnte. Sobald ich aber mehrere einzelne Testfälle gemeinsam als Test-Suite abgespielt habe, gerieten einzelne Testfälle ins Stocken. Erschwerend kam dazu, dass die einzelnen Testfälle teilweise aufeinander aufbauen. Se Builder stoppt nur in manchen Fällen die ganze Suite, wenn es bei einzelnen Testfällen zu einem Fehler kommt. Andernfalls wird im Fehlerfall der aktuelle Testfall abgebrochen und der nächste Testfall der Suite begonnen. Noch gravierender war das Verhalten im Grid, also bei Ausführung auf einem entfernten PC. Zuerst habe ich versucht, diejenigen Stellen in meiner Test-Suite ausfindig zu machen, die für die Unterbrechung verantwortlich sind. Allerdings waren die Stellen nicht bei jedem Durchlauf identisch, sondern scheinbar zufällig verteilt. Dies führte mich dazu, dass ich begonnen habe, jeden einzelnen Schritt jedes Testlaufs mit einer Pause zu verzögern. In Selenium IDE gabe es dafür den „Geschwindigkeitsregler“, auf den aber in Se Builder verzichtet wurde. Mit diesem Regler konnte man die Abspielgeschwindigkeit von Selenium IDE beeinflussen. Bei häufigen Abbrüchen der Testläufe war es ratsam, die Geschwindigkeit zu verringern. Da dies bei Se Builder nicht mehr möglich ist, kann man entsprechend viele Pausen setzen um so die Geschwindigkeit zu verringern. Mein Skript sieht dann beispielsweise so aus:

{
   "type": "pause",
   "waitTime": "500"
},
{
  "type": "waitForTextPresent",
  "text": "Main > Info Center"
},
{
  "type": "pause",
  "waitTime": "500"
},
{
  "type": "assertTextPresent",
  "text": "Main > Info Center"
},
{
  "type": "pause",
  "waitTime": "500"
},
{
  "type": "setElementText",
  "locator": {
"type": "id",
"value": "q"
  },
  "text": "Suchstring"
},
{
  "type": "pause",
  "waitTime": "500"
},
{
  "type": "clickElement",
  "locator": {
"type": "css selector",
"value": "#searchForm > input.search-submit"
  }
}

Auf diese Weise laufen die Skripte recht zuverlässig und vollständig durch. Im Allgemeinen verwende ich einen Pausenwert von 500 Millisekunden. An der ein oder anderen Stelle kann es erforderlich sein, den Wert auf 1.500 zu erhöhen.

Screenshots

Zur Kontrolle des Inhalts des Webbrowsers beim Durchlaufen eines Testfalls und insbesondere auch zur Fehlersuche, kann es hilfreich sein, Screenshots zu untersuchen. Mit der Methode saveScreenshot kann man Se Builder anweisen, einen Screenshot der aktuellen Webseite zu erstellen. Als Argument erwartet die Methode den Pfad zur Bilddatei. Bei dem Pfad kann es sich um einen UNC-Pfad handeln, so dass man z. B. ein zentrales Netzlaufwerk im Firmennetzwerk angeben kann. Bei der Benennung der Dateinamen habe ich mich für eine Kombination aus Datum und Name des Testlaufs entschieden. Auf diese Weise kann ich meine Screenshots gut sortieren und trotzdem einfach feststellen, wann und wo das Bildschirmfoto aufgenommen wurde. Mein Funktionsaufruf zur Erstellung von Screenshots sieht wie folgt aus:

{
  "type": "storeEval",
  "script": "var mm = new Array('01', '02', '03', '04', '05', '06','07', '08', '09', \
      '10', '11','12');var now = new Date();var yy = now.getFullYear();var m = now. \     
       getMonth();var dd = now.getDate();var HH = now.getHours();var MM = now.get \  
       Minutes();var SS = now.getSeconds();var MS = now.getMilliseconds();var  date= \ 
       (yy+mm[m]+dd+HH+MM+SS);return date;",
  "variable": "retval"
},
{
  "type": "saveScreenshot",
  "file": "\\\\share\\Public\\\\Screenshots\\${retval}_Testfall1.png"
}

Im nächsten Teil [8] dieser Reihe werden wir einen Blick auf Selenium Grid werfen. Damit lassen sich Tests in einem Grid auf unterschiedlichen Knoten mit unterschiedlichen Browsern abspielen.

Kategorien: SeleniumTesting

Zurück