Les mêmes écrans iOS fonctionnent en local, mais, une fois intégrés à une tâche de build distante, ils ne reçoivent parfois pas le message « prêt » envoyé par la page web. N’attribuez pas d’emblée cet échec au réseau du Mac cloud : si la page communique avec le code natif par un pont WKWebView, le problème peut venir du chargement, des champs du message ou du cycle de vie du gestionnaire. Une page HTML locale, versionnée avec le dépôt, permet de tester les échanges dans les deux sens sans dépendre d’une page externe.
Définir le contrat du pont
Établissez un protocole minimal : la page envoie {kind: "ready", version: 1} au code natif, qui lui renvoie {kind: "sync", value: "ok"}. Le premier message passe par window.webkit.messageHandlers.bridge.postMessage, le second par evaluateJavaScript. Les tests doivent vérifier séparément que les messages arrivent et que leurs champs ont le bon type, pas seulement que la page s’affiche.
Enregistrez le contenu suivant sous BridgeFixture.html, à un emplacement accessible à la cible de test, puis vérifiez que le fichier figure parmi ses ressources :
<!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 sert de point d’observation au test ; il ne fait pas partie du protocole de production. Le code de production et la fixture doivent partager les mêmes définitions de champs. Si un champ change, mettez les deux à jour et faites échouer explicitement le test en cas de modification incompatible. N’essayez pas de deviner la fin du chargement en attendant un nombre fixe de secondes.
Vérifier les deux sens dans le simulateur
Le test crée un WKUserContentController, y enregistre un WKScriptMessageHandler sous le nom bridge, puis place le contrôleur dans un WKWebViewConfiguration. Après avoir lu le HTML dans le bundle de test, il appelle loadHTMLString(_:baseURL:). À la réception de ready, vérifiez d’abord que message.body est un dictionnaire, puis que kind est une chaîne, que version est un nombre et que sa valeur est 1. Tout message inconnu doit faire échouer une assertion.
Pour tester l’envoi depuis le code natif, attendez que WKNavigationDelegate reçoive webView(_:didFinish:). Encodez ensuite le dictionnaire à envoyer en texte JSON avec JSONSerialization, insérez ce texte dans window.bridgeReceive(...), puis appelez evaluateJavaScript. N’insérez pas directement une saisie utilisateur dans la chaîne du script : les guillemets et les retours à la ligne pourraient en modifier le sens. Une fois l’appel terminé, lisez document.body.dataset.received et vérifiez que sa valeur est ok.
Le retour de
loadHTMLStringsignifie seulement que le chargement a commencé. Même si la page a déjà envoyéready, ce moment ne remplace pas l’événement de fin de navigation. Une expectation XCTest pour chaque sens de communication permet d’identifier celui qui ne répond pas.
Prévoyez aussi un chemin d’échec pour les erreurs de chargement et d’exécution du script. Fixez un délai d’expiration explicite pour les expectations et distinguez, dans les messages d’échec, « chargement de la page non terminé », « message non reçu par le code natif » et « message natif non accepté par la page ». Sans cela, la CI ne fournira qu’un délai d’attente expiré, sans autre indication.
Intégrer le test au build sur Mac cloud
Commencez par vérifier les configurations disponibles dans la console du Mac cloud ArmMesh. Une fois le nœud choisi, fixez la version de Xcode et celle du runtime du simulateur. Utilisez xcrun simctl list devices available pour trouver un appareil disponible et inscrivez l’UUID du simulateur retenu dans la configuration de la tâche ; ne vous fiez pas à une correspondance approximative sur le nom de l’appareil. Remplacez le projet, le scheme et l’UUID de la commande suivante par les valeurs du dépôt :
xcodebuild test \
-project Client.xcodeproj \
-scheme Client \
-destination 'platform=iOS Simulator,id=<SIMULATOR_UUID>' \
-only-testing:ClientTests/BridgeContractTests \
-resultBundlePath "$PWD/BridgeContract.xcresult"
Avant l’exécution, vérifiez que le scheme est partagé, que la cible de test contient la ressource HTML et que le chemin des résultats n’existe pas encore : xcodebuild n’écrasera pas un bundle de résultats existant comme s’il s’agissait d’un répertoire vide. En cas d’échec, conservez le .xcresult et les logs de build, et notez la version de Xcode ainsi que l’UUID du simulateur pour pouvoir relancer le test dans le même environnement.
Ce test n’utilise aucune page web distante : une panne réseau ne devrait donc pas en changer le résultat. Prévoyez séparément un test d’intégration utilisant la page réelle pour distinguer un contrat de pont rompu d’un échec de déploiement ou de requête.
Éviter les pièges de séquencement et de nettoyage
Si la page exécute postMessage avant l’enregistrement du gestionnaire, son premier message risque d’être perdu. Terminez donc la configuration et l’enregistrement avant de créer puis de charger la WebView. Ne réutilisez pas, entre les tests, un contrôleur portant d’anciens gestionnaires. À la fin de chaque test, appelez removeScriptMessageHandler(forName: "bridge") et libérez la référence au délégué de navigation de la WebView.
Testez également les cas de rejet : faites envoyer par la fixture un message sans version, puis un message dont version est une chaîne, et vérifiez que le code natif ne les accepte pas comme des messages ready valides. Si le protocole de production comporte un identifiant de requête, chaque accusé de réception doit correspondre à cet identifiant : un message tardif d’une requête précédente ne doit pas satisfaire l’expectation en cours. Lorsque des données utilisateur sont en jeu, ne consignez dans les logs de test que la structure des champs et des erreurs expurgées des données sensibles.
Terminez par une vérification : exécutez ce test seul, puis au sein de la suite complète. Des exécutions successives ne doivent produire aucun rappel en double, et un autre simulateur de même version doit donner les mêmes résultats d’assertion. La fixture locale garantit ainsi la stabilité du contrat d’interface, tandis que le test sur la page réelle vérifie l’intégration après déploiement ; chaque catégorie d’échec a sa propre origine identifiable.
Questions fréquentes
Pourquoi ne pas tester directement avec la page en production ?
Ses mises à jour et le réseau rendent les entrées variables. Un fichier HTML local fixe le contrat ; testez ensuite l’intégration réelle séparément.
Quand appeler JavaScript depuis le code natif ?
Attendez didFinish dans WKNavigationDelegate avant evaluateJavaScript : le retour de loadHTMLString ne garantit pas que la fonction existe déjà.
Intégrez un Mac cloud à configuration fixe à votre workflow
Louez des nœuds physiques dédiés à la journée, à la semaine, au mois ou au trimestre pour exécuter vos builds, automatisations et tâches d’inférence MLX sur des ressources matérielles fixes.