Eine Safari-WebDriver-Regressionsprüfung auf dem Cloud-Mac einrichten

Eine Safari-WebDriver-Regressionsprüfung auf dem Cloud-Mac einrichten

Nachdem ein verteiltes Team Änderungen an einer Website zusammengeführt hat, wird am ehesten nicht der Chromium-Pfad übersehen, sondern Safari-spezifisches Verhalten bei Formularen, Fokus und Layout. Statt kurz vor der Veröffentlichung per Remote Desktop jede Seite manuell durchzuklicken, empfiehlt sich ein fest eingerichteter Safari-WebDriver-Regressionsjob auf einem Cloud-Mac von GPUMini. Für jeden Release-Kandidaten werden dieselben kritischen Abläufe ausgeführt; bei einem Fehler bleibt der Seitenzustand für die spätere Analyse erhalten.

Zuerst reproduzierbare Ausführungsbedingungen festlegen

Die Safari-Automatisierung benötigt eine grafische macOS-Sitzung und lässt sich daher nicht einfach wie ein Headless-Browser ausführen. Zunächst wird ein eigener Testbenutzer eingerichtet, dessen grafische Sitzung angemeldet bleibt. Gleichzeitig muss sichergestellt sein, dass automatisierte Jobs nicht parallel zu manuellen Remote-Zugriffen laufen. Pro Benutzerverzeichnis darf jeweils nur eine Safari-Sitzung aktiv sein, damit sich Verlauf, Downloads und Fensterzustände nicht gegenseitig beeinflussen.

Bei der erstmaligen Vorbereitung des Knotens werden Treiberaktivierung und Diagnose in einem interaktiven Terminal ausgeführt:

sudo safaridriver --enable
safaridriver --diagnose
mkdir -p "$HOME/browser-tests/artifacts"

--enable gehört zur Vorbereitung des Knotens und darf nicht Bestandteil jedes automatisierten Jobs sein. Im regulären Betrieb wird nur die Diagnose ausgeführt. Schlägt sie fehl, muss der Test beendet werden, statt eine Reihe wenig aussagekräftiger Timeout-Ergebnisse zu erzeugen. Auch in der Python-Umgebung sollten die Versionen der Abhängigkeiten festgeschrieben sein. Eine bestimmte Version von selenium gehört in die Abhängigkeitsdatei des Projekts, anstatt bei jeder Ausführung die neueste Version zu installieren.

Die erste Grundregel für Browser-Regressionstests lautet nicht „Der Browser startet“, sondern: Derselbe Commit, dieselben Testdaten und dieselben Wartebedingungen müssen zum selben Ergebnis führen.

Externe Schwankungen mit lokalen Fixtures ausschließen

End-to-End-Abläufe wie Anmeldung, Suche und Bezahlvorgang können gegen eine Testumgebung ausgeführt werden. Für Komponenteninteraktionen und Browserkompatibilitätsprüfungen sollten jedoch möglichst lokale Fixtures verwendet werden. Werden Seiten für Formulare, Dialogfenster, Uploads und Tastaturnavigation im Repository abgelegt und über die Loopback-Adresse bereitgestellt, lassen sich Schwankungen bei DNS, Zertifikaten und externen Schnittstellen ausschließen.

set -euo pipefail

ROOT="$(cd "$(dirname "$0")" && pwd)"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$ARTIFACTS"

python3 -m http.server 8080 \
  --bind 127.0.0.1 \
  --directory "$ROOT/fixtures" \
  >"$ARTIFACTS/http-server.log" 2>&1 &
SERVER_PID=$!

cleanup() {
  kill "$SERVER_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM

"$ROOT/.venv/bin/python" "$ROOT/tests/safari_smoke.py"

Vor der Verwendung eines festen Ports wird mit lsof -nP -iTCP:8080 -sTCP:LISTEN geprüft, ob er bereits belegt ist. Teilen sich mehrere Jobs einen Knoten, dürfen Prozesse nicht wahllos beendet werden. Entweder stellt die Planungsebene den gegenseitigen Ausschluss sicher, oder jeder Job erhält einen fest zugewiesenen Port, der in der Testkonfiguration hinterlegt wird.

Wartebedingungen als fachliche Zustände formulieren

Eine der häufigsten Ursachen instabiler Tests ist eine feste Pause von einigen Sekunden nach einem Klick. Schon geringe Änderungen der Systemlast führen dazu, dass eine zu kurze Pause Fehlalarme erzeugt, während eine zu lange Pause die gesamte Testsuite ausbremst. Stattdessen sollte auf beobachtbare fachliche Zustände gewartet werden, etwa darauf, dass eine Schaltfläche anklickbar ist, eine Ergebnisliste erscheint oder ein Statustext den erwarteten Wert annimmt.

from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

artifacts = Path("artifacts")
driver = webdriver.Safari()
driver.set_page_load_timeout(20)

try:
    driver.get("http://127.0.0.1:8080/login.html")
    wait = WebDriverWait(driver, 12)

    wait.until(
        EC.visibility_of_element_located((By.ID, "email"))
    ).send_keys("tester@local.invalid")

    driver.find_element(By.ID, "continue").click()

    status = wait.until(
        EC.visibility_of_element_located((By.ID, "result"))
    )
    assert status.text == "Ready"
finally:
    driver.quit()

Für Selektoren sollten stabile id-Werte oder eigens für Tests vorgesehene Attribute bevorzugt werden. Hierarchische Selektoren, die sich bei Layoutänderungen verschieben, sind zu vermeiden. Assertions müssen außerdem das Ergebnis prüfen und nicht nur die Existenz eines Elements: Eine sichtbare Absenden-Schaltfläche bedeutet nicht, dass das Formular erfolgreich übermittelt wurde, und ein vorhandener Listencontainer bedeutet nicht, dass die Daten bereits gerendert sind.

Jeden Fehlerzustand rekonstruierbar machen

Der teuerste Zustand bei Browser-Tests lautet: „CI meldet einen Timeout, aber es gibt keine verwertbaren Informationen zum Seitenzustand.“ Sobald ein Testfall fehlschlägt, sollten daher unverzüglich ein Screenshot, der Seitenquelltext, die aktuelle URL, der Name des Testfalls und Zeitinformationen gespeichert werden. Wird erst nach Abschluss der gesamten Suite ein Screenshot erstellt, zeigt er häufig nur noch eine bereits weitergeleitete oder geschlossene Seite.

Fehlersymptom Zuerst speichern Zuerst prüfen
Element bleibt unsichtbar Screenshot, Seitenquelltext Overlay, responsives Layout, Selektor
Klick bleibt ohne Ergebnis Aktuelle URL, Zustand der Schaltfläche Deaktivierter Zustand, Fokus, Ereignisbindung
Zeitüberschreitung beim Laden der Seite URL, Dienstprotokolle Lokaler Dienst, Weiterleitung, blockierte Ressourcen
Treiber kann keine Sitzung erstellen Treiberdiagnose Grafische Sitzung, verbliebene Prozesse, Berechtigungen

Der Dateiname eines Screenshots sollte die Kennung des Testfalls und eine eindeutige Ausführungsnummer enthalten, jedoch keine Token, E-Mail-Adressen oder Projektschlüssel. Auch der Seitenquelltext kann Testdaten enthalten. Er muss daher vor der Archivierung ebenfalls bereinigt und mit einer klar definierten Aufbewahrungsfrist versehen werden.

Nachweise direkt am Fehlerpunkt speichern

Der Fehler-Hook des Testframeworks sollte eine einheitliche Funktion zur Beweissicherung aufrufen. Da auch das Speichern selbst fehlschlagen kann, müssen Fehler beim Erstellen des Screenshots und beim Schreiben des Seitenquelltexts getrennt abgefangen werden. Ein Archivierungsfehler darf die ursprüngliche Assertion nicht überdecken.

Verbliebene Sitzungen bereinigen statt blind neu zu starten

Wird ein Job unterbrochen, können Safari oder der Treiberprozess die Sitzung weiterhin belegen. Startet der nächste Lauf unmittelbar einen neuen Versuch, schlagen häufig mehrere Sitzungsaufbauten hintereinander fehl. Zuerst ist zu prüfen, ob unter dem Testbenutzer noch Jobs laufen. Nachdem ausgeschlossen wurde, dass ein anderer regulärer Test aktiv ist, kann die verbliebene Sitzung beendet werden. Auf gemeinsam genutzten Knoten dürfen Prozesse nicht pauschal anhand weit gefasster Prozessnamen bereinigt werden.

Ein erneuter Versuch ist nur bei bekannten, vorübergehenden Fehlern beim Sitzungsaufbau angemessen und darf höchstens einmal erfolgen. Fehlgeschlagene Assertions, Änderungen an der Seitenstruktur und abweichende fachliche Zustände dürfen nicht automatisch erneut getestet werden, da eine echte Regression sonst als sporadisches Problem erscheint. Ist der Test nach einem erneuten Versuch erfolgreich, muss dies gesondert als Instabilitätsereignis protokolliert werden, statt den Job als vollständig fehlerfrei zu markieren.

Abschließend wird die Prüfung in zwei Stufen aufgeteilt: Für jeden Commit laufen Smoke-Tests mit lokalen Fixtures; für Release-Kandidaten kommen einige wenige kritische End-to-End-Abläufe hinzu. Vor der Veröffentlichung ist zu prüfen, ob die Treiberdiagnose erfolgreich war, der Testbenutzer exklusiv verwendet wird, der Fixture-Dienst erreichbar ist, explizite Wartebedingungen keine festen Pausen enthalten, Fehlernachweise lesbar sind und die Bereinigungsfunktion garantiert ausgeführt wird. Das Ergebnis ist keine Sammlung von Browser-Skripten, die „gelegentlich funktionieren“, sondern eine langfristig nachvollziehbare Basis für Safari-Regressionstests.

Häufig gestellte Fragen

Kann Safari WebDriver ohne aktive grafische Anmeldung laufen?

Das ist keine verlässliche Grundlage. Verwenden Sie einen eigenen Testbenutzer mit aktiver grafischer macOS-Sitzung und prüfen Sie den Treiber vor jedem Testauftrag.

Sollten mehrere Safari-Sitzungen auf einem Cloud-Mac parallel laufen?

Beginnen Sie mit einer seriellen Sitzung pro Benutzerumgebung. Für mehr Durchsatz verteilen Sie Testgruppen auf unabhängige Knoten, statt ein Browserprofil gemeinsam zu nutzen.

Welche Daten sollten nach einem Safari-Testfehler gespeichert werden?

Speichern Sie Bildschirmfoto, Seitenquelle, aktuelle URL, Testnamen und Treiberdiagnose. Anwendungsprotokolle werden nur nach Entfernung vertraulicher Werte ergänzt.

Exklusiver physischer Knoten

Für Entwicklungs- und Build-Aufgaben einen Cloud-Mac auswählen

Zwei M4-Konfigurationen, vier Mietzeiträume und vier verfügbare Knoten vergleichen und anschließend die Bereitstellung passend zur Aufgabe abschließen.

Konfiguration auswählen und bestellen