Join the webinar on 'Enabling Perfect Digital Experiences for Retail Organizations with HeadSpin' on Oct 18
Hello everyone, and welcome back to the Appium Pro intro workshop. In this module, we are going to examine the concept of capabilities and using capabilities to start and stop Appium sessions on platforms and devices that we care about. So, you'll recall capabilities from our Appium overview session, or our session on the Appium architecture, where we discussed how the parameters to the new session command are a JSON object with keys and values.
So that is, again, what capabilities are. Basically, at the end of the day, they're a JSON object, a key value pair that is sent as part of the payload for session initialization. In Java, so when we're using the Appium, Java client, which we are in this workshop, they are a desired capabilities object that's passed to the driver class constructor.
So, when we create an instance of an Appium session, we use a special class for that, which we'll look at. In Appium, it's either Android driver or iOS driver. Those are the class names. And one of the arguments for this constructor is something called a DesiredCapabilities object. So, it's basically just a way of creating a key value pair that Appium needs in Java.
So, there are a few keys, in other words, a few capabilities that are required for every Appium session. For example, Appium needs to know what platform you want to run on and what device you want to use, and it needs these things so that it can pick the right driver to run your session.
It also needs to know a little bit about your app. Are you going to use an app that you've developed? Are you going to use an app that's installed on the device already? Are you going to use the built-in browser? That kind of thing. There are lots and lots of optional capabilities, well over a hundred of them, and they're all listed in the Appium documentation.
We're not going to give a comprehensive overview of capabilities in this workshop. We'll just use a couple of them and you can look at the documentation to see all of the rest of the capabilities you can use to adjust the way that the Appium sessions work.
So, what capabilities are required for each session? First of all, we have the platformName capability. It should be either the string iOS or Android for our purposes in this workshop, basically telling Appium which platform we want to automate. We can also tell Appium which version of that platform that we want to automate.
There is a difference obviously between iOS 12.2 and iOS 13.4 or whatever, so we can tell Appium which version we want to use to make sure that it picks the right level of the SDKs for the platform. We can also tell Appium which device we want to automate.
On Android, this doesn't make a whole lot of difference. Appium will just pick whichever device is connected and we can tell it specifically which device we want to use. If there is more than one device connected, on iOS, the name matters more because it determines which simulator Appium chooses to launch.
Then, finally, we need either the app or the browserName capability. For native apps, we should use the app capability and we should set it to an absolute path to our application file. So, on Android, the APK file, or on iOS, a .app file or a .app.zip file if we've compressed our .app directory.
So, this should be an absolute path on the system where Appium itself lives. So, in our testing, our test devices and our Appium server are located on the same computer, our computer, where we're running things, and our test code is there as well.
So, an absolute path anywhere on our local system will work. But it's important to remember that sometimes you might be running a test against an Appium server, which is located somewhere other than your own computer, in which case the application path needs to reflect a path on the place where the Appium server is running.
If you don't have an application there, but you do have a version of it online, you can also use a URL instead. For web app testing, we don't use the app capability; instead, we use the browserName capability.
All right. Before we look at how to start and stop Appium sessions using capabilities specifically, we should take a little tour of our sample project for this workshop.
So, there's a couple of pieces to highlight here. First of all, we have a build.gradle file. That's because this is a gradle-based Java project and the purpose of this file is mostly to define the dependencies that we need. So, I will go ahead and open up my terminal, and I am in the workshop directory here.
You could be looking at the workshop sample code files in your IDE, like IntelliJ, or you can use whatever program you like. I like to use Vim, so I'm going to go ahead and open up the project here. And I'm going to open up the build.gradle file.
You can see that there's a lot of stuff going on in here. The most important bits are just that we have our dependencies. So, we have our JUnit API that's for using the JUnit test runner. We've got the Appium Java client and we've got some more stuff for JUnit. That's basically the important stuff in the gradle file.
We also have a test task that we can run. We can make sure to show logging while we're running tests in case we want to print anything out from our tests. So, that is build.gradle. We also have the Java package for our tests. That's in the src/test/java/com/appiumpro/intro_workshop directory. So, we can take a little look inside there.
Here's a bunch of Java files that we're going to be running in this workshop. We also have a base class for our tests. That's to avoid repetition in all of our actual test code. So, this is just kind of standard programming pattern in object-oriented languages to have a base class that other classes inherit from and we keep all of the shared functionality in one spot.
So, let's have a look at our base test class. It's called TestBase.java. So, here it is. It's an abstract class because we never implement it or we never instantiated directly. It has an instance of Appium driver as a field and the Appium driver is the parent class for iOS driver and Android driver, which are the actual driver objects that we're going to be using.
And our base class has a set of desired capabilities on it. A couple of the helpful methods that we have on our base class: we have a method that gets the path for a resource, and that's about it. We have some setup and teardown functionality as well. So, this line here is a @BeforeEach annotation, which tells our test runner that we want run this method before every test that we run.
So, now we have called this method setUp, and here is where we actually instantiate our Appium sessions. So, this doesn't take place within the test code itself. This takes place in the base test class here, where if we have set as a capability, a platform name of Android, then we instantiate a new Android driver.
Otherwise, we instantiate a new iOS driver. So, at this point, we're creating the Appium session. When we call new AndroidDriver or new IOSDriver, what's happening under the hood is that the Appium Java client is making that POST request to the /session endpoint at the Appium server address, which of course we have defined here so that our drivers know where to find the server.
So, once we have a driver object set here, we know that we have a session and we can now use this driver object somewhere else in our test. Once our tests are done, we call this tear down method, ensuring that we quit the driver if the driver is currently active.
So, this is how we implement the API for starting and stopping sessions. To start a session, we call a new iOS driver or a new Android driver. And to stop the session, we call driver.quit. So, how do we define our capabilities? That's the other piece of this.
So, because capabilities can be a bit messy and long-winded, I've decided to keep them in their own class here called projects capabilities. So, this is just a class that's designed to hold on to the capabilities. We have a helper function here called localApp() that basically takes the name of an application and finds the actual path to that application inside the app directory.
Remember, we called the download_apps script earlier. It put the applications for us inside the apps directory, and now we can use this localApp function to get an absolute path to them, which is what we want for Appium. So, then, we have two sets of capabilities here; one's called AndroidBase, and one's called IOSBase.
So, let's take a look at AndroidBase. Here, we're creating a new DesiredCapabilities object. We are setting the platformName to Android, setting the deviceName to Android emulator, and setting the automationName, which has a capability that tells Appium specifically which driver we want to use, and we're setting that to UiAutomator2 to tell Appium we want to use the UiAutomator2 driver for this Android test.
Now, further below, you can see we have a method called TheAppAndroid. The test app we're using is called The App. And to define its capabilities, we first use the base capabilities that we've defined above, and then we add the app capability, which is a path to TheApp.apk in our project file.
And we add another capability called appWaitActivity, which we don't really need to talk about here. The iOS system is the same. We have our iOS base capabilities, which we could use to test multiple apps with. And then we have our application-specific capabilities where we add the actual application in our app directory.
So, before we actually run any tests, I think it's a good idea to show you the application that we're actually going to be playing around with. So, I'll show you on iOS. It's an application called TheApp. It's something that I built, and it doesn't do very much at all.
But what it does have is a bunch of controls that we can use for testing Appium. So, for example, we could go to a login prompt and try and enter a username or password. So, that's the application that we're going to be testing in this workshop.
I think that is it for the project tour. So, what we've seen so far is that actually the code for starting and stopping a session is in our base class. It's not going to be in the classes for each test that we want to run. But let's look at running a test, because we can't actually execute our base test class. It's not a test itself.
So, let's do a little bit of hands-on work here, and you can follow along with me. We're going to start and stop an iOS session. So, first of all, we're going to have a look at IOSCapabilitiesTest.java. So, here I am in my project. I'm going to run IOSCapabilitiesTest.java.
So, here's the file. it's actually quite short. This is our test class, IOSCapabilitiesTest, and it extends the TestBase class. So, the only thing we do is, in the constructor for this test class, we set the capabilities to what we need to launch the app on iOS.
So, we've encapsulated all of that in our project capabilities class, so we can just call it here in a nice and clean way. Now, we have our actual test. So, this @Test bit here is an annotation that tells JUnit that this method is a test. Now, we just have an empty method called start session test. That's because this test is not going to do anything.
It is just going to allow an Appium session to start and stop. And there's no code here because the code for starting a session is in the base class and the code for ending a session is in the base class as well. So, this doesn't actually need to do anything here.
So, now we can run this test. I'm going to run it from the command line using gradlew test, and I can actually specify the specific test name that I want using the --tests flag, and I believe this was called IOSCapabilitiesTest.
But before I run this, I need to make sure that my environment is set up and ready to go. So, I already have the iOS simulator up and running. But if I try and run this now on its own, we're going to get some errors. What is the error here? The actual error is called connection refused.
What could this be from? This is actually because we don't have an Appium server running yet. So, if you had left your Appium server running from a previous video, then this probably would have worked. But for me, I had shut down my Appium server also to demonstrate what happens if you try and run a test when you don't have an Appium server up and running.
So, let's fix this and let's run an Appium server. So, I'm running Appium here. Now that it's running, I can try this command again. And once it goes, I can see that the Appium server is now doing a bunch of stuff. So, this is the Appium server log. It's telling us everything that it's trying to do.
This is a great place to come for looking at potential issues, if something goes differently than you expect. And we've stopped receiving output here. So, let's go back to our gradle, and we can see that indeed our build was successful, one task was executed.
So, it looks like the test passed. Now, we weren't watching our simulator, so we didn't see it pass. So, let me run this again, and this time we'll have the simulator up and running. There we go. The app launched and the app shut down, and that's because all we did in this test was call the new driver creation, which starts a session, and we called driver.quit, which ends a session.
Now, if you're running this for the very first time, as I've been talking, you might've been noticing that your simulator is just sitting there and nothing is really happening. Appium continues to spit out logs, but it's taking an awful long time.
That's because the very first time you run an Appium test on a particular simulator, Appium has to compile something called WebDriverAgent. We can actually see a WebDriverAgent app here on my simulator. I've got a couple of different versions of it. You would probably just have one.
And WebDriver agent is the thing that actually lives on the device and facilitates communication between Appium and the XCUITest automation technology. So, the very first time you run an Appium test on a given version of Appium, Appium has to compile a WebDriverAgent so that it can be pushed to the simulator.
And this can take several minutes, so just sit tight. Eventually, I believe that this should work. And subsequent times of running it, you can try and run it again. It'll go very quickly. You can see that this task took less than 10 seconds for me in my particular case.
The next thing we want to do is look at the equivalent for starting an Android session. So, let's do that. This is AndroidCapabilitiesTest.java, and you can see that it's actually almost identical to the AndroidCapabilitiesTest. The only difference is that we have a different set of capabilities.
So, there's not much more to explain here. Let's just try and run it. Now, before we run it, we want to also make sure that we have an Android emulator up and running, and I do; it's sitting here, so we should be able to run this test. And I'll make sure that I've got my Appium server running and it's also running.
Great. So, I can run the gradlew test, but this time I'm going to run AndroidCapabilitiesTest. Double check that Appium is doing something, and it is. So, then, we'll go ahead and load up our emulator and wait for the app to launch. Very well. It launched and shut down too quickly for us to see, but it worked.
And again, the very first time you run this on your own, it might take a bit longer because some of the Appium stuff that runs on an Android device might need to be installed and start up the very first time. Okay. So, that's how we start and stop sessions.
I'm going to show you one more thing, which is if you're using IntelliJ, how you would run these tests as well. I've been using the command line because I think it's universal. It also helps if you're thinking of running Appium from CI at some point, where you won't have an IDE.
But we can use IntelliJ. So, here's the project that I imported in the previous video. If I want, I can go ahead and open up one of these test files. Here's IOSCapabilitiesTest. And we can see that because our build.gradle file defines the JUnit plugin, we actually have a little green button here, which enables us to run this as a test.
So, if I want, I can just click run startSessionTest. And now, IntelliJ is running this for me. And we're doing iOS so... And there we go. It looks like the app is installed and running. So, this is how it works to run from IntelliJ. (I should say that, to make this work, you may need the IntelliJ JUnit plugins.)
Great. So, with that, we have, again, learned how to start and stop sessions on iOS and Android, and even a little bit about how we might start designing a test suite, where we have a base test class, a helper that pulls capability sets for us, and then test files, which themselves are not responsible for anything other than picking the app that they use and performing their actual test logic.
So, in the next module, we'll look at actually how we can start doing things with Appium beyond just launching our app and quitting it again. See you then.