Capybara und AJAX – Die ultimative Lösung?

Wir bei Taktsoft arbeiten in unseren Projekten test-driven. Gerade bei kleinschrittigen Tests, die im Browser laufen sollen, standen wir oft vor folgendem Problem: Im Zusammenhang mit AJAX verliefen Tests oft nicht deterministisch, obwohl es im Code...

Wir bei Taktsoft arbeiten in unseren Projekten test-driven. Gerade bei kleinschrittigen Tests, die im Browser laufen sollen, standen wir oft vor folgendem Problem: Im Zusammenhang mit AJAX verliefen Tests oft nicht deterministisch, obwohl es im Code eigentlich keinen Fehler gab.

Diesem Phänomen wollten wir auf den Grund gehen und haben unsere übliche Testumgebung dafür projektunabhängig und sehr einfach nachgebaut: Capybara simuliert für uns im Browser, wie ein User über eine Rails 3 App einen Post mit dem Inhalt Title und Body erstellt oder löscht. Der Post wird dann in einer PostgreSQL-Datenbank gespeichert. Den Browser stellt in unserem Test-Szenario PhantomJS dar, der mittels Poltergeist mit Capybara kommuniziert.

Unser RSpec Feature Test, der prüft, ob der Post-Zähler von 0 auf 1 geht, schlägt direkt fehl:

Capybara & AJAX: die ultimative Lösung? — Präsentation Seite 3

Die ersten Lösungsansätze mussten wir relativ schnell verwerfen: Den Post im HTML-Code anhand der ID mit "#post_#{ID}" zu suchen geht nicht, da wir die ID des Posts, also die des neuen Datensatzes an dieser Stelle noch gar nicht kennen.

Zwei weitere Überlegungungen waren eine HTML-Klasse, beziehungsweise ein HTML class-Attribut nur für den Test in den Code zu schreiben oder eine Sleep-Funktion einzubauen. Die Sleep-Funktion hätte eine eingestellte Wartezeit gehabt, die nach dem Klick auf “Create Post” der Datenbank genügend Zeit zum Aktualisieren gibt, bevor das Ergebnis geprüft wird. Das würde zwar in diesem Fall zwar funktionieren, ist jedoch keine elegante Lösung und kommt daher nicht für uns in Frage. Auch gegen die zusätzliche Klasse haben wir uns entschieden, da wir keinen App-Code einbauen möchten, nur damit ein Test funktioniert.

Schlussendlich haben wir folgenden Ansatz weiter verfolgt: Mit Hilfe der globalen AJAX Event-Handler von jQuery jquery.ajaxStart und jquery.ajaxStop setzen wir ein HTML-Attribut an ein HTML-Tag, an dem sich wiederum der Status des AJAX-Requests “ablesen" lässt. Wir haben uns für den Body Tag entschieden, da er bei jeder HTML-Seite verfügbar ist.

In unserer prototypischen Implementierung (oder Proof of Concept) definieren wir dafür zwei Helper-Methoden. Zunächst wait_for_ajax_install, welche mittels page.execute_script die globalen AJAX Event-Handler installiert. Eine andere, wait_for_ajax_stop, definiert eine Expectation zur Abfrage des AJAX Status.

Capybara & AJAX: die ultimative Lösung? — Präsentation Seite 7 Capybara & AJAX: die ultimative Lösung? — Präsentation Seite 8

wait_for_ajax_install sollte nach einem visit aufgerufen werden und wait_for_ajax_stop vor der Expectation, für die der AJAX Request verarbeitet sein soll.

Und wie erhofft schlägt unser RSpec Feature Test mit dieser Implementierung nicht mehr fehl.

Den Code gibt es übrigens auf Github und über Forks, Kommentare und Anregungen freuen wir uns natürlich.


Zur Blog-Übersicht