Herzlich willkommen auf dem Blog der exensio GmbH

exensio ist ein unabhängiges Software-Unternehmen von erfahrenen IT Beratern und Architekten. Zu unseren Kunden zählen namhafte deutsche Großunternehmen.

exensio verbessert das Kommunikations- und Informationsmanagement von Unternehmen. Auf Basis sinnvoller und zweckmäßiger Technologien erarbeiten wir flexible und übergreifende Lösungen für webbasierte Informationssysteme. Abhängig von den Kundenanforderungen realisieren wir Web Applikationen sowie Lösungen auf Basis von Web Content Management- und Portalsystemen. Integrativ, wirtschaftlich und anwenderfreundlich!

Hier gelangen Sie zur exensio GmbH.

Mittwoch, 17. September 2014

LiMux - Software-Streit im Münchner Rathaus - eine Frage der Einstellung

Quelle: http://www.muenchen.de/
So titelte die Süddeutsche in einen Kommentar [1] über die vom frisch gewählten Münchner Oberbürgermeister Dieter Reiter losgetretene Diskussion, ob nicht doch von Linux auf Microsoft zurückgeschwenkt werden soll.

Vor ca. 10 Jahren wurde vom Münchner Stadtrat beschlossen, von Windows auf Linux zu migrieren. Der Gründe hierfür waren zum einen Kosteneinsparungen - aktuell geht man von 10 Mio Euro Einsparung aus - und eine Vorreiterrolle für offene Software zu zeigen.

Laut der offiziellen Homepage der Stadt München [2] scheint die Migration zu Linux eine Erfolgsgeschichte zu sein. Man darf sich derzeit fragen, warum überhaupt diese Diskussionen vom neuen Bürgermeister losgetreten wurden. Ein Schelm, wer hier an die Lobbyisten von Microsoft denkt.

Ehrlich gesagt fände ich es ausgesprochen positiv, wenn München bei Open-Source bliebe. In anderen Ländern innerhalb Europas scheint man ja auch den Weg gehen zu wollen. In Turin [3] steigt man von Windows auf Linux und die französische Gendarmerie [4] (es wird mit 40% Einsparung gerechnet) stellt gleichfalls auf Linux um. Womöglich handelt es sich hier also eher um einen Trend und aus diesem Grund sollte München eventuell froh sein, bereits so weit zu sein.

Open-Source ist ein bedeutsamer Gegenpol und die letzte Chance für die europäische Softwareindustrie - im Vergleich zur amerikanischen. Auch bei anderen Themen, wie Industrie 4.0 wird ebendies ein relevanter Punkt sein. Nur mit Open-Source ist es bei diesem Thema bspw. dem deutschen Maschinenbau möglich, von namhaften Softwareanbietern unabhängig zu bleiben. Dies ist um so essenzieller, da zukünftig davon auszugehen ist, dass der Maschinenbau mit digitalen Services (bspw. Predictive Analytics) rund um seine Maschinen ein überaus lukratives Zusatzgeschäft aufbauen kann.

[1] http://www.sueddeutsche.de/muenchen/software-streit-im-muenchner-rathaus-eine-frage-der-einstellung-1.2093653
[2] http://www.muenchen.de/rathaus/Stadtverwaltung/Direktorium/LiMux.html
[3] http://www.heise.de/newsticker/meldung/Turin-steigt-von-Windows-XP-auf-Linux-um-2391613.html
[4] http://www.wired.com/2013/09/gendarmerie_linux/

Montag, 1. September 2014

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

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>']
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 dieser Reihe werden wir einen Blick auf Selenium Grid werfen. Damit lassen sich Tests in einem Grid auf unterschiedlichen Knoten mit unterschiedlichen Browsern abspielen.

Links

[1] http://blog.exensio.de/2014/07/automatische-browsertests-mit-selenium.html
[2] http://blog.exensio.de/2014/08/automatische-browsertests-mit-selenium.html
[3] http://blog.exensio.de/2014/08/automatische-browsertests-mit-selenium_21.html
[4] http://sislands.com/coin70/week1/dialogbox.htm
[5] http://loc.modern.ie
[6] http://msdn.microsoft.com/en-us/library/ie/ff975198%28v=vs.85%29.aspx
[7] http://msdn.microsoft.com/en-us/library/ie/cc304115%28v=vs.85%29.aspx


Donnerstag, 21. August 2014

Automatische Browsertests mit Selenium: Tipps und Tricks mit Se Builder (Teil 3)

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 Teil gebe ich einige nützliche Tipps zum Erstellen von umfangreichen Test-Suiten. Es werden auch Tricks zum Umgang mit typischen Elementen eines Intranetportals verraten.
In den weiteren Teilen der Blogpost-Reihe geht es schließlich um das Client-Server-Szenario mit Selenium Grid sowie automatisierte Tests im Zusammenspiel mit dem CI Server Jenkins.

Herausforderungen mit Selenium Builder

Zum Kernprodukt von exensio gehört das Intranetportal Claretportal. Der große Funktionsumfang muss ausgiebig und zuverlässig getestet werden, um eine entsprechend hohe Softwarequalität des Produkts zu gewährleisten. Se Builder ist bei funktionalen Tests eine gute Unterstützung. Jedoch kann man bei der Erstellung von Testfällen einigen Tücken begegnen. Im vorliegenden Fall sind dies z.B.:
  • Die Verwendung des WYSIWYG Editor CKEditor, der in einem Popup geladen wird
  • Tooltips der Klasse jQuery Tooltipsy
  • JavaScript Menüs, die beim Überfahren mit dem Mauszeiger angezeigt werden
  • Testfallübergreifende Zustände
  • Dialog-Box Beispiele
  • ComboBox Beispiele
  • Sicherstellen der Verwendbarkeit bestimmter Browser und bestimmter Versionen, hier Internet Explorer 8
  • Einplanung von Wartezeiten

Der WYSIWYG Editor CKEditor

Der Editor wird im Claretportal üblicherweise per JavaScript in die Webseite eingebunden und steht als Objekt CKEDITOR zur Verfügung.
Probleme gibt es sowohl beim Test von Texteingaben, als auch beim Auslesen von Textinhalten des Editors. Bei Se Builder und WebDriver Tests kann Text nicht direkt in das Textfeld des Editors eingegeben werden. Beim Anlegen eines Textes ist das Textfeld hinter einem iFrame versteckt, so dass man nicht direkt darauf zugreifen kann. Beim Editieren eines bestehenden Textes ist das Textfeld zwar sichtbar und man kann direkt darauf zugreifen, allerdings dient dieser Text nur zur Anzeige. Änderungen an diesem Textfeld gehen beim Speichern verloren. Die Lösung besteht darin, per JavaScript auf die Instanz des Editors zuzugreifen. Im Selenium Builder geht das mit der Methode storeEval. Im Folgenden wird im 'Step 1' des JSON Skriptes von Selenium Builder die Instanz 'text' des CKEditors angelegt und der Text 'Automatisch eingefügter Text.' eingefügt.
{
  "type": "storeEval",
  "script": "CKEDITOR.instances['text'].setData( '<p>Mein Text.</p>');",
  "variable": ""
}
Die Methode "storeEval" akzeptiert als Argument "script" einen JavaScript-Ausdruck. Mit dem Aufruf "return" kann bei Bedarf ein Rückgabewert des JavaScript Programms zwischengespeichert werden. Im Argument "variable" würde dann der Name der Variablen für die spätere Verwendung festgelegt. Da wir keine Variable speichern wollen, können wir den Wert des Arguments leer lassen.
Beim Auslesen des Textinhalts des Editors hat Selenium Builder scheinbar ein Problem mit Zeilenumbrüchen ('\n' und '<p></p>') in Strings. Mittels Attribut "value" bzw. "GetData()" bekommt man den Wert des CKEditors zurück. Dieser Wert enthält allerdings Zeilenumbrüche. Will man den Wert direkt in Selenium Builder weiterverwenden, führt dies zu einem Fehler beim Ausführen des Skripts. Eine Lösung besteht im Ersetzen der Zeilenumbrüche vor dem Speichern der Variablen.
{
  "type": "storeEval",
  "script": "var retval = document.getElementById('text').value;retval = retval.replace(\"\",\"\");retval = retval.replace(\"\",\"\");retval = retval.trim();return retval;",
  "variable": "retval"
},
{
  "type": "storeEval",
  "script": "return CKEDITOR.instances['text'].setData('<p>' + String('${retval}' + '. Dieser Satz wurde nachträglich hinzugefügt.</p>') );",
  "variable": ""
}
Ich verwende in diesem Beispiel wieder die Methode"storeEval". Dieses Mal jedoch mit dem Aufruf "return" um den Rückgabewert JavaScript-Codes zwischenzuspeichern. Mit dem Wert "retval" des Arguments "variable" wird der Variablennamen für die spätere Verwendung des Rückgabewerts bezeichnet. Ich rufe die Methode "storeEval" zwei Mal nacheinander auf. Im ersten Schritt lese ich den Wert des Textinhalts des Editors aus und ersetze bestimmte Zeichen. Im zweiten Schritt erfolgt das Schreibe des Rückgabewertes aus Schritt 1 wieder zum Editor sowie das Einfügegen des eigenen Textes. Hier sieht man, wie im Argument "script" der Methode "storeEval" auf eine Variable des Se Builders zugegriffen wird. Im Beispiel verwende ich die Variable "retval" durch den Aufruf "${retval}".

jQuery Tooltipsy

Das jQuery Tooltip Plugin tooltipsy ist bei Webentwicklern beliebt, weil es eine schnelle und einfache Möglichkeit zur Darstellung von Tooltips bietet. Bei Se Builder gibt es hierfür die Methode "mouseOverElement" im Abschnitt "Input", die als Argument eine ID benötigt. Im Fall von Claretportal ist das jQuery plugin tooltipsy wie folgt eingebunden:

Bei der Aufnahme des Test-Skripts mit Se Builder wird deshalb ein CSS Selector verwendet, der u.a. auch die zufällige ID enthält. Im weiteren Verlauf der Aufnahme (mit der selben Session-ID, also ohne den Browser zu beenden) scheint vorerst alles zu funktionieren. Der verwendete CSS Selector ist allerdings nicht Session-übergreifend valide, so dass die Tests beim nächsten Öffnen des Browsers nicht mehr funktionieren. Die genauere Untersuchung des Elements, welches den Tooltip enthält, z.B. mit der Firefox Erweiterung FirePath, führt zu einem besser geeigneteren XPath. Der Test sieht dann wie folgt aus:
{
  "type": "mouseOverElement",
  "locator": 
    {
    "type": "xpath",
    "value": "//div[1]/div[3]/div/div/div/table/tbody/tr[2]/td[1]/div[1]/div/table/tbody/tr[1]/td[2]/span/img"
    }
},
{
  "type": "waitForTextPresent",
  "text": "Achtung"
}
Dabei ist es unbedingt empfehlenswert, den zu kontrollierenden Text des Tooltips mit der Methode "waitForTextPresent" zu prüfen. Denn die Methode "mouseOverElement" bewirkt nicht immer sofort das Erscheinen des Tooltips. Mitunter vergehen einige Sekunden, bis der Tooltip in der Art angezeigt wird, dass die Prüfung per "verifyTextPresent" funktioniert.

JavaScript Menüs

In Claretportal gibt es eine Hauptnavigation mit Menüeinträgen, die bei MouseOver Ereignissen angezeigt werden. Zunächst wurde versucht, mittels der Methode "mouseOverElement" ein Menü anzuzeigen. Dieser Versuch war allerdings nicht von Erfolg gekrönt. Ich habe unterschiedliche Lokatoren verwendet (CSS Selector, 2 x XPath, link text). Das Skript wurde immer erfolgreich ausgeführt. Allerdings wurde zu keinem Zeitpunkt das Untermenü der Hauptnavigation angezeigt. Danach habe ich versucht, den entsprechenden Menü-Eintrag des Untermenüs direkt mit der methode "clickElement" und dem bekannten CSS-Selector bzw. dem XPath anzusprechen. Allerdings verhielt sich WebDriver hier völlig richtig und quittierte diese Versuche mit der Meldung "Unable to locate element". Das Problem waren die nicht sichtbaren Menüs. Der zugehörige html Style ist "style.display = none". Als Lösung habe ich also eine "storeEval" Methode aufgerufen und darin folgenden JavaScript Code ausgeführt:
var drops = document.getElementsByClassName('drop').length;
In einer zweiten “storeEval” Methode habe ich dann diesen Code ausgeführt und damit das entsprechende Menü sichtbar geschaltet:
document.getElementsByClassName('drop')[${index}-1].style.display = 'block';
Die beiden Schritte sehen im JSON-Skript wie folgt aus:
{
  "type": "storeEval",
  "script": "return document.querySelectorAll('.drop').length;",
  "variable": "drops"
},
{
  "type": "storeEval",
  "script": "document.querySelectorAll('.drop')[${drops}-1].style.display = 'block';",
  "variable": ""
}
Anstatt "-1" statisch in den Quellcode des Test-Skripts zu schreiben, könnte dort auch eine Variable wie z. B. "${index}" verwendet werden. Dadurch wäre der Testfall flexibler.

Testfallübergreifende Zustände

Im zweiten Teil der Blogpost Reihe haben wir gesehen, dass es ratsam sein kann, einen umfangreichen Testfall in kleinere Testfälle aufzugliedern und diese in einer Test-Suite zu gruppieren. Im Folgenden verwende ich die einfache Test-Suite:
{
  "type": "suite",
  "seleniumVersion": "2",
  "formatVersion": 1,
  "scripts": [
    {
      "where": "local",
      "path": "test_blogpost_teil_3_schritt1.json"
    },
    {
      "where": "local",
      "path": "test_blogpost_teil_3_schritt2.json"
    }
  ],
  "shareState": true
}
Die Suite startet den Testfall "test_blogpost_teil_3_schritt1.json":
{
  "type": "script",
  "seleniumVersion": "2",
  "formatVersion": 2,
  "steps": [
    {
      "type": "get",
      "url": "http://blog.exensio.de/"
    },
    {
      "type": "storeText",
      "locator": {
        "type": "css selector",
        "value": "h2.title"
      },
      "variable": "teaser"
    }
  ],
  "data": {
    "configs": {
      "none": {}
    },
    "source": "none"
  },
  "inputs": [],
  "timeoutSeconds": 60
}
Außerdem wird der Testfall "test_blogpost_teil_3_schritt2.json" gestartet:
{
  "type": "script",
  "seleniumVersion": "2",
  "formatVersion": 2,
  "steps": [
    {
      "type": "setElementText",
      "locator": {
        "type": "name",
        "value": "search"
      },
      "text": "${teaser}"
    }
  ],
  "data": {
    "configs": {
      "none": {},
      "manual": {
        "retval": "Das ist ein Test"
      }
    },
    "source": "manual"
  },
  "inputs": [],
  "timeoutSeconds": 60
}
Mit den Standard-Einstellungen von Se Builder kann die Suite so nicht erfolgreich abgespielt werden. Beim Abspielen des zweiten Testfalls erhält man die Fehlermeldung "Variable not set: teaser". Das mag im ersten Augenblick verwundern, weil die Variable "teaser" im ersten Testfall explizit gesetzt wurde. Das Problem liegt an den Standard-Einstellungen von Se Builder, die beim Abspielen einer Suite die Zustände der einzelnen Testfälle streng voneinander unterschiedet. Das bedeutet, dass Variablen, Sessions und Cookies immer nur im aktuellen Testfall verwendet werden können. In manchen Szenarien mag diese Einstellung richtig sein. In unserem aktuellen Fall wollen wir den Zustand eines Testfalls auf einen anderen übertragen. Dazu setzt man im Se Builder Run Menü die Option "Share state across suite". Danach kann die Suite vollständig und erfolgreich abgespielt werden.

Der nächste Teil [3] dieser Reihe ist eine Fortsetzung der Tipps und Tricks. Darin werde ich den Umgang mit einer AlertBox beleuchten, eine Auswahl in einer ComboBox treffen und viele Pausen setzen. Es werden zwei Beispiele für die Gestaltung von Testfälle mit dem Internet Explorer 8 gezeigt.

Links

[1] Automatische Browsertests mit Selenium: Zielvorstellung und Einführung Selenium Builder (Teil 1)
[2] Automatische Browsertests mit Selenium: JSON Dateiformat und Test-Suite (Teil 2)
[3] Automatische Tests des User Interface einer Web Applikation: Tipps und Tricks mit Se Builder (Teil 4)

Montag, 18. August 2014

Szenarien für den Einsatz von IoT und BigData: Visualsierung von großen Datenmengen (Teil 6)

Im Teil 5 dieser Blog-Post Serie wurde auf die Visualisierung von Sensordaten mit Hilfe der JavaScript-Bibliothek D3.js eingegangen. In diesem Teil möchte ich nun auf den Umgang mit großen Datenmengen am Beispiel der erfassten Wetterdaten eingehen.


Als Ziel sollen alle bisher aufgezeichneten Messdaten in einem Liniendiagramm dargestellt werden. Das Diagramm soll auch bei großen Datenmengen vergleichbar rasch verfügbar sein. Zum Erstellen der Grafiken kommt wiederum die auf D3.js basierende Bibliothek Rickshaw zum Einsatz. Als Beispiel eines solchen Diagramms werden die bisher aufgezeichneten Messpunkte der Außentemperatur dargestellt.

Mit der folgenden Query ist eine Ermittlung und Aufbereitung aller Messpunkte der Außentemperatur für die Grafik möglich. Es wird hierzu wie im letzten Teil dieser Serie schon gezeigt, die "Date Histogramm" Aggregation verwendet.

 client.search({
        index: 'sensorraspberry',
        size: 0,
        body: {
            // Begin query.
             "query":{
                 "bool" : {
                     "must" : [
                         {
                             "term": {
                                 "type": "outdoor"
                             }
                         },
                         {
                             "range" : {
                                 "created" : {
                                     "gte": "now-1y"
                                 }
                             }
                         }
                     ]
                 }
              },
              "aggs" : {
                  "temp_over_time" : {
                      "date_histogram" : {
                          "field" : "created",
                          "interval" : "1m"
                      },
                      "aggs" : {
                        "temp" : { "avg" : { "field" : "temperature" } }
                      }          
                  }
              }
            // End query.
        }


Das Laden dieser Grafik benötigt aufgrund der Größe der Daten relativ lange. Des Weiteren sieht die Darstellung "kantig" aus, da jeder einzelne Messwert aufgeführt wird. Bei Ansichten über längere Zeiträume sind gemittelte Werte für die Visualisierung ausreichend. Hierdurch ist ein rascheres Laden der Daten und eine ansprechendere Darstellung möglich. Es werden nun über 12h-Intervalle gemittelte Werte angezeigt. Dies ermöglicht die Änderung des Intervalls für die Aggregation.

"aggs" : {
                  "temp_over_time" : {
                      "date_histogram" : {
                          "field" : "created",
                          "interval" : "12h"
                      },
                      "aggs" : {
                        "temp" : { "avg" : { "field" : "temperature" } }
                      }          
                  }
              }


Interesse besteht natürlich nicht nur an gemittelten Werten, sondern auch an den einzelnen Messwerten. Aus diesem Grund ist beim Hineinzoomen das Anzeigen von exakteren Werten notwendig. Das Hineinzoomen in die Grafik wird über die bereits durch Rickshaw mitgelieferte Scrollbar realisiert.


Der angezeigte Zeitbereich kann über Graph.window.xMin bzw. Graph.window.xMax abgefragt werden. Ist die Zeitspanne kleiner als ein definiertes Intervall, z.B. ein Monat, wird die Query angepasst. In diesem Zeitbereich kann beispielsweise über eine Stunde gemittelt werden. Die zurück gelieferten Messwerte werden in ein Datenarray hineingeschrieben und nach Zeitstempel sortiert. Danach erfolgt die Aktualisierung der Grafik mit graph.update().

Als Ergebnis erhält man folgendes Liniendiagramm. Es lassen sich beliebig viele Auflösungsstufen einbauen und somit können auch einzelne Messwerte bei detaillierten Hineinzoomen angezeigt werden.


Wird ein Ausschnitt gewählt, der wieder größer als ein Monat ist, wird das Array auf die ursprünglichen Daten zurückgesetzt. Hierdurch erreicht man, dass das Datenarray stets eine begrenzte Anzahl an Messpunkten aufweist. Ein schnelles Laden der Grafik ist damit möglich.

Durch das schnelle Laden können Dashboards gebaut werden, die mehrere Grafiken zur Langzeitanalyse enthalten und trotzdem rasch aufgebaut werden.



Fazit

Für die Visualisierung großer Datenmenge ist es sinnvoll Werte in definierten Zeitintervallen zu mitteln, da andernfalls die Gefahr von langen Ladezeiten besteht. Um auch Daten im Detail betrachten zu können, sind Zoomfunktionen mit dynamischer Nachlade-Funktionen empfehlenswert. Exakte Messwerte können hierdurch ohne lange Ladezeiten dargestellt werden.

Donnerstag, 14. August 2014

Szenarien für den Einsatz von IoT und BigData: Visualisierung von Sensordaten mit D3.js (Teil 5)

Nachdem in den vorgehenden Teilen dieser Blog-Post Serie die Sensordaten erfasst und abgelegt wurden, möchte ich in diesem Blog-Post auf die Visualisierung der abgelegten Daten eingehen. Die Daten werden mit Hilfe der Aggregationen in Elasticsearch aufbereitet und anschließend über JavaScript entsprechend visualisiert.


Für die Visualisierung kommt die JavaScript Bibliothek D3.js zum Einsatz. Dabei handelt es sich um eine freie Javascript Bibliothek, die von Mike Bostock speziell zur Manipulation von datenbasierten Dokumenten entwickelt wurde. Des Weiteren wird die auf D3.js aufbauende Bibliothek Rickshaw verwendet, die ein flotteres Erstellen von Graphen ermöglicht.
Mit Hilfe der Visualisierung wird ein Dashboard erstellt, in dem Wetterdaten, beispielsweise die Temperatur oder Luftfeuchtigkeit der jeweiligen Sensoren, grafisch aufbereitet werden. Hierbei soll sowohl eine momentane Anzeige als auch eine Realtimegrafik die Daten anschaulich repräsentieren.
Als Beispiel für ein solches Dashboard wird hier die Visualisierung der Indoor Sensordaten gezeigt.


Für die Momentenanzeige werden Gauges, also Messgeräte verwendet. Diese zeigen den aktuellen Wert an und werden automatisch aktualisiert, wenn ein neuer Messwert eines Sensors vorhanden ist.
Die Realtimegrafik stellt die Messwerte der letzen 24 Stunden dar. Sobald ein aktuellerer Messwert verfügbar ist, wird dieser ebenfalls hinzugefügt. Die Realtime Grafik ist wie ein Sliding Window zu verstehen. Es zeigt stets nur die letzen 24h an. Daten, die älter als 24h sind, werden aus der Grafik entfernt, rutschen also aus dem Fenster heraus. Des Weiteren ist es möglich über eine Scrollbar unterhalb der Grafik in beliebige Bereiche hineinzuzoomen. Diese Funktion wird bereits durch Rickshaw selbst geliefert und benötigt keinen weiteren Programmieraufwand.

Queries

Zunächst müssen die erforderlichen Daten durch eine Query über Elasticsearch abgerufen werden. Um den prinzipiellen Aufbau einer solchen Query zu erläutern wird das Beispiel eines Gauge verwendet.
        
var client = getESClient();

 setInterval(function () {
  client.search({
   index: 'sensorraspberry',
   body: { 
     "query": {
    "bool": {
      "must": [
     {
       "term": {
      "type": "indoor"
       }
     },
     {
       "range": {
      "created": {
        "gte": "now-1d"
      }
       }
     }
      ]
    }
     },
     "aggs": {
    "devices": {
      "terms": {
     "field": "device",
     "order": {
       "_term": "asc"
     }
      },
      "aggs": {
     "time": {
       "terms": {
      "field": "created",
      "order": {
        "_term": "desc"
      },
      "size": 1
       },
       "aggs": {
      "temp": {
        "avg": {
       "field": "temperature"
        }
      }
       }
     }
      }
    }
     },
     "size": 0
   }
   }


Zunächst wird der Index festgelegt, aus dem die Daten abgefragt werden. Wird kein Index festgelegt, so wird über alle Indizes die Suche gestartet. Die Suche kann über mehrere Suchkriterien eingeschränkt werden. In diesem Beispiel müssen die Suchergebnisse vom Typ „indoor“ sein. Außerdem sollen nur Daten des letzten Tages gefunden werden. Folglich werden alle notwendigen Daten zur späteren Visualisierung gefunden.
Aufgrund der Anzahl der Messergebnisse ist es sinnvoll die Daten zu gruppieren. Hierzu bietet Elasticsearch die Aggregationen an. Diese ermöglichen es Suchergebnisse direkt in verschiedene „Töpfe“ (buckets) einzusortieren. Eine Aggregation kann beliebig verschachtelt sein und über mehrere Optionen frei konfiguriert werden. In diesem Beispiel wird für jeden Sensor (device) ein eigener Topf erstellt. In diesen Töpfen werden die Daten nach dem Erstellungsdatum sortiert und nur der neuste Wert angezeigt. Die maximale Anzahl der zurückzuliefernden Werte lassen sich über „size“ festlegen.

Erstellen des Gauge

Das Gauge für die Temperatur kann über JavaScript erstellt werden. Verschiedene Optionen, wie beispielsweise die Größe oder auch Farbbereiche können festgelegt werden.

 google.load('visualization', '1', {packages: ['gauge']});
    google.setOnLoadCallback(function () {
        var data1 = google.visualization.arrayToDataTable([
            ['Label', 'Value'],
            [String.fromCharCode(186) + 'C', 0]
        ]);
 
        // Create and draw the visualization.
        var options1 = {
            width: 200,
            max: 50,
            redFrom: 40,
            redTo: 50,
            yellowFrom: 30,
            yellowTo: 40,
            greenFrom: 20,
            greenTo: 30,
            minorTicks: 10,
            majorTicks: ['0', '10', '20', '30', '40', '50']
        };
        var gaugechart1 = new google.visualization.Gauge(document.getElementById('visual_temp_server'));
        gaugechart1.draw(data1, options1);

Dies reicht aus um einen Gauge zu erstellen. Das Ergebnis ist im folgenden Bild zu sehen.


Query für Realtimegrafiken

Um einen Verlauf von Messpunkten zu visualisieren benötigt man die Daten nach Datum bzw. Uhrzeit sortiert. Für diesen Anwendungsfall bietet Elasticsearch eine spezielle Aggregation, die sogenannte „Date Histogram Aggregation“. Sie unterteilt die Suchergebnisse in frei wählbare Zeitabschnitte (Intervalle).

"aggs": {
 "devices": {
   "terms": {
  "field": "device",
  "order": {
    "_term": "asc"
  }
   },
   "aggs": {
  "time": {
    "terms": {
   "field": "created",
   "order": {
     "_term": "desc"
   },
   "size": 1
    },
    "aggs": {
   "temp": {
     "avg": {
    "field": "temperature"
     }
   }
    }
  }
   }
 }
},
 
In unserem Beispiel wird von den Sensoren alle 2 Sekunden ein Wert geliefert. Dies führt zu einer großen Anzahl an Daten. Für die Visualisierung genügt es gemittelte Werte, z.B. über Minuten, anzuzeigen. Die Query des Gauge muss nur leicht verändert werden, um die notwendigen Daten zu erhalten. Diese werden  anschließend in einem Array ablegt.

Erstellen der Grafik mit Messpunkteverlauf

Um die nun gewonnenen Messpunkte zu visualisieren, kommt die schon erwähnte Bibliothek Rickshaw zum Einsatz. Es muss lediglich der Graph erstellt und mit den Daten befüllt werden. Optionen welche Art von Graph bzw. auch welche Farben welchen Daten entsprechen sollen, lassen sich beliebig zuweisen.

var graph = new Rickshaw.Graph( {
              element: document.getElementById("chart_temp_indoor"),
              height: 300,
              renderer: 'line',
              stroke: true,
              preserve: true,
              series: [
              {color: 'steelblue', data: dataserver, name: 'Serverroom'},
              {color: 'green', data: dataroom1, name:'Office'},
              {color: 'red', data: dataroom3, name: 'Meeting room'},
              {color: 'brown', data: outside, name: 'Outdoor'},
              ]
              } );


Verschiedene Zusätze wie Legenden oder die Scrollbar können optional hinzugefügt werden. Das Ausführen des JavaScript führt zu folgender Darstellung.

 
Um nun zu erreichen, dass aus dem statisch aufgebauten Graphen eine Realtime Anzeige der letzen 24h wird, müssen neue Daten hinzugefügt, bzw. ältere Daten aus der Grafik entfernt werden. Hierzu wird die JavaScript Funktion setInterval() verwendet. In dieser wird in definierbaren Zeitabständen eine Query abgesetzt, die lediglich die neusten Werte der Sensoren als Ergebnis zurückbekommt. Die Query ist somit identisch mit der Gauge-Query .
Liegt nun ein neuer Messwert vor wird dieser mit der Arrayfunktion push(neuerMesswert) zu dem jeweiligen Datenarray hinzugefügt. Um den ältesten Messwert zu entfernen wird die Funktion shift() verwendet.
Sind die neuen Daten im Datenarray eingepflegt, kann die Grafik mittels graph.update() aktualisiert werden. Dies reicht bereits aus damit die Grafik Realtimefähig wird.

Fazit

Über Elasticsearch kann man einfach Queries bauen, die nach mehreren Kriterien gefiltert werden können. Die Ergebnisse der Suche lassen sich durch die Aggregation für den jeweiligen Zweck in verschiedene „Töpfe“ (buckets) gruppieren.
Durch die Verwendung der Bibliotheken D3 und Rickshaw können Daten ohne großen Programmieraufwand visualisiert werden. Da D3 auf Effizienz ausgelegt ist werden die Seiten auch mit mehreren Grafiken schnell aufgebaut.  

Montag, 4. August 2014

Automatische Browsertests mit Selenium: JSON Dateiformat und Test-Suite (Teil 2)

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 wird ein erweitertes Beispiel erstellt und der Einsatz der Test-Suite erklärt.

Im dritten Teil [2] der Blogpost Reihe geht es um Tipps und Tricks in Verbindung mit GUI-Elementen (z. B. CKEditor, Farben, Tooltips, Menüs und Dialog-Boxen). In den späteren Teilen wird auf ein Client-Server-Szenario mit Selenium Grid im Zusammenspiel mit dem CI Server Jenkins eingegangen.

Das JSON Dateiformat

Wie wir im ersten Teil bereits gelernt haben, speichert Se Builder die Tests standardmäßig im Dateiformat JSON ab. Das vom Se Builder verwendete Format macht den Versuch, eine Brücke zu schlagen zwischen dem Format der strikten Aufzählung von Schritten, wie es in Selenium IDE verwendet wird und den Anforderungen der API von Se Builder [3]. Der Quellcode des abgeschlossenen Beispiels aus Teil 1 dieser Blogpost Reihe sieht wie folgt aus:
{
  "type": "script",
  "seleniumVersion": "2",
  "formatVersion": 2,
  "steps": [
    {
      "type": "get",
      "url": "http://blog.exensio.de/"
    },
    {
      "type": "clickElement",
      "locator": {
        "type": "css selector",
        "value": "body"
      }
    },
    {
      "type": "setElementText",
      "locator": {
        "type": "name",
        "value": "search"
      },
      "text": "Consulting"
    },
    {
      "type": "clickElement",
      "locator": {
        "type": "css selector",
        "value": "input.gsc-search-button"
      }
    },
    {
      "type": "waitForTextPresent",
      "text": "(Teil 3) - Fokus"
    },
    {
      "type": "clickElement",
      "locator": {
        "type": "link text",
        "value": "exensio it blog: ClaretPortal wird mobil (Teil 3) - Fokus ..."
      }
    }
  ],
  "data": {
    "configs": {},
    "source": "none"
  },
  "inputs": []
}
Das Format im Einzelnen hat eine Art Kopfzeile, einen Body und eine Art Fußzeile. In der Kopfzeile wird die Version des Selenium Tools definiert, für die dieses Skript geschrieben ist und es wird die Version des JSON Formats festgelegt.

Der Rumpf enthält alle auszuführenden Schritte, teilweise mit Zusatzinformationen, teilweise negiert. Der einfachste Fall ist:
"type": "get",
"url": "http://blog.exensio.de/"
In diesem Beispiel wird das Kommando "get" mit dem Argument "http://blog.exensio.de/" verwendet. Die Verwendung der Funktion "setElementText" sieht dagegen wie folgt aus:
"type": "setElementText",
"locator": {
  "type": "name",
  "value": "search"
  },
"text": "Consulting"
In diesem Beispiel verwendet die Funktion "setElementText" einen Locator vom Typ "name" mit dem Wert "search". In das mit diesem Locator gefundene Element wird der Text "Consulting" eingegeben. Statt des Locators vom Typ "name" wäre auch u. a. ein Locator vom Typ "css selector" möglich gewesen. In diesem Fall hätte der Wert "input[name='search']" angegeben werden müssen. Alternativ dazu wäre auch der Typ "xpath" möglich gewesen. Der Wert würde dann "//div[@id='CustomSearch1_form']/form/table[1]/tbody/tr/td[1]/input" lauten.

Die Fußzeile enthält schließlich die evtl. vorhandenen Eingangsdaten. Eine typische Fußzeile sieht wie folgt aus und muss normalerweise nicht nachträglich im Editor bearbeitet werden:
"data": {
   "configs": {},
   "source": "none"
  },
"inputs": []
Neben diesen Gestaltungselementen für Test-Skripte gibt es Lokatoren, die zur Lokalisierung bzw. Definition von Zielen dienen, die Möglichkeit der Negation, falls eine Bedingung nicht zutreffen soll und das Speichern und Verwalten von Variablen.

Später in dieser Reihe werden wir alle diese Elemente näher betrachten und in einem Beispiel verwenden. Wir werden lernen, wie man Tests schnell und effizient über mehrere Dateien hinweg in einem Texteditor bearbeitet. Mit der Zeit werden wir feststellen, dass nicht jeder neue Schritt im Se Builder aufgezeichnet werden muss.

Tipp: Locators

Se Builder selbst bietet bereits eine ganz gute Möglichkeit, die unterschiedlichen Typen von Lokatoren zu verwenden und die tatsächlich benötigten Werte ausfindig zu machen. Reicht einem der bestehende Funktionsumfang von Se Builder nicht oder möchte man tiefer in das Thema CSS Selector oder XPath einsteigen, empfehle ich die Firefox Erweiterungen FirePath [4] und FireBug [5]. Mit FirePath kann man XPath Ausdrücke bzw. CSS- und jQuery-Selektoren bearbeiten, untersuchen und generieren. FireBug enthält eine Fülle an Web Entwicklungswerkzeugen, mit denen man CSS, HTML, und JavaScript in Echtzeit an jeder beliebigen Webseite untersuchen und bearbeiten kann.

Zusammenstellen einer Test-Suite

Mit dem bisher Gelernten lassen sich schon einige Skripte erstellen und damit viele Testfälle abdecken. Im professionellen Einsatz kann es allerdings sinnvoll sein, umfangreiche Testfälle in kleine Teil-Testfälle zu schneiden. Als Beispiel sei der Vorgang Anmelden bzw. Abmelden am System genannt. Für diesen Testfall könnte man genau ein Test-Skript aufnehmen. Allerdings scheint es geschickter zu sein, den Testfall zu untergliedern in den Fall „Anmelden“ und den Fall „Abmelden“. Auf diese Weise können die Teil-Testfälle in anderen Testfällen wieder verwendet werden. Dadurch spart man sich unnötigen Aufwand bei der Verwaltung der Testfälle (z. B. bei Änderungen am Anmeldevorgang). Das Abspielen der einzelnen Testfälle kann natürlich manuell im Se Builder erfolgen, indem zuerst der Anmelde-Testfall geöffnet und abgespielt wird. Und danach wird der Abmelde-Testfall geöffnet und abgespielt. Dieses Vorgehen ist allerdings langsam und ineffizient. Selenium hat stattdessen die Test-Suite eingeführt. Dabei handelt es sich um eine Gruppierung von einzelnen Testfällen, die in der angegebenen Reihenfolge nacheinander abgespielt werden. Eine Test-Suite wird ebenfalls im JSON Dateiformat abgespeichert.

Erstellen wir dazu ein kleines Beispiel, das wir später mit dem Testfall aus Teil 1 dieses Blogposts zu einer Suite kombinieren werden. Wir wollen die korrekte Funktion der Tag Cloud auf der rechten Seite des exensio IT Blogs prüfen. Dort finden wir eine Sammlung aller Labels, die in den Blogposts verwendet wurden. Klickt man ein Label an, werden alle Blogposts angezeigt, die mit diesem Label getaggt wurden.

Öffnen wir zunächst die Webseite des Blogs unter http://blog.exensio.de im Hauptfenster von Firefox. Danach starten wir das Add-on Se Builder (Strg+Alt+B) und klicken den Knopf "Selenium 2", um die Aufnahme eines neuen Testfalls für Selenium WebDriver zu beginnen. Der erste Schritt mit dem Kommando "get" ist bereits angelegt. Nach Klicken auf den Tag Cloud Begriff "IT-Consulting" werden alle Posts mit dem Label "IT-Consulting" angezeigt. Im Se Builder wurde ein zweiter Schritt mit dem Kommando "clickElement" hinzugefügt. Jetzt können wir die Aufnahme mit dem Knopf "Stop recording" beenden und den Rest per Hand eingeben. Hierzu fügen wir unterhalb des zweiten Schritts einen weiteren Schritt hinzu ("new step below"). Das Kommando "clickElement" ersetzen wir durch "verifyText" im Abschnitt "Verify". Diese Funktion erwartet die Argumente "locator" und "text". Definieren wir zuerst den Locator in dem wir auf das Wort "locator" und danach auf "Find a different target" klicken. Wählen wir dann im Firefox Hauptfenster die Box mit dem Text "Posts mit dem Label IT-Consulting werden angezeigt. Alle Posts anzeigen" und dem Hintergrund in grauer Farbe, siehe nachfolgende Abbildung:
Der Locator Typ im Se Builder wechselt sogleich zu "css selector" und der Wert "div.status-msg-body" wird übernommen. Bestätigen wir diese Änderung zunächst mit Klick auf den Knopf "Ok". Als Wert für das Attribut "text" geben wir den Text "Posts mit dem Label IT-Consulting werden angezeigt. Alle Posts anzeigen" ein. Das fertige Skript sieht dann wie folgt aus:
In der Test-Suite, die wir später erstellen wollen, werden wir zuerst unser Test-Skript aus Teil 1 dieser Blogpost Reihe starten. Darin führen wir bereits das Kommando "get" mit dem Wert "http://blog.exensio.de/" aus. Daher benötigen wir diesen Schritt im zweiten Testfall nicht mehr. Deshalb bewegen wir den Mauszeiger im Se Builder über den ersten Schritt mit dem Kommando "get". Links erscheint daraufhin ein Kontextmenü des Se Builders. Dort wählen wir den Eintrag "delete step" um den ersten Schritt zu löschen. Im exensio IT Blog wird auf Inhalte von Twitter verwiesen. Deren Ladezeit kann unter Umständen unerwartet groß sein. Um einen Timeout des Test-Skripts zu vermeiden, fügen wir zu Beginn des Skripts noch eine Pause ein. Dazu bewegen wir den Mauszeiger über den ersten Schritt, klicken im Kontextmenü den Eintrag "new step above" und wählen dann als Kommando die Funktion "pause" im Abschnitt "Misc". Dieses Kommando erwartet als Argument einen Wert in Millisekunden. Wir geben "500" ein und speichern dann das Skript ab. Das Skript sieht nun wie folgt aus:
Jetzt können wir unser Skript schließen, indem wir im Se Builder Menü "File" den Eintrag "Discard and start over" anklicken. Danach klicken wir auf der Grundebene des Se Builers den Text "Open a script or suite" an und wählen im anschließenden "Datei öffnen"-Dialog unser JSON Skript aus Teil 1 der Blogpost Reihe aus. Nun wählen wir im Se Builder Menü "Suite" den Eintrag "Add script from file" aus und fügen unser gerade erstelltes zweites Skript hinzu. Daraufhin lädt Se Builder das zweite Skript. Das erste Skript ist allerdings nicht geschlossen, sondern befindet sich nun an erster Stelle in unserer Test-Suite. Um die vollständige Test-Suite anschauen zu können, klicken wir wieder auf das Menü "Suite". Das Submenü von "Suite" ist jetzt umfangreicher und besteht aus den Einträgen "Save", "Discard", "Record", "Add", "Remove" sowie aus den beiden Test-Skripten. Das Test-Skript, welches aktuell im Builder angezeigt wird, ist fett dargestellt. Zuerst sollten wir jetzt unsere neue Test-Suite abspeichern. Klicken wir dazu im Menü "Suite" den Eintrag "Save suite".

Test-Suite abspielen

Abschließend sind wir bereit die Test-Suite das erste Mal abzuspielen. Noch steht die Suite allerdings auf unserem zweiten Test-Skript. Natürlich wollen wir mit dem ersten Skript beginnen, deshalb wechseln wir zunächst auf das erste Test-Skript. Dazu wählen wir im Menü "Suite" den Namen unseres ersten Test-Skriptes aus. Anschließend können wir die Suite abspielen, indem wir im Menü "Run" den Eintrag "Run suite locally" anklicken. Daraufhin öffnet sich im Se Builder ein zweiter Dialog, in dem die beiden Skripte unserer Suite aufgelistet werden. Wie bereits vom Abspielen eines Test-Skriptes bekannt, ändert sich auch in diesem zweiten Dialog die Hintergrundfarbe entsprechend dem Status beim Abspielen. Am Ende werden die Schritte des letzten Test-Skriptes und alle Test-Skripte der Suite angezeigt, vgl. nachfolgende Abbildung:
Um sich die Ergebnisse der einzelnen Test-Skripte anschauen zu können, kann man im "Suite-Fenster" rechts auf die entsprechenden Namen der Testfälle klicken. Danach werden alle Schritte des Testfalls mit den zugehörigen Hintergrundfarben im Builder angezeigt. Auf diese Weise lassen sich die Tests anschließend gut untersuchen.

JSON Quellcode der Suite

Werfen wir abschließend einen kurzen Blick auf den Quellcode der Selenium Test-Suite, die wir gerade erstellt haben. Der Quellcode sieht wie folgt aus:
{
  "type": "suite",
  "seleniumVersion": "2",
  "formatVersion": 1,
  "scripts": [
    {
      "where": "local",
      "path": "test_blogpost_teil_1.json"
    },
    {
      "where": "local",
      "path": " test_blogpost_teil_2.json"
    }
  ]
}
Der prinzipielle Aufbau ist uns schon vom JSON Format der Skriptdatei bekannt. Die Suitedatei besitzt den Typ "suite" und verfügt nur über eine "Kopfzeile". Die "Fußzeile" gibt es dagegen nicht. Im Rumpfbereich gibt es nur die Funktion "where" mit dem Argument "path", dessen Wert dem Pfad zu unserem Test-Skript entspricht. Man kann hier entweder absolute oder relative Pfade angeben.

Im nächsten Teil dieser Reihe gebe ich einige nützliche Tipps zum Erstellen von sehr umfangreichen Test-Suiten. Außerdem werden Tricks verraten, wie man z. B. Eingaben in den bekannten WYSIWYG Editor CKEditor simuliert und wie Tooltips der Klasse jQuery Tooltipsy ausgelesen werden oder wie man mit Alert Boxen umgeht.

Links

[1] Automatische Browsertests mit Selenium: Zielvorstellung und Einführung Selenium Builder (Teil 1)
[2] Automatische Browsertests mit Selenium: Tipps und Tricks mit Se Builder (Teil 3)
[3] https://github.com/sebuilder/se-builder/wiki/JSON-Format
[4] https://code.google.com/p/firepath
[5] https://www.getfirebug.com