06.072012

Seleniumtests in die Cloud auslagern: testingbot.com

Die u.a. von Google unterstützte Testsoftware für Webanwendungen Selenium kann in der Version 2 – WebDriver – unterschiedlichste Browser auf den wichtigsten Plattformen über deren native APIs fernsteuern und nach ihrem Status befragen. Man kann Tests auf seinem lokalen Entwicklungsrechner durchführen, oder ein Testgrid aufsetzen. Ein Testgrid besteht dabei aus einem Selenium-Hub und mehreren Workern, die sich am Hub mit ihren Fähigkeiten anmelden. Ein Testwunsch wird mit den gewünschten Fähigkeiten an den Hub geschickt, der den korrekten Worker vermittelt. Kommerzielle Anbieter erleichtern das Ausführen von Tests auf unterschiedlichen Plattformen.

Anders als die über das Selenium-IDE aufgenommenen "selenese"-Tests -- html-Dateien die in html-Tabellen Testschritte beschreiben – können WebDriver Tests in nahezu jeder beliebigen Programmiersprache verfasst werden. Damit werden echte, den HTTP-Rahmen sprengende, Integrationstests von Anwendungen möglich: Stößt eine Anwendung z.B. einen Mailversand an, kann der Test die erhaltene Mail abrufen und prüfen.

Die Kommunikation zwischen Testcode und Testumgebung erfolgt über ein HTTP basiertes Protokoll, dass in den Clientbibliotheken meist hervorragend gekapselt ist, d.h. die Protokollebene ist vom Testautoren durch der Sprache angemessene Abstraktionen verborgen.

Eine umfassende Testumgebung vorzuhalten ist aufwändig und nicht für alle Firmen wünschenswert. Es gibt aber Anbieter, die Seleniumgrids zur Verfügung stellen. Ein Anbieter in der Cloud ist testingbot.com, der Hub dort stellt Worker für Windows, Mac OS X, Linux, iOS und Android zur Verfügung – die Browserauswahl ist von der Plattform abhängig.

Ein erläutertes Beispiel für einen in node.js mit dem Tests auf testingbot erleichternden webdriverjs-basierten Modul tbwdjs folgt unten zum Abschluss dieser kurzen Einführung. node.js für die Entwicklung der Tests einzusetzen hat dann größeren Charm, wenn die Anwendung zum Teil in javascript Clientseitig im Browser läuft, oder wenn Tests javascript im Browser ausführen müssen – es gibt dann einfach weniger Brüche zwischen den lokalen und entfernten Testanteilen. Eine andere Sprache zu nutzen ist natürlich dann sinnvoll, wenn die Tests auf Modelle und Abstraktionen der Anwendung zugreifen sollen oder gar müssen – man spart sich dann Codedopplungen, und das ist natürlich immer wünschenswert.

Das Konzeptionell schöne an WebDriver ist, dass die Webanwendung nicht in einem abstrakten Kontext wie HttpUnit getestet wird, sondern in einer der Realität maximal angenäherten Umgebung. Crossbrowser und CrossOS Tests sind so nur eine Frage der eigenen Möglichkeiten oder der Bereitschaft, einen externen Dienstleister zu nutzen.

Nachteilig kann es sich auswirken, wenn der Test javascript oder CSS-Selektoren enthält, die von der Testumgebung (Plattform,. Browser, Version) nicht unterstützt oder divergierend zu anderen Kombinationen interpretiert wird – dann wird der Code komplizierter und muss Unterscheidungen vornehmen, oder es müssen unterschiedliche Tests deployed werden.

tbwdjs implementiert im Moment eine Vielzahl von Tests und Commands, die webdriverjs erweitern. Es ist aber die Bestrebung da, diese Erweiterungen so zu implementieren, dass sie mittels PullRequests webdriverjs aufgehen, so dass die gegen tbwdjs entwickelten Tests innerhalb des Seleniumuniversums grundsätzlich lauffähig sind.

Das Code-Beispiel:

  • Daten für die Testinteraktion
'use strict';

varwebdriverjs = require('tbwdjs');
varutil = require('util');

varusername = 'user.name';
varpassword = 'XXX';

varemail_addr = 'localpart@mysite.com';

varurl = 'http://my.site.com/';

varregVals = {};
regVals['#loginname'] = username;
regVals['#password'] = regVals['#checkpassword'] = password;
regVals['#email'] = email_addr;
  • Setup des webdriver-Clients (z.T. testingbot.com-spezifisch)
/* testingbot.com / selenium config */varbrowser = 'firefox';
varversion = 12;
varplatform = 'WINDOWS';

/* tags are called groups at testingbot.com  they ease navigating the test results*/vargroups = 'mytest';

/* Setup the selenium webdriver *//* identify the test as "(filename - .js) testdomain"*/varparts = __filename.split('/');
varname = parts[parts.length - 1].replace('.js', '') + ' ' + site;

/* credentials are in $HOME/.testingbot */varclient = webdriverjs.remote({
  host: 'hub.testingbot.com',
  name: name,
  desiredCapabilities: {
    browserName: browser,
    version: version,
    platform: platform,
    groups: groups,
    screenshot: true
  },
  logLevel: 'silent'
});
  • Initialisierung des Client und öffnen der URL. Hier wird u.U. eine virtuelle Maschine im testingbot-Grid hochgefahren – dann dauert der Testanfang etwas länger.
client.showInfo('nn' + url + ' ' + browser + ' ' + version);

try {
  client
    .init()
    .url(url)
  • Ist der Titel des Browserfensters korrekt? Wie lautet der interne Windowhandle des aktiven Fensters im Browser?
.titleMatches(/^my.site.com Title$/)
.windowHandle(function(handle) {
    if (handle.status === 0) {
      client.showInfo('Window handle is ' + handle.value);
    }
  })
  • Per javascript wird das input-Element mit Namen 'hokaey' gefüllt; der Test wartet bis zu 7 Sekunden auf einen Wert.
.waitForValueIn('input[name="hokaey"]', 7000)
  • Es kann javascirpt-Code in den aktiven Browser eingeschleust und dort ausgeführt werden
.evaluate(
  'return' +
  '/^[a-z0-9]{8}-[a-z0-9]{32}$/' +
  '.test(document' +
  '.querySelector('input[name="hokaey"]')' +
  '.value);',
  true, 'my.site did set hokaey')
  • Die Sichtbarkeit von Elementen des DOM kann über CSS-Selektoren erfragt werden
.cssVisible('.myOverlay1', false)
  • Einen Link mit der Maus anklicken
.click('a.doregister')
  • Die Ausführung des Tests pausieren, um die Arbeitszeit der Anwednung im Hintergrund zu berücksichtigen
.pause(1000)
  • Mit tbwdjs.setValues() können mehrere Werte von DOM-Elementen in einem Rutsch gesetzt werden
.cssVisible('.myOverlay1', true)
.setValues(regVals, null)
.pause(300) /* sometimes the click failes */
  • Neben einem einfachen click auf ein DOM-Element, kann auch ein click auf einen Button einfach simuliert werden.
.buttonClick('button.submit')
  • Auch auf DOM-Elemente kann gewartet werden, in diesem Fall bis zu 40 Sekunden
.waitFor('#registrationDoneOverlay', 40000)
  • Wenn durch den buttonClick ein weiteres Fenster (oder ein Frame) aufgemacht wurde, kann zu diesem gewechselt werden.
.pause(3000) /* wait for window to open */
.switchWindow()
.titleMatches(/.*hell yeah we did it.*/)
  • Einige der tbwdjs Methoden verändern das interne Array _errors; der Zugriff auf die Fehler wird sich zukünftig noch ändern, aber aktuell kann auf folgende Art durch den exit-Status des Testprogrammes der interne Fehlerzustand angezeigt werden
  .end(function() {
        process.exit(client._errors.length === 0 ? 0 : 1);
      });
} catch (err) {
  client.showInfo(util.inspect(err));
  process.exit(2);
}