Selenium Tutorial deutsch: Testen von dynamischen Webapplikationen mit PHP und Selenium Grid

Nachdem wir die Architektur von Selenium Grid verstanden und unseren ersten Selenium Hub und Node gestartet haben ist es nun an der Zeit, das erste PHP-Testscript zu schreiben.

Selenium Tutorial deutsch: Testfall

Das Testen unserer dynamischen Webanwendung soll folgendes beinhalten:

  • Aufrufen der Startseite
  • Warten, bis die Seite geladen ist
  • Anklicken eines Links, welcher AJAX triggert
  • Warten auf die Ausführung von AJAX
  • Prüfen des Inhalts eines bestimmten Elements
  • Bei fehlerhaftem Inhalt einen Screenshot erstellen
    Infografik #1: Schematische Darstellung eines Beispieltests mit Selenium Grid/Webdriver
    Infografik #1: Schematische Darstellung eines Beispieltests mit Selenium Grid/Webdriver

    Als Testobjekt nehmen wir eine mit AJAX-lastige Seite unseres Kunden: budenheim.com. Dort werden z.B. beim Klicken auf die meisten Links nicht wie gewohnt neue Seiten geladen, sondern der Inhalt dieser Seiten via asynchronem JavaScript nachgeladen. Da können wir uns austoben!

    Selenium Tutorial PHP und Selenium Grid: Step by Step

    Einstellungen im PHP-Code vornehmen

    In dem vorherigen Artikel wurde bereits gezeigt, wie man die für PHP nötigen Selenium-Bibliotheken installiert, um damit vernünftig arbeiten zu können. Wenn das geschehen ist, kann man diese wie folgt in sein PHP-Script einbinden:

    <?php
    require_once ('php-webdriver/lib/__init__.php');
    echo "Website-Check 1.0\n";

    Und wenn wir uns schon mal im Header des Quellcodes befinden, schreiben wir gleich fest, wie unser Hub zu erreichen ist:

    <?php
    $host = 'http://swuser-xubuntu:4444/wd/hub';

    Wobei für den URL dieses Schema angewendet wird:

    http://{Hub-Hostname/IP}:{Hub-Port}/wd/hub

    Jetzt definieren wir einen Browser, den wir dann im Selenium Grid steuern können. Nehmen wir in unserem Beispiel Google Chrome, der auf den bereits laufenden Node zeigt.

    <?php
    $chrome = DesiredCapabilities::chrome ( );

    Das gleiche Prinzip kann man auch für andere Browser anwenden:

    <?php
    $firefox = DesiredCapabilities::firefox ( );
    $ie = DesiredCapabilities::internetExplorer ( );

    Diese Angaben kann man noch etwas näher spezifizieren, um z.B. eine bestimmte Version unter einem bestimmten Betriebssystem anzusprechen:

    <?php
    $chrome = DesiredCapabilities::chrome ( );
    $chrome -> setPlatform ( 'WINDOWS' );
    $chrome->setVersion('40.0');

    Die Angaben hängen direkt mit den Parametern im Ausführungsbefehl des Nodes zusammen. Wenn man einen Selenium Node mit Google Chrome wie folgt gestartet hat …

    java -jar C:/selenium/selenium-server-standalone-2.44.0.jar \
     -role webdriver \
     -hub http://swuser-xubuntu:4444/grid/register \
     -browser browserName=chrome,maxInstances=4,*****platform=WINDOWS,version=40.0***** \
     -Dwebdriver.*****chrome*****.driver=C:/selenium/chromedriver.exe \
     -port 5556
    # man beachte die mit ***** hervorgehobenen Stellen

    … kann man genau diesen Node dadurch gezielt ansprechen, indem man folgende Angaben macht:

    Name Wert
    Browser chrome
    Platform WINDOWS
    Browser-Version 40.0

    So kann man z.B. in einem Selenium Grid mehrere Betriebssystem/Browser/Browser-Version-Kombination laufen lassen, ohne dass es zu ungewollten Konflikten kommt.

    Testen bei Browserstack.com

    Bei einer kleineren Testing-Suite ist es eher unwahrscheinlich, dass Browser und deren Versionen miteinander kollidieren. Aber bei einem sehr großen Grid, wie z.B. bei Browserstack, welche ein flüssige funktionierende Selenium-Umgebung bereitstellen, sollte man es sich angewöhnen seine DesiredCapabilities genau anzugeben, also mit Browser-Version und Betriebssystem.

    Bevor man Browserstack aber nutzen kann, muss man sein PHP-Script etwas anpassen:

    <?php
    $host = 'http://hub.browserstack.com/wd/hub/';
    $safari = DesiredCapabilities::safari();
    [...]
    $safari -> setCapability ( 'browserstack.user', "username" );
    $safari -> setCapability ( 'browserstack.key', "userkey" );

    So weiß Browserstack nämlich, unter welchem Account die Tests durchgeführt werden.

    Infografik #2: Fertiges Selenium Grid bei Browserstack mit unzähligen Browsern
    Infografik #2: Fertiges Selenium Grid bei Browserstack mit unzähligen Browsern

    Opera 28 und Chrome in einem Grid

    Die Implementierung von Opera in das Selenium Grid ist seit Neuestem etwas tricky. Denn Opera basiert bereits seit einiger Zeit auf der Chrome-Engine Blink und verwendet somit seit der Version 26 nicht mehr den OperaPrestoDriver, sondern den OperaChromiumDriver.

    Infografik #3: Verschiedene Engine-Versionen von Opera
    Infografik #3: Verschiedene Engine-Versionen von Opera

    Deswegen muss man auch bei der Implementierung ins Selenium Grid einiges beachten, damit man zwischen Chrome und Opera unterscheiden kann:

    OperaChromiumDriver: Starten des Opera-Nodes
    java -jar C:/selenium/selenium-server-standalone-2.44.0.jar \
      -role webdriver \
      -hub http://swuser-xubuntu:4444/grid/register \
      -browser browserName=*****chrome*****,maxInstances=4,platform=WINDOWS,version=*****opera28***** \
      -Dwebdriver.*****chrome*****.driver=C:/selenium/*****operachromedriver.exe***** \
      -port 5558
    # Man beachte die mit ***** hervorgehobenen Stellen

    In der Versionsangabe steht also explizit opera28. Der Selenium Opera Node, der hier gestartet wird, ist also mit dieser Versionskennung eindeutig ausgewiesen.

    Infografik #4: Die Version opera28 ist auch in der Selenium Grid Console sichtbar
    Infografik #4: Die Version opera28 ist auch in der Selenium Grid Console sichtbar

    Die Parameter des Startbefehls eines Google Chrome Nodes sehen im Vergleich zum Opera Chromium Node wie folgt aus (Unterschiede sind fettgedruckt):

    Parameter Chrome Opera Chromium
    browserName chrome chrome
    version 40.0 opera28
    Web-Driver-Name webdriver.chrome.driver webdriver.chrome.driver
    Web-Driver-Pfad C:/selenium/chromedriver.exe C:/selenium/operachromedriver.exe

    Die Unterschiede bestehen also in der Versionsangabe und im Pfad zur binären Datei des WebDrivers.

    OperaChromiumDriver: chrome als DesiredCapabilities angeben
    <?php
    $opera = DesiredCapabilities::chrome ( );

    (Ja, obwohl es Opera werden soll, muss man hier chrome als DesiredCapabilities angeben)

    Um jetzt im PHP-Code zwischen einem Google Chrome Node und einem Opera Chromium Node unterscheiden zu können, spezifiziert man einfach die oben angegebene Version:

    <?php
    
    # Google Chrome:
    $chrome = DesiredCapabilities::chrome ( );
    $chrome->setVersion('40.0');
    
    # Opera Chromium:
    $opera = DesiredCapabilities::chrome ( );
    $opera->setVersion('opera28');

    Aufrufen der Startseite

    Jetzt können wir einen RemoteWebdriver wie folgt verwenden, um die betreffende Seite aufzurufen:

    <?php
    $driver = RemoteWebDriver::create ( $host, $opera, 5000 );
    $driver -> get("http://www.budenheim.com");

    Ansprechen von Objekten

    Um in Selenium ein Objekt anzusprechen, gibt es einige Möglichkeiten zur Auswahl:

    • Über die Klasse des Elements (class-HTML-Attribut)
    • Über die ID des Elements (id-HTML-Attribut)
    • Über den XPath des Elements

    Die Übersicht aller Möglichkeiten gibt die Autovervollständigung in Eclipse:

    Infografik #5: Anhand dieser Kriterien kann man ein Element mit Selenium ansprechen
    Infografik #5: Anhand dieser Kriterien kann man ein Element mit Selenium ansprechen

    In unserem Beispiel suchen wir Elemente anhand ihres XPath aus.

    Kleines Helferlein: Firepath

    Mit der FirePath-Extension von FireBug für Firefox kann man den XPath zu einem bestimmten Objekt schnell und einfach herauszufinden oder einen bereits vorhanden XPath auf Richtigkeit zu prüfen:

    Infografik #6: FirePath hilft beim Finden eines Elements
    Infografik #6: FirePath hilft beim Finden eines Elements

    Den XPath kann man aus dem oberen Feld übernehmen und in seinen Testplan implementieren.

    In den PHP-Bindings für Selenium spricht man ein Element mit der XPath-Methode wie folgt an:

    <?php
    $object = $driver -> findElement (
      # Das ist die für uns relevante Stelle:
      WebDriverBy::xpath("//*[contains(text(),'Deutsch')]")
    );

    Jetzt kann ich über die Variable $object auf die Funktionen und Attribte des Elements, der den Text Deutsch enthält, zugreifen und ggf. verändern.

    Ladezeiten überbrücken

    Das ist ja schön, dass wir unsere Seite aufgerufen haben. Aber wie überbrückt man jetzt die Ladezeit? Was ist, wenn in der Seite Elemente dynamisch nachgeladen werden (AJAX)?

    PHP-sleep()-Methode

    Man kann entweder die Holzhammermethode anwenden und im PHP-Script einfach ein sleep(10) einbauen. Der Nachteil dieser Methode ist aber, dass das Testing so sehr viel unnötige Wartezeit beinhaltet. Das ist nicht mehr optimtiert und auch einfach nicht sauber programmiert.

    Warten auf Existenz eines Objekts

    Viel besser ist es hier, auf ein bestimmtes Objekt zu warten, bis es vorhanden ist. Dabei sprechen wir das Objekt über XPath an:

    <?php
    $driver ->
      wait ( 10 ) ->
      until (
        WebDriverExpectedCondition::presenceOfAllElementsLocatedBy (
          WebDriverBy::xpath("//*[contains(text(),'Deutsch')]")
        )
      );

    Selenium wartet hier maximal 10 Sekunden, bis das Objekt existiert. Wenn nicht, wirft es eine Exception.

    Auf Ausführung von AJAX warten

    Das gleiche Prinzip wenden wir an, wenn wir auf die Ausführung von AJAX warten: Wir triggern eine asynchrone Ausführung von einem Script, z.B. indem wir einen Link anklicken …

    <?php
    $object->click();

    … und wieder auf ein Element warten:

    <?php
    $driver ->
      wait ( 10 ) ->
      until (
        WebDriverExpectedCondition::presenceOfAllElementsLocatedBy (
          WebDriverBy::xpath("***** XPath des zu erwartenden Elements hier eintragen*****")
        )
      );

    Prüfen des Inhalts eines Elements

    Da wir unser Objekt bereits in der Variable $object referenziert haben, kann man auch ganz leicht auf dessen Eigenschaften (und logischerweise auch den Text) zugreifen:

    <?php
    $objectText = $object -> getText();

    Diesen Text kann man dann je nach Testimplementierung prüfen und dementsprechen reagieren.

    Screenshot erstellen

    Wenn z.B. der Inhalt eines Elements nicht korrekt ist, kann man von der Seite einen Screenshot erstellen:

    <?php
    $driver -> takeScreenshot ( '/tmp/screenshot.png' );

    Die Bildschirmaufnahme kann man dann entweder weiter bearbeiten (zuschneiden, bestimmte Elemente hervorheben, etc.) oder in seine Reports integrieren.

    Selenium Tutorial: Automated Website-Testing lohnt sich

    Mit Selenium kann man also nach etwas Einrichtungszeit effektive und zeitsparende Website-Tests implementieren. Um mehr Funktionsumfang zu erreichen, ist es möglich Linux-Tools über PHP anzukoppeln und weitere PHP-Bibliotheken, zum Beispiel zur Reportgenerierung, einzubinden. Da die PHP-Selenium-API objektorientiert ist, hat man eine große Bandbreite an Möglichkeiten, die bei entsprechender Implementierung nicht nur sehr gut wartbare Testanweisungen ermöglichen, sondern auch der Qualität der „in die Mangel genommenen“ Website zugutekommen.

    Infografiken: © schwarzer.de, Titelbild: I, Luc Viatour [GFDL, CC-BY-SA-3.0 or CC BY-SA 2.5-2.0-1.0], via Wikimedia Commons