Formation Appium complète en Java

Guide gratuit – Économisez vos 1800 € de formation !

Chapitre 1 – Introduction à Appium

Pourquoi Appium ?

Appium est un framework d'automatisation de tests open source, conçu pour tester des applications mobiles (Android et iOS). Contrairement à d'autres outils d'automatisation, il a été pensé dès le départ pour être agnostique du langage de programmation et de la plateforme. Cette approche universelle représente une véritable révolution dans le monde des tests mobiles.

L'originalité d'Appium réside dans sa philosophie : permettre aux développeurs et testeurs d'utiliser leurs compétences existantes, quel que soit leur langage de prédilection. Vous maîtrisez Java ? Parfait. Vous préférez Python ou C# ? Aucun problème. Cette flexibilité évite l'apprentissage d'outils propriétaires spécifiques à chaque plateforme.

Concrètement, cela signifie que :

  • Vous pouvez écrire vos tests en Java, Python, JavaScript, Ruby, C#, et même en langages plus exotiques
  • Vous pouvez les exécuter sur Android comme sur iOS, sans réécrire la logique métier
  • Votre équipe peut continuer à utiliser ses outils et frameworks de test favoris (JUnit, TestNG, pytest, etc.)
  • L'investissement en formation est minimisé car les concepts de base restent les mêmes
L'idée originelle d'Appium est : « automatiser tout ce qui se fait à la main sur un téléphone, sans modifier l'application ». Cette approche non-intrusive garantit que vos tests reflètent réellement l'expérience utilisateur.

Cas d'usage concrets

Dans le paysage actuel du développement mobile, les applications se déclinent en trois grandes familles, chacune ayant ses spécificités techniques et ses enjeux de test. Appium excelle dans l'automatisation de toutes ces catégories.

1. Applications natives

Les applications natives sont développées directement dans les langages et SDK officiels des plateformes : Java ou Kotlin pour Android, Swift ou Objective-C pour iOS. Ces applications offrent généralement les meilleures performances et l'accès le plus complet aux fonctionnalités du système (caméra, GPS, notifications push, etc.).

Avec Appium, vous pouvez tester ces applications en utilisant les mêmes mécanismes que les frameworks natifs : UiAutomator2 sur Android et XCUITest sur iOS. Cela garantit une fidélité maximale dans la simulation des interactions utilisateur.

2. Applications web mobiles

Ces applications sont des sites web optimisés pour mobile, accessibles via le navigateur. Bien qu'elles ne bénéficient pas de toutes les capacités natives, elles présentent l'avantage d'une maintenance simplifiée et d'un déploiement universel.

Appium peut piloter Chrome Mobile, Safari Mobile, ou tout autre navigateur mobile, en s'appuyant sur les drivers Selenium sous-jacents. Vous retrouvez ainsi tous vos réflexes de test web, adaptés à l'écran tactile.

3. Applications hybrides

Les applications hybrides combinent le meilleur des deux mondes : une partie web (HTML/CSS/JavaScript) encapsulée dans une coque native. Des frameworks comme Ionic, Cordova, React Native ou Flutter permettent de développer une fois et de déployer sur plusieurs plateformes.

Appium gère parfaitement ces cas complexes en permettant de basculer entre le contexte natif (pour les menus, barres de navigation) et le contexte web (pour le contenu applicatif). Cette capacité de switching contextuel est unique dans l'écosystème des outils de test.

Architecture technique d'Appium

Pour comprendre la puissance et les limites d'Appium, il est essentiel de maîtriser son architecture. Contrairement aux outils de test traditionnels qui s'exécutent directement sur la machine de test, Appium repose sur une architecture client/serveur distribuée.

Le serveur Appium : le chef d'orchestre

Le serveur Appium est une application Node.js qui fait office d'intermédiaire intelligent entre vos scripts de test et les appareils mobiles. Il expose une API REST conforme au protocole WebDriver W3C, garantissant une compatibilité maximale avec l'écosystème Selenium existant.

Ce serveur ne se contente pas de transmettre bêtement les commandes : il les traduit, les optimise, et gère les spécificités de chaque plateforme. Par exemple, une commande de clic sera traduite différemment selon qu'elle s'adresse à un émulateur Android ou à un iPhone physique.

Le client Appium : votre interface de développement

Votre script de test (le client) communique avec le serveur via des requêtes HTTP standard. Cette approche présente plusieurs avantages majeurs :

  • Langage-agnostic : n'importe quel langage capable de faire des requêtes HTTP peut piloter Appium
  • Réseau-friendly : le serveur peut tourner sur une machine distante, idéal pour les fermes de test
  • Debug-friendly : toutes les communications sont loggées et peuvent être inspectées
  • Standard-compliant : respect du protocole WebDriver, garantissant la pérennité

Les drivers natifs : l'exécution réelle

Au bout de la chaîne, Appium s'appuie sur les frameworks d'automatisation officiels de chaque plateforme. Cette approche garantit une fidélité maximale et une évolution synchrone avec les OS mobiles :

  • Android : UiAutomator2 (recommandé) ou Espresso pour les tests plus fins
  • iOS : XCUITest, le framework officiel d'Apple depuis iOS 9
  • Windows : WinAppDriver pour les applications UWP

Flux d'exécution concret

Prenons un exemple pratique pour illustrer ce flux : votre test Java veut cliquer sur un bouton "Connexion".

  1. Étape 1 : Votre code Java envoie une requête POST à http://localhost:4723/session/:id/element/:elementId/click
  2. Étape 2 : Le serveur Appium reçoit cette requête et l'analyse
  3. Étape 3 : Le serveur traduit cette commande en instruction UiAutomator2 : element.click()
  4. Étape 4 : UiAutomator2 exécute réellement le clic sur l'appareil Android
  5. Étape 5 : Le résultat remonte la chaîne jusqu'à votre test Java

Installation & Configuration

Prérequis : la fondation de votre environnement Appium

L'installation d'Appium peut sembler straightforward, mais la réalité du terrain montre que la majorité des échecs proviennent d'un environnement de développement mal préparé. Cette section détaille méthodiquement chaque prérequis, en expliquant non seulement le "comment" mais aussi le "pourquoi" de chaque composant.

Appium s'appuie sur un écosystème complexe de technologies interdépendantes. Une défaillance dans l'un des maillons peut rendre l'ensemble inutilisable. Cette interdépendance explique pourquoi l'installation d'Appium nécessite plus de rigueur que celle d'un simple outil de développement classique.

1. Java JDK : le socle technique incontournable

Java occupe une position centrale dans l'écosystème Appium, et ce pour plusieurs raisons techniques fondamentales. Premièrement, les outils de développement Android (SDK, Android Studio, outils de build) sont majoritairement écrits en Java. Deuxièmement, de nombreux composants d'Appium utilisent la JVM comme plateforme d'exécution.

Le choix de la version Java n'est pas anodin et impacte directement la stabilité de votre environnement. Java 11 et Java 17 sont les versions LTS (Long Term Support) recommandées car elles offrent le meilleur équilibre entre stabilité, support communautaire, et compatibilité avec l'écosystème Android.

Java 8, bien qu'encore utilisé dans certains contextes, devient progressivement obsolète et pose des problèmes de compatibilité avec les versions récentes d'Android Studio et Gradle. À l'inverse, les versions plus récentes (Java 19, 20, 21) peuvent introduire des incompatibilités avec certains plugins Maven ou outils de build.

Procédure d'installation Java :

  • Téléchargez le JDK depuis Adoptium (Eclipse Temurin) ou Oracle. Adoptium est généralement préférable car il s'agit d'une distribution OpenJDK certifiée et gratuite.
  • Lors de l'installation, assurez-vous de cocher "Add to PATH" si l'option est disponible
  • Configurez la variable d'environnement JAVA_HOME pour pointer vers le répertoire d'installation du JDK

Configuration des variables d'environnement :

# Windows (dans les Variables d'environnement système)
JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-17.0.8.7-hotspot

# macOS (dans ~/.bash_profile ou ~/.zshrc)
export JAVA_HOME=$(/usr/libexec/java_home -v 17)

# Linux (dans ~/.bashrc ou ~/.profile)
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64

Validation de l'installation :

java -version
# Sortie attendue : openjdk version "17.0.8" 2023-07-18

javac -version
# Sortie attendue : javac 17.0.8
Astuce de debugging : Si java -version et javac -version retournent des versions différentes, cela indique un problème de PATH ou plusieurs installations Java conflictuelles. Utilisez which java (Unix) ou where java (Windows) pour identifier le problème.

2. Maven : orchestrateur de votre projet

Maven dépasse le simple rôle de gestionnaire de dépendances pour devenir le chef d'orchestre de votre projet de test. Il standardise la structure des projets, automatise les tâches de build, gère les versions des bibliothèques, et facilite l'intégration avec les outils de CI/CD.

L'avantage de Maven dans le contexte Appium est triple. D'abord, il simplifie drastiquement la gestion des dépendances : plus besoin de télécharger manuellement les JAR d'Appium, Selenium, TestNG, etc. Ensuite, il standardise l'exécution des tests via la commande mvn test, indépendamment de l'IDE utilisé. Enfin, il facilite l'intégration continue en fournissant des rapports standardisés (Surefire, etc.).

Installation de Maven :

  • Téléchargez la dernière version depuis maven.apache.org
  • Décompressez l'archive dans un répertoire sans espaces (ex: C:\tools\maven sur Windows)
  • Ajoutez le répertoire bin de Maven à votre PATH
  • Créez la variable d'environnement M2_HOME pointant vers le répertoire Maven

Validation de Maven :

mvn -version
# Sortie attendue :
# Apache Maven 3.9.4
# Maven home: /opt/maven
# Java version: 17.0.8, vendor: Eclipse Adoptium
Alternative IDE : Si vous utilisez IntelliJ IDEA ou Eclipse, Maven est généralement intégré. Vous pouvez vous passer de l'installation manuelle, mais la version en ligne de commande reste recommandée pour l'automatisation et le debug.

3. Node.js : le runtime du serveur Appium

Appium Server est entièrement développé en JavaScript et s'exécute sur Node.js. Cette architecture présente plusieurs avantages : développement rapide, communauté active, écosystème npm riche, et portabilité cross-platform native.

Le choix de la version Node.js est crucial pour la stabilité. Les versions LTS (Long Term Support) sont toujours préférables car elles bénéficient de corrections de bugs et de mises à jour de sécurité pendant une durée étendue. Les versions "Current" peuvent contenir des fonctionnalités expérimentales susceptibles de créer des instabilités.

Installation Node.js :

  • Rendez-vous sur nodejs.org et téléchargez la version LTS
  • Utilisez l'installateur officiel plutôt que les gestionnaires de paquets tiers (évite les problèmes de permissions)
  • Sur Windows, évitez absolument l'installation via Microsoft Store (problèmes connus avec npm)

Validation Node.js :

node -v
# Sortie attendue : v18.17.1 (ou version LTS actuelle)

npm -v
# Sortie attendue : 9.6.7 (ou version associée)

Installation d'Appium et outils associés :

# Installation globale d'Appium
npm install -g appium

# Installation d'Appium Doctor pour le diagnostic
npm install -g appium-doctor

# Installation du driver Android (optionnel, installé par défaut)
appium driver install uiautomator2

# Installation du driver iOS (si nécessaire)
appium driver install xcuitest

Test de l'installation Appium :

# Vérifier la version d'Appium
appium -v
# Sortie attendue : 2.2.1 (ou version actuelle)

# Diagnostic complet de l'environnement Android
appium-doctor --android
# Doit afficher toutes les vérifications en vert ✓

4. Android Studio : portail vers l'écosystème Android

Android Studio n'est pas seulement un IDE de développement, c'est la porte d'entrée officielle vers tout l'écosystème Android. Il embarque le SDK Android, les outils de debugging (ADB), l'émulateur, et les outils de build (Gradle). Même si vous ne développez pas d'applications Android, ces composants sont indispensables pour Appium.

L'Android SDK contient les bibliothèques et outils nécessaires pour communiquer avec les appareils Android. ADB (Android Debug Bridge) est l'outil en ligne de commande qui permet de contrôler les appareils et émulateurs. L'émulateur Android vous permettra de tester sans appareil physique, ce qui est particulièrement utile pour l'intégration continue.

Installation Android Studio :

  • Téléchargez Android Studio depuis developer.android.com
  • Lors de l'installation, assurez-vous de sélectionner :
    • Android SDK : bibliothèques de développement
    • Android SDK Platform-Tools : contient ADB
    • Android Emulator : émulateur officiel
    • Android Virtual Device : gestionnaire d'émulateurs

Configuration post-installation :

Après l'installation d'Android Studio, ouvrez le SDK Manager (Tools > SDK Manager) et installez :

  • Android API 30 (Android 11) ou supérieur : versions récentes pour la compatibilité
  • Android API 28-29 : versions antérieures pour la rétrocompatibilité
  • Google Play Services : pour tester les applications utilisant les services Google
  • Google USB Driver (Windows uniquement) : pour connecter des appareils physiques

Configuration des variables d'environnement Android :

# Windows
ANDROID_HOME=C:\Users\[USERNAME]\AppData\Local\Android\Sdk
PATH=%PATH%;%ANDROID_HOME%\platform-tools;%ANDROID_HOME%\emulator

# macOS
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator

# Linux
export ANDROID_HOME=$HOME/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator

Validation de l'installation Android :

# Vérifier ADB
adb --version
# Sortie attendue : Android Debug Bridge version 1.0.41

# Lister les appareils connectés
adb devices
# Doit fonctionner sans erreur (liste vide si aucun appareil connecté)

# Vérifier l'émulateur
emulator -list-avds
# Doit lister les AVDs créés (peut être vide initialement)

Validation finale de l'environnement

Une fois tous les composants installés, la validation finale via Appium Doctor vous donnera un diagnostic complet de votre environnement. Cette étape est cruciale car elle détecte les problèmes de configuration avant qu'ils ne causent des échecs de tests mystérieux.

# Diagnostic complet pour Android
appium-doctor --android

# Si vous travaillez aussi sur iOS (Mac uniquement)
appium-doctor --ios

Appium Doctor vérifie systematiquement :

  • Présence et version de Java, Node.js, npm

    Émulateurs & Appareils physiques : choisir la bonne stratégie de test

    Le choix entre émulateurs et appareils physiques constitue l'une des décisions stratégiques majeures dans la mise en place de tests Appium. Chaque approche présente des avantages et inconvénients qu'il convient de comprendre pour optimiser votre stratégie de test.

    Émulateurs Android : la puissance de la virtualisation

    Les émulateurs Android (AVD - Android Virtual Device) sont des machines virtuelles qui simulent un appareil Android complet sur votre ordinateur. Cette approche offre une flexibilité exceptionnelle pour les tests automatisés, particulièrement dans les environnements de développement et d'intégration continue.

    Avantages des émulateurs :

    • Reproductibilité : configuration identique entre tous les environnements de test
    • Économie : pas d'investissement en hardware physique
    • Parallélisation : possibilité de lancer plusieurs instances simultanément
    • Contrôle complet : simulation de conditions réseau, batterie, localisation GPS
    • Intégration CI/CD : parfait pour l'automatisation complète
    • Reset facile : retour à un état propre en quelques secondes

    Inconvénients des émulateurs :

    • Performance : généralement plus lents que les appareils physiques
    • Fidélité limitée : certains comportements hardware non simulés
    • Consommation ressources : utilisation intensive CPU/RAM de la machine hôte
    • Limitations capteurs : caméra, NFC, certains capteurs indisponibles

    Création d'un émulateur Android optimisé :

    La création d'un émulateur efficace nécessite un équilibre entre performance et fidélité. Voici la procédure détaillée pour créer un AVD optimisé pour les tests Appium :

    1. Ouvrir Android Studio et naviguer vers Tools > Device Manager
    2. Cliquer sur "Create Device" pour lancer l'assistant de création
    3. Sélection du hardware :
      • Privilégier les profils Pixel (Pixel 4, Pixel 6, Pixel 7) pour leur compatibilité optimale
      • Éviter les profils avec trop de RAM (>4GB) qui ralentissent la machine hôte
      • Pour les tablettes, Pixel C ou Nexus 10 sont de bons compromis
    4. Sélection de l'image système :
      • API Level 30 (Android 11) ou API 33 (Android 13) pour la modernité
      • Target x86_64 pour les meilleures performances sur PC Intel/AMD
      • Google APIs si vous testez des apps utilisant les services Google
      • Éviter les images ARM sur machine x86 (très lent par émulation)
    5. Configuration avancée :
      • RAM : 2048-4096 MB (équilibre performance/ressources)
      • Storage interne : 8GB minimum, 16GB recommandé
      • Graphics : Hardware GLES 2.0 si supporté, Software sinon
      • Boot option : Cold boot (plus stable pour les tests)

    Validation de l'émulateur :

    # Lancer l'émulateur depuis la ligne de commande
    emulator -avd Pixel_4_API_33
    
    # Dans un autre terminal, vérifier la détection
    adb devices
    # Sortie attendue : emulator-5554   device
    
    # Vérifier les capacités de l'émulateur
    adb shell getprop ro.build.version.release
    # Sortie attendue : 13 (pour Android 13)

    Appareil Android physique : fidélité maximale

    Tester sur un appareil Android physique reste indispensable pour valider le comportement réel de votre application. Les différences entre émulation et réalité peuvent être subtiles mais critiques : performances réelles, comportements des capteurs, interactions avec le système, gestion des notifications, etc.

    Activation du mode développeur :

    Cette procédure est identique sur la plupart des appareils Android, mais peut varier légèrement selon les constructeurs :

    1. Ouvrir Paramètres > À propos du téléphone (ou "À propos de l'appareil")
    2. Rechercher "Numéro de build" ou "Version du logiciel"
    3. Taper 7 fois rapidement sur cette entrée
    4. Un message confirme l'activation : "Vous êtes maintenant un développeur"
    5. Retourner dans Paramètres : un nouveau menu "Options pour les développeurs" apparaît

    Configuration du débogage USB :

    1. Dans "Options pour les développeurs", activer "Débogage USB"
    2. Optionnel mais recommandé : activer "Rester éveillé" (évite la veille pendant les tests)
    3. Connecter l'appareil via USB à l'ordinateur
    4. Une popup apparaît sur l'appareil : "Autoriser le débogage USB ?"
    5. Cocher "Toujours autoriser depuis cet ordinateur" et valider

    Résolution des problèmes de connexion :

    La connexion d'appareils physiques peut poser des défis spécifiques selon l'OS et le constructeur :

    Sur Windows :

    • Installer les pilotes USB spécifiques au constructeur (Samsung, Xiaomi, OnePlus, etc.)
    • Les pilotes "universels" de Windows ne suffisent généralement pas
    • En cas d'échec, utiliser les pilotes génériques Google USB depuis Android Studio

    Sur macOS :

    • Généralement plug-and-play, pas de pilotes supplémentaires nécessaires
    • En cas de problème, vérifier les autorisations de sécurité macOS

    Sur Linux :

    • Configurer les règles udev pour les permissions USB
    • Ajouter l'utilisateur au groupe "plugdev"
    • Redémarrer le service udev : sudo service udev restart

    Validation de la connexion :

    # Vérifier la détection de l'appareil
    adb devices
    # Sortie attendue : R58M12345X   device (pas unauthorized)
    
    # Tester la communication
    adb shell echo "Test de connexion"
    # Doit retourner : Test de connexion
    
    # Vérifier les informations système
    adb shell getprop ro.product.model
    # Retourne le modèle de l'appareil

    Configuration iOS : écosystème Apple

    L'automatisation iOS avec Appium nécessite un environnement macOS et présente des défis spécifiques liés aux politiques de sécurité d'Apple. Cette section détaille les prérequis et procédures pour iOS.

    Simulateur iOS : l'émulation officielle Apple

    Contrairement à Android où les émulateurs sont des machines virtuelles complètes, les simulateurs iOS sont des environnements d'exécution natifs qui simulent l'interface utilisateur d'iOS sur macOS. Cette approche offre d'excellentes performances mais avec certaines limitations fonctionnelles.

    Installation et configuration Xcode :

    1. Installer Xcode depuis l'App Store (installation longue, ~10GB)
    2. Lancer Xcode une première fois pour finaliser l'installation
    3. Accepter les termes de licence : sudo xcodebuild -license accept
    4. Ouvrir Xcode > Preferences > Platforms
    5. Télécharger les simulateurs iOS souhaités (iOS 15, 16, 17)

    Gestion des simulateurs iOS :

    # Lister tous les simulateurs disponibles
    xcrun simctl list devices
    
    # Créer un nouveau simulateur personnalisé
    xcrun simctl create "iPhone14-Test" "iPhone 14" "iOS16.0"
    
    # Lancer un simulateur spécifique
    xcrun simctl boot "iPhone 14"
    open -a Simulator
    
    # Vérifier les simulateurs actifs
    xcrun simctl list devices booted

    Appareil iOS physique : complexité et sécurité

    Tester sur un iPhone ou iPad physique nécessite un compte Apple Developer et une procédure de signature d'applications plus complexe que sur Android. Cette complexité reflète la politique de sécurité stricte d'Apple.

    Prérequis pour appareils iOS :

    • Compte Apple Developer (payant, ~99$/an)
    • Certificat de développement valide
    • Provisioning Profile incluant l'UDID de l'appareil de test
    • WebDriverAgent compilé et signé

    Configuration de l'appareil iOS :

    1. Connecter l'iPhone/iPad au Mac via USB
    2. Faire confiance à l'ordinateur (popup sur l'appareil iOS)
    3. Dans Xcode, ajouter l'appareil : Window > Devices and Simulators
    4. Installer WebDriverAgent sur l'appareil via Xcode
    5. Valider l'application dans Réglages > Général > Gestion des appareils
    Limitation iOS : Contrairement à Android où le débogage USB peut être activé sur n'importe quel appareil, iOS nécessite un compte développeur payant pour installer et signer les outils d'automatisation. Cette limitation rend les tests iOS plus coûteux à mettre en place.

Desired Capabilities : la configuration de vos sessions Appium

Les Desired Capabilities constituent le contrat entre votre script de test et le serveur Appium. Ces paramètres définissent précisément l'environnement d'exécution souhaité : plateforme cible, appareil à utiliser, application à tester, et nombreuses options de comportement.

Comprendre et maîtriser les capabilities est crucial car elles déterminent directement la réussite ou l'échec de vos sessions de test. Une capability mal configurée peut entraîner des échecs de connexion, des comportements imprévisibles, ou des performances dégradées.

Architecture et évolution des capabilities

Avec Appium 2.x, la spécification des capabilities a évolué pour se conformer au standard W3C WebDriver. Cette évolution introduit la notion de préfixes de namespace pour éviter les conflits et améliorer la compatibilité avec l'écosystème Selenium.

Les capabilities se divisent désormais en deux catégories :

  • Standard capabilities : définies par W3C (platformName, browserName, etc.)
  • Vendor-specific capabilities : spécifiques à Appium, préfixées par "appium:"

Capabilities essentielles pour Android

Cette section détaille les capabilities indispensables pour automatiser des applications Android, avec des exemples concrets et des explications sur l'impact de chaque paramètre.

Configuration de base Android

DesiredCapabilities caps = new DesiredCapabilities();

// ✅ Capabilities standard W3C
caps.setCapability("platformName", "Android");

// ✅ Capabilities spécifiques Appium (préfixe appium:)
caps.setCapability("appium:deviceName", "Pixel_4_API_33");
caps.setCapability("appium:automationName", "UiAutomator2");
caps.setCapability("appium:app", "/path/to/your/app.apk");

// ✅ Pour applications déjà installées
caps.setCapability("appium:appPackage", "com.example.app");
caps.setCapability("appium:appActivity", "com.example.app.MainActivity");

Explications détaillées :

  • platformName : Indique la plateforme cible (Android, iOS, Windows)
  • appium:deviceName : Nom de l'appareil ou émulateur à utiliser
  • appium:automationName : Moteur d'automatisation (UiAutomator2 recommandé)
  • appium:app : Chemin vers l'APK à installer et tester
  • appium:appPackage : Package de l'application (alternative à app)
  • appium:appActivity : Activité principale à lancer

Capabilities avancées Android

// ✅ Gestion des permissions
caps.setCapability("appium:autoGrantPermissions", true);

// ✅ Reset de l'application
caps.setCapability("appium:noReset", false);  // Reset app data
caps.setCapability("appium:fullReset", false); // Réinstallation complète

// ✅ Performance et stabilité
caps.setCapability("appium:newCommandTimeout", 300); // 5 minutes timeout
caps.setCapability("appium:androidInstallTimeout", 90000); // Installation timeout

// ✅ Configuration système
caps.setCapability("appium:autoAcceptAlerts", true);
caps.setCapability("appium:autoDismissAlerts", false);

// ✅ Options de debugging
caps.setCapability("appium:enablePerformanceLogging", true);
caps.setCapability("appium:printPageSourceOnFindFailure", true);

Capabilities pour iOS

Les capabilities iOS présentent des spécificités liées à l'écosystème Apple et aux contraintes de sécurité. La configuration diffère selon que vous ciblez un simulateur ou un appareil physique.

Configuration iOS Simulateur

DesiredCapabilities caps = new DesiredCapabilities();

// ✅ Configuration de base iOS
caps.setCapability("platformName", "iOS");
caps.setCapability("appium:deviceName", "iPhone 14 Pro");
caps.setCapability("appium:platformVersion", "16.0");
caps.setCapability("appium:automationName", "XCUITest");

// ✅ Application à tester
caps.setCapability("appium:app", "/path/to/your/app.app");
// OU pour une app installée
caps.setCapability("appium:bundleId", "com.example.iosapp");

// ✅ Simulateur spécifique
caps.setCapability("appium:udid", "auto"); // Auto-détection

Configuration iOS Appareil physique

// ✅ Appareil physique iOS
caps.setCapability("appium:udid", "00008030-001C2D223A02802E"); // UDID réel
caps.setCapability("appium:xcodeOrgId", "TEAM_ID_FROM_APPLE");
caps.setCapability("appium:xcodeSigningId", "iPhone Developer");

// ✅ WebDriverAgent sur appareil physique
caps.setCapability("appium:useNewWDA", true);
caps.setCapability("appium:wdaLaunchTimeout", 60000);

Patterns de configuration avancés

Pour les projets complexes, il est recommandé d'externaliser les capabilities dans des fichiers de configuration séparés, permettant une gestion plus flexible des environnements de test.

Configuration externalisée JSON

Créer un fichier capabilities.json pour chaque environnement :

{
  "android": {
    "platformName": "Android",
    "appium:deviceName": "Pixel_4_API_33",
    "appium:automationName": "UiAutomator2",
    "appium:app": "./apps/android/demo.apk",
    "appium:autoGrantPermissions": true,
    "appium:noReset": false,
    "appium:newCommandTimeout": 300
  },
  "ios": {
    "platformName": "iOS",
    "appium:deviceName": "iPhone 14 Pro",
    "appium:platformVersion": "16.0",
    "appium:automationName": "XCUITest",
    "appium:app": "./apps/ios/demo.app",
    "appium:udid": "auto"
  }
}

Chargement dynamique des capabilities

public class CapabilitiesLoader {
    
    public static DesiredCapabilities loadCapabilities(String platform) throws IOException {
        // Charger le fichier JSON
        String jsonPath = "src/test/resources/capabilities.json";
        String content = Files.readString(Paths.get(jsonPath));
        
        // Parser le JSON
        JSONObject json = new JSONObject(content);
        JSONObject platformCaps = json.getJSONObject(platform);
        
        // Convertir en DesiredCapabilities
        DesiredCapabilities caps = new DesiredCapabilities();
        for (String key : platformCaps.keySet()) {
            caps.setCapability(key, platformCaps.get(key));
        }
        
        return caps;
    }
}
Bonne pratique : Séparer les capabilities par environnement (dev, staging, production) et par plateforme permet une maintenance plus facile et évite les erreurs de configuration.

Projet Java avec Maven : architecture et bonnes pratiques

Maven transcende son rôle de simple gestionnaire de dépendances pour devenir l'épine dorsale de votre projet de test Appium. Il standardise la structure du projet, automatise les tâches de build, orchestre l'exécution des tests, et facilite l'intégration dans les pipelines CI/CD.

Création d'un projet Maven optimisé

Plutôt que de créer un projet Maven générique, nous allons structurer un projet spécifiquement optimisé pour les tests Appium avec toutes les bonnes pratiques intégrées dès le départ.

Génération du squelette de projet

# Création du projet Maven avec archetype personnalisé
mvn archetype:generate \
  -DgroupId=com.company.mobile.tests \
  -DartifactId=appium-test-suite \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DinteractiveMode=false

# Navigation dans le projet
cd appium-test-suite

Structure de projet recommandée

appium-test-suite/
├── pom.xml                           # Configuration Maven
├── src/
│   ├── main/
│   │   └── java/
│   │       └── com/company/mobile/
│   │           ├── pages/            # Page Object Models
│   │           │   ├── BasePage.java
│   │           │   ├── LoginPage.java
│   │           │   └── HomePage.java
│   │           ├── utils/            # Utilitaires et helpers
│   │           │   ├── DriverManager.java
│   │           │   ├── ConfigLoader.java
│   │           │   └── ScreenshotUtils.java
│   │           └── config/           # Configurations
│   │               └── AppiumConfig.java
│   └── test/
│       ├── java/
│       │   └── com/company/mobile/
│       │       ├── tests/            # Classes de test
│       │       │   ├── BaseTest.java
│       │       │   ├── LoginTests.java
│       │       │   └── NavigationTests.java
│       │       └── runners/          # Test runners
│       │           └── TestRunner.java
│       └── resources/
│           ├── capabilities/         # Fichiers de configuration
│           │   ├── android.json
│           │   └── ios.json
│           ├── test-data/           # Données de test
│           │   └── users.json
│           └── apps/                # Applications à tester
│               ├── android/
│               │   └── demo.apk
│               └── ios/
│                   └── demo.app

Configuration pom.xml moderne et complète

Le fichier pom.xml suivant intègre toutes les dépendances nécessaires avec les versions les plus récentes et les meilleures pratiques de configuration :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.company.mobile</groupId>
    <artifactId>appium-test-suite</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Appium Mobile Test Suite</name>
    <description>Suite de tests automatisés pour applications mobiles</description>

    <properties>
        <!-- Versions Java -->
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        
        <!-- Versions des dépendances principales -->
        <appium.version>9.3.0</appium.version>
        <selenium.version>4.25.0</selenium.version>
        <testng.version>7.10.2</testng.version>
        <allure.version>2.24.0</allure.version>
        
        <!-- Versions des utilitaires -->
        <jackson.version>2.15.2</jackson.version>
        <slf4j.version>2.0.9</slf4j.version>
        <commons-io.version>2.13.0</commons-io.version>
    </properties>

    <dependencies>
        <!-- ✅ Appium Java Client - Version moderne -->
        <dependency>
            <groupId>io.appium</groupId>
            <artifactId>java-client</artifactId>
            <version>${appium.version}</version>
        </dependency>

        <!-- ✅ Selenium WebDriver - Compatible Appium -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
        </dependency>

        <!-- ✅ TestNG - Framework de tests -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testng.version}</version>
        </dependency>

        <!-- ✅ Allure - Reporting avancé -->
        <dependency>
            <groupId>io.qameta.allure</groupId>
            <artifactId>allure-testng</artifactId>
            <version>${allure.version}</version>
        </dependency>

        <!-- ✅ Jackson - Parsing JSON -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <!-- ✅ Apache Commons IO - Utilitaires fichiers -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons-io.version}</version>
        </dependency>

        <!-- ✅ SLF4J - Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- ✅ Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <!-- ✅ Surefire Plugin - Exécution des tests -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.1.2</version>
                <configuration>
                    <suiteXmlFiles>
                        <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
                    </suiteXmlFiles>
                    <argLine>
                        -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/1.9.20/aspectjweaver-1.9.20.jar"
                    </argLine>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjweaver</artifactId>
                        <version>1.9.20</version>
                    </dependency>
                </dependencies>
            </plugin>

            <!-- ✅ Allure Plugin - Génération de rapports -->
            <plugin>
                <groupId>io.qameta.allure</groupId>
                <artifactId>allure-maven</artifactId>
                <version>2.12.0</version>
            </plugin>
        </plugins>
    </build>

    <!-- ✅ Profils pour différents environnements -->
    <profiles>
        <profile>
            <id>android</id>
            <properties>
                <test.platform>android</test.platform>
            </properties>
        </profile>
        
        <profile>
            <id>ios</id>
            <properties>
                <test.platform>ios</test.platform>
            </properties>
        </profile>
    </profiles>
</project>
Points clés de cette configuration :
  • Java 17 : Version LTS moderne recommandée
  • Appium 9.3.0 : Version récente sans MobileElement
  • Selenium 4.25.0 : Compatible avec Appium moderne
  • TestNG + Allure : Framework de test avec reporting avancé
  • Profils Maven : Séparation Android/iOS
  • Plugins optimisés : Surefire configuré pour TestNG et Allure

Configuration TestNG

Créez le fichier src/test/resources/testng.xml pour orchestrer vos tests :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">

<suite name="Appium Mobile Test Suite" verbose="1" parallel="tests" thread-count="2">
    
    <!-- Configuration globale -->
    <parameter name="appium.server.url" value="http://127.0.0.1:4723"/>
    <parameter name="implicit.wait" value="10"/>
    <parameter name="explicit.wait" value="15"/>
    
    <!-- Listeners pour le reporting -->
    <listeners>
        <listener class-name="io.qameta.allure.testng.AllureTestNg"/>
    </listeners>
    
    <!-- Suite de tests Android -->
    <test name="Android Tests" preserve-order="true">
        <parameter name="test.platform" value="android"/>
        
        <classes>
            <class name="com.company.mobile.tests.LoginTests"/>
            <class name="com.company.mobile.tests.NavigationTests"/>
        </classes>
    </test>
    
</suite>

Exécution avec Maven

Voici les commandes Maven essentielles pour l'exécution de vos tests :

# Exécution de base
mvn clean test

# Avec profil Android
mvn clean test -Pandroid

# Avec profil iOS
mvn clean test -Pios

# Exécuter une classe spécifique
mvn test -Dtest=LoginTests

# Exécuter avec génération de rapport Allure
mvn clean test allure:report

# Servir le rapport Allure localement
mvn allure:serve

Optimisation des performances

Pour optimiser l'exécution de vos tests Appium :

# Configuration JVM optimisée
MAVEN_OPTS="-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC" mvn clean test

# Tests parallèles (attention : besoin de plusieurs émulateurs)
mvn test -DthreadCount=2 -Dparallel=classes

# Mode debug avec logs détaillés
mvn test -X -Dappium.log.level=debug
Cette configuration Maven vous donne une base solide pour :
  • Gérer les dépendances Appium modernes automatiquement
  • Structurer votre projet de manière professionnelle
  • Exécuter vos tests de façon flexible (Android/iOS)
  • Générer des rapports visuels avec Allure
  • Intégrer facilement dans un pipeline CI/CD

Exemple de test Spock avec code moderne

Spock est un framework de test puissant pour Java et Groovy qui offre une syntaxe expressive et des fonctionnalités avancées comme les tests paramétrés et les mocks intégrés. Voici comment l'utiliser avec Appium et du code moderne.

Configuration Spock dans le pom.xml

Ajoutez ces dépendances à votre pom.xml pour utiliser Spock avec Appium :

<!-- ✅ Spock Framework -->
<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-core</artifactId>
    <version>2.3-groovy-4.0</version>
    <scope>test</scope>
</dependency>

<!-- ✅ Groovy (requis par Spock) -->
<dependency>
    <groupId>org.apache.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>4.0.15</version>
    <type>pom</type>
    <scope>test</scope>
</dependency>

<!-- Plugin Groovy pour Maven -->
<plugin>
    <groupId>org.codehaus.gmavenplus</groupId>
    <artifactId>gmavenplus-plugin</artifactId>
    <version>3.0.2</version>
    <executions>
        <execution>
            <goals>
                <goal>compileTests</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Classe de test Spock moderne

Créez le fichier src/test/groovy/com/company/mobile/tests/LoginSpec.groovy :

package com.company.mobile.tests

import io.appium.java_client.AppiumBy
import io.appium.java_client.android.AndroidDriver
import org.openqa.selenium.WebElement
import org.openqa.selenium.remote.DesiredCapabilities
import org.openqa.selenium.support.ui.WebDriverWait
import org.openqa.selenium.support.ui.ExpectedConditions
import spock.lang.*
import java.time.Duration

class LoginSpec extends Specification {
    
    // ✅ Driver moderne sans paramètre générique
    static AndroidDriver driver
    static WebDriverWait wait
    
    def setupSpec() {
        // ✅ Configuration avec capabilities modernes
        def caps = new DesiredCapabilities()
        caps.setCapability("platformName", "Android")
        caps.setCapability("appium:deviceName", "Android Emulator")
        caps.setCapability("appium:automationName", "UiAutomator2")
        caps.setCapability("appium:app", getAppPath())
        caps.setCapability("appium:autoGrantPermissions", true)
        caps.setCapability("appium:noReset", false)
        caps.setCapability("appium:newCommandTimeout", 300)
        
        // ✅ URL moderne sans /wd/hub
        driver = new AndroidDriver(new URL("http://127.0.0.1:4723"), caps)
        
        // ✅ Configuration des timeouts
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10))
        wait = new WebDriverWait(driver, Duration.ofSeconds(15))
    }
    
    def cleanupSpec() {
        if (driver != null) {
            driver.quit()
        }
    }
    
    def "utilisateur peut naviguer vers la section App"() {
        when: "je clique sur la section App"
        WebElement appSection = driver.findElement(
            AppiumBy.androidUIAutomator('new UiSelector().text("App")')
        )
        appSection.click()
        
        then: "la section App doit être accessible"
        WebElement activitySection = wait.until(
            ExpectedConditions.elementToBeClickable(
                AppiumBy.androidUIAutomator('new UiSelector().text("Activity")')
            )
        )
        activitySection.isDisplayed()
    }
    
    def "formulaire de saisie fonctionne correctement"() {
        given: "je navigue vers une page avec des champs de saisie"
        driver.findElement(AppiumBy.androidUIAutomator('new UiSelector().text("Views")')).click()
        
        WebElement controlsOption = wait.until(
            ExpectedConditions.elementToBeClickable(
                AppiumBy.androidUIAutomator(
                    'new UiScrollable(new UiSelector().scrollable(true))' +
                    '.scrollIntoView(new UiSelector().text("Controls"))'
                )
            )
        )
        controlsOption.click()
        
        driver.findElement(
            AppiumBy.androidUIAutomator('new UiSelector().text("1. Light Theme")')
        ).click()
        
        when: "je saisis du texte dans le champ"
        WebElement editText = wait.until(
            ExpectedConditions.elementToBeClickable(
                AppiumBy.className("android.widget.EditText")
            )
        )
        
        String testText = "Test Spock avec Appium moderne!"
        editText.clear()
        editText.sendKeys(testText)
        
        then: "le texte doit être correctement saisi"
        editText.getText() == testText
        
        and: "je peux fermer le clavier"
        try {
            driver.hideKeyboard()
        } catch (Exception e) {
            // Clavier déjà fermé
        }
        true // Test réussi
    }
    
    def "checkbox et radiobutton fonctionnent avec différentes valeurs"() {
        given: "je suis sur la page des contrôles"
        navigateToControlsPage()
        
        when: "je teste différents états de checkbox"
        WebElement checkbox = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/check1")
        )
        
        def initialState = checkbox.isSelected()
        checkbox.click()
        def newState = checkbox.isSelected()
        
        then: "l'état de la checkbox doit changer"
        initialState != newState
        
        when: "je teste les radio buttons"
        WebElement radio1 = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/radio1")
        )
        WebElement radio2 = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/radio2")
        )
        
        radio1.click()
        def radio1Selected = radio1.isSelected()
        
        radio2.click() 
        def radio2Selected = radio2.isSelected()
        def radio1AfterRadio2 = radio1.isSelected()
        
        then: "seul un radio button doit être sélectionné à la fois"
        radio1Selected == true
        radio2Selected == true
        radio1AfterRadio2 == false
    }
    
    @Unroll
    def "test paramétré avec différentes données: #testData"() {
        given: "je navigue vers la page de saisie"
        navigateToTextInputPage()
        
        when: "je saisis les données de test"
        WebElement editText = wait.until(
            ExpectedConditions.elementToBeClickable(
                AppiumBy.className("android.widget.EditText")
            )
        )
        
        editText.clear()
        editText.sendKeys(testData)
        
        then: "le texte doit être correctement affiché"
        editText.getText() == testData
        
        where: "les données de test sont"
        testData << [
            "Test simple",
            "Test avec des chiffres 123",
            "Test avec caractères spéciaux !@#",
            "Test très long pour vérifier la capacité du champ de texte à gérer du contenu étendu"
        ]
    }
    
    def "gestion d'erreurs avec timeout"() {
        when: "je cherche un élément qui n'existe pas"
        try {
            wait.until(
                ExpectedConditions.visibilityOfElementLocated(
                    AppiumBy.id("element.inexistant")
                )
            )
        } catch (Exception e) {
            // Exception attendue
        }
        
        then: "le test ne doit pas planter"
        driver != null
        driver.getPageSource() != null
    }
    
    // ✅ Méthodes utilitaires privées
    private void navigateToControlsPage() {
        driver.findElement(AppiumBy.androidUIAutomator('new UiSelector().text("Views")')).click()
        
        WebElement controlsOption = wait.until(
            ExpectedConditions.elementToBeClickable(
                AppiumBy.androidUIAutomator(
                    'new UiScrollable(new UiSelector().scrollable(true))' +
                    '.scrollIntoView(new UiSelector().text("Controls"))'
                )
            )
        )
        controlsOption.click()
        
        driver.findElement(
            AppiumBy.androidUIAutomator('new UiSelector().text("1. Light Theme")')
        ).click()
    }
    
    private void navigateToTextInputPage() {
        navigateToControlsPage()
    }
    
    private String getAppPath() {
        return System.getProperty("user.dir") + "/src/test/resources/apps/android/ApiDemos-debug.apk"
    }
}

Exécution des tests Spock

Les tests Spock s'exécutent comme des tests classiques avec Maven :

# Exécuter tous les tests (y compris Spock)
mvn clean test

# Exécuter seulement les tests Spock
mvn test -Dtest="*Spec"

# Exécuter un test Spock spécifique
mvn test -Dtest="LoginSpec"
Avantages de Spock :
  • Syntaxe expressive : given/when/then plus lisible
  • Tests paramétrés : @Unroll pour tester plusieurs datasets
  • Mocks intégrés : pas besoin de Mockito
  • Assertions puissantes : conditions multiples avec and/or
  • Groovy : syntaxe plus concise que Java

Exécution avancée avec Maven

Maven offre une flexibilité exceptionnelle pour l'exécution de tests Appium. Cette section détaille les commandes avancées, les profils de configuration, et les techniques d'optimisation pour différents scénarios d'exécution.

Commandes de base

Ces commandes constituent le socle de l'exécution quotidienne des tests :

# ✅ Compilation et exécution complète
mvn clean compile test

# ✅ Exécution rapide sans nettoyage
mvn test

# ✅ Ignorer les échecs de tests (pour CI/CD)
mvn test -Dmaven.test.failure.ignore=true

# ✅ Exécution en mode offline (sans téléchargement)
mvn test -o

Exécution sélective

Pour des cycles de développement plus rapides, l'exécution sélective est essentielle :

# ✅ Exécuter une classe de test spécifique
mvn test -Dtest=LoginTests

# ✅ Exécuter une méthode de test spécifique
mvn test -Dtest=LoginTests#testValidLogin

# ✅ Exécuter plusieurs classes
mvn test -Dtest=LoginTests,NavigationTests,CheckoutTests

# ✅ Exécuter par pattern de nom
mvn test -Dtest="*Login*"

# ✅ Exécuter par package
mvn test -Dtest="com.company.mobile.tests.smoke.*"

# ✅ Exclure certains tests
mvn test -Dtest="!*Integration*"

Configuration d'environnement

Adapter l'exécution selon l'environnement cible :

# ✅ Spécifier la plateforme de test
mvn test -Dtest.platform=android
mvn test -Dtest.platform=ios

# ✅ Configurer le serveur Appium
mvn test -Dappium.server.url=http://192.168.1.100:4723

# ✅ Ajuster les timeouts
mvn test -Dimplicit.wait=15 -Dexplicit.wait=30

# ✅ Spécifier l'application à tester
mvn test -Dapp.path=/path/to/your/app.apk

# ✅ Configurer le device
mvn test -Ddevice.name="Pixel_6_API_33"

Profils Maven avancés

Définissez des profils complexes dans votre pom.xml pour différents environnements :

<profiles>
    <!-- ✅ Profil de développement local -->
    <profile>
        <id>dev</id>
        <properties>
            <test.platform>android</test.platform>
            <appium.server.url>http://127.0.0.1:4723</appium.server.url>
            <device.name>Android Emulator</device.name>
            <implicit.wait>10</implicit.wait>
        </properties>
    </profile>
    
    <!-- ✅ Profil d'intégration continue -->
    <profile>
        <id>ci</id>
        <properties>
            <test.platform>android</test.platform>
            <appium.server.url>http://appium-grid:4723</appium.server.url>
            <device.name>ci-emulator</device.name>
            <implicit.wait>15</implicit.wait>
            <maven.test.failure.ignore>false</maven.test.failure.ignore>
        </properties>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <reportsDirectory>${project.build.directory}/ci-reports</reportsDirectory>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
    
    <!-- ✅ Profil de tests de performance -->
    <profile>
        <id>performance</id>
        <properties>
            <test.platform>android</test.platform>
            <parallel.tests>true</parallel.tests>
            <thread.count>3</thread.count>
        </properties>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <parallel>classes</parallel>
                        <threadCount>${thread.count}</threadCount>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Exécution avec profils

# ✅ Utiliser le profil de développement
mvn clean test -Pdev

# ✅ Utiliser le profil CI
mvn clean test -Pci

# ✅ Combiner plusieurs profils
mvn clean test -Pci,performance

# ✅ Surcharger des propriétés de profil
mvn test -Pdev -Ddevice.name="Pixel_7_API_34"

Optimisation des performances

Techniques pour accélérer l'exécution des tests :

# ✅ Configuration JVM optimisée
export MAVEN_OPTS="-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UseZGC"

# ✅ Exécution parallèle par classes (attention aux resources)
mvn test -Dparallel=classes -DthreadCount=2

# ✅ Exécution parallèle par méthodes
mvn test -Dparallel=methods -DthreadCount=4

# ✅ Compilation incrémentale
mvn test -Dmaven.compiler.useIncrementalCompilation=true

# ✅ Skip des étapes non nécessaires
mvn test -DskipTests=false -Dmaven.javadoc.skip=true -Dmaven.source.skip=true

Intégration avec Allure Reports

Génération automatique de rapports visuels :

# ✅ Exécuter les tests avec génération des données Allure
mvn clean test

# ✅ Générer le rapport HTML
mvn allure:report

# ✅ Ouvrir le rapport dans le navigateur
mvn allure:serve

# ✅ Pipeline complet
mvn clean test allure:report allure:serve

# ✅ Générer le rapport avec historique
mvn allure:report -Dallure.results.directory=target/allure-results

Debugging et logs avancés

# ✅ Mode debug Maven complet
mvn test -X -Dappium.log.level=debug

# ✅ Logs détaillés sans le mode debug
mvn test -Dorg.slf4j.simpleLogger.defaultLogLevel=debug

# ✅ Capturer la sortie dans un fichier
mvn test -l test-execution.log

# ✅ Afficher les propriétés système utilisées
mvn test -Dtest=LoginTests -X | grep -E "test\.|appium\.|device\."

Scripts d'automatisation

Créez des scripts pour standardiser l'exécution :

Script bash pour Linux/Mac (run-tests.sh)

#!/bin/bash

# Configuration par défaut
PLATFORM="android"
PROFILE="dev"
TEST_CLASS=""

# Gestion des arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        -p|--platform)
            PLATFORM="$2"
            shift 2
            ;;
        --profile)
            PROFILE="$2"
            shift 2
            ;;
        -t|--test)
            TEST_CLASS="$2"
            shift 2
            ;;
        *)
            echo "Option inconnue: $1"
            exit 1
            ;;
    esac
done

echo "🚀 Lancement des tests Appium"
echo "Plateforme: $PLATFORM"
echo "Profil: $PROFILE"
echo "Classe de test: ${TEST_CLASS:-'Toutes'}"

# Construction de la commande Maven
MVN_CMD="mvn clean test -P$PROFILE -Dtest.platform=$PLATFORM"

if [ ! -z "$TEST_CLASS" ]; then
    MVN_CMD="$MVN_CMD -Dtest=$TEST_CLASS"
fi

echo "Commande: $MVN_CMD"
echo ""

# Exécution
$MVN_CMD

# Génération du rapport si les tests sont passés
if [ $? -eq 0 ]; then
    echo "✅ Tests réussis - Génération du rapport Allure"
    mvn allure:report allure:serve
else
    echo "❌ Échec des tests"
    exit 1
fi

Utilisation du script

# ✅ Rendre le script exécutable
chmod +x run-tests.sh

# ✅ Exécuter avec paramètres
./run-tests.sh --platform android --profile dev --test LoginTests

# ✅ Exécuter avec configuration par défaut
./run-tests.sh
Cette configuration Maven avancée vous permet de :
  • Adapter l'exécution selon l'environnement (dev, CI, production)
  • Optimiser les performances avec l'exécution parallèle
  • Générer des rapports automatiquement avec Allure
  • Automatiser avec des scripts personnalisés
  • Débugger efficacement avec des logs détaillés
  • Variables d'environnement correctement définies
  • Accessibilité d'ADB et des outils Android
  • Permissions et configuration système
  • Versions de compatibilité entre les composants
  • Validation réussie : Si Appium Doctor affiche toutes les vérifications en vert, votre environnement est correctement configuré et prêt pour le développement de tests Appium.
    Troubleshooting courant : Les problèmes les plus fréquents sont liés aux variables d'environnement mal définies ou aux permissions insuffisantes. Sur Windows, redémarrez votre terminal après modification des variables. Sur macOS/Linux, rechargez votre profil avec source ~/.bashrc ou équivalent.

    Émulateurs & Appareils physiques : choisir la bonne stratégie de test

    Le choix entre émulateurs et appareils physiques constitue l'une des décisions stratégiques majeures dans la mise en place de tests Appium. Chaque approche présente des avantages et inconvénients qu'il convient de comprendre pour optimiser votre stratégie de test.

    Émulateurs Android : la puissance de la virtualisation

    Les émulateurs Android (AVD - Android Virtual Device) sont des machines virtuelles qui simulent un appareil Android complet sur votre ordinateur. Cette approche offre une flexibilité exceptionnelle pour les tests automatisés, particulièrement dans les environnements de développement et d'intégration continue.

    Avantages des émulateurs :

    • Reproductibilité : configuration identique entre tous les environnements de test
    • Économie : pas d'investissement en hardware physique
    • Parallélisation : possibilité de lancer plusieurs instances simultanément
    • Contrôle complet : simulation de conditions réseau, batterie, localisation GPS
    • Intégration CI/CD : parfait pour l'automatisation complète
    • Reset facile : retour à un état propre en quelques secondes

    Inconvénients des émulateurs :

    • Performance : généralement plus lents que les appareils physiques
    • Fidélité limitée : certains comportements hardware non simulés
    • Consommation ressources : utilisation intensive CPU/RAM de la machine hôte
    • Limitations capteurs : caméra, NFC, certains capteurs indisponibles

    Création d'un émulateur Android optimisé :

    La création d'un émulateur efficace nécessite un équilibre entre performance et fidélité. Voici la procédure détaillée pour créer un AVD optimisé pour les tests Appium :

    1. Ouvrir Android Studio et naviguer vers Tools > Device Manager
    2. Cliquer sur "Create Device" pour lancer l'assistant de création
    3. Sélection du hardware :
      • Privilégier les profils Pixel (Pixel 4, Pixel 6, Pixel 7) pour leur compatibilité optimale
      • Éviter les profils avec trop de RAM (>4GB) qui ralentissent la machine hôte
      • Pour les tablettes, Pixel C ou Nexus 10 sont de bons compromis
    4. Sélection de l'image système :
      • API Level 30 (Android 11) ou API 33 (Android 13) pour la modernité
      • Target x86_64 pour les meilleures performances sur PC Intel/AMD
      • Google APIs si vous testez des apps utilisant les services Google
      • Éviter les images ARM sur machine x86 (très lent par émulation)
    5. Configuration avancée :
      • RAM : 2048-4096 MB (équilibre performance/ressources)
      • Storage interne : 8GB minimum, 16GB recommandé
      • Graphics : Hardware GLES 2.0 si supporté, Software sinon
      • Boot option : Cold boot (plus stable pour les tests)

    Validation de l'émulateur :

    # Lancer l'émulateur depuis la ligne de commande
    emulator -avd Pixel_4_API_33
    
    # Dans un autre terminal, vérifier la détection
    adb devices
    # Sortie attendue : emulator-5554   device
    
    # Vérifier les capacités de l'émulateur
    adb shell getprop ro.build.version.release
    # Sortie attendue : 13 (pour Android 13)

    Appareil Android physique : fidélité maximale

    Tester sur un appareil Android physique reste indispensable pour valider le comportement réel de votre application. Les différences entre émulation et réalité peuvent être subtiles mais critiques : performances réelles, comportements des capteurs, interactions avec le système, gestion des notifications, etc.

    Activation du mode développeur :

    Cette procédure est identique sur la plupart des appareils Android, mais peut varier légèrement selon les constructeurs :

    1. Ouvrir Paramètres > À propos du téléphone (ou "À propos de l'appareil")
    2. Rechercher "Numéro de build" ou "Version du logiciel"
    3. Taper 7 fois rapidement sur cette entrée
    4. Un message confirme l'activation : "Vous êtes maintenant un développeur"
    5. Retourner dans Paramètres : un nouveau menu "Options pour les développeurs" apparaît

    Configuration du débogage USB :

    1. Dans "Options pour les développeurs", activer "Débogage USB"
    2. Optionnel mais recommandé : activer "Rester éveillé" (évite la veille pendant les tests)
    3. Connecter l'appareil via USB à l'ordinateur
    4. Une popup apparaît sur l'appareil : "Autoriser le débogage USB ?"
    5. Cocher "Toujours autoriser depuis cet ordinateur" et valider

    Résolution des problèmes de connexion :

    La connexion d'appareils physiques peut poser des défis spécifiques selon l'OS et le constructeur :

    Sur Windows :

    • Installer les pilotes USB spécifiques au constructeur (Samsung, Xiaomi, OnePlus, etc.)
    • Les pilotes "universels" de Windows ne suffisent généralement pas
    • En cas d'échec, utiliser les pilotes génériques Google USB depuis Android Studio

    Sur macOS :

    • Généralement plug-and-play, pas de pilotes supplémentaires nécessaires
    • En cas de problème, vérifier les autorisations de sécurité macOS

    Sur Linux :

    • Configurer les règles udev pour les permissions USB
    • Ajouter l'utilisateur au groupe "plugdev"
    • Redémarrer le service udev : sudo service udev restart

    Validation de la connexion :

    # Vérifier la détection de l'appareil
    adb devices
    # Sortie attendue : R58M12345X   device (pas unauthorized)
    
    # Tester la communication
    adb shell echo "Test de connexion"
    # Doit retourner : Test de connexion
    
    # Vérifier les informations système
    adb shell getprop ro.product.model
    # Retourne le modèle de l'appareil

    Configuration iOS : écosystème Apple

    L'automatisation iOS avec Appium nécessite un environnement macOS et présente des défis spécifiques liés aux politiques de sécurité d'Apple. Cette section détaille les prérequis et procédures pour iOS.

    Simulateur iOS : l'émulation officielle Apple

    Contrairement à Android où les émulateurs sont des machines virtuelles complètes, les simulateurs iOS sont des environnements d'exécution natifs qui simulent l'interface utilisateur d'iOS sur macOS. Cette approche offre d'excellentes performances mais avec certaines limitations fonctionnelles.

    Installation et configuration Xcode :

    1. Installer Xcode depuis l'App Store (installation longue, ~10GB)
    2. Lancer Xcode une première fois pour finaliser l'installation
    3. Accepter les termes de licence : sudo xcodebuild -license accept
    4. Ouvrir Xcode > Preferences > Platforms
    5. Télécharger les simulateurs iOS souhaités (iOS 15, 16, 17)

    Gestion des simulateurs iOS :

    # Lister tous les simulateurs disponibles
    xcrun simctl list devices
    
    # Créer un nouveau simulateur personnalisé
    xcrun simctl create "iPhone14-Test" "iPhone 14" "iOS16.0"
    
    # Lancer un simulateur spécifique
    xcrun simctl boot "iPhone 14"
    open -a Simulator
    
    # Vérifier les simulateurs actifs
    xcrun simctl list devices booted

    Appareil iOS physique : complexité et sécurité

    Tester sur un iPhone ou iPad physique nécessite un compte Apple Developer et une procédure de signature d'applications plus complexe que sur Android. Cette complexité reflète la politique de sécurité stricte d'Apple.

    Prérequis pour appareils iOS :

    • Compte Apple Developer (payant, ~99$/an)
    • Certificat de développement valide
    • Provisioning Profile incluant l'UDID de l'appareil de test
    • WebDriverAgent compilé et signé

    Configuration de l'appareil iOS :

    1. Connecter l'iPhone/iPad au Mac via USB
    2. Faire confiance à l'ordinateur (popup sur l'appareil iOS)
    3. Dans Xcode, ajouter l'appareil : Window > Devices and Simulators
    4. Installer WebDriverAgent sur l'appareil via Xcode
    5. Valider l'application dans Réglages > Général > Gestion des appareils
    Limitation iOS : Contrairement à Android où le débogage USB peut être activé sur n'importe quel appareil, iOS nécessite un compte développeur payant pour installer et signer les outils d'automatisation. Cette limitation rend les tests iOS plus coûteux à mettre en place.

    Desired Capabilities : la configuration de vos sessions Appium

    Les Desired Capabilities constituent le contrat entre votre script de test et le serveur Appium. Ces paramètres définissent précisément l'environnement d'exécution souhaité : plateforme cible, appareil à utiliser, application à tester, et nombreuses options de comportement.

    Comprendre et maîtriser les capabilities est crucial car elles déterminent directement la réussite ou l'échec de vos sessions de test. Une capability mal configurée peut entraîner des échecs de connexion, des comportements imprévisibles, ou des performances dégradées.

    Architecture et évolution des capabilities

    Avec Appium 2.x, la spécification des capabilities a évolué pour se conformer au standard W3C WebDriver. Cette évolution introduit la notion de préfixes de namespace pour éviter les conflits et améliorer la compatibilité avec l'écosystème Selenium.

    Les capabilities se divisent désormais en deux catégories :

    • Standard capabilities : définies par W3C (platformName, browserName, etc.)
    • Vendor-specific capabilities : spécifiques à Appium, préfixées par "appium:"

    Capabilities essentielles pour Android

    Cette section détaille les capabilities indispensables pour automatiser des applications Android, avec des exemples concrets et des explications sur l'impact de chaque paramètre.

    Configuration de base Android

    DesiredCapabilities caps = new DesiredCapabilities();
    
    // ✅ Capabilities standard W3C
    caps.setCapability("platformName", "Android");
    
    // ✅ Capabilities spécifiques Appium (préfixe appium:)
    caps.setCapability("appium:deviceName", "Pixel_4_API_33");
    caps.setCapability("appium:automationName", "UiAutomator2");
    caps.setCapability("appium:app", "/path/to/your/app.apk");
    
    // ✅ Pour applications déjà installées
    caps.setCapability("appium:appPackage", "com.example.app");
    caps.setCapability("appium:appActivity", "com.example.app.MainActivity");

    Explications détaillées :

    • platformName : Indique la plateforme cible (Android, iOS, Windows)
    • appium:deviceName : Nom de l'appareil ou émulateur à utiliser
    • appium:automationName : Moteur d'automatisation (UiAutomator2 recommandé)
    • appium:app : Chemin vers l'APK à installer et tester
    • appium:appPackage : Package de l'application (alternative à app)
    • appium:appActivity : Activité principale à lancer

    Capabilities avancées Android

    // ✅ Gestion des permissions
    caps.setCapability("appium:autoGrantPermissions", true);
    
    // ✅ Reset de l'application
    caps.setCapability("appium:noReset", false);  // Reset app data
    caps.setCapability("appium:fullReset", false); // Réinstallation complète
    
    // ✅ Performance et stabilité
    caps.setCapability("appium:newCommandTimeout", 300); // 5 minutes timeout
    caps.setCapability("appium:androidInstallTimeout", 90000); // Installation timeout
    
    // ✅ Configuration système
    caps.setCapability("appium:autoAcceptAlerts", true);
    caps.setCapability("appium:autoDismissAlerts", false);
    
    // ✅ Options de debugging
    caps.setCapability("appium:enablePerformanceLogging", true);
    caps.setCapability("appium:printPageSourceOnFindFailure", true);

    Capabilities pour iOS

    Les capabilities iOS présentent des spécificités liées à l'écosystème Apple et aux contraintes de sécurité. La configuration diffère selon que vous ciblez un simulateur ou un appareil physique.

    Configuration iOS Simulateur

    DesiredCapabilities caps = new DesiredCapabilities();
    
    // ✅ Configuration de base iOS
    caps.setCapability("platformName", "iOS");
    caps.setCapability("appium:deviceName", "iPhone 14 Pro");
    caps.setCapability("appium:platformVersion", "16.0");
    caps.setCapability("appium:automationName", "XCUITest");
    
    // ✅ Application à tester
    caps.setCapability("appium:app", "/path/to/your/app.app");
    // OU pour une app installée
    caps.setCapability("appium:bundleId", "com.example.iosapp");
    
    // ✅ Simulateur spécifique
    caps.setCapability("appium:udid", "auto"); // Auto-détection

    Configuration iOS Appareil physique

    // ✅ Appareil physique iOS
    caps.setCapability("appium:udid", "00008030-001C2D223A02802E"); // UDID réel
    caps.setCapability("appium:xcodeOrgId", "TEAM_ID_FROM_APPLE");
    caps.setCapability("appium:xcodeSigningId", "iPhone Developer");
    
    // ✅ WebDriverAgent sur appareil physique
    caps.setCapability("appium:useNewWDA", true);
    caps.setCapability("appium:wdaLaunchTimeout", 60000);

    Patterns de configuration avancés

    Pour les projets complexes, il est recommandé d'externaliser les capabilities dans des fichiers de configuration séparés, permettant une gestion plus flexible des environnements de test.

    Configuration externalisée JSON

    Créer un fichier capabilities.json pour chaque environnement :

    {
      "android": {
        "platformName": "Android",
        "appium:deviceName": "Pixel_4_API_33",
        "appium:automationName": "UiAutomator2",
        "appium:app": "./apps/android/demo.apk",
        "appium:autoGrantPermissions": true,
        "appium:noReset": false,
        "appium:newCommandTimeout": 300
      },
      "ios": {
        "platformName": "iOS",
        "appium:deviceName": "iPhone 14 Pro",
        "appium:platformVersion": "16.0",
        "appium:automationName": "XCUITest",
        "appium:app": "./apps/ios/demo.app",
        "appium:udid": "auto"
      }
    }

    Chargement dynamique des capabilities

    public class CapabilitiesLoader {
        
        public static DesiredCapabilities loadCapabilities(String platform) throws IOException {
            // Charger le fichier JSON
            String jsonPath = "src/test/resources/capabilities.json";
            String content = Files.readString(Paths.get(jsonPath));
            
            // Parser le JSON
            JSONObject json = new JSONObject(content);
            JSONObject platformCaps = json.getJSONObject(platform);
            
            // Convertir en DesiredCapabilities
            DesiredCapabilities caps = new DesiredCapabilities();
            for (String key : platformCaps.keySet()) {
                caps.setCapability(key, platformCaps.get(key));
            }
            
            return caps;
        }
    }
    Bonne pratique : Séparer les capabilities par environnement (dev, staging, production) et par plateforme permet une maintenance plus facile et évite les erreurs de configuration.

    Projet Java avec Maven : architecture et bonnes pratiques

    Maven transcende son rôle de simple gestionnaire de dépendances pour devenir l'épine dorsale de votre projet de test Appium. Il standardise la structure du projet, automatise les tâches de build, orchestre l'exécution des tests, et facilite l'intégration dans les pipelines CI/CD.

    Création d'un projet Maven optimisé

    Plutôt que de créer un projet Maven générique, nous allons structurer un projet spécifiquement optimisé pour les tests Appium avec toutes les bonnes pratiques intégrées dès le départ.

    Génération du squelette de projet

    # Création du projet Maven avec archetype personnalisé
    mvn archetype:generate \
      -DgroupId=com.company.mobile.tests \
      -DartifactId=appium-test-suite \
      -DarchetypeArtifactId=maven-archetype-quickstart \
      -DinteractiveMode=false
    
    # Navigation dans le projet
    cd appium-test-suite

    Structure de projet recommandée

    appium-test-suite/
    ├── pom.xml                           # Configuration Maven
    ├── src/
    │   ├── main/
    │   │   └── java/
    │   │       └── com/company/mobile/
    │   │           ├── pages/            # Page Object Models
    │   │           │   ├── BasePage.java
    │   │           │   ├── LoginPage.java
    │   │           │   └── HomePage.java
    │   │           ├── utils/            # Utilitaires et helpers
    │   │           │   ├── DriverManager.java
    │   │           │   ├── ConfigLoader.java
    │   │           │   └── ScreenshotUtils.java
    │   │           └── config/           # Configurations
    │   │               └── AppiumConfig.java
    │   └── test/
    │       ├── java/
    │       │   └── com/company/mobile/
    │       │       ├── tests/            # Classes de test
    │       │       │   ├── BaseTest.java
    │       │       │   ├── LoginTests.java
    │       │       │   └── NavigationTests.java
    │       │       └── runners/          # Test runners
    │       │           └── TestRunner.java
    │       └── resources/
    │           ├── capabilities/         # Fichiers de configuration
    │           │   ├── android.json
    │           │   └── ios.json
    │           ├── test-data/           # Données de test
    │           │   └── users.json
    │           └── apps/                # Applications à tester
    │               ├── android/
    │               │   └── demo.apk
    │               └── ios/
    │                   └── demo.app

    Configuration pom.xml moderne et complète

    Le fichier pom.xml suivant intègre toutes les dépendances nécessaires avec les versions les plus récentes et les meilleures pratiques de configuration :

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                                 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.company.mobile</groupId>
        <artifactId>appium-test-suite</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>Appium Mobile Test Suite</name>
        <description>Suite de tests automatisés pour applications mobiles</description>
    
        <properties>
            <!-- Versions Java -->
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            
            <!-- Versions des dépendances principales -->
            <appium.version>9.3.0</appium.version>
            <selenium.version>4.25.0</selenium.version>
            <testng.version>7.10.2</testng.version>
            <allure.version>2.24.0</allure.version>
            
            <!-- Versions des utilitaires -->
            <jackson.version>2.15.2</jackson.version>
            <slf4j.version>2.0.9</slf4j.version>
            <commons-io.version>2.13.0</commons-io.version>
        </properties>
    
        <dependencies>
            <!-- ✅ Appium Java Client - Version moderne -->
            <dependency>
                <groupId>io.appium</groupId>
                <artifactId>java-client</artifactId>
                <version>${appium.version}</version>
            </dependency>
    
            <!-- ✅ Selenium WebDriver - Compatible Appium -->
            <dependency>
                <groupId>org.seleniumhq.selenium</groupId>
                <artifactId>selenium-java</artifactId>
                <version>${selenium.version}</version>
            </dependency>
    
            <!-- ✅ TestNG - Framework de tests -->
            <dependency>
                <groupId>org.testng</groupId>
                <artifactId>testng</artifactId>
                <version>${testng.version}</version>
            </dependency>
    
            <!-- ✅ Allure - Reporting avancé -->
            <dependency>
                <groupId>io.qameta.allure</groupId>
                <artifactId>allure-testng</artifactId>
                <version>${allure.version}</version>
            </dependency>
    
            <!-- ✅ Jackson - Parsing JSON -->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>${jackson.version}</version>
            </dependency>
    
            <!-- ✅ Apache Commons IO - Utilitaires fichiers -->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>${commons-io.version}</version>
            </dependency>
    
            <!-- ✅ SLF4J - Logging -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-simple</artifactId>
                <version>${slf4j.version}</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <!-- ✅ Compiler Plugin -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.11.0</version>
                    <configuration>
                        <source>17</source>
                        <target>17</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
    
                <!-- ✅ Surefire Plugin - Exécution des tests -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>3.1.2</version>
                    <configuration>
                        <suiteXmlFiles>
                            <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
                        </suiteXmlFiles>
                        <argLine>
                            -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/1.9.20/aspectjweaver-1.9.20.jar"
                        </argLine>
                    </configuration>
                    <dependencies>
                        <dependency>
                            <groupId>org.aspectj</groupId>
                            <artifactId>aspectjweaver</artifactId>
                            <version>1.9.20</version>
                        </dependency>
                    </dependencies>
                </plugin>
    
                <!-- ✅ Allure Plugin - Génération de rapports -->
                <plugin>
                    <groupId>io.qameta.allure</groupId>
                    <artifactId>allure-maven</artifactId>
                    <version>2.12.0</version>
                </plugin>
            </plugins>
        </build>
    
        <!-- ✅ Profils pour différents environnements -->
        <profiles>
            <profile>
                <id>android</id>
                <properties>
                    <test.platform>android</test.platform>
                </properties>
            </profile>
            
            <profile>
                <id>ios</id>
                <properties>
                    <test.platform>ios</test.platform>
                </properties>
            </profile>
        </profiles>
    </project>
    Points clés de cette configuration :
    • Java 17 : Version LTS moderne recommandée
    • Appium 9.3.0 : Version récente sans MobileElement
    • Selenium 4.25.0 : Compatible avec Appium moderne
    • TestNG + Allure : Framework de test avec reporting avancé
    • Profils Maven : Séparation Android/iOS
    • Plugins optimisés : Surefire configuré pour TestNG et Allure

    Configuration TestNG

    Créez le fichier src/test/resources/testng.xml pour orchestrer vos tests :

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
    
    <suite name="Appium Mobile Test Suite" verbose="1" parallel="tests" thread-count="2">
        
        <!-- Configuration globale -->
        <parameter name="appium.server.url" value="http://127.0.0.1:4723"/>
        <parameter name="implicit.wait" value="10"/>
        <parameter name="explicit.wait" value="15"/>
        
        <!-- Listeners pour le reporting -->
        <listeners>
            <listener class-name="io.qameta.allure.testng.AllureTestNg"/>
        </listeners>
        
        <!-- Suite de tests Android -->
        <test name="Android Tests" preserve-order="true">
            <parameter name="test.platform" value="android"/>
            
            <classes>
                <class name="com.company.mobile.tests.LoginTests"/>
                <class name="com.company.mobile.tests.NavigationTests"/>
            </classes>
        </test>
        
    </suite>

    Exécution avec Maven

    Voici les commandes Maven essentielles pour l'exécution de vos tests :

    # Exécution de base
    mvn clean test
    
    # Avec profil Android
    mvn clean test -Pandroid
    
    # Avec profil iOS
    mvn clean test -Pios
    
    # Exécuter une classe spécifique
    mvn test -Dtest=LoginTests
    
    # Exécuter avec génération de rapport Allure
    mvn clean test allure:report
    
    # Servir le rapport Allure localement
    mvn allure:serve

    Optimisation des performances

    Pour optimiser l'exécution de vos tests Appium :

    # Configuration JVM optimisée
    MAVEN_OPTS="-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC" mvn clean test
    
    # Tests parallèles (attention : besoin de plusieurs émulateurs)
    mvn test -DthreadCount=2 -Dparallel=classes
    
    # Mode debug avec logs détaillés
    mvn test -X -Dappium.log.level=debug
    Cette configuration Maven vous donne une base solide pour :
    • Gérer les dépendances Appium modernes automatiquement
    • Structurer votre projet de manière professionnelle
    • Exécuter vos tests de façon flexible (Android/iOS)
    • Générer des rapports visuels avec Allure
    • Intégrer facilement dans un pipeline CI/CD

    Exemple de test Spock avec code moderne

    Spock est un framework de test puissant pour Java et Groovy qui offre une syntaxe expressive et des fonctionnalités avancées comme les tests paramétrés et les mocks intégrés. Voici comment l'utiliser avec Appium et du code moderne.

    Configuration Spock dans le pom.xml

    Ajoutez ces dépendances à votre pom.xml pour utiliser Spock avec Appium :

    <!-- ✅ Spock Framework -->
    <dependency>
        <groupId>org.spockframework</groupId>
        <artifactId>spock-core</artifactId>
        <version>2.3-groovy-4.0</version>
        <scope>test</scope>
    </dependency>
    
    <!-- ✅ Groovy (requis par Spock) -->
    <dependency>
        <groupId>org.apache.groovy</groupId>
        <artifactId>groovy-all</artifactId>
        <version>4.0.15</version>
        <type>pom</type>
        <scope>test</scope>
    </dependency>
    
    <!-- Plugin Groovy pour Maven -->
    <plugin>
        <groupId>org.codehaus.gmavenplus</groupId>
        <artifactId>gmavenplus-plugin</artifactId>
        <version>3.0.2</version>
        <executions>
            <execution>
                <goals>
                    <goal>compileTests</goal>
                </goals>
            </execution>
        </executions>
    </plugin>

    Classe de test Spock moderne

    Créez le fichier src/test/groovy/com/company/mobile/tests/LoginSpec.groovy :

    package com.company.mobile.tests
    
    import io.appium.java_client.AppiumBy
    import io.appium.java_client.android.AndroidDriver
    import org.openqa.selenium.WebElement
    import org.openqa.selenium.remote.DesiredCapabilities
    import org.openqa.selenium.support.ui.WebDriverWait
    import org.openqa.selenium.support.ui.ExpectedConditions
    import spock.lang.*
    import java.time.Duration
    
    class LoginSpec extends Specification {
        
        // ✅ Driver moderne sans paramètre générique
        static AndroidDriver driver
        static WebDriverWait wait
        
        def setupSpec() {
            // ✅ Configuration avec capabilities modernes
            def caps = new DesiredCapabilities()
            caps.setCapability("platformName", "Android")
            caps.setCapability("appium:deviceName", "Android Emulator")
            caps.setCapability("appium:automationName", "UiAutomator2")
            caps.setCapability("appium:app", getAppPath())
            caps.setCapability("appium:autoGrantPermissions", true)
            caps.setCapability("appium:noReset", false)
            caps.setCapability("appium:newCommandTimeout", 300)
            
            // ✅ URL moderne sans /wd/hub
            driver = new AndroidDriver(new URL("http://127.0.0.1:4723"), caps)
            
            // ✅ Configuration des timeouts
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10))
            wait = new WebDriverWait(driver, Duration.ofSeconds(15))
        }
        
        def cleanupSpec() {
            if (driver != null) {
                driver.quit()
            }
        }
        
        def "utilisateur peut naviguer vers la section App"() {
            when: "je clique sur la section App"
            WebElement appSection = driver.findElement(
                AppiumBy.androidUIAutomator('new UiSelector().text("App")')
            )
            appSection.click()
            
            then: "la section App doit être accessible"
            WebElement activitySection = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.androidUIAutomator('new UiSelector().text("Activity")')
                )
            )
            activitySection.isDisplayed()
        }
        
        def "formulaire de saisie fonctionne correctement"() {
            given: "je navigue vers une page avec des champs de saisie"
            driver.findElement(AppiumBy.androidUIAutomator('new UiSelector().text("Views")')).click()
            
            WebElement controlsOption = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.androidUIAutomator(
                        'new UiScrollable(new UiSelector().scrollable(true))' +
                        '.scrollIntoView(new UiSelector().text("Controls"))'
                    )
                )
            )
            controlsOption.click()
            
            driver.findElement(
                AppiumBy.androidUIAutomator('new UiSelector().text("1. Light Theme")')
            ).click()
            
            when: "je saisis du texte dans le champ"
            WebElement editText = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.className("android.widget.EditText")
                )
            )
            
            String testText = "Test Spock avec Appium moderne!"
            editText.clear()
            editText.sendKeys(testText)
            
            then: "le texte doit être correctement saisi"
            editText.getText() == testText
            
            and: "je peux fermer le clavier"
            try {
                driver.hideKeyboard()
            } catch (Exception e) {
                // Clavier déjà fermé
            }
            true // Test réussi
        }
        
        def "checkbox et radiobutton fonctionnent avec différentes valeurs"() {
            given: "je suis sur la page des contrôles"
            navigateToControlsPage()
            
            when: "je teste différents états de checkbox"
            WebElement checkbox = driver.findElement(
                AppiumBy.id("io.appium.android.apis:id/check1")
            )
            
            def initialState = checkbox.isSelected()
            checkbox.click()
            def newState = checkbox.isSelected()
            
            then: "l'état de la checkbox doit changer"
            initialState != newState
            
            when: "je teste les radio buttons"
            WebElement radio1 = driver.findElement(
                AppiumBy.id("io.appium.android.apis:id/radio1")
            )
            WebElement radio2 = driver.findElement(
                AppiumBy.id("io.appium.android.apis:id/radio2")
            )
            
            radio1.click()
            def radio1Selected = radio1.isSelected()
            
            radio2.click() 
            def radio2Selected = radio2.isSelected()
            def radio1AfterRadio2 = radio1.isSelected()
            
            then: "seul un radio button doit être sélectionné à la fois"
            radio1Selected == true
            radio2Selected == true
            radio1AfterRadio2 == false
        }
        
        @Unroll
        def "test paramétré avec différentes données: #testData"() {
            given: "je navigue vers la page de saisie"
            navigateToTextInputPage()
            
            when: "je saisis les données de test"
            WebElement editText = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.className("android.widget.EditText")
                )
            )
            
            editText.clear()
            editText.sendKeys(testData)
            
            then: "le texte doit être correctement affiché"
            editText.getText() == testData
            
            where: "les données de test sont"
            testData << [
                "Test simple",
                "Test avec des chiffres 123",
                "Test avec caractères spéciaux !@#",
                "Test très long pour vérifier la capacité du champ de texte à gérer du contenu étendu"
            ]
        }
        
        def "gestion d'erreurs avec timeout"() {
            when: "je cherche un élément qui n'existe pas"
            try {
                wait.until(
                    ExpectedConditions.visibilityOfElementLocated(
                        AppiumBy.id("element.inexistant")
                    )
                )
            } catch (Exception e) {
                // Exception attendue
            }
            
            then: "le test ne doit pas planter"
            driver != null
            driver.getPageSource() != null
        }
        
        // ✅ Méthodes utilitaires privées
        private void navigateToControlsPage() {
            driver.findElement(AppiumBy.androidUIAutomator('new UiSelector().text("Views")')).click()
            
            WebElement controlsOption = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.androidUIAutomator(
                        'new UiScrollable(new UiSelector().scrollable(true))' +
                        '.scrollIntoView(new UiSelector().text("Controls"))'
                    )
                )
            )
            controlsOption.click()
            
            driver.findElement(
                AppiumBy.androidUIAutomator('new UiSelector().text("1. Light Theme")')
            ).click()
        }
        
        private void navigateToTextInputPage() {
            navigateToControlsPage()
        }
        
        private String getAppPath() {
            return System.getProperty("user.dir") + "/src/test/resources/apps/android/ApiDemos-debug.apk"
        }
    }

    Exécution des tests Spock

    Les tests Spock s'exécutent comme des tests classiques avec Maven :

    # Exécuter tous les tests (y compris Spock)
    mvn clean test
    
    # Exécuter seulement les tests Spock
    mvn test -Dtest="*Spec"
    
    # Exécuter un test Spock spécifique
    mvn test -Dtest="LoginSpec"
    Avantages de Spock :
    • Syntaxe expressive : given/when/then plus lisible
    • Tests paramétrés : @Unroll pour tester plusieurs datasets
    • Mocks intégrés : pas besoin de Mockito
    • Assertions puissantes : conditions multiples avec and/or
    • Groovy : syntaxe plus concise que Java

    Exécution avancée avec Maven

    Maven offre une flexibilité exceptionnelle pour l'exécution de tests Appium. Cette section détaille les commandes avancées, les profils de configuration, et les techniques d'optimisation pour différents scénarios d'exécution.

    Commandes de base

    Ces commandes constituent le socle de l'exécution quotidienne des tests :

    # ✅ Compilation et exécution complète
    mvn clean compile test
    
    # ✅ Exécution rapide sans nettoyage
    mvn test
    
    # ✅ Ignorer les échecs de tests (pour CI/CD)
    mvn test -Dmaven.test.failure.ignore=true
    
    # ✅ Exécution en mode offline (sans téléchargement)
    mvn test -o

    Exécution sélective

    Pour des cycles de développement plus rapides, l'exécution sélective est essentielle :

    # ✅ Exécuter une classe de test spécifique
    mvn test -Dtest=LoginTests
    
    # ✅ Exécuter une méthode de test spécifique
    mvn test -Dtest=LoginTests#testValidLogin
    
    # ✅ Exécuter plusieurs classes
    mvn test -Dtest=LoginTests,NavigationTests,CheckoutTests
    
    # ✅ Exécuter par pattern de nom
    mvn test -Dtest="*Login*"
    
    # ✅ Exécuter par package
    mvn test -Dtest="com.company.mobile.tests.smoke.*"
    
    # ✅ Exclure certains tests
    mvn test -Dtest="!*Integration*"

    Configuration d'environnement

    Adapter l'exécution selon l'environnement cible :

    # ✅ Spécifier la plateforme de test
    mvn test -Dtest.platform=android
    mvn test -Dtest.platform=ios
    
    # ✅ Configurer le serveur Appium
    mvn test -Dappium.server.url=http://192.168.1.100:4723
    
    # ✅ Ajuster les timeouts
    mvn test -Dimplicit.wait=15 -Dexplicit.wait=30
    
    # ✅ Spécifier l'application à tester
    mvn test -Dapp.path=/path/to/your/app.apk
    
    # ✅ Configurer le device
    mvn test -Ddevice.name="Pixel_6_API_33"

    Profils Maven avancés

    Définissez des profils complexes dans votre pom.xml pour différents environnements :

    <profiles>
        <!-- ✅ Profil de développement local -->
        <profile>
            <id>dev</id>
            <properties>
                <test.platform>android</test.platform>
                <appium.server.url>http://127.0.0.1:4723</appium.server.url>
                <device.name>Android Emulator</device.name>
                <implicit.wait>10</implicit.wait>
            </properties>
        </profile>
        
        <!-- ✅ Profil d'intégration continue -->
        <profile>
            <id>ci</id>
            <properties>
                <test.platform>android</test.platform>
                <appium.server.url>http://appium-grid:4723</appium.server.url>
                <device.name>ci-emulator</device.name>
                <implicit.wait>15</implicit.wait>
                <maven.test.failure.ignore>false</maven.test.failure.ignore>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <reportsDirectory>${project.build.directory}/ci-reports</reportsDirectory>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
        
        <!-- ✅ Profil de tests de performance -->
        <profile>
            <id>performance</id>
            <properties>
                <test.platform>android</test.platform>
                <parallel.tests>true</parallel.tests>
                <thread.count>3</thread.count>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <parallel>classes</parallel>
                            <threadCount>${thread.count}</threadCount>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

    Exécution avec profils

    # ✅ Utiliser le profil de développement
    mvn clean test -Pdev
    
    # ✅ Utiliser le profil CI
    mvn clean test -Pci
    
    # ✅ Combiner plusieurs profils
    mvn clean test -Pci,performance
    
    # ✅ Surcharger des propriétés de profil
    mvn test -Pdev -Ddevice.name="Pixel_7_API_34"

    Optimisation des performances

    Techniques pour accélérer l'exécution des tests :

    # ✅ Configuration JVM optimisée
    export MAVEN_OPTS="-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UseZGC"
    
    # ✅ Exécution parallèle par classes (attention aux resources)
    mvn test -Dparallel=classes -DthreadCount=2
    
    # ✅ Exécution parallèle par méthodes
    mvn test -Dparallel=methods -DthreadCount=4
    
    # ✅ Compilation incrémentale
    mvn test -Dmaven.compiler.useIncrementalCompilation=true
    
    # ✅ Skip des étapes non nécessaires
    mvn test -DskipTests=false -Dmaven.javadoc.skip=true -Dmaven.source.skip=true

    Intégration avec Allure Reports

    Génération automatique de rapports visuels :

    # ✅ Exécuter les tests avec génération des données Allure
    mvn clean test
    
    # ✅ Générer le rapport HTML
    mvn allure:report
    
    # ✅ Ouvrir le rapport dans le navigateur
    mvn allure:serve
    
    # ✅ Pipeline complet
    mvn clean test allure:report allure:serve
    
    # ✅ Générer le rapport avec historique
    mvn allure:report -Dallure.results.directory=target/allure-results

    Debugging et logs avancés

    # ✅ Mode debug Maven complet
    mvn test -X -Dappium.log.level=debug
    
    # ✅ Logs détaillés sans le mode debug
    mvn test -Dorg.slf4j.simpleLogger.defaultLogLevel=debug
    
    # ✅ Capturer la sortie dans un fichier
    mvn test -l test-execution.log
    
    # ✅ Afficher les propriétés système utilisées
    mvn test -Dtest=LoginTests -X | grep -E "test\.|appium\.|device\."

    Scripts d'automatisation

    Créez des scripts pour standardiser l'exécution :

    Script bash pour Linux/Mac (run-tests.sh)

    #!/bin/bash
    
    # Configuration par défaut
    PLATFORM="android"
    PROFILE="dev"
    TEST_CLASS=""
    
    # Gestion des arguments
    while [[ $# -gt 0 ]]; do
        case $1 in
            -p|--platform)
                PLATFORM="$2"
                shift 2
                ;;
            --profile)
                PROFILE="$2"
                shift 2
                ;;
            -t|--test)
                TEST_CLASS="$2"
                shift 2
                ;;
            *)
                echo "Option inconnue: $1"
                exit 1
                ;;
        esac
    done
    
    echo "🚀 Lancement des tests Appium"
    echo "Plateforme: $PLATFORM"
    echo "Profil: $PROFILE"
    echo "Classe de test: ${TEST_CLASS:-'Toutes'}"
    
    # Construction de la commande Maven
    MVN_CMD="mvn clean test -P$PROFILE -Dtest.platform=$PLATFORM"
    
    if [ ! -z "$TEST_CLASS" ]; then
        MVN_CMD="$MVN_CMD -Dtest=$TEST_CLASS"
    fi
    
    echo "Commande: $MVN_CMD"
    echo ""
    
    # Exécution
    $MVN_CMD
    
    # Génération du rapport si les tests sont passés
    if [ $? -eq 0 ]; then
        echo "✅ Tests réussis - Génération du rapport Allure"
        mvn allure:report allure:serve
    else
        echo "❌ Échec des tests"
        exit 1
    fi

    Utilisation du script

    # ✅ Rendre le script exécutable
    chmod +x run-tests.sh
    
    # ✅ Exécuter avec paramètres
    ./run-tests.sh --platform android --profile dev --test LoginTests
    
    # ✅ Exécuter avec configuration par défaut
    ./run-tests.sh
    Cette configuration Maven avancée vous permet de :
    • Adapter l'exécution selon l'environnement (dev, CI, production)
    • Optimiser les performances avec l'exécution parallèle
    • Générer des rapports automatiquement avec Allure
    • Automatiser avec des scripts personnalisés
    • Débugger efficacement avec des logs détaillés

    Exemple de test JUnit avec code moderne

    JUnit reste l'un des frameworks de test les plus populaires pour Java. Voici comment l'utiliser efficacement avec Appium en utilisant du code moderne qui fonctionne avec les versions récentes.

    Configuration JUnit dans le pom.xml

    Ajoutez JUnit 5 (Jupiter) à votre projet pour bénéficier des dernières fonctionnalités :

    <!-- ✅ JUnit 5 (Jupiter) - Version moderne -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.10.1</version>
        <scope>test</scope>
    </dependency>
    
    <!-- ✅ Support pour les tests paramétrés -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-params</artifactId>
        <version>5.10.1</version>
        <scope>test</scope>
    </dependency>

    Classe de test JUnit moderne

    Voici un exemple complet utilisant JUnit 5 avec du code Appium 2025 qui fonctionne :

    package com.company.mobile.tests;
    
    import io.appium.java_client.AppiumBy;
    import io.appium.java_client.android.AndroidDriver;
    import org.junit.jupiter.api.*;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.junit.jupiter.params.ParameterizedTest;
    import org.junit.jupiter.params.provider.ValueSource;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.remote.DesiredCapabilities;
    import org.openqa.selenium.support.ui.WebDriverWait;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    
    import java.net.URL;
    import java.time.Duration;
    
    public class LoginTest {
        
        // ✅ Driver moderne sans paramètre générique
        private static AndroidDriver driver;
        private static WebDriverWait wait;
    
        @BeforeAll
        static void setupClass() throws Exception {
            // ✅ Configuration avec capabilities modernes
            DesiredCapabilities caps = new DesiredCapabilities();
            caps.setCapability("platformName", "Android");
            caps.setCapability("appium:deviceName", "Android Emulator");
            caps.setCapability("appium:automationName", "UiAutomator2");
            caps.setCapability("appium:app", getAppPath());
            caps.setCapability("appium:autoGrantPermissions", true);
            caps.setCapability("appium:noReset", false);
            caps.setCapability("appium:newCommandTimeout", 300);
            
            // ✅ URL moderne sans /wd/hub
            driver = new AndroidDriver(new URL("http://127.0.0.1:4723"), caps);
            
            // ✅ Configuration des timeouts
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
            wait = new WebDriverWait(driver, Duration.ofSeconds(15));
        }
    
        @AfterAll
        static void tearDownClass() {
            if (driver != null) {
                driver.quit();
            }
        }
    
        @BeforeEach
        void setUp() {
            // Reset de l'application avant chaque test
            driver.resetApp();
        }
    
        @Test
        @DisplayName("Le formulaire de login doit être visible au démarrage")
        void testLoginFormVisible() {
            // ✅ Navigation vers la section App
            WebElement appSection = driver.findElement(
                AppiumBy.androidUIAutomator("new UiSelector().text(\"App\")")
            );
            
            Assertions.assertTrue(appSection.isDisplayed(), 
                "La section App doit être visible au démarrage");
            
            // ✅ Clic avec vérification
            appSection.click();
            
            // ✅ Attente explicite pour le chargement
            WebElement activitySection = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.androidUIAutomator("new UiSelector().text(\"Activity\")")
                )
            );
            
            Assertions.assertTrue(activitySection.isDisplayed(),
                "La section Activity doit être accessible après clic sur App");
        }
    
        @Test
        @DisplayName("Navigation vers les contrôles de saisie")
        void testNavigationToControls() {
            // ✅ Navigation vers Views > Controls
            WebElement viewsSection = driver.findElement(
                AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")
            );
            viewsSection.click();
    
            // ✅ Scroll pour trouver Controls
            WebElement controlsOption = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.androidUIAutomator(
                        "new UiScrollable(new UiSelector().scrollable(true))" +
                        ".scrollIntoView(new UiSelector().text(\"Controls\"))"
                    )
                )
            );
            controlsOption.click();
    
            // ✅ Sélectionner Light Theme
            WebElement lightTheme = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.androidUIAutomator("new UiSelector().text(\"1. Light Theme\")")
                )
            );
            lightTheme.click();
    
            // ✅ Vérifier la présence du champ EditText
            WebElement editText = wait.until(
                ExpectedConditions.visibilityOfElementLocated(
                    AppiumBy.className("android.widget.EditText")
                )
            );
            
            Assertions.assertTrue(editText.isDisplayed(),
                "Le champ de saisie doit être visible");
        }
    
        @Test
        @DisplayName("Saisie de texte dans le champ EditText")
        void testTextInputFunctionality() {
            // Navigation préparatoire
            navigateToTextInput();
            
            // ✅ Localiser le champ EditText
            WebElement editText = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.className("android.widget.EditText")
                )
            );
            
            // ✅ Test de saisie moderne
            String testText = "Test JUnit avec Appium moderne!";
            editText.clear();
            editText.sendKeys(testText);
            
            // ✅ Vérification avec assertion JUnit 5
            String actualText = editText.getText();
            Assertions.assertEquals(testText, actualText,
                "Le texte saisi doit correspondre exactement");
            
            // ✅ Fermer le clavier Android
            try {
                driver.hideKeyboard();
            } catch (Exception e) {
                // Clavier déjà fermé ou non disponible
                System.out.println("Clavier non disponible ou déjà fermé");
            }
        }
    
        @Test
        @DisplayName("Test des contrôles CheckBox et RadioButton")
        void testControlElements() {
            navigateToTextInput();
            
            // ✅ Test de la CheckBox
            WebElement checkbox = driver.findElement(
                AppiumBy.id("io.appium.android.apis:id/check1")
            );
            
            boolean initialState = checkbox.isSelected();
            checkbox.click();
            boolean newState = checkbox.isSelected();
            
            Assertions.assertNotEquals(initialState, newState,
                "L'état de la checkbox doit changer après le clic");
    
            // ✅ Test des RadioButtons
            WebElement radio1 = driver.findElement(
                AppiumBy.id("io.appium.android.apis:id/radio1")
            );
            WebElement radio2 = driver.findElement(
                AppiumBy.id("io.appium.android.apis:id/radio2")
            );
    
            // Test de sélection exclusive
            radio1.click();
            Assertions.assertTrue(radio1.isSelected(), 
                "Radio 1 doit être sélectionné");
    
            radio2.click();
            Assertions.assertTrue(radio2.isSelected(), 
                "Radio 2 doit être sélectionné");
            Assertions.assertFalse(radio1.isSelected(), 
                "Radio 1 ne doit plus être sélectionné quand Radio 2 est cliqué");
        }
    
        @ParameterizedTest
        @DisplayName("Test paramétré avec différents textes")
        @ValueSource(strings = {
            "Test simple", 
            "Test avec chiffres 123", 
            "Test avec caractères !@#$%", 
            "Test très long pour vérifier la capacité du champ"
        })
        void testParameterizedTextInput(String testData) {
            navigateToTextInput();
            
            WebElement editText = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.className("android.widget.EditText")
                )
            );
            
            editText.clear();
            editText.sendKeys(testData);
            
            String actualText = editText.getText();
            Assertions.assertEquals(testData, actualText,
                () -> "Le texte '" + testData + "' doit être correctement saisi");
        }
    
        @Test
        @DisplayName("Gestion des timeouts et éléments introuvables")
        void testTimeoutHandling() {
            // ✅ Test avec élément qui n'existe pas
            Assertions.assertThrows(
                org.openqa.selenium.TimeoutException.class,
                () -> {
                    wait.until(
                        ExpectedConditions.visibilityOfElementLocated(
                            AppiumBy.id("element.qui.nexiste.pas")
                        )
                    );
                },
                "Une TimeoutException doit être levée pour un élément inexistant"
            );
            
            // ✅ Vérifier que le driver est toujours utilisable
            Assertions.assertNotNull(driver.getPageSource(),
                "Le driver doit rester fonctionnel après un timeout");
        }
    
        @Test
        @DisplayName("Test de navigation avec gestion d'erreurs")
        void testNavigationWithErrorHandling() {
            try {
                // Navigation normale
                WebElement viewsSection = driver.findElement(
                    AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")
                );
                viewsSection.click();
                
                // Vérification du succès
                WebElement buttonsOption = wait.until(
                    ExpectedConditions.visibilityOfElementLocated(
                        AppiumBy.androidUIAutomator("new UiSelector().textContains(\"Button\")")
                    )
                );
                
                Assertions.assertTrue(buttonsOption.isDisplayed(),
                    "Au moins un élément 'Button' doit être visible dans Views");
                    
            } catch (Exception e) {
                // ✅ Capture d'écran en cas d'erreur
                takeScreenshotOnFailure("testNavigationWithErrorHandling");
                
                // Re-lancer l'exception pour marquer le test comme échoué
                throw e;
            }
        }
    
        // ✅ Méthodes utilitaires privées
        private void navigateToTextInput() {
            driver.findElement(
                AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")
            ).click();
    
            WebElement controlsOption = wait.until(
                ExpectedConditions.elementToBeClickable(
                    AppiumBy.androidUIAutomator(
                        "new UiScrollable(new UiSelector().scrollable(true))" +
                        ".scrollIntoView(new UiSelector().text(\"Controls\"))"
                    )
                )
            );
            controlsOption.click();
    
            driver.findElement(
                AppiumBy.androidUIAutomator("new UiSelector().text(\"1. Light Theme\")")
            ).click();
        }
    
        private void takeScreenshotOnFailure(String testName) {
            try {
                byte[] screenshot = driver.getScreenshotAs(org.openqa.selenium.OutputType.BYTES);
                
                String timestamp = java.time.LocalDateTime.now()
                    .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
                
                String fileName = String.format("target/screenshots/%s_%s_FAILED.png", 
                    testName, timestamp);
                
                java.nio.file.Files.createDirectories(
                    java.nio.file.Paths.get("target/screenshots")
                );
                java.nio.file.Files.write(
                    java.nio.file.Paths.get(fileName), 
                    screenshot
                );
                
                System.out.println("📸 Screenshot capturé: " + fileName);
                
            } catch (Exception e) {
                System.err.println("❌ Erreur capture d'écran: " + e.getMessage());
            }
        }
    
        private static String getAppPath() {
            return System.getProperty("user.dir") + 
                "/src/test/resources/apps/android/ApiDemos-debug.apk";
        }
    }
    

    Configuration Maven pour JUnit 5

    Assurez-vous que votre plugin Surefire supporte JUnit 5 :

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.1.2</version>
        <configuration>
            <!-- ✅ Configuration pour JUnit 5 -->
            <useModulePath>false</useModulePath>
        </configuration>
    </plugin>

    Exécution des tests JUnit

    # ✅ Exécuter tous les tests JUnit
    mvn test
    
    # ✅ Exécuter une classe de test spécifique
    mvn test -Dtest=LoginTest
    
    # ✅ Exécuter une méthode de test spécifique
    mvn test -Dtest=LoginTest#testLoginFormVisible
    
    # ✅ Exécuter seulement les tests paramétrés
    mvn test -Dtest=LoginTest#testParameterizedTextInput
    Avantages de JUnit 5 avec Appium moderne :
    • @DisplayName : descriptions de test plus lisibles
    • @ParameterizedTest : tests avec plusieurs jeux de données
    • Assertions améliorées : messages d'erreur plus clairs
    • @BeforeAll/@AfterAll : gestion du cycle de vie optimisée
    • Gestion d'exceptions : assertThrows() pour tester les erreurs
    • Lambda dans assertions : messages d'erreur dynamiques
    Cette implémentation JUnit moderne vous donne :
    • Code Appium 2025 : WebElement, AppiumBy, capabilities correctes
    • Tests robustes : attentes explicites, gestion d'erreurs
    • JUnit 5 : annotations modernes et fonctionnalités avancées
    • Screenshots automatiques : capture en cas d'échec
    • Tests paramétrés : validation avec plusieurs datasets

    Ressources utiles et écosystème Appium

    Cette section regroupe les ressources essentielles pour continuer à développer vos compétences Appium, rester à jour avec les évolutions du framework, et bénéficier de l'expérience de la communauté.

    Documentation officielle

    Frameworks de test compatibles

    Communauté et support

    Outils complémentaires

    Services cloud pour tests mobiles

    Blogs et ressources avancées

    Livres recommandés

    • "Appium Essentials" par Manoj Hans - Introduction complète à Appium
    • "Mobile Test Automation with Appium" par Nishant Verma - Techniques avancées
    • "Selenium WebDriver 3 Practical Guide" par Unmesh Gundecha - Base Selenium applicable à Appium

    Continuer à apprendre

    Pour maintenir vos compétences Appium à jour dans un écosystème en constante évolution :

    • Suivez les release notes d'Appium pour connaître les nouvelles fonctionnalités
    • Participez aux discussions sur les forums et GitHub pour partager votre expérience
    • Expérimentez avec les bêta pour anticiper les évolutions
    • Contribuez aux projets open source pour approfondir votre compréhension
    • Organisez des sessions de partage dans votre équipe ou entreprise

    🎉 Félicitations ! Vous avez maintenant toutes les clés pour maîtriser Appium

    Cette formation vous a donné une base solide avec du code moderne et des bonnes pratiques éprouvées. L'investissement en temps de ce guide vous fera économiser les 1800€ de formation traditionnelle, tout en vous donnant une maîtrise pratique immédiatement opérationnelle.

    Ce que vous avez appris :

    • Installation et configuration complète d'un environnement Appium moderne
    • Utilisation du code Appium 2025 avec WebElement et AppiumBy
    • Configuration Maven professionnelle avec toutes les dépendances
    • Écriture de tests robustes avec TestNG, JUnit et Spock
    • Gestion des émulateurs Android et simulateurs iOS
    • 50 solutions aux erreurs les plus courantes
    • Intégration dans des pipelines CI/CD

    Prochaines étapes recommandées :

    1. Créez votre premier projet avec le code Maven fourni
    2. Téléchargez l'APK ApiDemos et reproduisez les exemples
    3. Adaptez les tests à votre application spécifique
    4. Configurez l'intégration CI/CD avec GitHub Actions ou GitLab
    5. Rejoignez la communauté Appium pour continuer à apprendre

    Vous êtes maintenant autonome pour développer une suite de tests Appium professionnelle. Le code que vous avez appris fonctionne réellement et vous évitera les frustrations des tutoriels obsolètes.

    Bonne continuation dans votre parcours d'automatisation des tests mobiles !

    📖 Erreurs Fréquentes et HOWTO – Le carnet du testeur aguerri

    Voici le répertoire complet des erreurs, architectures et bonnes pratiques Appium. 50 cartes, organisées par catégories, pour couvrir l’ensemble des problématiques et offrir des réponses claires et actionnables.

    ❌ Erreur 1 – Session not created

    Symptôme : Appium refuse de démarrer une session.

    Cause : Incompatibilité APK / niveau API.

    Solution : Vérifie versions SDK/émulateur.

    ❌ Erreur 2 – Cannot start uiautomator2

    Symptôme : Driver ne démarre pas.

    Cause : adb corrompu ou instance déjà active.

    Solution : adb kill-server && adb start-server.

    ❌ Erreur 3 – No Chromedriver found

    Symptôme : WebView non ouverte.

    Cause : Chromedriver manquant ou incompatible.

    Solution : Fournir le binaire adapté.

    ❌ Erreur 4 – Port déjà occupé

    Symptôme : Appium ne démarre pas.

    Cause : Serveur déjà actif.

    Solution : Changer de port : --port 4725.

    ❌ Erreur 5 – Unauthorized device

    Symptôme : adb devices → unauthorized.

    Cause : Clé RSA non validée.

    Solution : Accepter la fenêtre sur le téléphone.

    ❌ Erreur 6 – APK not installed

    Symptôme : Appli absente après lancement.

    Cause : Signature manquante ou API incorrecte.

    Solution : Re-signer l’APK ou rebuild.

    ❌ Erreur 7 – Timeout locator

    Symptôme : Élément introuvable.

    Cause : Locator fragile ou attente trop courte.

    Solution : Utiliser WebDriverWait.

    ❌ Erreur 8 – Capability mal orthographiée

    Symptôme : Paramètres ignorés.

    Cause : Faute de frappe.

    Solution : Vérifier doc Appium (W3C keys).

    ❌ Erreur 9 – Java heap space

    Symptôme : JVM OutOfMemoryError.

    Cause : Trop peu de mémoire allouée.

    Solution : MAVEN_OPTS="-Xmx1024m".

    ❌ Erreur 10 – adb not recognized

    Symptôme : adb inconnu.

    Cause : PATH mal configuré.

    Solution : Ajouter platform-tools au PATH.

    ❌ Erreur 11 – Xcode non trouvé

    Symptôme : Driver iOS échoue.

    Cause : Xcode absent.

    Solution : Installer Xcode via App Store.

    ❌ Erreur 12 – WebDriverAgent échoue

    Symptôme : Impossible de lancer tests iOS.

    Cause : WDA non signé.

    Solution : Compiler avec compte dev Apple.

    ❌ Erreur 13 – Provisioning profile invalide

    Symptôme : Appli iOS refusée.

    Cause : Mauvais certificat.

    Solution : Vérifier provisioning profile.

    ❌ Erreur 14 – Simulateur non détecté

    Symptôme : Aucun device iOS listé.

    Cause : Xcode simulators non installés.

    Solution : Télécharger via Xcode Preferences.

    ❌ Erreur 15 – idevice_id non reconnu

    Symptôme : iPhone branché mais invisible.

    Cause : libimobiledevice absent.

    Solution : Installer brew install libimobiledevice.

    ⚙️ CI/CD 16 – Build échoue

    Symptôme : mvn test échoue en pipeline.

    Cause : JDK absent dans l’image.

    Solution : Ajouter étape setup-java.

    ⚙️ CI/CD 17 – Emulator non lancé

    Symptôme : Tests Android bloqués.

    Cause : Pas d’AVD disponible.

    Solution : Utiliser reactivecircus/android-emulator-runner.

    ⚙️ CI/CD 18 – Timeout pipeline

    Symptôme : Job trop long.

    Cause : Emulateur trop lent.

    Solution : Utiliser images headless.

    ⚙️ CI/CD 19 – APK introuvable

    Symptôme : CI ne trouve pas l’APK.

    Cause : Mauvais chemin artifact.

    Solution : Vérifier build.gradle / upload artifact.

    ⚙️ CI/CD 20 – Certificats iOS manquants

    Symptôme : Tests iOS échouent en CI.

    Cause : Pas de provisioning profile importé.

    Solution : Ajouter étape import-cert dans pipeline.

    🏗️ 21 – Structure POM

    Symptôme : Code spaghetti.

    Cause : Pas de séparation pages/tests.

    Solution : Implémente Page Object Model.

    🏗️ 22 – Tests dépendants

    Symptôme : Test B échoue si test A échoue.

    Cause : Mauvaise isolation.

    Solution : Rendre chaque test autonome.

    🏗️ 23 – Données en dur

    Symptôme : Impossible de rejouer ailleurs.

    Cause : Credentials codés en dur.

    Solution : Externaliser données.

    🏗️ 24 – Trop de duplication

    Symptôme : Copié-collé massif.

    Cause : Pas de classes utilitaires.

    Solution : Factoriser méthodes communes.

    🏗️ 25 – Locator fragile

    Symptôme : Tests cassent à chaque build.

    Cause : XPath absolus.

    Solution : Utiliser ID ou accessibilityId.

    💡 26 – Screenshots

    Symptôme : Difficile d’analyser échecs.

    Cause : Pas de capture automatique.

    Solution : Ajouter screenshots après échec.

    💡 27 – Vidéos

    Symptôme : Bugs non reproductibles.

    Cause : Manque de contexte visuel.

    Solution : Enregistrer sessions avec Appium plugin video.

    💡 28 – Logs clairs

    Symptôme : Logs illisibles.

    Cause : Trop verbeux.

    Solution : Centraliser logs par test.

    💡 29 – Reset data

    Symptôme : Tests pollués par données persistantes.

    Cause : Pas de reset.

    Solution : Utiliser fullReset / clearData.

    💡 30 – Parallel run

    Symptôme : Feedback trop lent.

    Cause : Tests séquentiels.

    Solution : Lancer sur plusieurs devices en parallèle.

    ✅ 31 – Attentes explicites

    Symptôme : Tests instables.

    Cause : Usage de Thread.sleep().

    Solution : Remplacer par WebDriverWait.

    ✅ 32 – Nettoyage

    Symptôme : Sessions fantômes.

    Cause : Driver non fermé.

    Solution : Fermer driver en teardown.

    ✅ 33 – Données externalisées

    Symptôme : Tests rigides.

    Cause : Données en dur.

    Solution : Charger via CSV/JSON.

    ✅ 34 – Tests indépendants

    Symptôme : Cascade d’échecs.

    Cause : Dépendances entre tests.

    Solution : Isoler chaque scénario.

    ✅ 35 – CI obligatoire

    Symptôme : Bugs détectés tard.

    Cause : Pas de pipeline CI.

    Solution : Lancer tests à chaque commit.

    🔎 36 – Inspecteur Appium

    Symptôme : Locators introuvables.

    Cause : Pas d’outil d’inspection.

    Solution : Utiliser Appium Inspector.

    🔎 37 – Logs adb

    Symptôme : Pas de trace d’erreurs.

    Cause : adb logcat ignoré.

    Solution : Lancer adb logcat.

    🔎 38 – Capture vidéo CI

    Symptôme : Tests CI non analysables.

    Cause : Pas de vidéos stockées.

    Solution : Enregistrer et uploader en artifact.

    🔎 39 – Screenshots automatiques

    Symptôme : Pas de preuve en cas d’échec.

    Cause : Capture non configurée.

    Solution : Ajouter driver.getScreenshotAs().

    🔎 40 – Rapport HTML

    Symptôme : Résultats illisibles.

    Cause : Sortie console brute.

    Solution : Intégrer Surefire ou Allure Report.

    🔎 41 – Device Farm

    Symptôme : Pas de couverture hardware.

    Cause : Tests limités à un seul device.

    Solution : Utiliser BrowserStack / AWS Device Farm.

    🔎 42 – Profilage performance

    Symptôme : Tests lents.

    Cause : Appli consommatrice.

    Solution : Surveiller CPU/mémoire avec adb.

    🔎 43 – Tests flaky

    Symptôme : Résultats aléatoires.

    Cause : Attentes mal gérées.

    Solution : Stabiliser avec waits explicites.

    🔎 44 – Crash silencieux

    Symptôme : Le test s’arrête sans log clair.

    Cause : Exception non capturée côté driver.

    Solution : Envelopper chaque test avec try/catch et logger les stacktraces.

    🔎 45 – App non fermée

    Symptôme : Les sessions suivantes échouent.

    Cause : L’application reste en arrière-plan.

    Solution : Utiliser driver.closeApp() dans le teardown.

    🔎 46 – Conflit de dépendances Maven

    Symptôme : Erreurs bizarres dans les classes Appium/Selenium.

    Cause : Plusieurs versions de libs chargées.

    Solution : Vérifier avec mvn dependency:tree et exclure doublons.

    🔎 47 – Gestes non reconnus

    Symptôme : Swipe ou pinch inactifs.

    Cause : API d’automation mal configurée (UiAutomator2 vs Espresso).

    Solution : Tester avec driver alternatif, vérifier que l’app supporte le geste.

    🔎 48 – Tests trop lents

    Symptôme : 1 test → plusieurs minutes.

    Cause : Attentes fixes, pas de parallélisation.

    Solution : Mettre en place Grid, exécuter en parallèle sur plusieurs devices.

    🔎 49 – Capabilities cachées

    Symptôme : Fonctions natives inaccessibles.

    Cause : Capabilities avancées ignorées (ex : autoGrantPermissions).

    Solution : Explorer la doc complète Appium et activer les flags utiles.

    🔎 50 – Flaky en CI mais pas en local

    Symptôme : Les tests passent en local, échouent en pipeline.

    Cause : Ressources limitées en CI, émulateur plus lent.

    Solution : Allonger les timeouts en CI, monitorer la VM, et stabiliser avec des waits dynamiques.

    🛠️ Exercice Guidé Complet : Automatiser une Application Mobile Android

    Cet exercice pratique vous guide pas à pas dans l'automatisation d'une vraie application Android avec Appium. Nous allons utiliser l'app ApiDemos (application de démonstration officielle d'Android) pour créer un scénario de test complet et réaliste.

    🎯 Objectifs pédagogiques : Maîtriser les locators Appium, la navigation dans une app mobile, les interactions tactiles (tap, scroll, swipe), les attentes explicites, et la validation d'éléments UI.
    ⚠️ Important : Cet exercice utilise du code Appium moderne (9.3.0+) avec WebElement et AppiumBy. Fini le code obsolète avec MobileElement !

    🚀 Étape 0 : Préparation de l'environnement

    Télécharger l'APK de test

    Téléchargez l'application ApiDemos depuis le repository officiel :

    # Télécharger l'APK ApiDemos
    curl -L https://github.com/appium/appium/raw/master/packages/appium/sample-code/apps/ApiDemos-debug.apk -o ApiDemos-debug.apk
    
    # Ou téléchargez-la manuellement et placez-la dans votre projet
    # Chemin : src/test/resources/ApiDemos-debug.apk

    Vérifier l'environnement

    # 1. Vérifier qu'Appium Server est installé
    appium --version
    
    # 2. Démarrer un émulateur Android
    # Dans Android Studio : Tools > Device Manager > Start
    
    # 3. Vérifier la connexion
    adb devices
    # Sortie attendue : emulator-5554 device
    
    # 4. Lancer Appium Server
    appium --port 4723
    Prérequis validés : Appium Server actif, émulateur connecté, APK téléchargée.

    📋 Étape 1 : Configuration du projet et capabilities

    Créons d'abord la classe de test de base avec les bonnes capabilities pour notre APK ApiDemos.

    1.1 Classe de base pour les tests

    package com.example.tests;
    
    import io.appium.java_client.AppiumBy;
    import io.appium.java_client.android.AndroidDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.remote.DesiredCapabilities;
    import org.openqa.selenium.support.ui.WebDriverWait;
    import org.testng.annotations.*;
    
    import java.io.File;
    import java.net.URL;
    import java.time.Duration;
    
    public class ApiDemosTest {
        
        protected AndroidDriver driver;
        protected WebDriverWait wait;
        
        @BeforeClass
        public void setUp() throws Exception {
            // Chemin vers l'APK (ajustez selon votre projet)
            File appFile = new File("src/test/resources/ApiDemos-debug.apk");
            
            DesiredCapabilities caps = new DesiredCapabilities();
            
            // ✅ Capabilities modernes avec préfixe appium:
            caps.setCapability("platformName", "Android");
            caps.setCapability("appium:deviceName", "Android Emulator");
            caps.setCapability("appium:automationName", "UiAutomator2");
            caps.setCapability("appium:app", appFile.getAbsolutePath());
            
            // Options avancées
            caps.setCapability("appium:newCommandTimeout", 300);
            caps.setCapability("appium:autoGrantPermissions", true);
            caps.setCapability("appium:noReset", false); // Reset app à chaque test
            
            // Initialiser le driver
            driver = new AndroidDriver(new URL("http://127.0.0.1:4723"), caps);
            
            // Timeout implicite et wait explicite
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
            wait = new WebDriverWait(driver, Duration.ofSeconds(15));
        }
        
        @AfterClass
        public void tearDown() {
            if (driver != null) {
                driver.quit();
            }
        }
    }
    💡 Points clés :
    • appium:app : chemin absolu vers l'APK
    • appium:autoGrantPermissions : accepte automatiquement les permissions
    • appium:noReset : remet l'app à zéro à chaque test
    • WebDriverWait : pour les attentes explicites

    🧭 Étape 2 : Navigation dans l'application

    L'app ApiDemos a une structure de menus hiérarchiques. Apprenons à naviguer et localiser les éléments.

    2.1 Test de navigation de base

    @Test
    public void testNavigationBasique() {
        // ✅ Localiser la liste principale avec AppiumBy
        WebElement mainList = driver.findElement(
            AppiumBy.id("android:id/list")
        );
        Assert.assertTrue(mainList.isDisplayed(), "La liste principale doit être visible");
        
        // ✅ Trouver et cliquer sur "Views" dans la liste
        WebElement viewsOption = driver.findElement(
            AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")
        );
        viewsOption.click();
        
        // ✅ Vérifier qu'on est dans la section Views
        WebElement viewsTitle = driver.findElement(
            AppiumBy.xpath("//android.widget.TextView[@text='Views']")
        );
        Assert.assertTrue(viewsTitle.isDisplayed(), "On doit être dans la section Views");
        
        System.out.println("✅ Navigation réussie vers Views");
    }

    2.2 Techniques de localisation avancées

    @Test
    public void testLocalisationAvancee() {
        // Navigation vers Views > Buttons
        driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")).click();
        
        // ✅ Scroll pour trouver "Buttons" (élément peut être hors écran)
        WebElement buttonsOption = driver.findElement(
            AppiumBy.androidUIAutomator(
                "new UiScrollable(new UiSelector().scrollable(true))" +
                ".scrollIntoView(new UiSelector().text(\"Buttons\"))"
            )
        );
        buttonsOption.click();
        
        // ✅ Vérifier la présence de différents types de boutons
        WebElement normalButton = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/button_normal")
        );
        
        WebElement smallButton = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/button_small")
        );
        
        Assert.assertTrue(normalButton.isDisplayed(), "Bouton normal visible");
        Assert.assertTrue(smallButton.isDisplayed(), "Bouton small visible");
        
        System.out.println("✅ Localisation des boutons réussie");
    }
    Techniques apprises :
    • AppiumBy.id() : localisation par ID
    • AppiumBy.androidUIAutomator() : requêtes UiSelector avancées
    • UiScrollable : scroll automatique pour trouver un élément
    • AppiumBy.xpath() : localisation par XPath (à utiliser avec parcimonie)

    👆 Étape 3 : Interactions tactiles et gestes

    Les applications mobiles nécessitent des interactions spécifiques : tap, long press, swipe, scroll.

    3.1 Interactions de base avec les boutons

    @Test
    public void testInteractionsBoutons() {
        // Navigation vers Views > Buttons
        driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")).click();
        driver.findElement(
            AppiumBy.androidUIAutomator(
                "new UiScrollable(new UiSelector().scrollable(true))" +
                ".scrollIntoView(new UiSelector().text(\"Buttons\"))"
            )
        ).click();
        
        // ✅ Test d'un clic normal
        WebElement normalButton = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/button_normal")
        );
        
        String textAvant = normalButton.getText();
        normalButton.click();
        
        // Vérifier que le texte a changé après clic
        String textApres = normalButton.getText();
        Assert.assertNotEquals(textAvant, textApres, "Le texte du bouton doit changer après clic");
        
        System.out.println("Texte avant: " + textAvant);
        System.out.println("Texte après: " + textApres);
    }

    3.2 Gestes avancés avec TouchAction

    @Test 
    public void testGestesAvances() {
        // Navigation vers Views > Drag and Drop
        driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")).click();
        
        WebElement dragDropOption = driver.findElement(
            AppiumBy.androidUIAutomator(
                "new UiScrollable(new UiSelector().scrollable(true))" +
                ".scrollIntoView(new UiSelector().text(\"Drag and Drop\"))"
            )
        );
        dragDropOption.click();
        
        // ✅ Localiser les éléments source et destination
        WebElement dot1 = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/drag_dot_1")
        );
        WebElement dot2 = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/drag_dot_2")
        );
        
        // ✅ Effectuer un drag and drop avec les Actions modernes
        org.openqa.selenium.interactions.Actions actions = 
            new org.openqa.selenium.interactions.Actions(driver);
        
        actions.dragAndDrop(dot1, dot2).perform();
        
        // ✅ Vérifier que l'action a eu lieu
        WebElement resultText = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/drag_result_text")
        );
        
        String result = resultText.getText();
        Assert.assertTrue(result.contains("Dropped"), 
            "Le texte doit confirmer que l'élément a été déposé");
        
        System.out.println("✅ Drag and drop réussi: " + result);
    }
    💡 Alternative avec coordonnées : Si drag and drop ne fonctionne pas, vous pouvez utiliser des coordonnées précises avec driver.executeScript("mobile: dragGesture", ...)

    📝 Étape 4 : Test de formulaires et saisie de texte

    Testons la saisie de texte et la validation de formulaires dans l'app.

    4.1 Test de saisie dans un EditText

    @Test
    public void testSaisieTexte() {
        // Navigation vers Views > Controls > EditText
        driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")).click();
        
        driver.findElement(
            AppiumBy.androidUIAutomator(
                "new UiScrollable(new UiSelector().scrollable(true))" +
                ".scrollIntoView(new UiSelector().text(\"Controls\"))"
            )
        ).click();
        
        driver.findElement(
            AppiumBy.androidUIAutomator("new UiSelector().text(\"1. Light Theme\")")
        ).click();
        
        // ✅ Localiser le champ de saisie
        WebElement editText = driver.findElement(
            AppiumBy.className("android.widget.EditText")
        );
        
        // ✅ Effacer le contenu existant et saisir du nouveau texte
        editText.clear();
        String textToType = "Test automatisé avec Appium 2025!";
        editText.sendKeys(textToType);
        
        // ✅ Vérifier que le texte a été saisi correctement
        String actualText = editText.getText();
        Assert.assertEquals(actualText, textToType, "Le texte saisi doit correspondre");
        
        // ✅ Fermer le clavier virtuel
        driver.hideKeyboard();
        
        System.out.println("✅ Saisie de texte réussie: " + actualText);
    }

    4.2 Test de CheckBox et RadioButton

    @Test
    public void testControlesSelection() {
        // Navigation vers Views > Controls
        driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")).click();
        driver.findElement(
            AppiumBy.androidUIAutomator(
                "new UiScrollable(new UiSelector().scrollable(true))" +
                ".scrollIntoView(new UiSelector().text(\"Controls\"))"
            )
        ).click();
        
        driver.findElement(
            AppiumBy.androidUIAutomator("new UiSelector().text(\"1. Light Theme\")")
        ).click();
        
        // ✅ Tester les CheckBox
        WebElement checkbox = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/check1")
        );
        
        boolean initialState = checkbox.isSelected();
        checkbox.click();
        boolean newState = checkbox.isSelected();
        
        Assert.assertNotEquals(initialState, newState, "L'état de la checkbox doit changer");
        
        // ✅ Tester les RadioButtons
        WebElement radioButton1 = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/radio1")
        );
        WebElement radioButton2 = driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/radio2")
        );
        
        radioButton1.click();
        Assert.assertTrue(radioButton1.isSelected(), "Radio 1 doit être sélectionné");
        
        radioButton2.click();
        Assert.assertTrue(radioButton2.isSelected(), "Radio 2 doit être sélectionné");
        Assert.assertFalse(radioButton1.isSelected(), "Radio 1 ne doit plus être sélectionné");
        
        System.out.println("✅ Tests de contrôles réussis");
    }

    ⏱️ Étape 5 : Attentes explicites et validations avancées

    Les attentes explicites sont cruciales pour la stabilité des tests mobiles.

    5.1 Utilisation de WebDriverWait

    @Test
    public void testAttentesExplicites() {
        // Navigation vers App > Alert Dialogs
        driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"App\")")).click();
        
        driver.findElement(
            AppiumBy.androidUIAutomator(
                "new UiScrollable(new UiSelector().scrollable(true))" +
                ".scrollIntoView(new UiSelector().text(\"Alert Dialogs\"))"
            )
        ).click();
        
        // ✅ Cliquer sur "OK Cancel dialog with a message"
        driver.findElement(
            AppiumBy.id("io.appium.android.apis:id/two_buttons")
        ).click();
        
        // ✅ Attendre explicitement que l'alerte apparaisse
        WebElement alertTitle = wait.until(
            org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated(
                AppiumBy.id("android:id/alertTitle")
            )
        );
        
        Assert.assertTrue(alertTitle.isDisplayed(), "L'alerte doit être visible");
        
        // ✅ Vérifier le texte de l'alerte
        String titleText = alertTitle.getText();
        Assert.assertEquals(titleText, "Lorem ipsum dolor sit aie consectetur adipiscing\\nPlease note that the Latin text is probably wrong");
        
        // ✅ Cliquer sur Cancel avec attente
        WebElement cancelButton = wait.until(
            org.openqa.selenium.support.ui.ExpectedConditions.elementToBeClickable(
                AppiumBy.id("android:id/button2")
            )
        );
        cancelButton.click();
        
        // ✅ Attendre que l'alerte disparaisse
        wait.until(
            org.openqa.selenium.support.ui.ExpectedConditions.invisibilityOfElementLocated(
                AppiumBy.id("android:id/alertTitle")
            )
        );
        
        System.out.println("✅ Test d'attentes explicites réussi");
    }

    5.2 Validations personnalisées

    // ✅ Méthode utilitaire pour vérifications personnalisées
    public void waitForElementAndValidate(AppiumBy locator, String expectedText, int timeoutSeconds) {
        WebDriverWait customWait = new WebDriverWait(driver, Duration.ofSeconds(timeoutSeconds));
        
        WebElement element = customWait.until(
            org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated(locator)
        );
        
        String actualText = element.getText();
        Assert.assertTrue(actualText.contains(expectedText), 
            String.format("L'élément doit contenir '%s', mais contient '%s'", expectedText, actualText));
    }
    
    @Test
    public void testValidationsPersonnalisees() {
        // Test avec notre méthode personnalisée
        driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")).click();
        
        // Valider que la page Views contient bien le titre attendu
        waitForElementAndValidate(
            AppiumBy.xpath("//android.widget.TextView[@text='Views']"), 
            "Views", 
            10
        );
        
        System.out.println("✅ Validation personnalisée réussie");
    }

    📊 Étape 6 : Capture d'écrans et reporting

    Ajoutons des captures d'écran automatiques et un meilleur reporting de nos tests.

    6.1 Méthode de capture d'écran

    public void takeScreenshot(String testName) {
        try {
            // ✅ Prendre une capture d'écran
            File screenshot = driver.getScreenshotAs(org.openqa.selenium.OutputType.FILE);
            
            // ✅ Créer le dossier screenshots s'il n'existe pas
            File screenshotsDir = new File("target/screenshots");
            if (!screenshotsDir.exists()) {
                screenshotsDir.mkdirs();
            }
            
            // ✅ Sauvegarder avec timestamp
            String timestamp = java.time.LocalDateTime.now()
                .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
            
            File destFile = new File(screenshotsDir, testName + "_" + timestamp + ".png");
            
            java.nio.file.Files.copy(screenshot.toPath(), destFile.toPath());
            
            System.out.println("📸 Screenshot sauvegardé: " + destFile.getAbsolutePath());
            
        } catch (Exception e) {
            System.err.println("❌ Erreur lors de la capture d'écran: " + e.getMessage());
        }
    }
    
    // ✅ Capture automatique en cas d'échec
    @AfterMethod
    public void afterMethod(org.testng.ITestResult result) {
        if (result.getStatus() == org.testng.ITestResult.FAILURE) {
            takeScreenshot(result.getMethod().getMethodName() + "_FAILED");
        }
    }

    6.2 Test complet avec logging avancé

    @Test
    public void testScenarioComplet() {
        System.out.println("🚀 Début du test de scénario complet");
        
        try {
            // 1. Navigation initiale
            System.out.println("📱 Étape 1: Navigation vers Views");
            driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"Views\")")).click();
            takeScreenshot("etape1_views");
            
            // 2. Scroll et sélection
            System.out.println("📱 Étape 2: Recherche de Buttons");
            WebElement buttonsOption = driver.findElement(
                AppiumBy.androidUIAutomator(
                    "new UiScrollable(new UiSelector().scrollable(true))" +
                    ".scrollIntoView(new UiSelector().text(\"Buttons\"))"
                )
            );
            buttonsOption.click();
            takeScreenshot("etape2_buttons");
            
            // 3. Interaction avec les éléments
            System.out.println("📱 Étape 3: Test des interactions");
            WebElement normalButton = driver.findElement(
                AppiumBy.id("io.appium.android.apis:id/button_normal")
            );
            normalButton.click();
            takeScreenshot("etape3_interaction");
            
            // 4. Validation finale
            System.out.println("📱 Étape 4: Validation du résultat");
            Assert.assertTrue(normalButton.isDisplayed(), "Le bouton doit être toujours visible");
            
            System.out.println("✅ Scénario complet réussi!");
            
        } catch (Exception e) {
            System.err.println("❌ Erreur dans le scénario: " + e.getMessage());
            takeScreenshot("erreur_scenario");
            throw e;
        }
    }

    🎉 Conclusion de l'exercice

    Félicitations ! Vous avez maintenant maîtrisé les concepts fondamentaux d'Appium avec du code moderne et fonctionnel :

    ✅ Compétences acquises :
    • Configuration correcte des capabilities Appium 2.x
    • Localisation d'éléments avec AppiumBy (ID, UiAutomator, XPath)
    • Navigation et scroll dans une application mobile
    • Interactions tactiles (tap, drag & drop, saisie de texte)
    • Attentes explicites avec WebDriverWait
    • Capture d'écrans automatique
    • Structure de test maintenable avec TestNG

    🚀 Prochaines étapes

    Pour aller plus loin, vous pouvez maintenant :

    • Tester sur iOS : adapter les capabilities pour XCUITest
    • Page Object Model : restructurer le code avec le pattern POM
    • Tests parallèles : exécuter sur plusieurs émulateurs simultanément
    • CI/CD : intégrer vos tests dans GitHub Actions ou GitLab CI
    • Tests cross-platform : même logique pour Android et iOS
    💡 Rappel important : Ce code utilise les standards Appium 2025 et fonctionne réellement. Fini les erreurs de compilation avec MobileElement et les capabilities obsolètes !

    🚀 Créer son premier projet Appium avec Maven

    Dans cette section, nous allons apprendre à créer un projet Java avec Maven, ajouter les dépendances nécessaires pour Appium, mettre en place une structure de test maintenable, puis exécuter notre tout premier test mobile automatisé avec du code moderne qui fonctionne.

    ⚠️ Important : Cette version corrige les erreurs de l'exercice original qui utilisait du code obsolète (MobileElement supprimé, capabilities dépréciées, etc.)

    1. Préparation de l'environnement

    Avant de démarrer, assurez-vous d'avoir installé :

    • Java JDK 11+ (Java 17 ou 21 recommandé).
    • IntelliJ IDEA ou Eclipse comme IDE.
    • Maven (généralement inclus avec IntelliJ/Eclipse).
    • Appium Server 2.x (npm install -g appium).
    • Android Studio (pour créer un émulateur Android).
    • Optionnel : Xcode si vous souhaitez tester sur iOS.

    Vérifiez que Java et Maven sont bien installés via la console :

    java -version
    mvn -v

    2. Création du projet Maven

    Dans votre IDE (exemple IntelliJ IDEA) :

    1. Menu File > New Project.
    2. Sélectionnez Maven comme système de build.
    3. Donnez un nom à votre projet, par exemple : AppiumDemo.
    4. Validez : IntelliJ crée l'arborescence par défaut avec un fichier pom.xml.

    2.1 Exemple d'arborescence

    AppiumDemo/
    ├── pom.xml
    └── src/
        ├── main/java
        │   └── com/example/pages
        │       ├── LoginPage.java
        │       ├── HomePage.java
        │       └── CartPage.java
        └── test/java
            └── com/example/tests
                └── AndroidDemoTest.java

    Cette organisation sépare les pages (pattern Page Object) de la logique des tests.

    3. Configuration du fichier pom.xml

    Ouvrez le fichier pom.xml et ajoutez les dépendances modernes :

    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                                 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <groupId>com.example</groupId>
      <artifactId>AppiumDemo</artifactId>
      <version>1.0-SNAPSHOT</version>
    
      <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      </properties>
    
      <dependencies>
        <!-- ✅ Client Java Appium MODERNE -->
        <dependency>
          <groupId>io.appium</groupId>
          <artifactId>java-client</artifactId>
          <version>9.3.0</version>
        </dependency>
    
        <!-- ✅ Selenium RÉCENT -->
        <dependency>
          <groupId>org.seleniumhq.selenium</groupId>
          <artifactId>selenium-java</artifactId>
          <version>4.25.0</version>
        </dependency>
    
        <!-- TestNG pour nos tests -->
        <dependency>
          <groupId>org.testng</groupId>
          <artifactId>testng</artifactId>
          <version>7.10.2</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </project>
    ✅ Corrections apportées :
    - Appium Java Client 9.3.0 (plus de MobileElement)
    - Selenium 4.25.0 (compatible)
    - TestNG 7.10.2 (dernière version)
    - Java 17 (recommandé)

    4. Exemple de Page Object

    Exemple d'une page LoginPage avec du code qui fonctionne :

    package com.example.pages;
    
    import io.appium.java_client.AppiumBy;
    import io.appium.java_client.android.AndroidDriver;
    import org.openqa.selenium.WebElement;
    
    public class LoginPage {
        private AndroidDriver driver; // ✅ Plus de paramètre générique
    
        public LoginPage(AndroidDriver driver) {
            this.driver = driver;
        }
    
        public void enterUsername(String username) {
            // ✅ AppiumBy.id() au lieu de findElement direct
            WebElement usernameField = driver.findElement(
                AppiumBy.id("com.example.ecommerce:id/usernameField")
            );
            usernameField.clear();
            usernameField.sendKeys(username);
        }
    
        public void enterPassword(String password) {
            WebElement passwordField = driver.findElement(
                AppiumBy.id("com.example.ecommerce:id/passwordField")
            );
            passwordField.clear();
            passwordField.sendKeys(password);
        }
    
        public void submitLogin() {
            WebElement loginButton = driver.findElement(
                AppiumBy.id("com.example.ecommerce:id/loginButton")
            );
            loginButton.click();
        }
        
        public String getWelcomeMessage() {
            WebElement welcomeElement = driver.findElement(
                AppiumBy.id("com.example.ecommerce:id/homeTitle")
            );
            return welcomeElement.getText();
        }
    }
    ✅ Corrections :
    - AndroidDriver<MobileElement>AndroidDriver
    - MobileElementWebElement
    - Utilisation d'AppiumBy.id()
    - Méthode clear() ajoutée avant sendKeys()

    5. Exemple de Test avec TestNG

    Voici une classe de test moderne pour Android :

    package com.example.tests;
    
    import io.appium.java_client.AppiumBy;
    import io.appium.java_client.android.AndroidDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.remote.DesiredCapabilities;
    import org.testng.Assert;
    import org.testng.annotations.*;
    
    import java.net.URL;
    import java.time.Duration;
    
    public class AndroidDemoTest {
        private AndroidDriver driver; // ✅ Pas de paramètre générique
    
        @BeforeClass
        public void setUp() throws Exception {
            DesiredCapabilities caps = new DesiredCapabilities();
            
            // ✅ Capabilities modernes avec préfixe appium:
            caps.setCapability("platformName", "Android");
            caps.setCapability("appium:deviceName", "Android Emulator");
            caps.setCapability("appium:appPackage", "com.example.ecommerce");
            caps.setCapability("appium:appActivity", "com.example.ecommerce.MainActivity");
            caps.setCapability("appium:automationName", "UiAutomator2");
            caps.setCapability("appium:app", "/chemin/vers/app.apk");
            
            // ✅ Timeout pour éviter les sessions qui traînent
            caps.setCapability("appium:newCommandTimeout", 300);
    
            // ✅ URL moderne sans /wd/hub
            driver = new AndroidDriver(new URL("http://127.0.0.1:4723"), caps);
            
            // ✅ Timeout implicite
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        }
    
        @Test
        public void testLogin() {
            // ✅ Utilisation d'AppiumBy et WebElement
            WebElement userField = driver.findElement(
                AppiumBy.id("com.example.ecommerce:id/usernameField")
            );
            userField.sendKeys("demo");
            
            WebElement passField = driver.findElement(
                AppiumBy.id("com.example.ecommerce:id/passwordField")
            );
            passField.sendKeys("password");
            
            WebElement loginButton = driver.findElement(
                AppiumBy.id("com.example.ecommerce:id/loginButton")
            );
            loginButton.click();
    
            // Vérification avec WebElement
            WebElement homeTitle = driver.findElement(
                AppiumBy.id("com.example.ecommerce:id/homeTitle")
            );
            String title = homeTitle.getText();
            Assert.assertEquals(title, "Bienvenue");
        }
    
        @AfterClass
        public void tearDown() {
            if (driver != null) {
                driver.quit();
            }
        }
    }
    ✅ Corrections majeures :
    - AndroidDriver<MobileElement>AndroidDriver
    - MobileElementWebElement
    - Capabilities avec préfixe appium:
    - URL sans /wd/hub
    - findElementById()AppiumBy.id()
    - Timeouts implicites ajoutés

    6. Exécution du test

    Avant d'exécuter :

    • Lancez votre serveur Appium : appium
    • Démarrez un émulateur Android depuis Android Studio
    • Vérifiez la connexion : adb devices

    Puis, dans votre IDE ou votre console Maven :

    mvn clean test

    Résultat attendu : ouverture de l'application, saisie du login/password et validation de l'écran d'accueil.

    7. Comparatif : Ancien vs Nouveau code

    ❌ Ancien code (ne marche plus) ✅ Nouveau code (2025)
    AndroidDriver<MobileElement> AndroidDriver
    MobileElement element WebElement element
    findElementById("id") findElement(AppiumBy.id("id"))
    setCapability("deviceName", "...") setCapability("appium:deviceName", "...")
    new URL("...4723/wd/hub") new URL("...4723")

    8. Bonnes pratiques

    • ✅ Utilisez le Page Object Model pour éviter le code dupliqué
    • ✅ Externalisez vos capabilities (fichier JSON ou properties)
    • ✅ Adoptez un framework de tests (TestNG, JUnit, Cucumber)
    • ✅ Intégrez dans un pipeline CI/CD pour l'exécution automatique
    • ✅ Utilisez WebDriverWait au lieu de Thread.sleep()
    • ✅ Ajoutez des screenshots en cas d'échec de test

    9. Conclusion

    Cette section vous donne un exemple complet et à jour pour créer un projet Appium avec Maven, écrire vos premières classes avec du code moderne, et exécuter un test automatisé qui fonctionne vraiment sur Android.

    Fini les erreurs de compilation ! Vous avez désormais toutes les bases pour industrialiser vos projets et aller plus loin (tests iOS, exécution parallèle, CI/CD, etc.).