大家好!我的名字是塔拉斯·叶戈罗夫;我是 inDrive 的一名工程师。我将向您展示我们如何建立一个能够每天在 iOS 和 Android 设备上运行超过 5,000 次测试的基础设施。秘诀很简单:我们使用了 Selenoid。
去年,我们的同事进行了一项汽车测试研究,作为研究的一部分,我们参与了一项调查。
我们对调查结果感到满意,因此我们决定写一篇文章与您分享我们的经验并获得一些建议作为回报。我们认为将材料分为两部分是个好主意:第一部分侧重于 Android,第二部分侧重于 iOS。
让我们从安卓开始。
Selenoid 是一种流行的工具,用于在 Docker 容器中运行和管理浏览器和 Android 模拟器。您可以在文档中阅读更多相关信息。
要编写 Appium 测试,我们使用:
browsers.json
配置文件:
{ "android": { "default": "10.0", "versions": { "10.0": { "image": "browsers/android:10.0", "port": "4444", "path": "/wd/hub" } } } }
模拟器图像在image
中指定。 aerokube的小伙伴已经准备好了 Android 模拟器的现成镜像。您可以在此处或此处查看它们。这些图像在任何方面都没有区别。
我们以图片browsers/android:10.0
为例。必须预先下载图像: docker pull browsers/android:10.0
,否则测试将无法运行:
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
您可以通过浏览器中的链接http://localhost:4444检查 Selenoid 是否正常工作:
You are using Selenoid 1.10.7!
在驱动程序的 Appium 测试中指定 Selenoid 地址:
... val driver = AndroidDriver(URL("http://localhost:4444/wd/hub"), capabilities) ...
... capabilities.setCapability("appium:app", "https://storage.example.com/builds/app.apk") ...
如果无法提供链接,您可以指定构建路径:**
... capabilities.setCapability("appium:app", "/builds/app.apk") ...
其中/builds/app.apk
是运行模拟器的容器内的路径。要使此选项正常工作,请务必在browsers.json
中指定volumes
:
{ "android": { "default": "10.0", "versions": { "10.0": { ... "volumes": [ "/home/username/app.apk:/builds/app.apk:ro" ] ... } } } }
其中/home/username/app.apk
是主机平台上构建的路径。
就是这样,我们几乎已经设置了 Selenoid,现在我们可以尝试运行测试了:
./mvnw test
但是,不幸的是,测试将无法运行。让我们调查一下,看看出了什么问题。
启动失败后要做的第一件事是检查 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]
我们看到状态是 SERVICE_STARTUP_FAILED。转到文档并查看状态值:
SERVICE_STARTUP_FAILED - Failed to start Docker container or driver binary
该错误并没有告诉您太多信息,还需要更多信息。最好看看容器日志。让我们通过启用日志记录来做到这一点:
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
我们还在功能部分启用日志:
... capabilities.setCapability("enableLog", true) ...
使用浏览器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.
容器日志在这里也没什么用,因为您看不到 Appium 日志。现在让我们尝试启用它们。为此,让我们看一下脚本入口 point.sh :
... if [ -z "$VERBOSE" ]; then APPIUM_ARGS="$APPIUM_ARGS --log-level error" else EMULATOR_ARGS="$EMULATOR_ARGS -verbose" fi ...
要启用 Appium 日志,必须将参数VERBOSE=true
和APPIUM_ARGS=--log-level debug
传递给容器:
{ "android": { "default": "10.0", "versions": { "10.0": { ... "env": [ "VERBOSE=true", "APPIUM_ARGS=--log-level debug" ] ... } } } }
要使Appium能够调试日志,需要传递VERBOSE;在这种情况下,模拟器日志打开并开始填充“ether””但我们为未来的图像修复了它 =)
现在传递给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 ...
从日志中可以看出,Appium 找不到我们的元素。让我们看看模拟器屏幕上发生了什么。为此,我们必须运行 Selenoid UI:
docker run -d \ --name selenoid-ui \ -p 8080:8080 \ --link selenoid:selenoid \ aerokube/selenoid-ui:1.10.4 \ --selenoid-uri "http://selenoid:4444"
转到http://0.0.0.0:8080并打开 Selenoid UI:
确保在测试中启用 VNC 和视频录制:
... capabilities.setCapability("enableVNC", true) capabilities.setCapability("enableVideo", true) ...
Selenoid 启动命令最终看起来像这样:
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
测试启动并运行后打开 Selenoid UI:
这是它的视频。
我们找到了启动错误的原因。伟大的!让我们继续。
事实证明,如果没有 Google Play 服务,Selenoid 模拟器的图像将无法工作。要补救这种情况,您必须自己构建模拟器映像。来自 aerokube 的人已经组装了您为此所需的一切:一个包含照片和文档的存储库。
selenium
文件夹。./automate_android.sh
并回答问题。这就是我们案例中的样子:
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
当我看到问题 Add Android quick boot snapshot? 时,我以为是模拟器快照。但是如果您查看代码,它会调用安装 APK 应用程序所需的脚本。基本上,它不会给我们带来任何收益。
我们已经使用快照模拟器来加速容器启动,但我们将在其他文章中讨论。
构建镜像后,系统将提示我们将其推送到注册表中。由于我们还不需要它,我们将谢绝此提议:
Push? >> n
我们已将构建与 Google Play 服务放在一起。请记住更改 browsers.json 中的图像参数,然后重新启动 Selenoid。
现在让我们尝试重新运行测试:
[INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
我们所做的:
还有一件事我想告诉你:
同时,在下一部分中,我们将告诉您我们如何扩展基础架构并在 iOS 上运行测试。