Salut tout le monde! Je m'appelle Taras Egorov; Je suis ingénieur chez inDrive. Je vais vous montrer comment nous avons mis en place une infrastructure capable d'exécuter plus de 5 000 tests par jour sur des appareils iOS et Android combinés. Le secret est simple : nous avons utilisé Selenoid.
L'année dernière, nos collègues ont mené une étude sur les tests automatiques, et nous avons participé à une enquête dans le cadre de l'étude.
Nous avons été satisfaits des résultats de l'enquête, nous avons donc décidé d'écrire un article pour partager notre expérience avec vous et obtenir des conseils en retour. Nous avons pensé que c'était une bonne idée de diviser le matériel en deux parties : la première se concentrant sur Android et la seconde sur iOS.
Commençons par Android.
Selenoid est un outil populaire pour exécuter et gérer des navigateurs et des émulateurs Android dans un conteneur Docker. Vous pouvez en savoir plus à ce sujet dans la documentation .
Pour écrire des tests Appium, nous utilisons :
browsers.json
:
{ "android": { "default": "10.0", "versions": { "10.0": { "image": "browsers/android:10.0", "port": "4444", "path": "/wd/hub" } } } }
L'image de l'émulateur est spécifiée dans image
. Les gars d' Aerokube ont préparé des images prêtes à l'emploi d'émulateurs Android. Vous pouvez les consulter ici ou ici . Les images ne diffèrent en rien les unes des autres.
Prenons l'image browsers/android:10.0
comme exemple. L'image doit être téléchargée au préalable : docker pull browsers/android:10.0
, sinon les tests ne se lanceront pas :
Original error: create container: Error response from daemon: No such image: browsers/android:10.0
docker run -d \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$(pwd)/selenoid/config/":/etc/selenoid/:ro \ -p 4444:4444 \ --name selenoid \ aerokube/selenoid:1.10.7
Vous pouvez vérifier si Selenoid fonctionne correctement en suivant le lien http://localhost:4444 dans votre navigateur :
You are using Selenoid 1.10.7!
Spécifiez l'adresse Selenoid dans les tests Appium dans le pilote :
... val driver = AndroidDriver(URL("http://localhost:4444/wd/hub"), capabilities) ...
... capabilities.setCapability("appium:app", "https://storage.example.com/builds/app.apk") ...
Si vous ne pouvez pas fournir de lien, vous pouvez spécifier le chemin d'accès au build :**
... capabilities.setCapability("appium:app", "/builds/app.apk") ...
Où /builds/app.apk
est le chemin à l'intérieur du conteneur où l'émulateur est exécuté. Pour que cette option fonctionne correctement, assurez-vous de spécifier les volumes
dans browsers.json
:
{ "android": { "default": "10.0", "versions": { "10.0": { ... "volumes": [ "/home/username/app.apk:/builds/app.apk:ro" ] ... } } } }
Où /home/username/app.apk
est le chemin d'accès au build sur la plate-forme hôte.
Ça y est, nous avons presque configuré Selenoid, et maintenant nous pouvons essayer de lancer les tests :
./mvnw test
Mais, malheureusement, les tests ne pourront pas fonctionner. Examinons cela et voyons ce qui ne va pas.
La première chose à faire après un démarrage raté est de consulter les journaux Selenoid :
docker logs selenoid
[INIT] [Loading configuration files...] [INIT] [Loaded configuration from /etc/selenoid/browsers.json] [INIT] [Video Dir: /opt/selenoid/video] [INIT] [Your Docker API version is 1.41] [INIT] [Timezone: UTC] [INIT] [Listening on :4444] [NEW_REQUEST] [unknown] [172.17.0.1] [NEW_REQUEST_ACCEPTED] [unknown] [172.17.0.1] [LOCATING_SERVICE] [android] [10.0] [USING_DOCKER] [android] [10.0] [CREATING_CONTAINER] [selenoid/android:10.0] [STARTING_CONTAINER] [selenoid/android:10.0] [75e454341da7fc4b58ba104a5180813bac6cd7c124037a759b6c976e65b168fa] [CONTAINER_STARTED] [selenoid/android:10.0] [75e454341da7fc4b58ba104a5180813bac6cd7c124037a759b6c976e65b168fa] [0.40s] [0] [REMOVING_CONTAINER] [75e454341da7fc4b58ba104a5180813bac6cd7c124037a759b6c976e65b168fa] [0] [CONTAINER_REMOVED] [75e454341da7fc4b58ba104a5180813bac6cd7c124037a759b6c976e65b168fa] [0] [SERVICE_STARTUP_FAILED] [http://172.17.0.3:4444/wd/hub does not respond in 30s]
Nous voyons que le statut est SERVICE_STARTUP_FAILED. Accédez à la documentation et regardez la valeur de statut :
SERVICE_STARTUP_FAILED - Failed to start Docker container or driver binary
L'erreur ne vous dit pas grand-chose, et plus d'informations sont nécessaires. Il serait bon de jeter un œil aux journaux de conteneurs. Faisons cela en activant la journalisation :
docker run -d \ -p 4444:4444 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$(pwd)/selenoid/config/":/etc/selenoid/:ro \ -v "$(pwd)/selenoid/logs/":/opt/selenoid/logs/ \ aerokube/selenoid:1.10.7 \ -log-output-dir /opt/selenoid/logs
Nous activons également les journaux dans la section Capacités :
... capabilities.setCapability("enableLog", true) ...
Exécutez les tests et consultez les journaux à l'aide du navigateur http://localhost:4444/logs/ :
2023-04-16T13:44:43.909768530Z Waiting X server... 2023-04-16T13:44:44.009494775Z Logging to: /tmp/fluxbox.log 2023-04-16T13:44:44.047587277Z Waiting X server... 2023-04-16T13:44:44.151933325Z Waiting X server... 2023-04-16T13:44:44.262850410Z * daemon not running; starting now at tcp:5037 2023-04-16T13:44:44.457972956Z * daemon started successfully 2023-04-16T13:44:44.458249266Z adb: no devices/emulators found 2023-04-16T13:44:45.463480812Z adb: no devices/emulators found 2023-04-16T13:44:46.471547723Z adb: no devices/emulators found 2023-04-16T13:44:47.476093515Z adb: no devices/emulators found 2023-04-16T13:44:48.481987351Z adb: no devices/emulators found 2023-04-16T13:44:49.486503149Z adb: no devices/emulators found 2023-04-16T13:44:50.492757801Z adb: no devices/emulators found 2023-04-16T13:44:51.499094108Z adb: no devices/emulators found 2023-04-16T13:44:52.505862428Z adb: no devices/emulators found 2023-04-16T13:44:53.513276412Z adb: no devices/emulators found 2023-04-16T13:44:54.520642210Z adb: no devices/emulators found 2023-04-16T13:44:55.527420189Z adb: no devices/emulators found 2023-04-16T13:44:56.534631013Z adb: no devices/emulators found 2023-04-16T13:44:57.316094939Z WARNING. Using fallback path for the emulator registration directory. 2023-04-16T13:44:57.335415397Z checkValid: hw configs not eq 2023-04-16T13:44:57.541959741Z adb: device offline 2023-04-16T13:44:58.547907700Z adb: device offline 2023-04-16T13:44:58.565504866Z emulator: WARNING: System image is writable 2023-04-16T13:44:58.565528396Z emulator: Cold boot: different AVD configuration 2023-04-16T13:44:58.565532576Z Your emulator is out of date, please update by launching Android Studio: 2023-04-16T13:44:58.565536346Z - Start Android Studio 2023-04-16T13:44:58.565539506Z - Select menu "Tools > Android > SDK Manager" 2023-04-16T13:44:58.565543076Z - Click "SDK Tools" tab 2023-04-16T13:44:58.565546216Z - Check "Android Emulator" checkbox 2023-04-16T13:44:58.565549216Z - Click "OK" 2023-04-16T13:44:58.565552146Z 2023-04-16T13:44:59.554451514Z adb: device offline 2023-04-16T13:45:00.560926060Z adb: device offline 2023-04-16T13:45:01.568777440Z adb: device offline 2023-04-16T13:45:12.124226047Z emulator: INFO: boot completed 2023-04-16T13:45:12.124251007Z emulator: INFO: boot time 27848 ms 2023-04-16T13:45:12.124255077Z emulator: Increasing screen off timeout, logcat buffer size to 2M. 2023-04-16T13:45:12.152557294Z emulator: Revoking microphone permissions for Google App.
Les journaux de conteneur ne sont pas non plus d'une grande aide ici, car vous ne pouvez pas voir les journaux d'Appium. Essayons maintenant de les activer. Pour ce faire, regardons l' entrée de script point.sh :
... if [ -z "$VERBOSE" ]; then APPIUM_ARGS="$APPIUM_ARGS --log-level error" else EMULATOR_ARGS="$EMULATOR_ARGS -verbose" fi ...
Pour activer les journaux Appium, les paramètres VERBOSE=true
et APPIUM_ARGS=--log-level debug
: doivent être transmis au conteneur :
{ "android": { "default": "10.0", "versions": { "10.0": { ... "env": [ "VERBOSE=true", "APPIUM_ARGS=--log-level debug" ] ... } } } }
Pour permettre à Appium de déboguer les journaux, vous devez passer VERBOSE ; dans ce cas, les journaux de l'émulateur s'allument et commencent à remplir "l'éther"” Mais nous avons corrigé cela pour les futures images =)
Maintenant il suffit de passer à APPIUM_ARGS=-log-level debug
.
... [HTTP] --> POST /wd/hub/session/c89fa9c2-ca2b-49cd-ab31-590eeccf77d1/element [HTTP] {"using":"id","value":"authorization_edittext_phone"} [debug] [W3C (c89fa9c2)] Calling AppiumDriver.findElement() with args: ["id","authorization_edittext_phone"," c89fa9c2-ca2b-49cd-ab31-590eeccf77d1"] [debug] [BaseDriver] Valid locator strategies for this request: xpath, id, class name, accessibility id, -and roid uiautomator [debug] [BaseDriver] Waiting up to 0 ms for condition [debug] [WD Proxy] Matched '/element' to command name 'findElement' [debug] [WD Proxy] Proxying [POST /element] to [POST http://127.0.0.1:8200/wd/hub/session/65943f03-3b35-4d3eb221-d6dc7988f935/element] with body: {"strategy":"id","selector": "authorization_edittext_phone","context":"","multiple":false} [WD Proxy] Got response with status 404: {"sessionId":"65943f03-3b35-4d3e-b221-d6dc7988f935","value":{"error" :"no such element","message":"An element could not be located on the page using the given search parameters","stacktrace":"io.appium.uiautomator2.common.exceptions.El ementNotFoundException: An element could not be located on the page using the given search parameters\n\tat io.appium.uiautomator2.handler.FindElement.safeHandle(Find Element.java:73)\n\tat io.appium.uiautomator2.handler.request.SafeRequestHandler.handle(SafeRequestHandler.java:41)\n\tat io.appium.uiautomator2.server.AppiumServlet. handleRequest(AppiumServlet.java:253)\n\tat io.appium.uiautomator2.server.AppiumServlet.handleHttpRequest(AppiumServlet.java:247)\n\tat io.appium.uiautomator2.http.Se rverHandler.channelRead(ServerHandler.java:68)\n\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:366)\n\tat io .netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:352)\n\tat io.netty.chann... [debug] [W3C] Matched W3C error code 'no such element' to NoSuchElementError [debug] [W3C (c89fa9c2)] Encountered internal error running command: NoSuchElementError: An element could not be located on the page using the given search parameters. [debug] [W3C (c89fa9c2)] at AndroidUiautomator2Driver.findElOrEls (/opt/node_modules/appium/node_modules/appium-android-driver/lib/commands/find.js:75:11) [debug] [W3C (c89fa9c2)] at process._tickCallback (internal/process/next_tick.js:68:7) [HTTP] <-- POST /wd/hub/session/c89fa9c2-ca2b-49cd-ab31-590eeccf77d1/element 404 23 ms - 444 ...
Comme le montrent les journaux, Appium ne trouve pas notre élément. Voyons ce qui se passe sur l'écran de l'émulateur. Pour ce faire, nous devons exécuter l'interface utilisateur Selenoid :
docker run -d \ --name selenoid-ui \ -p 8080:8080 \ --link selenoid:selenoid \ aerokube/selenoid-ui:1.10.4 \ --selenoid-uri "http://selenoid:4444"
Accédez à http://0.0.0.0:8080 et ouvrez l'interface utilisateur de Selenoid :
Assurez-vous d'activer VNC et l'enregistrement vidéo dans les tests :
... capabilities.setCapability("enableVNC", true) capabilities.setCapability("enableVideo", true) ...
La commande de démarrage Selenoid finit par ressembler à ceci :
docker run -d \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "(pwd)/selenoid/logs/":/opt/selenoid/logs/ \ -v /opt/selenoid/video/:/opt/selenoid/video/ \ -e OVERRIDE_VIDEO_OUTPUT_DIR="/opt/selenoid/video/" \ -p 4444:4444 \ -name selenoid \ aerokube/selenoid:1.10.7 \ -log-output-dir /opt/selenoid/logs
Ouvrez l'interface utilisateur de Selenoid une fois que les tests sont opérationnels :
Et en voici une vidéo .
Nous avons trouvé la cause de l'erreur de démarrage. Super! Allons-nous en.
Il s'avère que les images des émulateurs Selenoid ne fonctionneront pas sans les services Google Play. Pour remédier à cette situation, vous devez créer vous-même une image d'émulateur. Les gars d'Aerokube ont rassemblé tout ce dont vous avez besoin pour cela : un référentiel avec des photos et de la documentation .
selenium
../automate_android.sh
et répondez aux questions. Voici à quoi cela ressemble dans notre cas :
Specify Appium version: [1.18.1] >> 1.18.1 Specify Android image type (possible values: "default", "google_apis", "google_apis_playstore", "android-tv", "android-wear"): [default] >> google_apis Specify Application Binary Interface (possible values: "armeabi-v7a", "arm64-v8a", "x86", "x86_64"): [x86] >> x86 Specify Android version: [8.1] >> 10.0 Specify device preset name if needed (eg "Nexus 4"): >> Specify SD card size, Mb: [500] >> Specify userdata.img size, Mb: [500] >> Are you building a Chrome Mobile image (for mobile web testing): [n] >> y Specify Chromedriver version if needed (required for Chrome Mobile): >> 74.0.3729.6 Specify image tag: [selenoid/chrome-mobile:74.0] >> android-emulator:10.0 Add Android quick boot snapshot? [y] >> n
Quand j'ai vu la question Ajouter un instantané de démarrage rapide Android ?, j'ai pensé qu'il s'agissait d'un instantané d'émulateur. Mais si vous regardez le code , il appelle un script , qui est nécessaire pour installer les applications APK. Fondamentalement, cela ne nous rapporte aucun gain.
Nous utilisons déjà des émulateurs d'instantanés pour accélérer les lancements de conteneurs , mais nous en reparlerons dans d'autres articles.
Une fois l'image construite, nous serons invités à la pousser dans le registre. Comme nous n'en avons pas encore besoin, nous déclinons l'offre :
Push? >> n
Nous avons mis en place une version avec les services Google Play. N'oubliez pas de modifier le paramètre d'image dans browsers.json et de redémarrer Selenoid.
Essayons maintenant de relancer les tests :
[INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
Et voici une vidéo du test.
Ce que nous avons fait:
Autre chose dont je voudrais vous parler :
En attendant, dans la partie suivante, nous vous expliquerons comment nous avons fait évoluer l'infrastructure et effectué des tests sur iOS.