Seite wählen

Aus der QA-Küche: Selenium IDE an jung-dynamischen IDs

von | Feb 13, 2012 | Icinga, Development

Inzwischen habe ich die wichtigsten Teile von icinga-web erfasst und indiziert (ich erwähnte diesen Vorgang bereits beiläufig) und sogar die meisten der nötigen Testfälle sind be- und geschrieben, wenngleich diese hie- und da Nachbesserungen und Erweiterungen erfahren werden. Den Index selbst halte ich allerdings für schwer verdauliche Kost für Blog-Einträge. Stattdessen serviere ich ein paar Grundlagen im Umgang mit Selenium IDE mit ausgewählten Problemen der Saison, die sich nicht schnell genug auf die Bäume retten konnten.
Ein kurzer Blick auf icinga-web verrät uns: ein paar Eigenheiten machen es einem nicht leicht, automatisierte Tests durchzuführen. Als da wären zu nennen:

  • Recht umfangreiche, dynamische Struktur mit nur einigen wenigen fest vergebenen IDs (mehr für zukünftige Versionen geplant).
  • Zeitkritische, Performance-abhängige Vorgänge (Datenbankzugriff, Authentifizierung)
  • Im Hintergrund (die per Selenium natürlich angesprochen werden können) befindliche, je nach Nutzerrechten variierende und durch Plug-Ins erweiterbare Elemente

Die Basis: Ich hab da mal was vorbereitet …
Einigen Komplikationen lassen sich durch regelmäßiges Zurücksetzen des Benutzerprofils, kurz gehaltenen (ggf. in mehrere Abschnitte aufgeteilte) Testfällen, löschen der Cookies und möglichst wenig geöffneten Modulen vorbeugen. Als Grundlage dient Icinga mit einer leicht modifizierten Testkonfiguration, etwa mit zugefügten Custom Variables und geänderten „Flapping“-Einstellungen, um nachvollziehbare Werte zu erhalten. Neue Commits erkennt und installiert Jenkins CI automatisch, initialisiert die Datenbank neu, entfernt Überbleibsel wie Logs, Cachedateien etc. und startet schließlich der Selenium Server mit unserer Test-Suite.
Ja, wo laufen sie denn – Verwirrung (vor)programmiert
Trotz aller Vorsichtsmaßnahmen besteht permanent Verwechslungs- und „Nicht-Wiedererkennungs“-Gefahr von Elementen, egal ob nun per ID, xPath, Text oder gar css-Pfad Identifiziert. Da ist es immer wieder erfrischend, was bei vermeintlich unfehlbaren, simplen Click&Verify-Prüfungen alles schief gehen kann. Für die Vorspeise nehmen wir (also das „Kinderarzt-wir“) uns einen Harmlosigkeit heuchelnden „OK“-Knopf zur Brust, den wir (ver)drücken wollen. Bei der Aufzeichnung ermittelt Selenium IDE kurz und bündig:
//div/div/div/div[2]/div/table/tbody/tr/td[2]/table/tbody/tr/td/table/tbody/tr/td/table/tbody/tr[2]/td[2]/em/button
Das ist zum einen weder beim durchsehen der Test-Cases selbst als auch dem Testprotokoll sonderlich aussagekräftig. Zum anderen könnten schon kleinen Änderungen an unserem Test oder der Software eine Korrektur des Pfads nötig machen. Also gestalten wir das ganze ein wenig „unschärfer“, nach dem Motto: So genau wie nötig, so ungenau wie möglich (Hoho! Aufgemerkt – als Lehrer wäre ich große Klasse im Dumme-Lehrsprüche-klopfen …). Mit Firebug den Code betrachtet, kommen wir auf
<button id=“ext-gen225″ type=“button“ style=“background-color: transparent;“>OK</button>
wobei die id=“ext-gen225″ wie erwähnt lustig wechselt. Dagegen lässt sich die class selbst, wenn der Knopf irgendwo anders in der Struktur landet, mit Selenium finden. Als Target sieht das so aus:
//*/em/button[contains(@class, ‚x-btn-text icinga-action-icon-ok‘)]
contains ist in diesem speziellen Fall streng genommen nicht erforderlich. Es ist der exakte Wert und verändert sich nicht. Ebenso könnte man sich /em/button sparen. Es dient der Veranschaulichung. Ganz banal könnten wir auch nach dem Text des Knopfes (oder eines Links) fahnden (beides targets):
link=OK
oder
//*[.=’OK‘]
Trotz allem versagen diese zwei Varianten, sobald ein weiteres Knopf-Pendant mit den gesuchten Attributen vorhanden ist (was des öfteren vorkommt) und wir würden immer das in der Struktur weiter oben angesiedelte „OK“ erwischen.
Direkt ist besser
Bei Funktionen, die lediglich Mittel zum Zweck sind (d.h. nicht einen vorhandenen Dialog, Knopf etc. direkt prüfen soll), etwa „Ausloggen“, können wir zwar einen irgendwo im Code vorhandenen Link via target
//a[contains(@href, ‚/icinga-web/modules/appkit/logout?logout=1‘)]
ausfindig machen und ansprechen. Narrensicher ist es dagegen, die URL direkt aufzurufen oder Seleniums deleteAllVisibleCookies dafür zu missbrauchen. Seite anschließend neu laden, fertig.
Hin und wieder leistet bei dem zwar bequemen, aber eingeschränkten Selenium IDE ein eingestreuter JavaScript-Befehl gute Dienste, wie letztlich im Code der Seite zu finden. Hier ein Beispiel, ob die Zeitmarken der gewählten Zeitzone entsprechen:

Command Target Value
store javascript{new Date().getHours()}  localtime
verifyText //td/div/div/div  *${localtime}*



Oder hier ein direkter Aufruf der Sucheingabe und setzten des Fokus darauf:

Command Target Value
runScript AppKit.search.SearchHandler.getSearchbox().showSearch();
runScript AppKit.search.SearchHandler.getSearchbox().focus();

Alles zu seiner Zeit
Selenium haut die Befehle teils so schnell raus, dass es bei diversen Gelegenheiten wie Markieren, Abhaken, Tastatureingaben und anschließendem Speichern von Einstellungen zu Geschmacksverirrungen kommen kann. Zwar kann es fruchten, die Ausführungsgeschwindigkeit mit setSpeed für bestimmte Abschnitte herabzusetzen oder Pausen zu setzen. Die bessere Lösung ist es jedoch für Gewöhnlich, anderweitig zu sichern, dass die gewünschten Elemente, Eingaben etc. vorhanden sind. Denn wie langsam es laufen muss und darf, ist ein Schätzwert, der je nach Serverauslastung auch mal schwanken kann. Zudem läuft nach einem Fehlschlag womöglich der Rest der Test ebenfalls nur sehr langsam weiter.
Ob ein Element vorhanden ist, genügt oft nicht als Kriterium, beispielsweise, wenn sich dieses im Hintergrund befindet, wie die Globale Suche. Neben Befehlen wie waitForVisible bieten sich als Alternative oft Werte wie attributes, values, positions … zu Prüfung an, die sich bei dem Vorgang ändern. Wir lassen Selenium erst lostippen, wenn das Suchfeld unserer Aufmerksamkeit gewiss ist:

Command Target Value
waitForAttribute name=global_search@class x-form-text x-form-field x-form-focus

Oder lassen warten, bis die Daten unseres Test-Users tatsächlich geladen sind und in den Feldern erscheinen:

Command Target Value
waitForValue name=user_name test

Da alle fünf Minuten die Authentifizierung erneuert wird, kann es vorkommen, dass in einem ungünstigen Moment gesendete Kommandos fehlschlagen. Dem wirken wir mit einem per flowControl-Erweiterung zubereiteten Nachtisch entgegen und senden bei einem Fehlschlag einfach erneut (und drücken die Daumen, nicht ewig der Schleife zu hängen ;)):

Command Target Value
label retrysend
clickAt css=div.x-grid3-row-checker 1,1
clickAt //button[.=&quot;Commands&quot;] 1,1
waitForVisible //*[contains(@class, ‚x-menu-item-icon icinga-icon-comment‘)]
clickAt //*[contains(@class, ‚x-menu-item-icon icinga-icon-comment‘)] 1,1
waitForTextPresent persistent
type name=comment testcomment
clickAt name=comment 10,10
clickAt //*[contains(@class,’x-btn-text icinga-icon-accept‘)] 10,10
storeText //div[2]/div/div/div/div/div/div[2]/span
while storedVars.fail == ‚Authentification failed‘
click //*[.=’OK‘;]
goto retrysend
endWhile

0 Kommentare

Trackbacks/Pingbacks

  1. Aus der QA-Küche 2: Beliebte Probleme mit Selenium und Jenkins › NETWAYS Blog - [...] im vorangegangenen Beitrag möchte ich hier zwar nichts weltbewegendes, jedoch nützliches zu Tests via Selenium [...]
  2. Weekly Snap: Icinga Training, Testing with Selenium & Apprenticeships › NETWAYS Blog - [...] then tested Icinga Web with the help of Selenium IDE to deal with dynamic IDs and Gunnar shared his…

Einen Kommentar abschicken

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Mehr Beiträge zum Thema Icinga | Development

Mein PHP-Trainingsprojekt

PHP Schulung Vor kurzem haben wir begonnen, eine neue Programmiersprache zu lernen – PHP. In der ersten Woche haben wir mit den Grundlagen wie Variablen, Arrays, Schleifen begonnen und uns schrittweise zu komplizierterer Syntax wie Funktionen, Objekten und Klassen...