Aus Linux-Magazin 10/2006

Automatische Regressionstests mit Selenium

Das Testen komplexer Webapplikationen erfordert nicht unbedingt teure proprietäre Tools wie Test Director oder Silk Performer: Selenium gibt's umsonst. Es steuert alle gängigen Browser unter verschiedenen Betriebssystemen fern und lässt sich unter anderem mit Perl programmieren.

Ob eine Webapplikation nach einer Veränderung des Code noch funktioniert, vermag der Entwickler nur dann mit Sicherheit zu sagen, wenn er alle Funktionen mit einem Browser durchspielt, also jede Seite aufschlägt, jeden Button drückt und jedes Feld ausfüllt. Und das nicht nur einmal, denn sowohl Erfolgs- als auch Fehlerszenarios sollte man mehrfach überprüfen.

Dem Entwickler oder den Mitarbeitern der Abteilung für Qualitätssicherung macht es sicherlich wenig Freude, solche monotonen Tests manuell auszuführen. Zudem birgt dies Vorgehen die Gefahr menschlicher Fehler. Ein automatischer Test erledigt die Aufgabe besser.

Javascript im Test

Einfache Webapplikation lassen sich mit Screenscrapern wie dem CPAN-Modul WWW::Mechanize testen. Dieses Tool steigt jedoch aus, wenn Javascript ins Spiel kommt. Zwar mangelt es nicht an Javascript-Implementationen mit zugehörigen Perl-Schnittstellen, doch für die Interaktion zwischen der Javascript-Engine und dem DOM (Document Object Model) des Browsers gab es bisher keine zufriedenstellende Lösung.

Die Aufgabe ist auch ganz schön komplex: Javascript manipuliert das HTML geladener Seiten, löst sich wiederholende Aktionen aus, öffnet Fenster oder holt neue Daten vom Server und lässt sie in die Seite einfließen. Außerdem hat jeder Browser seine Tücken und Unterschiede in der DOM-Implementation.

Einfach und effektiv

Nun hat die Firma Thoughtworks das Open-Source-Projekt Selenium [2] veröffentlicht, mit dem das Problem einfach zu lösen ist. Das in Java geschriebene Programm füttert einen Webbrowser mit Javascript-Code, der es ihm ermöglicht, die Kontrolle über den Webclient zu übernehmen und Tests zu automatisieren. Welche Anweisungen der Selenium-Server dem Browser erteilt, bestimmen in Perl, Python, Ruby, Java oder Dotnet geschriebene Skripte. Die Kontrolle über den Datenverkehr zwischen Internet und Browser behält Selenium, indem es sich als Proxy dazwischenschaltet.

Abbildung 1 zeigt ein einfaches Webformular, das Userkommentare entgegennimmt. Vor dem Kommentartext gibt der Benutzer seine E-Mail-Adresse an, damit ihm der Webmaster antworten kann. Damit das E-Mail-Feld nicht irrtümlich leer bleibt oder anderen Text enthält, überprüft das Javascript-Programm die Eingabe, wenn der Benutzer den »Send«-Button drückt. Stellt es einen Fehler fest, zeigt es eine Warnung an, statt den Kommentar an den Server zu schicken.

Bei Eingabe einer syntaktisch gültigen Mailadresse gibt das hinter der »ACTION«-URL stehende CGI-Skript »/cgi/feedback.cgi« die Meldung »Thanks for your feedback!« zurück. Der Test soll das Aufklappen des Warnfensters im Falle einer ungültigen Eingabe ebenso verifizieren wie die Erfolgsmeldung.

Listing 1 zeigt das Testskript »emailcheck«, das zunächst eine Verbindung zum Selenium-Server aufbaut. Dieser öffnet auf den Befehl »start()« hin einen Firefox-Browser in einer isolierten Session. »open()« veranlasst ihn danach dazu, die HTML-Seite aus Abbildung 1 vom Webserver zu holen, die den Javascript-Code zur E-Mail-Prüfung enthält.

Listing 1:
»emailcheck«

01 #!/usr/bin/perl -w
02 use WWW::Selenium;
03 use Log::Log4perl qw(:easy);
04 Log::Log4perl->easy_init($DEBUG);
05
06 my $url = "http://perlmeister.com";
07
08 my $sel = WWW::Selenium->new(
09   host => "localhost",
10   port => 4444,
11   browser => "*firefox " .
12     "$ENV{FIREFOX_HOME}/firefox-bin",
13   browser_url => $url,
14 );
15
16   DEBUG "Starting";
17 $sel->start();
18
19 $sel->open("$url/test/mail.html");
20   DEBUG "Typing email";
21 $sel->type("from", 'abcde');
22   DEBUG "Clicking send";
23 $sel->click("send");
24 my $alert = $sel->get_alert();
25   DEBUG "alert was '$alert'";
26
27   DEBUG "Opening";
28 $sel->open("$url/test/mail.html");
29   DEBUG "Typing email";
30 $sel->type("from", 'abcde@foo.com');
31   DEBUG "Clicking send";
32 $sel->click("send");
33 $sel->wait_for_page_to_load(50000);
34 my $body = $sel->get_body_text();
35   DEBUG "Response was '$body'";
36
37 $sel->stop();
Abbildung 1: Das zu testende Webformular nimmt eine E-Mail-Adresse entgegen und prüft die Eingabe auf Gültigkeit.

Abbildung 1: Das zu testende Webformular nimmt eine E-Mail-Adresse entgegen und prüft die Eingabe auf Gültigkeit.

Gültig nur mit Klammeraffe

Die Methode »type()« simuliert einen tippenden Benutzer und nimmt als Argumente den Namen des »<INPUT>«-Felds eines HTML-Formulars sowie den Eingabetext entgegen. Im ersten Testfall füllt WWW::Selenium in Zeile 21 den String »abcde« ins E-Mail-Feld. Dies erkennt das Skript am Wert des »name«-Attributs, in diesem Fall »from«. Die anschließend aufgerufene Methode »click()« mit dem Namen des Submit-Buttons als Argument – er heißt im Beispielformular »send« – simuliert einen Klick auf diesen Schaltknopf.

Da der Adressen-String in diesem Fall weder ein »@«-Zeichen noch einen Punkt enthält, handelt es sich bei der Eingabe eindeutig um eine ungültige E-Mail-Adresse, die Webseite zeigt deshalb als Antwort nur den Warndialog. Die Methode »get_alert()« verwertet diese Ausgabe in Zeile 24 und liefert den Fehlertext zurück. Zu Testzwecken gibt »emailcheck« den gefundenen Textstring auf dem Bildschirm aus. Abbildung 2 zeigt die vollständige Debug-Ausgabe des Skripts.

Abbildung 2: Das Testskript »emailcheck« steuert über Selenium einen Firefox-Browser. Es simuliert die Eingabe einer ungültigen E-Mail-Adresse und gibt die Antwort des Webservers aus.

Abbildung 2: Das Testskript »emailcheck« steuert über Selenium einen Firefox-Browser. Es simuliert die Eingabe einer ungültigen E-Mail-Adresse und gibt die Antwort des Webservers aus.

Im zweiten Testfall ab Zeile 27 gibt der Simulator den String »abcde@foo.com« ins E-Mail-Feld ein, also eine syntaktisch korrekte Adresse. Nach dem »click()«-Aufruf wartet der Simulator mit »wait_for_page_to_load()« bis zu 50 Sekunden (50000 Millisekunden), bis sich die aus dem Klick auf den Button resultierende Seite aufgebaut hat. »get_body_text()« nimmt dann das Ergebnis entgegen und gibt den String aus. Am Ende schließt »stop()« den anfangs mit »start()« geöffneten Browser wieder.

Regressionstests mit System

Da Selenium-Skripte hauptsächlich zu Regressionstests dienen, steht mit Test::WWW::Selenium ein CPAN-Modul bereit, das den Selenium-Client um das in [4] besprochene Test Anything Protocol (TAP) erweitert. Skripte, die Ausgaben in diesem Format erzeugen, lassen sich mit Modulen wie dem ebenfalls im CPAN erhältlichen Test::Harness zu stetig wachsenden Regressionstest-Suiten zusammenfassen, die übersichtlich formatierte Ergebnisse liefern.

Test::WWW::Selenium erweitert die Klasse »WWW::Selenium« um eine Reihe von Befehlen, die wiederum Selenium-Kommandos ausgeben und prüfen, ob sie die erwartete Wirkung zeigen. An die Stelle von »open()« zum Laden einer Webseite tritt der Aufruf »open_ok()«. Im Erfolgsfall kommt gemäß dem TAP-Protokoll die Antwort »ok 1« zurück. Tritt ein Fehler auf, lautet der Ausgabewert »not ok 1«.

Bei den WWW::Selenium-Methoden für das Einholen von Seitendetails wie »get_body_text()« oder »get_title()« streicht der Programmierer das »get_« und hängt den Namen einer Prüfmethode aus dem Fundus von Test::More an. So prüft »title_is()« zum Beispiel, ob der von »get_title()« gelieferte Seitentitel auch exakt mit dem in » abgelegten String übereinstimmt. Das von »get_body_text()« abgeleitete »body_text_like()« gibt eine Erfolgsmeldung aus, falls der Seitentext dem in der Variablen » gespeicherten regulären Ausdruck entspricht.

Der Zombie googelt

Das Testskript »gtest« (Listing 2) startet zunächst eine Firefox-Sitzung und öffnet darin die Google-Homepage. Dort gibt der Browser den String »schilli« ins Suchfeld ein und betätigt den Button »Google Search«. Die in »gtest« festgelegten Testfälle prüfen, ob die Suche erfolgreich war und ob in der Trefferliste der String »perlmeister« auftaucht. Die Ausgabe zeigt Abbildung 3.

Listing 2:
»gtest«

01 #!/usr/bin/perl -w
02 use Test::WWW::Selenium;
03 use Test::More tests => 4;
04
05 my $url = "http://www.google.com";
06
07 my $sel = Test::WWW::Selenium->new(
08   host => "localhost",
09   port => 4444,
10   browser => "*firefox " .
11     "$ENV{FIREFOX_HOME}/firefox-bin",
12   browser_url => $url,
13 );
14
15 $sel->open_ok($url);
16
17 $sel->type_ok("q", "schilli",
18               "Type query");
19
20 $sel->click_ok(
21         '//input[@value="Google Search"]',
22         "Clicking Search");
23 $sel->wait_for_page_to_load(5000);
24
25 $sel->body_text_like(qr/perlmeister/,
26         "perlmeister found");
Abbildung 3: »gtest« startet einen Firefox-Browser, den das Skript fernsteuert und auf Google lenkt. Die Suchergebnisse analysiert es automatisch.

Abbildung 3: »gtest« startet einen Firefox-Browser, den das Skript fernsteuert und auf Google lenkt. Die Suchergebnisse analysiert es automatisch.

Damit die Methode »click()« weiß, welchen Button der Browser betätigen soll, nimmt sie normalerweise den Wert des »name«-Attributs des Buttons als Parameter entgegen. Alternativ bestimmt man das Klickziel über einen so genannten Element Locator und steuert das »<INPUT>«-Element eines Formulars über die Variable »value« an:

//input[@value="Google Search"]

Die Dokumentation von WWW::Selenium führt eine ausführliche Liste von Methoden auf, mit denen der Client den Selenium-Server und darüber den Browser fernsteuert. Selenium unterstützt die ganze Palette des Browser-Schnickschnacks, vom Öffnen mehrerer Popup-Fenster über das Auslösen von Javascript-Events bis zum Platzieren des Textcursors in Formelementen.

Abbildung 4 zeigt eine Firefox-Sitzung, die Selenium fernsteuert. In der oberen Hälfte stehen die per Javascript eingespeisten Bedien- und Kontrollelemente, unten ist die getestete Website zu sehen. Der Zombie-Browser verwendet die unveränderten Standardeinstellungen, deshalb ist die personalisierbare Toolbar leer und das Suchfenster ist auf Google voreingestellt, der Browser des Autors nutzt dagegen die Suchmaschine seines Arbeitgebers. Selenium legt zum Testen ein eigenes Firefox-Profil an und ignoriert somit die im Browser vorgenommenen Benutzereinstellungen.

Abbildung 4: Zum Testen legt Selenium ein eigenes Profil im Browser an und ignoriert die vom Benutzer vorgenommenen Einstellungen. Dann lässt es ihn eine Seite ansteuern, während in der oberen Fensterhälfte die Fernsteuerungskontrolle steht.

Abbildung 4: Zum Testen legt Selenium ein eigenes Profil im Browser an und ignoriert die vom Benutzer vorgenommenen Einstellungen. Dann lässt es ihn eine Seite ansteuern, während in der oberen Fensterhälfte die Fernsteuerungskontrolle steht.

Ein prüfender Blick auf die Adressenzeile des Browsers zeigt, dass auch hier einiges von der Norm abweicht. Die dargestellte URL »http://www.google.com/selenium-server/…« jubelt Google einen Selenium-Server unter, der auf der Website des Suchgiganten natürlich nicht installiert ist: Die oben vorgestellten Testskripte kommunizieren nicht direkt mit dem Browser, sondern über die Selenium Remote Control als Mittelsmann.

Zwei in einem

Der Selenium-Server erfüllt zwei Funktionen zugleich. Fordert ein Testskript eine Internetseite an, leitet die Selenium-Fernsteuerung den Auftrag an den Browser weiter. Ihn hat Selenium aber so eingestellt, dass er nicht direkt mit dem Internet kommuniziert, sondern über einen Proxy. Diese Aufgabe übernimmt ebenfalls Selenium. So bekommt der Server mit, welche Daten der Browser aus dem Internet anfordert und zurückerhält, und kann den Verkehr zwischen beiden Parteien beliebig manipulieren.

Bittet die Selenium-Fernsteuerung den Browser, eine URL anzufordern, hängt sie an die Internetadresse Kontrollparameter und eine Session-ID an. Der Browser holt die angegebene Webseite anschließend über den Proxy. Dann erhält der Selenium-Server den Auftrag zurück, filtert die für ihn bestimmten Nachrichten heraus, kontaktiert den angegebenen Webserver und holt die Antwort ab. Diese ergänzt der Proxy um Javascript-Code, der im Browser Fernsteuerungsaktionen wie das Tippen von Text in ein Eingabefeld auslöst.

Freiwillige Fernkontrolle

Der Selenium-Server steuert den Browser also nicht wirklich fern, sondern dieser fragt auf Grund des vom Proxy gelieferten Javascript-Code regelmäßig nach neuen Anweisungen. So manipuliert die Fernsteuerung den Browser auf ähnliche Weise wie ein Firefox-Plugin. Diese Methode hat den Vorteil, dass Selenium die Browser verschiedener Hersteller ohne zwingende Plugin-Installation nach seiner Pfeife tanzen lässt.

Der Selenium-Server ist bei [2] unter einer Apache-Lizenz verfügbar. Das Zip-Archiv enthält den Java-Quelltext und die kompilierte ».jar«-Datei. Den janusköpfigen Server startet der Befehl:

LD_LIBRARY_PATH=/Pfad/zu/firefox-1.5 java U-jar selenium-server.jar

Dabei ist es wichtig, dass sich die dynamischen Libraries der Firefox-Installation, beispielsweise die Datei »libmozjs.so«, im Firefox-Verzeichnis befinden. Ist der Browser standardgemäß installiert, entfällt die Angabe von »LD_LIBRARY_PATH«. Das Gleiche gilt für den Konstruktor »new« der Klasse »WWW::Selenium«: Statt des langwierigen Installationspfads darf der Parameter »browser« einfach den Wert »”*firefox”« erhalten, wenn die Binärdatei im Pfad liegt. Dann kann auch die in den Listings verwendete Umgebungsvariable »FIREFOX_HOME« ungesetzt bleiben.

Der Selenium-Server verwendet Port 4444 und lässt dort mit sich reden. Im Konstruktor von »WWW::Selenium« steht diese Information im Parameter »port«, den das Testskript ebenfalls auf 4444 vordefiniert.

Eine bisher nicht überwundene Hürde stellen die Sicherheitsbeschränkungen dar, denen Javascript unterliegt. Um die Gefahr des Missbrauchs einzudämmen, darf der Javascript-Code einer Domain den Inhalt einer Webseite nicht manipulieren, wenn diese auf einer anderen Domain liegt.

Deshalb nimmt der Konstruktor der Klasse »WWW::Selenium« mit dem Parameter »browser_url« die Basis-URL der zu testenden Website entgegen. Tests können nur auf der angegebenen Domain erfolgen, Skripte zum Testen des Zusammenspiels mehrerer Domains sind noch nicht möglich. Die nächste Selenium-Version sieht jedoch bereits eine Möglichkeit vor, die Basis-URL dynamisch zu verändern.

Auch als Plugin

Das Projekt bietet auch ein Firefox-Plugin namens Selenium IDE an. Es öffnet nach der Aktivierung ein Dialogfenster (Abbildung 5) und springt dann sofort in einen Aufnahmemodus, um im Browser ausgeführte Aktionen zu protokollieren. Dazu verwendet es die Selenium-eigene Sprache Selenese. Selenium IDE reproduziert die mitgeschnittenen Schritte bei Bedarf und ermöglicht damit die Definition von Testsuiten. Die protokollierten Daten lassen sich auch extrahieren, um daraus umfangreiche Regressionstests in Perl zu schreiben.

Abbildung 5: Das Firefox-Plugin Selenium IDE protokolliert und extrahiert Browser-Aktionen.

Abbildung 5: Das Firefox-Plugin Selenium IDE protokolliert und extrahiert Browser-Aktionen.

Ein mit Selenium ausgestattetes Perl-Skript steuert auch Browser auf anderen Rechnern und Betriebssystemen fern. So lässt sich die Kompatibilität einer Webapplikation für eine Reihe von Browsern prüfen. Um etwa Microsofts Internet Explorer unter Windows fernzusteuern, kopiert man die ».jar«-Datei der Selenium Remote Control auf ein Windows-System und startet den Server mit:

java -jar selenium-server.jar

Das setzt allerdings auch auf dem Windows-System eine Java-Laufzeitumgebung voraus.

Die IP-Adresse des Windows-Rechners ist mit »ipconfig« in der Eingabeaufforderung zu erfahren, im Beispiel heißt sie »192.168.0.70«. Damit die Testsuite des Linux-Steuerungsrechners mit dem Internet Explorer funktioniert, definiert man im Konstruktor von »WWW::Selenium« die IP, den Port und den Browser:

host => "192.168.0.70",
port => 4444,
browser => "*iexplore",

Die Testskripte laufen weiterhin auf dem ursprünglichen System.

Endlich überall testen

Selenium kommt mit allen verbreiteten Browser unter Linux, Windows und Mac OS X klar. Der Fernsteuerungsserver läuft zudem auf allen von Java unterstützten Plattformen. (csc)

Infos

[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2006/10/Perl]

[2] Selenium: [http://www.openqa.org/selenium]

[3] Selenium IDE, Firefox-Plugin: [http://www.openqa.org/selenium-ide]

[4] Über Regressionstests mit Perl, Michael Schilli, “Kontrolle ist besser”: Linux-Magazin 11/05, S. 106, [https://www.linux-magazin.de/Artikel/ausgabe/2005/11/perl/perl.html]

Der Autor


Michael Schilli arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat “Goto Perl 5” (deutsch) und “Perl Power” (englisch) für Addison-Wesley geschrieben und ist unter [mschilli@perlmeister.com] zu erreichen.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben