Engineering-Praxis

Den bidirektionalen WKWebView-Bridge-Vertrag testen

Den bidirektionalen WKWebView-Bridge-Vertrag testen

Dieselben iOS-Seiten funktionieren lokal einwandfrei, doch in einem Remote-Build kommt die „Bereit“-Nachricht der Webseite gelegentlich nicht an. Solche Fehler sollten nicht vorschnell dem Netzwerk des Cloud-Macs zugeschrieben werden: Wenn Webseite und nativer Code über eine WKWebView-Bridge kommunizieren, kommen auch der Ladeablauf, Nachrichtenfelder und der Lebenszyklus des Handlers als Ursache infrage. Eine lokale HTML-Datei im Repository macht die Kommunikation in beide Richtungen als Vertrag testbar – ohne Abhängigkeit von einer externen Seite.

Zuerst den Bridge-Vertrag festlegen

Definieren Sie ein minimales Protokoll: Die Webseite sendet {kind: "ready", version: 1} an den nativen Code; der native Code sendet {kind: "sync", value: "ok"} an die Webseite. Die erste Nachricht läuft über window.webkit.messageHandlers.bridge.postMessage, die zweite über evaluateJavaScript. Der Test muss für beide Richtungen nachweisen, dass die Nachricht ankommt und die Felder die richtigen Typen haben. Dass die Seite angezeigt wird, genügt nicht.

Speichern Sie den folgenden Inhalt als BridgeFixture.html, sodass das Test-Target darauf zugreifen kann, und nehmen Sie die Datei in dessen Ressourcen auf:

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<body>
<script>
window.bridgeReceive = function (message) {
  if (message.kind !== "sync" || typeof message.value !== "string") {
    throw new Error("Invalid native message");
  }
  document.body.dataset.received = message.value;
};
window.webkit.messageHandlers.bridge.postMessage({
  kind: "ready",
  version: 1
});
</script>
</body>
</html>

dataset.received dient nur als Beobachtungspunkt für den Test und gehört nicht zum Produktionsprotokoll. Produktionscode und Testdatei sollten dieselben Felddefinitionen verwenden. Ändern sich Felder, müssen beide Seiten aktualisiert werden; inkompatible Änderungen sollen den Test eindeutig fehlschlagen lassen. Versuchen Sie nicht, den Abschluss des Ladevorgangs mit einer festen Wartezeit zu erraten.

Beide Richtungen im Simulator prüfen

Der Test erstellt einen WKUserContentController, registriert einen WKScriptMessageHandler unter dem Namen bridge und setzt den Controller in eine WKWebViewConfiguration ein. Anschließend liest er die HTML-Datei aus dem Test-Bundle und ruft loadHTMLString(_:baseURL:) auf. Trifft ready ein, prüfen Sie zuerst, ob message.body ein Dictionary ist. Prüfen Sie dann, ob kind ein String und version eine Zahl mit dem Wert 1 ist. Unbekannte Nachrichten müssen eine Assertion auslösen.

Für die vom nativen Code ausgehende Richtung warten Sie, bis der WKNavigationDelegate den Aufruf webView(_:didFinish:) erhält. Kodieren Sie danach das zu sendende Dictionary mit JSONSerialization als JSON-Text, setzen Sie es in window.bridgeReceive(...) ein und rufen Sie evaluateJavaScript auf. Fügen Sie Benutzereingaben nicht direkt in einen Skript-String ein: Anführungszeichen und Zeilenumbrüche können dessen Bedeutung verändern. Nach Abschluss des Aufrufs lesen Sie document.body.dataset.received aus und prüfen, ob der Wert ok ist.

Dass loadHTMLString zurückkehrt, bedeutet nur, dass der Ladevorgang begonnen hat. Selbst wenn die Webseite bereits ready gesendet hat, darf dieser Zeitpunkt das Ereignis für den Abschluss der Navigation nicht ersetzen. Legen Sie für jede Richtung eine eigene XCTest-Expectation an, damit erkennbar ist, welcher Teil nicht geantwortet hat.

Auch Ladefehler und Fehler bei der Skriptausführung müssen den Test fehlschlagen lassen. Geben Sie den Expectations klare Timeouts und unterscheiden Sie in der Fehlerausgabe zwischen „Seite nicht fertig geladen“, „Nachricht nicht beim nativen Code angekommen“ und „native Nachricht von der Webseite nicht angenommen“. Sonst liefert CI nur einen allgemeinen Timeout.

Den Test in den Cloud-Build einbinden

Prüfen Sie zunächst in der Konsole des ArmMesh Cloud-Macs die aktuell verfügbaren Konfigurationen. Nachdem Sie einen Knoten ausgewählt haben, legen Sie die Xcode-Version und die Simulator-Runtime fest. Ermitteln Sie mit xcrun simctl list devices available die verfügbaren Geräte und tragen Sie die UUID des gewählten Simulators in die Job-Konfiguration ein. Verlassen Sie sich nicht auf einen unscharfen Abgleich des Gerätenamens. Ersetzen Sie im folgenden Befehl Projekt, Scheme und UUID durch die tatsächlichen Werte des Repositorys:

xcodebuild test \
  -project Client.xcodeproj \
  -scheme Client \
  -destination 'platform=iOS Simulator,id=<SIMULATOR_UUID>' \
  -only-testing:ClientTests/BridgeContractTests \
  -resultBundlePath "$PWD/BridgeContract.xcresult"

Prüfen Sie vor dem Start, ob das Scheme freigegeben ist, das Test-Target die HTML-Datei als Ressource enthält und der Ergebnispfad noch nicht existiert. xcodebuild überschreibt ein vorhandenes Ergebnis-Bundle nicht wie ein leeres Verzeichnis. Bewahren Sie bei Fehlern die .xcresult-Datei und das Build-Log auf. Notieren Sie außerdem die Xcode-Version und die Simulator-UUID, damit der Test in derselben Umgebung erneut ausgeführt werden kann.

Dieser Test benötigt keine entfernte Webseite; Netzwerkprobleme sollten sein Ergebnis daher nicht beeinflussen. Erst ein separater Integrationstest mit der tatsächlichen Seite kann zwischen einem defekten Bridge-Vertrag und einem fehlgeschlagenen Deployment oder Request unterscheiden.

Fallstricke bei Ablauf und Bereinigung vermeiden

Wenn die Seite postMessage ausführt, bevor der Handler registriert ist, kann bereits die erste Nachricht verloren gehen. Schließen Sie deshalb Konfiguration und Registrierung ab, bevor Sie die WebView erstellen und laden. Verwenden Sie im Test keinen Controller erneut, in dem noch ein alter Handler registriert ist. Rufen Sie am Ende des Testfalls removeScriptMessageHandler(forName: "bridge") auf und lösen Sie die Referenz auf den Navigations-Delegate der WebView.

Testen Sie auch die Ablehnung ungültiger Nachrichten: Lassen Sie die Testdatei eine Nachricht ohne version und eine Nachricht mit version als String senden. Stellen Sie sicher, dass der native Code keine davon als gültiges ready behandelt. Enthält das Produktionsprotokoll eine Request-ID, muss die Antwort anhand dieser ID zugeordnet werden. Eine verspätete Nachricht aus einem früheren Request darf die Expectation des aktuellen Requests nicht erfüllen. Wenn Nutzerinhalte beteiligt sind, gehören nur die Feldstruktur und bereinigte Fehlermeldungen ins Test-Log.

Führen Sie zum Abschluss einen Akzeptanzcheck durch: Starten Sie den Test zunächst einzeln und danach als Teil der vollständigen Testsuite. Bei wiederholten Läufen dürfen keine doppelten Callbacks auftreten; ein anderer Simulator mit derselben Version muss dieselben Assertion-Ergebnisse liefern. Damit prüft die lokale Testdatei zuverlässig den Schnittstellenvertrag, während der Test mit der tatsächlichen Seite das Integrationsverhalten nach dem Deployment abdeckt. Beide Fehlerarten lassen sich klar zuordnen.

Häufig gestellte Fragen

Warum keine produktive Webseite als Testeingabe verwenden?

Bereitstellungen und Netzwerkfehler verändern die Eingabe. Eine lokale Datei fixiert den Vertrag; die Integration wird separat geprüft.

Wann darf nativer Code die JavaScript-Funktion aufrufen?

Erst nach didFinish des WKNavigationDelegate. Die Rückkehr von loadHTMLString bestätigt noch nicht, dass die Seitenfunktion bereitsteht.

Exklusives Apple Silicon

Binde einen Cloud-Mac mit fester Konfiguration in deinen Workflow ein

Miete dedizierte physische Knoten tage-, wochen-, monats- oder quartalsweise und nutze feste Hardwareressourcen für Builds, Automatisierung und MLX-Inferenz.

Verfügbare Konfigurationen ansehen