Every Appium automation session is given a unique ID when the session is first created. Webdriver HTTP commands after the initial POST to the /session
endpoint all have paths which start with /session/:sessionId
, where :sessionId
is the unique identifier for this session, returned in the initial /session
response.
Since Appium has a one-to-one relationship between sessions and devices, if a session is already running, any new sessions will automate other devices available to the Appium server. A device will not be reused for a session until the session is ended via an HTTP DELETE request sent to /session/:sessionId
.
When it comes to your test script, the only thing which differentiates the client (sometimes called driver
) object from the client for a different test, is the session ID. All the client libraries automatically save the session ID when a sessions is started and include in in the path for all further commands. This is the only state a client object needs to keep, and we can take advantage of this to unlock some useful flexibility.
I, and others, have run into many cases where we want to control an automation session which is already in progress, but cannot share the client object itself. Sometimes I will have a Java test script paused halfway through a test in debugging mode, and want to connect my Javascript REPL to quickly try out some commands in the current app state. I can't pass the Java client object to my Javascript REPL, but I can easily copy and paste the session ID into my REPL and connect the client.
Another use case testers run into is when, for some reason driven by necessity, part of a test run is operated in a different scope. It could be you want to duplicate the client object and pass it to another thread, or maybe you are recovering from an error where the driver object has been destroyed but you need to delete the session on the server still. Maybe you are in a very unfortunate position where your test codebase is split across projects and you need to continue your session in another language entirely.
This is a pattern which should not be used in an average test suite, but I've seen many cases where it's use is very valid.
For most clients this trick is very easy. The clients have a method for getting the current session ID, and another accessible field for setting the session ID. There are examples of how to do this online for most of the clients, but the Java solution is particularly difficult. Years ago it was relatively easy, but recent updates have made it trying.
I'm working on a pull request to the Appium Java client to add a new constructor which make this very easy. I wasn't able to finish in time for this article, but next week the following constructor should be available for all to use:
String sessionId = "d8080676-634d-49fc-8624-fc7b57c5d530";
AppiumDriver driver = new AppiumDriver("http://localhost:4723/wd/hub" , sessionId);
This results in a client object which will send commands to the given session ID, without starting a new session. Notice we don't need desired capabilities, since the session has already been started. If you need to get the desired capabilities which created the session, they are returned by the getAllSessionDetails()
method.
This was much trickier to implement than I bargained for: the Selenium client which the Appium client extends attempts to create a new session as part of the constructor. Since we want to connect to an existing session, we have to skip this initialization. I ended up taking a slightly different approach, but could have saved myself some frustration if I had read this great explanation of the code involved posted by Tarun Lalwani.
We will attempt to introduce these changes in the Selenium client so even more can benefit. Hopefully this saves some headaches.
That's all for this week. Once the pull request is approved and a new version published, this sample code will work.