¡Hola a todos! Mi nombre es Taras Egorov; Soy ingeniero en inDrive. Le mostraré cómo configuramos una infraestructura capaz de ejecutar más de 5000 pruebas por día en dispositivos iOS y Android combinados. El secreto es simple: usamos Selenoid.
El año pasado, nuestros colegas realizaron un estudio de pruebas automáticas y participamos en una encuesta como parte del estudio.
Estamos satisfechos con los resultados de la encuesta, por lo que decidimos escribir un artículo para compartir nuestra experiencia con usted y obtener algunos consejos a cambio. Nos pareció buena idea dividir el material en dos partes: la primera centrada en Android y la segunda en iOS.
Comencemos con Android.
Selenoid es una herramienta popular para ejecutar y administrar navegadores y emuladores de Android en un contenedor Docker. Puede leer más sobre esto en la documentación .
Para escribir pruebas de Appium, usamos:
browsers.json
:
{ "android": { "default": "10.0", "versions": { "10.0": { "image": "browsers/android:10.0", "port": "4444", "path": "/wd/hub" } } } }
La imagen del emulador se especifica en image
. Los chicos de aerokube han preparado imágenes listas para usar de emuladores de Android. Puedes consultarlos aquí o aquí . Las imágenes no difieren entre sí de ninguna manera.
Tomemos como ejemplo la imagen browsers/android:10.0
. La imagen debe descargarse previamente: docker pull browsers/android:10.0
, de lo contrario no se ejecutarán las pruebas:
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
Puede verificar si Selenoid está funcionando correctamente siguiendo el enlace http://localhost:4444 en su navegador:
You are using Selenoid 1.10.7!
Especifique la dirección de Selenoid en las pruebas de Appium en el controlador:
... val driver = AndroidDriver(URL("http://localhost:4444/wd/hub"), capabilities) ...
... capabilities.setCapability("appium:app", "https://storage.example.com/builds/app.apk") ...
Si no puede proporcionar un enlace, puede especificar la ruta a la compilación: **
... capabilities.setCapability("appium:app", "/builds/app.apk") ...
Donde /builds/app.apk
es la ruta dentro del contenedor donde se ejecuta el emulador. Para que esta opción funcione correctamente, asegúrese de especificar los volumes
en browsers.json
:
{ "android": { "default": "10.0", "versions": { "10.0": { ... "volumes": [ "/home/username/app.apk:/builds/app.apk:ro" ] ... } } } }
Donde /home/username/app.apk
es la ruta a la compilación en la plataforma host.
Eso es todo, casi hemos configurado Selenoid, y ahora podemos intentar ejecutar las pruebas:
./mvnw test
Pero, desafortunadamente, las pruebas no podrán ejecutarse. Echemos un vistazo a esto y veamos qué está mal.
Lo primero que debe hacer después de un inicio fallido es verificar los registros de 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]
Vemos que el estado es SERVICE_STARTUP_FAILED. Vaya a la documentación y mire el valor de estado:
SERVICE_STARTUP_FAILED - Failed to start Docker container or driver binary
El error no dice mucho, y se requiere más información. Sería bueno echar un vistazo a los registros del contenedor. Hagámoslo habilitando el registro:
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
También habilitamos los registros en la sección Capacidades:
... capabilities.setCapability("enableLog", true) ...
Ejecute las pruebas y revise los registros usando el navegador 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.
Los registros del contenedor tampoco son de mucha ayuda aquí, porque no puede ver los registros de Appium. Ahora intentemos habilitarlos. Para hacer esto, veamos el punto de entrada del script.sh :
... if [ -z "$VERBOSE" ]; then APPIUM_ARGS="$APPIUM_ARGS --log-level error" else EMULATOR_ARGS="$EMULATOR_ARGS -verbose" fi ...
Para habilitar los registros de Appium, los parámetros VERBOSE=true
y APPIUM_ARGS=--log-level debug
: se deben pasar al contenedor:
{ "android": { "default": "10.0", "versions": { "10.0": { ... "env": [ "VERBOSE=true", "APPIUM_ARGS=--log-level debug" ] ... } } } }
Para habilitar Appium para depurar registros, debe pasar VERBOSE; en este caso, los registros del emulador se encienden y comienzan a llenar el "éter". Pero lo arreglamos para futuras imágenes =)
Ahora basta con pasar a 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 ...
Como se ve en los registros, Appium no puede encontrar nuestro elemento. Veamos qué sucede en la pantalla del emulador. Para hacer esto, tenemos que ejecutar la interfaz de usuario de Selenoid:
docker run -d \ --name selenoid-ui \ -p 8080:8080 \ --link selenoid:selenoid \ aerokube/selenoid-ui:1.10.4 \ --selenoid-uri "http://selenoid:4444"
Vaya a http://0.0.0.0:8080 y abra la interfaz de usuario de Selenoid:
Asegúrese de habilitar VNC y grabación de video en las pruebas:
... capabilities.setCapability("enableVNC", true) capabilities.setCapability("enableVideo", true) ...
El comando de inicio de Selenoid termina luciendo así:
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
Abra la interfaz de usuario de Selenoid una vez que las pruebas estén en funcionamiento:
Y aquí un video de ello .
Encontramos la causa del error de inicio. ¡Excelente! Vamonos.
Resulta que las imágenes de los emuladores de Selenoid no funcionarán sin los servicios de Google Play. Para remediar esta situación, debe crear una imagen de emulador usted mismo. Los chicos de aerokube han montado todo lo necesario para ello: un repositorio con fotos y documentación .
selenium
../automate_android.sh
y responda las preguntas. Así es como se ve en nuestro caso:
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
Cuando vi la pregunta ¿Agregar una instantánea de inicio rápido de Android?, pensé que era una instantánea del emulador. Pero si observa el código , llama a un script , que se necesita para instalar aplicaciones APK. Básicamente, no nos da ninguna ganancia.
Ya usamos emuladores de instantáneas para acelerar los lanzamientos de contenedores , pero hablaremos de eso en otros artículos.
Una vez que se crea la imagen, se nos pedirá que la insertemos en el registro. Dado que aún no lo necesitamos, rechazaremos amablemente la oferta:
Push? >> n
Hemos creado una compilación con los servicios de Google Play. Recuerde cambiar el parámetro de la imagen en browsers.json y reinicie Selenoid.
Ahora intentemos volver a ejecutar las pruebas:
[INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
Y aquí hay un video de la prueba.
Lo que hemos hecho:
Algo más que me gustaría comentarte:
Mientras tanto, en la siguiente parte, le diremos cómo ampliamos la infraestructura y realizamos pruebas en iOS.