Selenium StaleElementReferenceException: Wenn »schnell« mal zu schnell ist

Manchmal erlebt man beim Testen noch echte Überraschungen. Zum Beispiel dann, wenn gut geplante und lokal ausgeführte Tests urplötzlich nicht mehr funktionieren, wenn sie auf einem anderen Rechner oder einem Buildserver ausgeführt werden. So meldete sich die Browser-Automatisierung »Selenium« plötzlich mit dieser erheiternden Meldung:

StaleElementReferenceException: Message: Element not found in the cache - perhaps the page has changed since it was looked up

Das Leben als Tester ist nicht immer einfach. Besonders dann nicht, wenn man mit Fehlern konfrontiert wird, die es eigentlich nicht geben kann. Und schon überhaupt nicht, wenn diese Fehler, die es nicht geben kann, dann auch noch Tests selbst betreffen.

Genau in dieser undankbaren Situation befand ich mich nun die vergangenen Tage. Ich habe eine Reihe an Tests mit Selenium für Python implementiert, um auf einer Testseite einen Registrierungsprozess zu validieren. Eigentlich nichts tragisches: Auf der Seite muss ein Button geklickt werden. Dann öffnet sich ein Dialog, dessen Felder mit Eingaben befüllt werden wollen. Da die Bedienung tastaturgestützt funktionieren soll, wird auf die Verwendung der Maus bei den Tests verzichtet und zwischen den einzelnen Schritten mit der Enter-Taste fortgeschritten.

Die Tests waren alle implementiert und funktionierten auf meinem Notebook perfekt. Ich fand zwar – wie erwartet – noch den einen oder anderen Bug in der Implementierung des Formulars, aber am Ende stand ein gutes Set von Tests. Ich war zufrieden. Die Spezifikation war ausreichend abgedeckt. Also wurde alles committed und ins zentrale Repository gepusht. Letzterer Vorgang triggerte sogleich den Build auf dem CI-Server, welcher – selbstverständlich – auch alle Testfälle durchführt. Doch hier geschieht es. Statt des erwarteten grünen Buildergebnisses grinst mich eine hässliche rote Statusmeldung an. Der Build ging schief. Nahezu alle der gerade implementierten Tests gingen in die Binsen. Gemeinsam hatten sie diese Fehlermeldung:

selenium.common.exceptions.StaleElementReferenceException: Message: Element not found in the cache - perhaps the page has changed since it was looked up
Stacktrace:
    at fxdriver.cache.getElementAt (resource://fxdriver/modules/web-element-cache.js:9407)
    at Utils.getElementAt (file:///tmp/tmpythk_nhj/extensions/fxdriver@googlecode.com/components/driver-component.js:8992)
    at FirefoxDriver.prototype.findElementInternal_ (file:///tmp/tmpythk_nhj/extensions/fxdriver@googlecode.com/components/driver-component.js:10713)
    at FirefoxDriver.prototype.findChildElement (file:///tmp/tmpythk_nhj/extensions/fxdriver@googlecode.com/components/driver-component.js:10735)
    at DelayedCommand.prototype.executeInternal_/h (file:///tmp/tmpythk_nhj/extensions/fxdriver@googlecode.com/components/command-processor.js:12614)
    at DelayedCommand.prototype.executeInternal_ (file:///tmp/tmpythk_nhj/extensions/fxdriver@googlecode.com/components/command-processor.js:12619)
    at DelayedCommand.prototype.execute/< (file:///tmp/tmpythk_nhj/extensions/fxdriver@googlecode.com/components/command-processor.js:12561)

Diese Fehlermeldung wird normalerweise dann geworfen, wenn man mit Selenium ein Element selektiert hat, dieses aber zum Zeitpunkt der Verwendung im Test nicht mehr Bestandteil der Seite ist. Dies geschieht beispielsweise dann, wenn man zu einer anderen Seite gewechselt ist – durch Klicken eines Links oder so. Doch genau das gibt es für dieses Registrierungsformular nicht. Es ist ein Formular auf einer Seite. Mehr nicht. Wenn für das Formular das Submit-Event ausgelöst wird, werkelt JavaScript-Code los und erledigt ein paar Dinge mit AJAX-Requests. Aber eben keine neue oder andere Seite.

Dummerweise gibt es keine direkte Möglichkeit, den Buildserver und die dortigen Firefox-Fenster zu beobachten. Ein weiteres Mal werde ich daran erinnert, dass man bei Selenium-Tests im Fehlerfall vorsehen sollte, einen Screenshot zu erstellen. In meiner Verzweiflung (und Faulheit, die Screenshot-Lösung nicht implementieren zu wollen) probiere ich die Tests nun noch auf zwei weiteren Rechnern, die einen Bildschirm haben, aus. Rechner 1 zeigt das erwartete Verhalten: Die Tests laufen grün. Dann kommt Rechner zwei. Auch hier erwarte ich nichts anderes, doch in den Augenwinkeln nehme ich auf einmal ein unbekanntes Bild wahr, das so gar nicht zur Anwendung unter Test passt:

Plötzliche Fehlermeldung im Test-Firefox-Fenster

Anhand der URL, die in der 404-Fehlermeldung wiedergegeben wurde, war dann doch schnell klar, was hier passiert: Der JavaScript-Submit-Handler hat versagt und es wurde vom Browser versucht, nach Senden der Enter-Taste einen ganz normalen HTTP-Post an die URL auszuführen, die als Pseudo-URL im action-Attribut des Registrierungsformulars steht. Dies geschah niemals bei den manuellen Vortests, nie auf meinem Notebook, nie auf Test-Rechner 1. Aber reproduzierbar und dauerhaft auf Test-Rechner 2. Ich teste also händisch das Formular auf Test-Rechner 2. Nun, im manuellen Test, funktioniert alles. Das Formular arbeitet genau so, wie ich es erwarte. Und auch genau so, wie es der Test erwartet.

Liegt es also an Selenium? Doch wieso funktioniert es dann auf manchen Rechnern und auf anderen nicht? Liegt es an der Selenium-Version? Nein, diese ist bei allen Tests gleich. Ebenso die Firefox-Version. Und die verwendete Python-Version. Auch das Betriebssystem der Testrechner lässt keinen schnellen Rückschluss zu: Hier gibt es ein buntes Sammelsurium aus openSUSE® Tumbleweed, Ubuntu® Linux, einer Debian® nachempfundenen Iceweasel-Umgebung im Docker-Container und Windows® 8.1, wobei Windows® 8.1 und Ubuntu® funktionieren, openSUSE® und Debian® hingegen nicht.

Ich fange an, mit attachtem Remote Debugger die Tests auszuführen. Ich will wissen, in welchem Zustand sich Browser und Dokument befinden, wenn der Fehler hochkommt. Doch nun tritt der Fehler auch auf der Tumbleweed-Maschine nicht mehr auf. A/B-Vergleich: Ohne attachten Debugger kommt es zum Fehler, mit attachtem Debugger laufen alle Tests grün. Damit kommt endlich Licht ins Dunkel: Es muss also irgendwas mit dem Timing zu tun haben.

Nach zwei-drei Versuchen fällt mir die Lösung wie Schuppen von den Augen: Die Debian®- und die openSUSE®-Maschine sind leistungsmäßig in einer ganz anderen Klasse als mein Notebook und der Windows® 8.1-PC. Sie verfügen über stärkere CPUs und SSDs. Und genau hier liegt das Problem: Selenium kann sehr schnell beginnen, Eingaben zu tätigen, noch bevor der Browser den On-Submit-Handler im Formular installieren kann. Somit kommt es beim »Drücken« der Enter-Taste durch Selenium zur Form Submission als HTTP-POST-Request – und damit korrekterweise zum geworfenen Fehler: In der Tat gibt es schon eine neue Seite im Browser (die 404-Fehlerseite) wenn der Test mit der Auswertung der erwarteten Elemente beginnt.

Die Lösung ist dann denkbar einfach. Bevor mit Eingaben begonnen wird, setze ich einfach ein sleep(1). Der Test ist nun stabil. Ich muss mich mal noch tiefer in die Selenium Explicit Waits einarbeiten. Vielleicht gibt noch eine elegantere Lösung. Vorerst bin ich zufrieden.

Manchmal ist ein schneller Rechner einfach »zu schnell«.


Kommentare

Noch keine Kommentare.