I spent quite some time on internet looking for a satisfactory solution I didn't find up to now and I guess it could interest several people around.
I am doing Test Automation for an application for mobile (for the moment Android, but iOS also in the future). To be independent from my underlying OS, I would prefer to use containers to deploy the application.
Would you have recommendations how to proceed with it?
Here is one of my tentative with docker compose:
version: "3.9"
services:
selenium_hub:
image: selenium/hub:3.14.0-curium
networks:
my_network:
aliases:
- selenium_hub
ports:
- 4444:4444
nexus_emulator:
image: budtmo/docker-android
privileged: true
scale: 1
depends_on:
- selenium_hub
networks:
my_network:
aliases:
- nexus_emulator
ports:
- 6080:6080
- 4723:4723
- 5554:5554
- 5555:5555
volumes:
- /var/lib/docker/volumes/appium-tests-e2e/src/test/resources/apps:/root/tmp/
- /var/lib/docker/volumes/video-nexus_7.1.1:/tmp/video
environment:
- DEVICE=Nexus 5
- CONNECT_TO_GRID=true
- APPIUM=true
- SELENIUM_HOST=selenium_hub
- AUTO_RECORD=true
networks:
my_network:
driver: bridge
ipam:
config:
- subnet: 172.16.0.0/24
gateway: 172.16.0.1
To be complete, on a Windows 11, the start up fails. On an Ubuntu machine, it starts correctly.
When I call on the 4444 port, the hub process replies:
user@machine:/home/xxxx# curl localhost:4444
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="/assets/displayhelpservlet.css" media="all"/>
<link href="/assets/favicon.ico" rel="icon" type="image/x-icon" />
<script src="/assets/jquery-3.1.1.min.js" type="text/javascript"></script>
<script src="/assets/displayhelpservlet.js" type="text/javascript"></script>
<script type="text/javascript">
var json = Object.freeze('{"consoleLink": "\u002fgrid\u002fconsole","type": "Grid Hub","class": "org.openqa.grid.web.servlet.DisplayHelpServlet$DisplayHelpServletConfig","version": "3.14.0"}');
</script>
</head>
<body>
<div id="content">
<div id="help-heading">
<h1><span id="logo"></span></h1>
<h2>Selenium <span class="se-type"></span> v.<span class="se-version"></span></h2>
</div>
<div id="content-body">
<p>
Whoops! The URL specified routes to this help page.
</p>
<p>
For more information about Selenium <span class="se-type"></span> please see the
<a class="se-docs">docs</a> and/or visit the <a class="se-wiki">wiki</a>.
<span id="console-item">
Or perhaps you are looking for the Selenium <span class="se-type"></span> <a class="se-console">console</a>.
</span>
</p>
<p>
Happy Testing!
</p>
</div>
<div>
<footer id="help-footer">
Selenium is made possible through the efforts of our open source community, contributions from
these <a href="https://github.com/SeleniumHQ/selenium/blob/master/AUTHORS">people</a>, and our
<a href="http://www.seleniumhq.org/sponsors/">sponsors</a>.
</footer>
</div>
</div>
</body>
</html>
user@machine:/home/xxxx# curl localhost:4444/wd/hub
{
"sessionId": null,
"status": 13,
"value": {
"class": "java.lang.NullPointerException",
"error": "unknown error",
"message": null,
"stackTrace": [
{
"className": "org.openqa.grid.internal.ExternalSessionKey",
"fileName": "ExternalSessionKey.java",
"lineNumber": 79,
"methodName": "fromWebDriverRequest"
},
{
"className": "org.openqa.grid.web.servlet.handler.WebDriverRequest",
"fileName": "WebDriverRequest.java",
"lineNumber": 58,
"methodName": "extractSession"
},
{
"className": "org.openqa.grid.web.servlet.handler.RequestHandler",
"fileName": "RequestHandler.java",
"lineNumber": 240,
"methodName": "getSession"
},
{
"className": "org.openqa.grid.web.servlet.handler.RequestHandler",
"fileName": "RequestHandler.java",
"lineNumber": 123,
"methodName": "process"
},
{
"className": "org.openqa.grid.web.servlet.DriverServlet",
"fileName": "DriverServlet.java",
"lineNumber": 85,
"methodName": "process"
},
{
"className": "org.openqa.grid.web.servlet.DriverServlet",
"fileName": "DriverServlet.java",
"lineNumber": 63,
"methodName": "doGet"
},
{
"className": "javax.servlet.http.HttpServlet",
"fileName": "HttpServlet.java",
"lineNumber": 687,
"methodName": "service"
},
{
"className": "javax.servlet.http.HttpServlet",
"fileName": "HttpServlet.java",
"lineNumber": 790,
"methodName": "service"
},
{
"className": "org.seleniumhq.jetty9.servlet.ServletHolder",
"fileName": "ServletHolder.java",
"lineNumber": 860,
"methodName": "handle"
},
{
"className": "org.seleniumhq.jetty9.servlet.ServletHandler",
"fileName": "ServletHandler.java",
"lineNumber": 535,
"methodName": "doHandle"
},
{
"className": "org.seleniumhq.jetty9.server.handler.ScopedHandler",
"fileName": "ScopedHandler.java",
"lineNumber": 143,
"methodName": "handle"
},
{
"className": "org.seleniumhq.jetty9.security.SecurityHandler",
"fileName": "SecurityHandler.java",
"lineNumber": 548,
"methodName": "handle"
},
{
"className": "org.seleniumhq.jetty9.server.handler.HandlerWrapper",
"fileName": "HandlerWrapper.java",
"lineNumber": 132,
"methodName": "handle"
},
{
"className": "org.seleniumhq.jetty9.server.handler.ScopedHandler",
"fileName": "ScopedHandler.java",
"lineNumber": 190,
"methodName": "nextHandle"
},
{
"className": "org.seleniumhq.jetty9.server.session.SessionHandler",
"fileName": "SessionHandler.java",
"lineNumber": 1595,
"methodName": "doHandle"
},
{
"className": "org.seleniumhq.jetty9.server.handler.ScopedHandler",
"fileName": "ScopedHandler.java",
"lineNumber": 188,
"methodName": "nextHandle"
},
{
"className": "org.seleniumhq.jetty9.server.handler.ContextHandler",
"fileName": "ContextHandler.java",
"lineNumber": 1253,
"methodName": "doHandle"
},
{
"className": "org.seleniumhq.jetty9.server.handler.ScopedHandler",
"fileName": "ScopedHandler.java",
"lineNumber": 168,
"methodName": "nextScope"
},
{
"className": "org.seleniumhq.jetty9.servlet.ServletHandler",
"fileName": "ServletHandler.java",
"lineNumber": 473,
"methodName": "doScope"
},
{
"className": "org.seleniumhq.jetty9.server.session.SessionHandler",
"fileName": "SessionHandler.java",
"lineNumber": 1564,
"methodName": "doScope"
},
{
"className": "org.seleniumhq.jetty9.server.handler.ScopedHandler",
"fileName": "ScopedHandler.java",
"lineNumber": 166,
"methodName": "nextScope"
},
{
"className": "org.seleniumhq.jetty9.server.handler.ContextHandler",
"fileName": "ContextHandler.java",
"lineNumber": 1155,
"methodName": "doScope"
},
{
"className": "org.seleniumhq.jetty9.server.handler.ScopedHandler",
"fileName": "ScopedHandler.java",
"lineNumber": 141,
"methodName": "handle"
},
{
"className": "org.seleniumhq.jetty9.server.handler.HandlerWrapper",
"fileName": "HandlerWrapper.java",
"lineNumber": 132,
"methodName": "handle"
},
{
"className": "org.seleniumhq.jetty9.server.Server",
"fileName": "Server.java",
"lineNumber": 530,
"methodName": "handle"
},
{
"className": "org.seleniumhq.jetty9.server.HttpChannel",
"fileName": "HttpChannel.java",
"lineNumber": 347,
"methodName": "handle"
},
{
"className": "org.seleniumhq.jetty9.server.HttpConnection",
"fileName": "HttpConnection.java",
"lineNumber": 256,
"methodName": "onFillable"
},
{
"className": "org.seleniumhq.jetty9.io.AbstractConnection$ReadCallback",
"fileName": "AbstractConnection.java",
"lineNumber": 279,
"methodName": "succeeded"
},
{
"className": "org.seleniumhq.jetty9.io.FillInterest",
"fileName": "FillInterest.java",
"lineNumber": 102,
"methodName": "fillable"
},
{
"className": "org.seleniumhq.jetty9.io.ChannelEndPoint$2",
"fileName": "ChannelEndPoint.java",
"lineNumber": 124,
"methodName": "run"
},
{
"className": "org.seleniumhq.jetty9.util.thread.strategy.EatWhatYouKill",
"fileName": "EatWhatYouKill.java",
"lineNumber": 247,
"methodName": "doProduce"
},
{
"className": "org.seleniumhq.jetty9.util.thread.strategy.EatWhatYouKill",
"fileName": "EatWhatYouKill.java",
"lineNumber": 140,
"methodName": "produce"
},
{
"className": "org.seleniumhq.jetty9.util.thread.strategy.EatWhatYouKill",
"fileName": "EatWhatYouKill.java",
"lineNumber": 131,
"methodName": "run"
},
{
"className": "org.seleniumhq.jetty9.util.thread.ReservedThreadExecutor$ReservedThread",
"fileName": "ReservedThreadExecutor.java",
"lineNumber": 382,
"methodName": "run"
},
{
"className": "org.seleniumhq.jetty9.util.thread.QueuedThreadPool",
"fileName": "QueuedThreadPool.java",
"lineNumber": 708,
"methodName": "runJob"
},
{
"className": "org.seleniumhq.jetty9.util.thread.QueuedThreadPool$2",
"fileName": "QueuedThreadPool.java",
"lineNumber": 626,
"methodName": "run"
},
{
"className": "java.lang.Thread",
"fileName": "Thread.java",
"lineNumber": 748,
"methodName": "run"
}
],
"stacktrace": "java.lang.NullPointerException\n\tat org.openqa.grid.internal.ExternalSessionKey.fromWebDriverRequest(ExternalSessionKey.java:79)\n\tat org.openqa.grid.web.servlet.handler.WebDriverRequest.extractSession(WebDriverRequest.java:58)\n\tat org.openqa.grid.web.servlet.handler.RequestHandler.getSession(RequestHandler.java:240)\n\tat org.openqa.grid.web.servlet.handler.RequestHandler.process(RequestHandler.java:123)\n\tat org.openqa.grid.web.servlet.DriverServlet.process(DriverServlet.java:85)\n\tat org.openqa.grid.web.servlet.DriverServlet.doGet(DriverServlet.java:63)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:687)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n\tat org.seleniumhq.jetty9.servlet.ServletHolder.handle(ServletHolder.java:860)\n\tat org.seleniumhq.jetty9.servlet.ServletHandler.doHandle(ServletHandler.java:535)\n\tat org.seleniumhq.jetty9.server.handler.ScopedHandler.handle(ScopedHandler.java:143)\n\tat org.seleniumhq.jetty9.security.SecurityHandler.handle(SecurityHandler.java:548)\n\tat org.seleniumhq.jetty9.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)\n\tat org.seleniumhq.jetty9.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:190)\n\tat org.seleniumhq.jetty9.server.session.SessionHandler.doHandle(SessionHandler.java:1595)\n\tat org.seleniumhq.jetty9.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188)\n\tat org.seleniumhq.jetty9.server.handler.ContextHandler.doHandle(ContextHandler.java:1253)\n\tat org.seleniumhq.jetty9.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168)\n\tat org.seleniumhq.jetty9.servlet.ServletHandler.doScope(ServletHandler.java:473)\n\tat org.seleniumhq.jetty9.server.session.SessionHandler.doScope(SessionHandler.java:1564)\n\tat org.seleniumhq.jetty9.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166)\n\tat org.seleniumhq.jetty9.server.handler.ContextHandler.doScope(ContextHandler.java:1155)\n\tat org.seleniumhq.jetty9.server.handler.ScopedHandler.handle(ScopedHandler.java:141)\n\tat org.seleniumhq.jetty9.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)\n\tat org.seleniumhq.jetty9.server.Server.handle(Server.java:530)\n\tat org.seleniumhq.jetty9.server.HttpChannel.handle(HttpChannel.java:347)\n\tat org.seleniumhq.jetty9.server.HttpConnection.onFillable(HttpConnection.java:256)\n\tat org.seleniumhq.jetty9.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279)\n\tat org.seleniumhq.jetty9.io.FillInterest.fillable(FillInterest.java:102)\n\tat org.seleniumhq.jetty9.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124)\n\tat org.seleniumhq.jetty9.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:247)\n\tat org.seleniumhq.jetty9.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:140)\n\tat org.seleniumhq.jetty9.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131)\n\tat org.seleniumhq.jetty9.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:382)\n\tat org.seleniumhq.jetty9.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:708)\n\tat org.seleniumhq.jetty9.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:626)\n\tat java.lang.Thread.run(Thread.java:748)\n"
}
}
Here's a better-formatted copy of the stack trace:
java.lang.NullPointerException
at org.openqa.grid.internal.ExternalSessionKey.fromWebDriverRequest(ExternalSessionKey.java:79)
at org.openqa.grid.web.servlet.handler.WebDriverRequest.extractSession(WebDriverRequest.java:58)
at org.openqa.grid.web.servlet.handler.RequestHandler.getSession(RequestHandler.java:240)
at org.openqa.grid.web.servlet.handler.RequestHandler.process(RequestHandler.java:123)
at org.openqa.grid.web.servlet.DriverServlet.process(DriverServlet.java:85)
at org.openqa.grid.web.servlet.DriverServlet.doGet(DriverServlet.java:63)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.seleniumhq.jetty9.servlet.ServletHolder.handle(ServletHolder.java:860)
at org.seleniumhq.jetty9.servlet.ServletHandler.doHandle(ServletHandler.java:535)
at org.seleniumhq.jetty9.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.seleniumhq.jetty9.security.SecurityHandler.handle(SecurityHandler.java:548)
at org.seleniumhq.jetty9.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.seleniumhq.jetty9.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:190)
at org.seleniumhq.jetty9.server.session.SessionHandler.doHandle(SessionHandler.java:1595)
at org.seleniumhq.jetty9.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188)
at org.seleniumhq.jetty9.server.handler.ContextHandler.doHandle(ContextHandler.java:1253)
at org.seleniumhq.jetty9.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168)
at org.seleniumhq.jetty9.servlet.ServletHandler.doScope(ServletHandler.java:473)
at org.seleniumhq.jetty9.server.session.SessionHandler.doScope(SessionHandler.java:1564)
at org.seleniumhq.jetty9.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166)
at org.seleniumhq.jetty9.server.handler.ContextHandler.doScope(ContextHandler.java:1155)
at org.seleniumhq.jetty9.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.seleniumhq.jetty9.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.seleniumhq.jetty9.server.Server.handle(Server.java:530)
at org.seleniumhq.jetty9.server.HttpChannel.handle(HttpChannel.java:347)
at org.seleniumhq.jetty9.server.HttpConnection.onFillable(HttpConnection.java:256)
at org.seleniumhq.jetty9.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279)
at org.seleniumhq.jetty9.io.FillInterest.fillable(FillInterest.java:102)
at org.seleniumhq.jetty9.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124)
at org.seleniumhq.jetty9.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:247)
at org.seleniumhq.jetty9.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:140)
at org.seleniumhq.jetty9.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131)
at org.seleniumhq.jetty9.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:382)
at org.seleniumhq.jetty9.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:708)
at org.seleniumhq.jetty9.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:626)
at java.lang.Thread.run(Thread.java:748)
Stack trace made readable by https://TidyTrace.com
The Android emulator looks up also: user@machine:/home/xxxx# curl localhost:4723 {"status":9,"value":{"error":"unknown command","message":"The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource","stacktrace":""}}
My test tool is a Katalon Engine. I have difficulties to make it work with Android this way, using a remote connection.
My remote server URL: http://10.1.100.45:4444/wd/hub
Remote server type: Appium
Appium driver: Android Driver
2023-12-08 17:32:44.755 ERROR c.k.katalon.core.main.TestCaseExecutor - ❌ Test Cases/Toto FAILED.
Reason:
com.kms.katalon.core.exception.StepFailedException: Unable to start app with application ID: 'my_emulator' (Root cause: org.openqa.selenium.SessionNotCreatedException: Unable to create a new remote session. Please check the server log for more details. Original error: Unable to parse remote response: <html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 500 Server Error</title>
</head>
<body><h2>HTTP ERROR 500</h2>
<p>Problem accessing /wd/hub/session. Reason:
<pre> Server Error</pre></p><h3>Caused by:</h3><pre>java.io.IOException: org.openqa.grid.common.exception.GridException: Cannot extract a capabilities from the request: {
"desiredCapabilities": {
"appActivity": ".Settings",
"appPackage": "com.android.settings",
"newCommandTimeout": 1800,
"autoGrantPermissions": true,
"platformName": "Android",
"platform": "ANDROID",
"systemPort": 8221
},
...
Your recommendations are welcome. Cheers