Stefani Gerber Senior Software Engineer

Selenium RC

Dec 1, 2015 6:44:42 PM

Einführung

2004 hat Jason Huggins eine JavaScript-Bibliothek erstellt, die es ihm ermöglichte, einen Browser zu steuern und somit Interaktionen zu automatisieren. Diese wurde später unter dem Namen Selenium Remote Control opensource veröffentlicht. Später entstand Selenium Webdriver, mit welchem im mich im nächsten Beitrag befassen werde.

Selenium RC ist offiziell deprecated. In besonderen Umgebungen, z.B. wenn nur ältere Versionen eines Browsers verwendet werden, kann es nötig sein, Selenium RC weiterhin einzusetzen. Abgesehen davon lässt sich mit Selenium RC schön illustrieren, wie Testskripte und Browser interagieren.

Wie funktioniert Selenium RC?

Selenium RC hat mehrere Bestandteile:

  • Das Core umfasst die JavaScript-Bibliothek, die schlussendlich den Browser steuert.
  • Der Server startet den Browser inklusive der JavaScript-Bibliothek. Danach leitet er sämtliche Befehle vom Testskript an das in den Browser injizierte JavaScript weiter und allfällige Resultate wieder zurück an das Testskript.
  • Die Client Libraries / Bindings bieten Schnittstellen in verschiedenen Programmiersprachen an, um den Server anzusteuern. Sie werden also verwendet, um das Testskript zu implementieren.

Weshalb Selenium RC?

Im Vergleich zu Selenium IDE (siehe auch Blogeintrag zu Selenium IDE) bietet Selenium RC viel mehr Möglichkeiten:

  • Es können Schleifen oder bedingte Anweisungen verwendet werden; es ist so möglich, das Testskript über Parameter zu steuern oder auf den Zustand der zu testenden Software zu reagieren. Ein Beispiel ist, die Befehle zum Einloggen auf die zu testende Software nur dann auszuführen, wenn die Applikation auf die Login-Seite weiterleitet.
  • Die Testskripte können automatisch gestartet werden und können so in den Continuous Build integriert werden. So kann auch das GUI regress-getestet werden.
  • Es können auch andere Browser als Firefox gesteuert werden.
  • Da die Kommunikation zwischen dem Testskript und dem Server über HTTP läuft, ist es nicht zwingend notwendig, dass sich beides auf derselben Maschine befindet. Somit lassen sich Selenium-Tests auch remote durchführen.

XPath

Im nachfolgenden Beispiel werden die Elemente auf der Webseite mit XPath, einer Abfragesprache für XML durchgeführt. Selenium erlaubt es auch, die Elemente per CSS anzusprechen. Browser-Plugins erleichtern hier das Entwickeln, z.B. FirePath.

Beispiel eines Testskripts

Im letzten Blogbeitrag habe ich die Exportmöglichkeit von Selenium IDE in verschiedene Formate wie z.B. Java-Source-Code erwähnt und auch folgendes Beispiel abgebildet:

package com.example.tests;

import com.thoughtworks.selenium.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.regex.Pattern;

public class SampleTestCase {
    private Selenium selenium;

    @Before
    public void setUp() throws Exception {
        selenium = new DefaultSelenium("localhost", 4444, "*chrome", "https://www.google.ch/");
        selenium.start();
    }

    @Test
    public void testSampleTestCase() throws Exception {
        selenium.open("/");
        selenium.type("id=lst-ib", "Bluesky");
        selenium.click("name=btnG");
        selenium.click("link=Bluesky IT-Solutions AG");
        selenium.waitForPageToLoad("30000");
        verifyEquals("SOFTWARE DEVELOPMENT", selenium.getText("//div[@id='menu']/ul/li[2]/a/div/div"));
    }

    @After
    public void tearDown() throws Exception {
        selenium.stop();
    }
}

Anhand von diesem lassen sich ein paar Punkte gut illustrieren:

  • Das Laden der Seite (nach Absenden der Suchanfrage) muss abgewartet werden. Hier mit der Zeile
    selenium.waitForPageToLoad("30000");

    Übergeben wird der Methode ein Timeout (hier 30s); ist bis dahin die Seite nicht geladen, bricht das Skript ab.

  • technisches Detail: die Testklasse muss von SeleneseTestCase erben, damit sie kompiliert (verifyEquals kann sonst nicht erkannt werden)
  • in der Selenium-IDE hatte ich die Geschwindigkeit mit dem Slider auf langsam gesetzt, dies muss ich im Java-Code noch nachführen, sonst läuft das Skript schneller, als Google suchen kann und resultiert somit in einem Fehler.
    selenium.setSpeed("1000");
  • Das Markup von Google hat sich inzwischen geändert: der Submit-Button hat im Name-Attribut nun nicht mehr den Wert btnG sondern btnK stehen. Wichtig ist also, potentiell mehrfach-verwendete Selektoren zentral zu definieren.
  • Auch die Seite von Bluesky ist nicht mehr dieselbe: der zweite Menüeintrag war vor ein paar Monaten noch SOFTWARE DEVELOPMENT, jetzt ist er DIGITAL PRESENCE. Nun stellt sich die Frage, was wir mit
    verifyEquals("SOFTWARE DEVELOPMENT", selenium.getText("//div[@id='menu']/ul/li[2]/a/div/div"));

    testen wollen:

    • wollen wir sicherstellen, dass der zweite Menüeintrag immer SOFTWARE DEVELOPMENT heissen wird, ist der Test so ok. Der Selektor //div[@id=’menu‘]/ul/li[2]/a/div/div ist sehr spezifisch, d.h. er gibt sehr stark vor, wie die Markupstruktur auszusehen hat. Weniger einschränkend wäre z.B. //*[@id=’menu‘]//li[2].
    • wollen wir mit dem verifyEquals aber nur eine Vorbedingung für den nächsten Testschritt überprüfen, nämlich dass ein bestimmter Menüeintrag vorhanden ist, weil wir damit interagieren wollen, ist der Selektor anzupassen
      verifyTrue(selenium.isElementPresent("//*[@id='menu']//*[contains(text(), 'DIGITAL PRESENCE')]"));

Nach diesen Anpassungen läuft das Testskript erfolgreich durch und sieht folgendermassen aus:

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.thoughtworks.selenium.*;

public class SampleTestCase extends SeleneseTestCase {
    private Selenium selenium;
    private static String googleSubmitButton = "name=btnK";

    @Before
    public void setUp() throws Exception {
        selenium = new DefaultSelenium("localhost", 4444, "*chrome", "https://www.google.ch/");
        selenium.start();
        selenium.setSpeed("1000");
    }

    @Test
    public void testSampleTestCase() throws Exception {
        selenium.open("/");
        selenium.type("id=lst-ib", "Bluesky");
        selenium.click(googleSubmitButton);
        selenium.click("link=Bluesky IT-Solutions AG");
        selenium.waitForPageToLoad("30000");
        verifyTrue(selenium.isElementPresent("//*[@id='menu']//*[contains(text(), 'DIGITAL PRESENCE')]"));
    }

    @After
    public void tearDown() throws Exception {
        selenium.stop();
    }
}

 

Tipps

  • Robuste Selektoren: In den meisten Fällen verändert sich die zu testende Software über die Zeit. So werden neue Features eingefügt, design-bedingte Wrapper erstellt oder HTML-Tags umgeschrieben. Oft haben diese Änderungen aber nichts mit dem spezifischen Test zu tun. Wenn wir z.B. den Selektor //div[@id=’menu‘]/ul/li[2]/a/div/div verwenden, um einen bestimmten Menüeintrag auszuwählen wird das Testskript failen, sobald die Markup-Struktur des Menüs ändert oder auf der Seite ein neuer Menüeintrag vor dem angestrebten eingefügt wird. Besser ist ein weniger spezifischer Selektor; z.B.  oder noch besser table[@class=’statistics‘] weil der unempfindlicher ist auf Änderungen im weiteren Umfeld.

Grenzen von Selenium RC

  • Die Steuerung durch JavaScript verhält sich nicht immer genau gleich wie die tatsächliche Benutzer-Interaktion mit der Applikation.
  • Da die Steuerung des Browsers mit JavaScript ausgeführt wird kann es zu unterschiedlichem Verhalten bei verschiedenen Browsern kommen, wenn sie das entsprechende JavaScript-Codestück unterschiedlich interpretieren.

Ausblick

In einem nächsten Beitrag werden wir sehen, wie sich Selenium Webdriver von Selenium RC unterscheidet.