Olá a todos! Meu nome é Taras Egorov; Sou engenheiro da inDrive. Vou mostrar como montamos uma infraestrutura capaz de rodar mais de 5.000 testes por dia em dispositivos iOS e Android combinados. O segredo é simples: usamos Selenoid.
No ano passado, nossos colegas conduziram um estudo sobre testes automáticos e nós participamos de uma pesquisa como parte do estudo.
Ficamos satisfeitos com os resultados da pesquisa, então decidimos escrever um artigo para compartilhar nossa experiência com você e obter alguns conselhos em troca. Achamos uma boa ideia dividir o material em duas partes: a primeira com foco no Android e a segunda no iOS.
Vamos começar com o Android.
Selenoid é uma ferramenta popular para executar e gerenciar navegadores e emuladores Android em um contêiner Docker. Você pode ler mais sobre isso na documentação .
Para escrever testes do Appium, usamos:
browsers.json
:
{ "android": { "default": "10.0", "versions": { "10.0": { "image": "browsers/android:10.0", "port": "4444", "path": "/wd/hub" } } } }
A imagem do emulador é especificada em image
. Os caras do aerokube prepararam imagens prontas de emuladores Android. Você pode conferir aqui ou aqui . As imagens não diferem umas das outras de forma alguma.
Vamos usar a imagem browsers/android:10.0
como exemplo. A imagem deve ser baixada previamente: docker pull browsers/android:10.0
, caso contrário os testes não serão executados:
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
Você pode verificar se o Selenoid está funcionando corretamente seguindo o link http://localhost:4444 em seu navegador:
You are using Selenoid 1.10.7!
Especifique o endereço Selenoid nos testes Appium no driver:
... val driver = AndroidDriver(URL("http://localhost:4444/wd/hub"), capabilities) ...
... capabilities.setCapability("appium:app", "https://storage.example.com/builds/app.apk") ...
Se não for possível fornecer um link, você pode especificar o caminho para a compilação:**
... capabilities.setCapability("appium:app", "/builds/app.apk") ...
Onde /builds/app.apk
é o caminho dentro do contêiner onde o emulador está sendo executado. Para que esta opção funcione corretamente, certifique-se de especificar os volumes
em browsers.json
:
{ "android": { "default": "10.0", "versions": { "10.0": { ... "volumes": [ "/home/username/app.apk:/builds/app.apk:ro" ] ... } } } }
Em que /home/username/app.apk
é o caminho para a compilação na plataforma host.
É isso, quase configuramos o Selenoid, e agora podemos tentar rodar os testes:
./mvnw test
Mas, infelizmente, os testes não poderão ser executados. Vamos investigar isso e ver o que há de errado.
A primeira coisa a fazer após uma falha na inicialização é verificar os logs do 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 o status é SERVICE_STARTUP_FAILED. Vá para a documentação e veja o valor do status:
SERVICE_STARTUP_FAILED - Failed to start Docker container or driver binary
O erro não informa muito e mais informações são necessárias. Seria bom dar uma olhada nos logs do container. Vamos fazer isso ativando o log:
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
Também habilitamos logs na seção Capabilities:
... capabilities.setCapability("enableLog", true) ...
Execute os testes e revise os logs usando o 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.
Os logs do contêiner também não ajudam muito aqui, porque você não pode ver os logs do Appium. Agora vamos tentar ativá-los. Para fazer isso, vamos ver a entrada do script point.sh :
... if [ -z "$VERBOSE" ]; then APPIUM_ARGS="$APPIUM_ARGS --log-level error" else EMULATOR_ARGS="$EMULATOR_ARGS -verbose" fi ...
Para ativar os logs do Appium, os parâmetros VERBOSE=true
e APPIUM_ARGS=--log-level debug
: devem ser passados para o container:
{ "android": { "default": "10.0", "versions": { "10.0": { ... "env": [ "VERBOSE=true", "APPIUM_ARGS=--log-level debug" ] ... } } } }
Para permitir que o Appium depure logs, você precisa passar VERBOSE; neste caso, os logs do emulador ligam e começam a preencher o "ether"” Mas corrigimos isso para imagens futuras =)
Agora basta passar para 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 visto nos logs, o Appium não consegue encontrar nosso elemento. Vamos ver o que está acontecendo na tela do emulador. Para fazer isso, temos que executar a IU do Selenoid:
docker run -d \ --name selenoid-ui \ -p 8080:8080 \ --link selenoid:selenoid \ aerokube/selenoid-ui:1.10.4 \ --selenoid-uri "http://selenoid:4444"
Acesse http://0.0.0.0:8080 e abra a IU do Selenoid:
Certifique-se de ativar o VNC e a gravação de vídeo nos testes:
... capabilities.setCapability("enableVNC", true) capabilities.setCapability("enableVideo", true) ...
O comando de inicialização do Selenoid fica assim:
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 a IU do Selenoid assim que os testes estiverem em execução:
E aqui está um vídeo dele .
Encontramos a causa do erro de inicialização. Ótimo! Vamos continuar.
Acontece que as imagens dos emuladores Selenoid não funcionam sem os serviços do Google Play. Para remediar essa situação, você mesmo deve criar uma imagem do emulador. Os caras do aerokube reuniram tudo o que você precisa para isso: um repositório com fotos e documentação .
selenium
../automate_android.sh
e responda às perguntas. É assim que fica no nosso 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
Quando vi a pergunta Adicionar instantâneo de inicialização rápida do Android?, pensei que fosse um instantâneo do emulador . Mas se você olhar o código , ele chama um script , que é necessário para instalar aplicativos APK. Basicamente, não nos dá nenhum ganho.
Já usamos emuladores de snapshot para acelerar lançamentos de contêineres , mas falaremos sobre isso em outros artigos.
Depois que a imagem for criada, seremos solicitados a enviá-la para o registro. Como ainda não precisamos disso, gentilmente recusaremos a oferta:
Push? >> n
Montamos uma compilação com os serviços do Google Play. Lembre-se de alterar o parâmetro de imagem em browsers.json e reinicie o Selenoid.
Agora vamos tentar refazer os testes:
[INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
E aqui está um vídeo do teste.
O que fizemos:
Outra coisa que gostaria de contar a vocês:
Enquanto isso, na próxima parte, contaremos como escalamos a infraestrutura e fizemos testes no iOS.