Email-Korrespondenz in Selenium integrieren

Das häufigste Testszenario, in dem Emails integriert sind, ist der Registrierungsvorgang bei einer Internet-Anwendung. Nachdem der neue User das Kontaktdatenformular ausgefüllt und abgeschickt hat, bekommt er eine Email in die Mailbox, welche ein Sicherheitsmerkmal (Link, Passwort) enthält, dessen Benutzung die Registrierung erfolgreich abschließt.

Nun ist der Einsatz von Selenium weitgehend auf die Reichweite des Browsers beschränkt, will sagen, dass man nicht ohne andere Tools und Aufwand auf Mailclients (Outlook, Thunderbird, …) bei der Automatisierung der Tests zurückgreifen kann. Es gibt jedoch die Möglichkeit sich eines Webmail-Clients zu bedienen, der eine Administrationsoberfläche für Emails darstellt, die vollständig innerhalb eines Browsers bedient werden kann. Den Ansatz über einen Instant-Email-Dienst (spambog.com, instant-mail.de, …) zu gehen, empfehle ich für den produktiven Testeinsatz nicht, da diese im Emailempfang mindestens verzögert, wenn nicht gar gänzlich unzuverlässig sind. So hatte beispielsweise spambog.de bei einem Einsatz eine Verzögerung von genau einer Stunde. Bei meinem aktuellen Auftrag: www.hiorg-server.de wird ausdrücklich von der Nutzung von den bekannten Mailprovidern (web.de, hotmail.de, …) abgeraten, da manche Funktionen des hiorg-server.de dort als Spam gewertet werden (natürlich ohne es zu sein), weshalb diese Möglichkeit auch wegfällt. Ich nehme mal an, dass die meisten Tester mindestens einen Webspace mit eigener Domain und angeschlossenem Webmail-Client besitzen. Dies ist für den Standardgebrauch in Selenium ausreichend. Ich benutze für diesen Artikel Atmail, weshalb auch meine WebMail-Klasse auf diesen WebMailer ausgerichtet ist.


Die Auslagerung der Emailfunktionen in eine separate Mail-Klasse entspricht der Anforderung des Page Object Patterns, dass man die Navigation beliebig umbauen kann, ohne jedoch die Testklassen selbst ändern zu müssen. Dieses Design schließt auch ein, dass der domain part der Email-Adresse (wiki) nicht in der Testklasse auftaucht. Diese Testklasse kenne nur die Methoden der Mail-Klasse, ohne ihre Implementierungsdetails, also in unserem Fall:
new WebMail().getRegistrationText(String keywordInSubject, boolean löscheEmail)
1.) Das keywordInSubject ist notwendig, um die Email meines Threads eindeutig in meiner Mailbox zu identifizieren. Bei paralleler Ausführung unterschiedlicher Testcases (mit Email-Beteiligung) kann man naturgemäß als Tester nicht die Reihenfolge vorausahnen, wann welcher Thread die Mailbox erreicht, um dort „seine“ Email abzuholen. Hier entsteht also eine Race-Condition zwischen den parallel getesteten Testcases. Logiken, wie „nimm die erste Email in der Mailbox“ scheiden daher aus. Wir benötigen eine Möglichkeit wie der Thread „seine“ Email erkennt, diese also eindeutig in der Mailbox unterscheidbar ist.
2.) mit boolean löscheEmail kann ich mich entscheiden, ob nach der Prüfung die Registriermail gelöscht werden kann, um unseren Stall sauber zu halten…

Und hier der Code:
EmailTest.java

package de.hiorgserver.testhiorgserver;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.Assert;
import org.testng.annotations.Test;

public class EmailTest {

    @Test
    public void testEmail() throws MalformedURLException, InterruptedException {

        String username = "cloudbees_it-kosmopo";
        String accessKey = "XXX";
        String url = "http://" + username + ":" + accessKey + "@ondemand.saucelabs.com:80/wd/hub";
        

        DesiredCapabilities cap = DesiredCapabilities.firefox(); //Browserdefaults auswählen
        cap.setCapability("version", "15"); //Version des Browsers festlegen
        cap.setCapability("platform", "Windows 2008"); //Betriebssystem festlegen
        cap.setCapability("username", username); //Mein Username bei SauceLabs
        cap.setCapability("accessKey", accessKey); //Mein Access-key bei SauceLabs

        WebDriver driver = new RemoteWebDriver(new URL(url),cap);

        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        WebMail webmailer = new WebMail(driver, "test");
        String emailText = webmailer.getRegistrationText("mein Blog", true);

        Assert.assertTrue(emailText.contains("Benutzerkonto"), "Prüfung, ob der Registrierungstext das Schlüsselwort 'Benutzerkono' enthält.");
        driver.quit();
    }
}

WebMail.java

package de.hiorgserver.testhiorgserver;

import java.util.List;
import java.util.Set;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.interactions.Actions;

public class WebMail {
    WebDriver driver;
    String mainWindow;
    
    WebMail (WebDriver driver,String localPart) {
        this.driver = driver;
        
        String url = "http://webmail.it-kosmopolit.de/";
        String password = "XXX";

        //Öffne ein neues Fenster, dann kann ich es zwischendurch immer wieder verwenden.
        mainWindow = driver.getWindowHandle();
        JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript("window.open('','Email-Fenster')");
        driver.switchTo().window("Email-Fenster");
        
        driver.get(url);
        driver.switchTo().frame("GroupingFrame");
        driver.findElement(By.id("username")).sendKeys(localPart);
        driver.findElement(By.id("password")).sendKeys(password);
        driver.findElement(By.name("Submit")).click();
    }

    String getRegistrationText(String keywordInSubject, boolean löscheEmail) throws InterruptedException {
        if ( ! driver.getCurrentUrl().contains("http://webmailtest.it-kosmopolit.de")) driver.switchTo().window("Email-Fenster") ;
        
        Thread.sleep(2000); //nur für's Schauvideo angehalten.
        
        Actions action = new Actions(driver);
        action.doubleClick(driver.findElement(By.xpath("//*[contains(text(),'" + keywordInSubject + "')]")));
        action.perform();
        
        Thread.sleep(2000); //nur für's Schauvideo angehalten.
        
        driver.switchTo().frame("msgwindow1");
        String link = driver.findElement(By.xpath("//*[contains(text(),'Registrierung')]")).getText();
        
        if (löscheEmail) {
            driver.switchTo().defaultContent();
            driver.findElement(By.id("Folderdelete")).click();
        }
        Thread.sleep(2000); //nur für's Schauvideo angehalten.
        
        driver.get(link);
        
        Thread.sleep(2000); //nur für's Schauvideo angehalten.
        
        String text = driver.findElement(By.tagName("body")).getText();
        
        driver.switchTo().window(mainWindow);
        
        return text;
    }
}

Und hier der Testverlauf samt Video: https://saucelabs.com/tests/5e80dba5bfe74327aa68aaaefaa08737#
Hinweis: der Test würde noch schneller laufen, wenn ich nicht den Thread mehrmals schlafen gelegt hätte, damit der Programmverlauf im Video leichter nachvollzogen werden kann.

„Verschärfte“ Testbedingungen
Die Internetanwendung kann erzwingen, dass die Email-Adressen innerhalb der Internet-Anwendung eindeutig sein müssen. Dies ist beispielsweise dann der Fall, wenn die Email gleichzeitig die Bezeichnung des Users ist. Wenn ich nun gegen eine Testmaschine teste, habe ich bei einer hartcodierten Emailadresse nur einen Test frei. Jeden wiederholten/parallelen Testdurchlauf wird mir die Testumgebung quittieren mit: „Ihre Email-Adresse ist bereits vergeben“. Dann müsste theoretisch jedes Mal die Datenbasis der Testumgebung auf den „Zeitpunkt 0“ zurückgestellt werden – ein hoher manueller Aufwand!

Einen Lösungsansatz für diese verschärften Testbedingungen möchte ich an dieser Stelle nur mal skizzieren:

  1. Neuen Webspace incl. Domain erstellen
  2. als local part (wiki) einen Timestamp (natürlich nur erlaubte Zeichen) nehmen. Durch den (Millisekunden-genauen) Timestamp ist mit hoher Wahrscheinlichkeit die verwendete Email-Adresse unique. Anhand des Empfängers kann man die Emails im Webmail-Client also eindeutig identifizieren.
  3. Forward mail of non-existent user to following-default Emailadresse: test@neuedomain.de
  4. test@neuedomain.de per Webmail-client aufrufen