Übungsaufgaben zu GUI-Tests mit Selenium: Page Objects und stabile Tests für die Mitfahrgesuche

https://bildung.social/@oerinformatik/

https://oer-informatik.de/flurmonitor-gui-test-selenium-uebungsaufgaben

Selenium, WebDriver, GUI-Test, Page Object, Locator, WebDriverWait, instabiler Test, StaleElementReferenceException, Bildschirmfoto, Mitfahrgesuche, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (Bearbeitungszeit insgesamt 58 min, mehrere Aufgaben): Die Seite der Mitfahrgesuche bekommt eigene GUI-Tests. In diesen Übungen wählst Du Locatoren für ihre Elemente und sagst voraus, was Selenium findet, schreibst ein Page Object und Tests für die Gesuche, lässt den Browser bei einem Fehlschlag ein Bildschirmfoto ablegen und stabilisierst einen Test, der in der Pipeline mal grün und mal rot ist [AI-Assisted]. (Zuletzt geändert am 11.11.2026)

Dieser Artikel ist Teil einer Artikelreihe über den Flurmonitor mit Thymeleaf: Projektübersicht / Scrum-Projektstart / Thymeleaf einbinden / Templates / Ansichtsmodell / Integrationstest der Web-Schicht / Konfiguration und Formatierung / Aktualisierung / Hinweise mit Formularen / Robustheit und Betrieb / GUI-Test mit Selenium / Übungsaufgaben zu GUI-Test mit Selenium / Akzeptanztest / Change Request Keycloak

Diese Übungsaufgaben gehören zum Artikel GUI-Tests mit Selenium und Page Objects: der Flurmonitor im echten Browser. Alle Aufgaben spielen im Projektteam der Mitfahr-App Mitfahrlabor und setzen den Übungsstrang zu den Mitfahrgesuchen fort. Ausgangspunkt ist das Projekt im Ordner src/a10-gui-test-selenium des Backend-Repositorys mit den Page Objects und der GuiTestBasis aus dem Artikel. Den Startcode für die Gesuche findest Du dort im Unterordner uebung-mitfahrgesuche; kopiere seinen Ordner src über den Ordner src des Projekts mitfahrlabor. Er enthält den MitfahrgesucheController mit den Adressen /flurmonitor/gesuche und /flurmonitor/{monitor}/gesuche (Parameter ort optional), die GesucheAufbereitung, das Template flurmonitor-gesuche.html und den Platzhalter GesuchService mit zwei offenen Gesuchen für Montag, 16.11.2026: Gütersloh zur Schule (07:00–08:00 Uhr) und Bielefeld nach Hause (15:30–16:30 Uhr). Die Lösung der Story MON-13h aus den letzten Übungen ist im Startcode nicht enthalten.

Klassendiagramm der Page Objects des Flurmonitors: Testklassen erben von GuiTestBasis und nutzen FlurmonitorSeite, AnmeldeSeite und HinweisVerwaltungSeite, die über das Interface WebDriver den Browser steuern (mit KI erstellt)

Hinweise zur Bearbeitung: Bis zur Teil-1-Prüfung der IT-Berufe solltest Du die Basiskompetenz beherrschen, also Aufgabe 1.1. Als FIA, FIDP oder FIDV musst Du zur Teil-2-Prüfung Aufgaben auf Kernkompetenz-Ebene lösen können, das sind Aufgabe 2.1 und Aufgabe 2.2. Von FIA wird zusätzlich erwartet, dass ihnen Aufgaben auf Profilkompetenz-Ebene (Aufgabe 3.1) keine Probleme bereiten. Notiere Deine Antworten zuerst vollständig, bevor Du die Beispiellösung aufklappst. Code-Aufgaben prüfst Du am besten im Projekt: ./gradlew guiTest startet die GUI-Tests, mit -Dgui.sichtbar=true siehst Du den Browser bei der Arbeit (Chrome muss installiert sein). Einzelne Testklassen startest Du mit --tests '*GesucheGuiTest'. Bei Begründungen, Bezeichnern, Locatoren und Testnamen sind andere fachlich richtige Lösungen ebenso richtig.

Du möchtest eigene Aufgaben zum Üben von einem KI-Sprachmodell generieren lassen?

Kopiere den Text in ein KI-Sprachmodell Deiner Wahl und passe Kompetenzstufe, Anzahl, Bearbeitungszeit und Szenario bei Bedarf an. Wie die Aufgaben aufgebaut sind, beschreibt der Artikel Wie sind die Aufgaben aufgebaut?. Prüfe die Ergebnisse mit dem Lehrartikel: KI-Sprachmodelle machen Fehler.

Erstelle neue Übungsaufgaben zum Thema „GUI-Tests mit Selenium: Page
Objects und stabile Tests für die Mitfahrgesuche“.

Bauplan: Halte Dich an Aufbau, Kompetenzstufen, Operatoren,
Anforderungsbereiche, Punkte und Zeitschätzung, die im Artikel
https://oer-informatik.de/aufgaben-aufbau beschrieben sind.

Fachliche Grundlage:
- https://oer-informatik.de/flurmonitor-gui-test-selenium
- https://oer-informatik.de/szenario-mitfahrlabor
- Stilbeispiel: https://oer-informatik.de/flurmonitor-gui-test-selenium-uebungsaufgaben

Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- Selenium
- WebDriver
- GUI-Test
- Page Object
- Locator
- WebDriverWait
- instabiler Test
- StaleElementReferenceException
- Bildschirmfoto
- Mitfahrgesuche

Kompetenzstufe: Kernkompetenz (alternativ: Basiskompetenz, Profilkompetenz, Expertise)
Anzahl: 1 Aufgabe mit Teilaufgaben
Bearbeitungszeit in Minuten ca.: <5 bis 30 min>
Szenario: eines der Szenarien aus dem Bauplan

Vorgehen:
1. Lies zuerst die verlinkten Artikel. Kannst Du keine Webseiten
   abrufen, sag mir das, statt zu raten.
2. Steht bei einer Angabe noch ein Platzhalter in <…>, frag mich
   zuerst danach. Richte Punkte und Zahl der Teilaufgaben nach der
   Bearbeitungszeit (ca. 0,9 min je Punkt).
3. Gib je Aufgabe Titel mit Kompetenzstufe, Ausgangssituation und
   Teilaufgaben mit Operator und Punktzahl aus, dazu die geschätzte
   Bearbeitungszeit.
4. Gib die Beispiellösungen mit Punkteverteilung erst aus, wenn ich
   danach frage.

Aufgabe 1.1: Locatoren für die Seite der Gesuche (Basiskompetenz)

(ca. 15 min Bearbeitungszeit)

Oksana Melnyk möchte die Seite /flurmonitor/mensa/gesuche mit Selenium testen. Im Browser liefert sie (gekürzt) dieses HTML:

<header>
  <nav class="navbar navbar-expand-md bg-primary" aria-label="Hauptnavigation">…</nav>
  <div class="container-fluid px-4 pt-4">
    <h1 class="display-5 fw-bold text-primary">Mitfahrgesuche</h1>
    <p class="lead">Flurmonitor: Mensa</p>
    <p><span>Offene Gesuche:</span> <strong>2</strong></p>
  </div>
</header>
<main class="container-fluid px-4 py-4">
  <div class="table-responsive">
    <table class="table table-striped align-middle caption-top abfahrten">
      <caption>Offene Mitfahrgesuche</caption>
      <thead>
        <tr><th scope="col">Tag</th><th scope="col">Zeitfenster</th><th scope="col">Ort</th>
            <th scope="col">Treffpunkt</th><th scope="col">Richtung</th><th scope="col">Notiz</th></tr>
      </thead>
      <tbody>
        <tr><td><time datetime="2026-11-16">Mo 16.11.</time></td><td>07:00–08:00 Uhr</td><td>Gütersloh</td>
            <td>Hauptbahnhof, ZOB Steig 3</td><td>zur Schule</td><td>komme mit dem Rad zum Bahnhof</td></tr>
        <tr><td><time datetime="2026-11-16">Mo 16.11.</time></td><td>15:30–16:30 Uhr</td><td>Bielefeld</td>
            <td>Bahnhof Brackwede</td><td>nach Hause</td><td></td></tr>
      </tbody>
    </table>
  </div>
</main>
  1. Ordne jedem Bestandteil eines Selenium-Tests seine Aufgabe zu: Testklasse, WebDriver-Bibliothek, ChromeDriver, Chrome, Selenium Manager. (4 Punkte)
Bestandteile eines Selenium-Tests
Bestandteil Aufgabe
Testklasse beschreibt Ablauf und Prüfungen (JUnit, AssertJ), ruft Methoden der Page Objects bzw. des WebDriver auf
WebDriver-Bibliothek stellt get, findElement, click … bereit und schickt sie als W3C-WebDriver-Anfragen (HTTP) an den Treiber
ChromeDriver eigener Prozess, nimmt die Anfragen an und steuert Chrome über dessen Schnittstelle
Chrome lädt und rendert die Seiten, führt JavaScript aus
Selenium Manager sucht den installierten Browser und lädt den passenden Treiber (bei Bedarf auch Chrome for Testing)

Bewertung: je zwei richtige Zuordnungen 1 Punkt, höchstens 4 Punkte.

  1. Gib für die folgenden Elemente je einen geeigneten Locator in Java an: die Überschrift „Mitfahrgesuche“, die Zahl der offenen Gesuche, alle Zellen der Spalte „Ort“. (6 Punkte)
By ueberschrift = By.tagName("h1");                                  // einzige h1 der Seite
By anzahl = By.cssSelector("header p strong");                       // einzige Zahl im Kopf
By orte = By.cssSelector("table.abfahrten tbody td:nth-child(3)");   // dritte Zelle jeder Zeile

Möglich ist auch By.cssSelector("main tbody tr td:nth-child(3)") oder für die Überschrift By.cssSelector("header h1"). Je Element 2 Punkte, davon 1 Punkt für einen funktionierenden und 1 Punkt für einen nicht unnötig langen, nicht absoluten Locator. Ein Locator über den Text „Mitfahrgesuche“ funktioniert, ist aber wegen der englischen Fassung der Seite anfällig (1 Punkt).

  1. Beurteile, ob die folgenden Locatoren robust oder anfällig für Änderungen sind, und begründe kurz. (4 Punkte)
Locatoren zum Beurteilen
Nr. Locator robust oder anfällig? Begründung
1 By.xpath("/html/body/main/div/table/tbody/tr[1]/td[3]")
2 By.cssSelector("table.abfahrten tbody tr")
3 By.xpath("//th[text()='Ort']")
4 By.className("table-striped")
Beurteilte Locatoren
Nr. Locator robust oder anfällig? Begründung
1 By.xpath("/html/body/main/div/table/tbody/tr[1]/td[3]") anfällig: absoluter Pfad, bricht bei jedem zusätzlichen Element (z. B. einem Hinweis über der Tabelle)
2 By.cssSelector("table.abfahrten tbody tr") eher robust: hält sich an die eigene Klasse abfahrten und an die Tabellenstruktur; noch besser wäre eine id oder ein data--Attribut an der Tabelle
3 By.xpath("//th[text()='Ort']") anfällig: hängt vom sichtbaren Text ab, der in der englischen Fassung „Place“ o. Ä. lautet
4 By.className("table-striped") anfällig: Bootstrap-Klasse für die Gestaltung, verschwindet, wenn die Tabelle anders aussehen soll

Je Zeile 1 Punkt (Einstufung und Begründung).

  1. Sage voraus, was browser.findElement(By.id("gesuche")) und browser.findElements(By.id("gesuche")) auf dieser Seite liefern. (2 Punkte)

Die Seite hat kein Element mit id="gesuche". findElement wirft deshalb eine NoSuchElementException, findElements liefert eine leere Liste. Je Aussage 1 Punkt.

Aufgabe 2.1: Page Object und GUI-Test für die Gesuche (Kernkompetenz)

(ca. 15 min Bearbeitungszeit)

Product Owner Thomas Becker möchte, dass die Seite der Gesuche denselben Schutz bekommt wie der Monitor der Fahrten. Für die Stories MON-13a und MON-13d (Gesuche je Monitor, Filter nach Ort) sollen GUI-Tests entstehen, und nach AK-MON-11.3 bekommt auch diese Seite ein Page Object.

  1. Schreibe das Page Object GesucheSeite im Paket de.dbh.mitfahrlabor.guitest.seiten mit einer statischen Methode oeffnen(WebDriver browser, String basisadresse, String monitor, String ort) (ort darf null sein) und den Methoden anzahl() (Zahl im Kopf), orte() (Spalte „Ort“ aller Zeilen als List<String>) und zeigtKeineGesuche() (true, wenn der Satz „Gerade keine offenen Gesuche.“ angezeigt wird; er steht als <p class="fs-4"> direkt in main). (8 Punkte)
package de.dbh.mitfahrlabor.guitest.seiten;

import java.util.List;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

/** Page Object für /flurmonitor/{monitor}/gesuche (MON-13d). */
public class GesucheSeite {

    private static final By ANZAHL = By.cssSelector("header p strong");
    private static final By ZEILEN = By.cssSelector("table.abfahrten tbody tr");
    private static final By KEINE = By.cssSelector("main > p.fs-4");

    private final WebDriver browser;

    public GesucheSeite(WebDriver browser) {
        this.browser = browser;
    }

    /** Öffnet die Gesuche eines Monitors; ort darf null sein (dann alle Orte). */
    public static GesucheSeite oeffnen(WebDriver browser, String basisadresse, String monitor, String ort) {
        String filter = ort == null ? "" : "?ort=" + ort;
        browser.get(basisadresse + "/flurmonitor/" + monitor + "/gesuche" + filter);
        return new GesucheSeite(browser);
    }

    public int anzahl() {
        return Integer.parseInt(browser.findElement(ANZAHL).getText());
    }

    /** Spalte „Ort“ (dritte Zelle) aller Zeilen. */
    public List<String> orte() {
        return browser.findElements(ZEILEN).stream()
                .map(zeile -> zeile.findElement(By.cssSelector("td:nth-child(3)")).getText()).toList();
    }

    public boolean zeigtKeineGesuche() {
        return browser.findElements(KEINE).stream().anyMatch(WebElement::isDisplayed);
    }

    public String titel() {
        return browser.getTitle();
    }
}

Bewertung: Locatoren als Konstanten bzw. an einer Stelle 1 Punkt, oeffnen mit Filter 2 Punkte, anzahl 1 Punkt, orte 2 Punkte, zeigtKeineGesuche mit findElements statt findElement (kein Fehler, wenn der Satz fehlt) 2 Punkte. Die Methode titel() wird erst in Aufgabe 2.2 gebraucht. Die Seite lädt nichts nach; ein Warten im Konstruktor ist deshalb nicht nötig, schadet aber nicht.

  1. Die beiden Gesuche gelten für Montag, 16.11.2026; die Uhr der GuiTestBasis steht aber auf Dienstag, 20.10.2026. Erkläre, was die Tests deshalb ohne Änderung sehen würden, und stelle die Uhr nur für Deine Testklasse auf Montag, 16.11.2026, 06:00 Uhr. Ein Feld mit @TestBean und demselben Namen in der Unterklasse ersetzt das Feld der Basisklasse. (2 Punkte)

Die GesucheAufbereitung zeigt nur Gesuche im Zeitfenster ab „jetzt“. Am 20.10.2026 liegt der 16.11.2026 außerhalb, die Seite zeigt „Gerade keine offenen Gesuche.“ und die Anzahl 0 (1 Punkt).

@TestBean(methodName = "montag")
private Clock uhr;

/** Die Gesuche im Startcode gelten für Montag, 16.11.2026; die Uhr steht auf 06:00 Uhr an diesem Tag. */
static Clock montag() {
    return Clock.fixed(Instant.parse("2026-11-16T05:00:00Z"), UhrKonfiguration.ZEITZONE);
}

(1 Punkt; 05:00 Uhr UTC entspricht 06:00 Uhr in Deutschland im November.) Weil sich die Uhr unterscheidet, startet Spring für diese Testklasse einen eigenen Anwendungskontext.

  1. Schreibe die Testklasse GesucheGuiTest mit drei Tests: Der Monitor in der Mensa zeigt beide Gesuche in zeitlicher Reihenfolge; der Monitor am Ausgang Parkplatz C zeigt mit dem Filter ort=Bielefeld nur Bielefeld; mit ort=Herford zeigt der Monitor in der Mensa die Anzahl 0 und den Satz für keine Gesuche. (6 Punkte)
package de.dbh.mitfahrlabor.guitest;

import static org.assertj.core.api.Assertions.assertThat;

import java.time.Clock;
import java.time.Instant;

import org.junit.jupiter.api.Test;
import org.springframework.test.context.bean.override.convention.TestBean;

import de.dbh.mitfahrlabor.UhrKonfiguration;
import de.dbh.mitfahrlabor.guitest.seiten.GesucheSeite;

class GesucheGuiTest extends GuiTestBasis {

    @TestBean(methodName = "montag")
    private Clock uhr;

    static Clock montag() {
        return Clock.fixed(Instant.parse("2026-11-16T05:00:00Z"), UhrKonfiguration.ZEITZONE);
    }

    @Test
    void mensaZeigtAlleGesuche() {
        GesucheSeite seite = GesucheSeite.oeffnen(browser, adresse(""), "mensa", null);

        assertThat(seite.anzahl()).isEqualTo(2);
        assertThat(seite.orte()).containsExactly("Gütersloh", "Bielefeld");
    }

    @Test
    void parkplatzMitOrtFilter() {
        GesucheSeite seite = GesucheSeite.oeffnen(browser, adresse(""), "parkplatz-c", "Bielefeld");

        assertThat(seite.orte()).containsExactly("Bielefeld");
    }

    @Test
    void ortOhneGesucheZeigtSatz() {
        GesucheSeite seite = GesucheSeite.oeffnen(browser, adresse(""), "mensa", "Herford");

        assertThat(seite.anzahl()).isZero();
        assertThat(seite.zeigtKeineGesuche()).isTrue();
    }
}

Je Test 2 Punkte (Aufruf des Page Objects 1 Punkt, passende Prüfung 1 Punkt). containsExactly prüft auch die Reihenfolge. Die Tests enthalten keine Locatoren mehr; das ist der Sinn des Page Objects.

Aufgabe 2.2: Fehlerseite im Browser und Bildschirmfoto bei Fehlschlag (Kernkompetenz)

(ca. 13 min Bearbeitungszeit)

Für die Story MON-13h im Mitfahrlabor gilt AK-MON-13h.2: Wird die Seite der Gesuche für einen unbekannten Monitor aufgerufen, zeigt ein Browser die Seite „Seite nicht gefunden“ statt JSON. Wenn ein GUI-Test in der Pipeline scheitert, steht im Bericht bisher nur die Ausnahme; was der Browser in diesem Moment gezeigt hat, ist verloren. Jonas Wolff schlägt deshalb vor, bei jedem Fehlschlag ein Bildschirmfoto abzulegen.

  1. Schreibe einen GUI-Test, der AK-MON-13h.2 für die Adresse /flurmonitor/flur-c/gesuche prüft. Die Fehlerseite hat den Titel „Mitfahrlabor – Seite nicht gefunden“. (3 Punkte)
class GesucheFehlerGuiTest extends GuiTestBasis {

    @Test
    void unbekannterMonitorZeigtFehlerseite() {
        GesucheSeite seite = GesucheSeite.oeffnen(browser, adresse(""), "flur-c", null);

        assertThat(seite.titel()).isEqualTo("Mitfahrlabor – Seite nicht gefunden");
    }
}

1 Punkt für den Aufruf, 1 Punkt für die Prüfung des Titels (oder der Überschrift), 1 Punkt für die Methode titel() im Page Object (browser.getTitle()). Die Uhr spielt hier keine Rolle.

  1. Ergänze die GuiTestBasis so, dass bei einem gescheiterten Test ein Bildschirmfoto unter build/reports/bildschirmfotos/<Testklasse>-<Testmethode>.png entsteht. Nutze dafür eine JUnit-Erweiterung, die das Interface AfterTestExecutionCallback umsetzt, und TakesScreenshot. Begründe, warum die Aufnahme nicht in einer @AfterEach-Methode oder in einem TestWatcher stehen kann. (6 Punkte)
/** Scheitert ein Test, legt diese Erweiterung ein Bildschirmfoto ab – vor browserBeenden(). */
@RegisterExtension
final AfterTestExecutionCallback bildschirmfoto = kontext -> {
    if (kontext.getExecutionException().isPresent() && browser != null) {
        Path ziel = Path.of("build", "reports", "bildschirmfotos",
                kontext.getRequiredTestClass().getSimpleName() + "-" + kontext.getRequiredTestMethod().getName()
                        + ".png");
        Files.createDirectories(ziel.getParent());
        Files.write(ziel, ((TakesScreenshot) browser).getScreenshotAs(OutputType.BYTES));
    }
};

Dazu die Importe java.nio.file.Files, java.nio.file.Path, org.junit.jupiter.api.extension.AfterTestExecutionCallback, org.junit.jupiter.api.extension.RegisterExtension, org.openqa.selenium.OutputType und org.openqa.selenium.TakesScreenshot.

Bewertung: Erweiterung registriert 1 Punkt, nur bei Ausnahme 1 Punkt, Dateiname und Ordner 1 Punkt, Aufnahme mit TakesScreenshot 1 Punkt.

Begründung (2 Punkte): Eine @AfterEach-Methode erfährt nicht, ob der Test gescheitert ist. Ein TestWatcher erfährt es, wird aber erst nach allen @AfterEach-Methoden aufgerufen; dann hat browserBeenden() den Browser schon mit quit() beendet. AfterTestExecutionCallback läuft direkt nach der Testmethode und vor @AfterEach.

  1. Starte den Test aus a) im Startcode. Er scheitert mit expected: "Mitfahrlabor – Seite nicht gefunden" but was: "". Beschreibe, was das Bildschirmfoto vermutlich zeigt, und erkläre die Ursache. Behebe den Fehler mit einer Änderung an einer Zeile von FlurmonitorStoerung. (3 Punkte)

Das Bildschirmfoto zeigt eine Zeile JSON (Problem Details mit Status 404), die Chrome als Text darstellt; ein JSON-Dokument hat keinen <title>, daher der leere Titel (1 Punkt). Ursache: Der MitfahrgesucheController wirft eine ResponseStatusException. FlurmonitorStoerung gilt nur für den FlurmonitorController (assignableTypes), deshalb antwortet der globale @RestControllerAdvice ProblemBehandlung mit JSON (1 Punkt). Korrektur (1 Punkt):

@ControllerAdvice(assignableTypes = { FlurmonitorController.class, MitfahrgesucheController.class })

Danach leitet FlurmonitorStoerung die Ausnahme mit sendError(404) an /error weiter, und der Browser bekommt error/404.html. Eine eigene Lösung mit einem Advice nur für die Gesuche (wie in den Übungen zum letzten Artikel) ist ebenso richtig.

  1. Ergänze den Job gui-test in .gitlab-ci.yml so, dass die Bildschirmfotos nach einem roten Lauf zum Herunterladen bereitstehen. (2 Punkte)
  artifacts:
    when: always
    paths:
      - build/reports/tests/guiTest/
      - build/reports/bildschirmfotos/
    reports:
      junit: build/test-results/guiTest/TEST-*.xml

1 Punkt für den Pfad, 1 Punkt für when: always (ohne diese Angabe hebt GitLab Artefakte nur bei erfolgreichen Jobs auf; when: on_failure ist ebenso richtig).

Aufgabe 3.1: Einen instabilen GUI-Test stabilisieren (Profilkompetenz)

(ca. 15 min Bearbeitungszeit)

In Sprint 3 hat Oksana Melnyk zwei weitere Tests für das Nachladen geschrieben, bevor die Page Objects fertig waren. Beide verwenden die GuiTestBasis (Nachladen alle 2 Sekunden) und legen eine Fahrt über den AngebotService an (neueFahrt()). Der folgende Code ist absichtlich fehlerhaft:

@Test
void fassungA() throws InterruptedException {
    browser.get(adresse("/flurmonitor/foyer"));
    int vorher = Integer.parseInt(browser.findElement(By.cssSelector("#aktuell > p strong")).getText());
    neueFahrt();
    Thread.sleep(2000);
    int nachher = Integer.parseInt(browser.findElement(By.cssSelector("#aktuell > p strong")).getText());
    assertThat(nachher).isEqualTo(vorher + 1);
}

@Test
void fassungB() throws InterruptedException {
    browser.get(adresse("/flurmonitor/foyer"));
    WebElement anzahl = browser.findElement(By.cssSelector("#aktuell > p strong"));
    int vorher = Integer.parseInt(anzahl.getText());
    neueFahrt();
    Thread.sleep(5000);
    assertThat(Integer.parseInt(anzahl.getText())).isEqualTo(vorher + 1);
}

Im Daily Scrum berichtet sie: „Fassung A ist auf meinem Rechner in zehn Läufen zehnmal grün. In der Pipeline war sie in den letzten acht Läufen dreimal rot, mit expected: 2 but was: 1. Fassung B ist überall rot, mit StaleElementReferenceException: stale element reference: stale element not found in the current frame.“ Im Team fallen drei Vorschläge: Jonas Wolff will in Fassung A Thread.sleep(10000) schreiben, eine andere Entwicklerin will Fassung A bis zum Ende des Sprints mit @Disabled abschalten, damit die Pipeline grün ist, und ein dritter Vorschlag ist, gescheiterte GUI-Tests in der Pipeline automatisch bis zu dreimal zu wiederholen.

  1. Erkläre, warum Fassung A lokal grün und in der Pipeline manchmal rot ist. Beziehe Dich auf den Ablauf des Nachladens. (4 Punkte)

Das Skript lädt den Bereich #aktuell 2 Sekunden nach dem Laden der Seite und dann alle 2 Sekunden nach (setTimeout nach jeder Antwort). Fassung A liest die Anzahl etwa 2 Sekunden nach get() plus die Zeit für findElement und neueFahrt(), also nur knapp nach dem ersten Abruf (1 Punkt). Auf einem schnellen Rechner ist die Antwort mit der neuen Fahrt dann schon da (1 Punkt). In der Pipeline teilen sich mehrere Jobs den Rechner: Dauert der Abruf länger (Server, Browser, Netz), ist er beim Lesen noch nicht fertig, oder der erste Abruf fand vor neueFahrt() statt, sodass die Fahrt erst im übernächsten Takt erscheint. Der Test liest dann den alten Wert (2 Punkte). Mit 300 Millisekunden simulierter Netzlatenz im Browser scheitert Fassung A in jedem Lauf.

  1. Erkläre die Fehlermeldung von Fassung B. (3 Punkte)

Fassung B merkt sich das WebElement des <strong> vor dem Nachladen (1 Punkt). Das Skript ersetzt beim Nachladen alle Kinder von #aktuell durch neue Elemente (replaceChildren); das gemerkte Element gehört danach nicht mehr zum Dokument (1 Punkt). Nach 5 Sekunden hat das Nachladen sicher mindestens einmal stattgefunden, deshalb scheitert getText() in jedem Lauf mit StaleElementReferenceException (1 Punkt).

  1. Schreibe beide Tests als einen stabilen Test um. Verwende die FlurmonitorSeite aus dem Artikel. (5 Punkte)
@Test
void neueFahrtErscheint() {
    FlurmonitorSeite seite = FlurmonitorSeite.oeffnen(browser, adresse(""), "foyer");
    int vorher = seite.anzahlOffen();

    neueFahrt();

    seite.wartenBisAnzahl(vorher + 1); // wartet höchstens 10 s, ignoriert StaleElementReferenceException
    assertThat(seite.anzahlOffen()).isEqualTo(vorher + 1);
}

Bewertung: kein Thread.sleep 1 Punkt, Warten auf eine Bedingung mit Höchstzeit 2 Punkte, kein gemerktes WebElement bzw. Suche bei jedem Zugriff 1 Punkt, Nutzung des Page Objects 1 Punkt. Die letzte Zeile ist eigentlich überflüssig, weil wartenBisAnzahl sonst mit TimeoutException scheitert; sie macht die Absicht aber lesbar. Ohne Page Object ist eine Lösung mit new WebDriverWait(browser, Duration.ofSeconds(10)).ignoring(StaleElementReferenceException.class).until(…) ebenso richtig. Der stabile Test ist auch mit 300 Millisekunden simulierter Netzlatenz grün.

  1. Nimm zu den drei Vorschlägen des Teams Stellung. Beziehe die Definition of Done und das Sprint Goal von Sprint 3 ein. (4 Punkte)
  • Thread.sleep(10000) macht den Test wahrscheinlich grün, aber nicht sicher, und verlängert jeden Lauf um 10 Sekunden, auch wenn die Fahrt nach 2 Sekunden da ist. Die Ursache (Warten auf eine Zeit statt auf ein Ereignis) bleibt (1 Punkt).

  • @Disabled macht die Pipeline grün, prüft aber genau das Nachladen nicht mehr, das MON-11 automatisch prüfen soll (AK-MON-11.2). Ein abgeschalteter Test täuscht vor, dass das Sprint Goal erreicht ist; MON-11 wäre nach der Definition of Done nicht fertig. Wenn überhaupt, dann nur kurz, sichtbar im Sprint Backlog als eigene Aufgabe und mit Rücksprache mit dem Product Owner (1,5 Punkte).

  • Automatisches Wiederholen versteckt instabile Tests: Die Pipeline wird grün, obwohl der Test die Hälfte der Zeit scheitert, und echte Fehler, die nur manchmal auftreten (z. B. ein Wettlauf im Skript), fallen nicht mehr auf. Es verlängert außerdem rote Läufe. Vertretbar ist es höchstens als Übergang, wenn wiederholte Tests im Bericht sichtbar bleiben und behoben werden (1,5 Punkte).

Fazit: Den Test mit einer Wartebedingung reparieren (Teilaufgabe c) ist die einzige Lösung, die zum Sprint Goal passt; sie kostet weniger Zeit als jede der drei Umgehungen. Eine begründete andere Gewichtung ist ebenso richtig.


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zu GUI-Tests mit Selenium: Page Objects und stabile Tests für die Mitfahrgesuche” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/flurmonitor-gui-test-selenium-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/java-springboot/Backend. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: