Test Automation for a Mobile Application using Appium / Android Emulator / Containers / Katalon

126 views Asked by At

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>&nbsp;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: {
    &quot;desiredCapabilities&quot;: {
        &quot;appActivity&quot;: &quot;.Settings&quot;,
        &quot;appPackage&quot;: &quot;com.android.settings&quot;,
        &quot;newCommandTimeout&quot;: 1800,
        &quot;autoGrantPermissions&quot;: true,
        &quot;platformName&quot;: &quot;Android&quot;,
        &quot;platform&quot;: &quot;ANDROID&quot;,
        &quot;systemPort&quot;: 8221
    },
...

Your recommendations are welcome. Cheers

0

There are 0 answers