Many novice automation engineers wonder how to write their first autotest for an Android app. In this article, I would like to answer this question.
A great deal has been written on the subject. There are detailed explanations of how the PageObject
Pattern is used in test writing and much more. But our task is to write our first simple test in Java for the authorization screen. Let's dive in and look at how this problem can be solved!
Introduction to Mobile App Testing
For the time being, when testing almost any mobile app, you will have to deal with two operating systems: iOS and Android. This article will look into how to deal with Android apps.
To choose a suitable approach to mobile app testing, we must be clear on the type of app involved. Currently, there are three types of apps:
-
Native apps - These are apps that are installed and run directly on a smartphone. Native apps don't involve working or interacting with a browser and the Internet;
-
Web-based apps - These apps are adapted for desktop and mobile versions;
-
Hybrid apps - Native apps with built-in Capability to open web pages.
Having decided on the type of mobile app to be used, we can safely move on to developing the tests. Tests are commonly understood to consist of a sequence of steps that, once completed, bring the product to the desired state. During the testing process, the entire set of pre-determined tests is executed.
Software quality assurance testing generally involves two types: manual and automated. In manual testing, tests are performed by a tester; this position is typically referred to as Manual QA Engineer. The tester is the link between the tests and the app. It looks something like this:
It is inaccurate to say that a tester works directly with the app. While running the tests, they press buttons or enter data into fields. In other words, they interact with the app interface. Also, the tester works with both the service API and the network interface, CLI, GUI, etc. While APIs are used by client programs to interact with the server, the GUI is used by humans.
Manual testing is undoubtedly good, but let's look at replacing manual labor with automated processes. Instead of a tester, software clients interact with the app. Who is going to run the tests? Sure, that's correct! The testing framework will be responsible for running the tests. It runs tests, shows the results, stores the history of test runs, and performs many more functions.
To work with the GUI interface, we need special adapters, also known as drivers (yes, yes, many of you will remember that to play sound on the computer, or, let's say, to make sure that the video card transmits video display signals, some drivers have to be installed). For example, our test includes a step involving a tap on the Search button. The driver receives the command to tap the Search button and then passes that command on to the desired interface in our app. This ensures that the Search button is pressed and the test is passed successfully. However, if there is no button to push or, for some reason, it's not packed, there will be an error, and the test will crash.
More than the driver's functionality is often needed to solve some non-standard tasks, or the logging level needs to be increased. Then, various wrappers are used to take care of such problems. For example, Kakao or Kaspresso. These wrappers can vary widely, depending on the task involved. Some are nothing but syntactic sugar (syntactic features that don't affect software behavior but make the language more human-friendly). In contrast, others cover a relatively broad range of complicated tasks.
Installing Tools
Selecting a Language
Various languages can be used to automate testing processes, for example, Java, Python, Kotlin, Swift, JavaScript, and many more. It all depends on your preferences and the goals you pursue. We will use Java in our example. You need to download and install it from the official website to do this.
Android Studio
An Android app can be run on a physical device or emulator. Mostly, autotests are run on emulators. You need to download and install Android Studio from the official website to do this.
Appium
The following tool we can use is Appium (an open-source tool that helps automate Android and iOS apps). There are two options for running Appium: UI or console-based. To install and run the console version, we additionally need NodeJs. Note that the UI version can be downloaded from the official website and will be ready immediately.
Appium Inspector
We need a GUI inspector to find locators (a locator is a path to an item in the interface that an automated test (autotest) can use to find it).
Appium Inspector is simply an Appium client with a user interface that displays the screen of the app being tested, where we can view the paths to the items.
IntelliJ IDEA
We will be developing tests in IntelliJ IDEA, also available for download from the official website, where I suggest you choose the Community Edition.
The App
We will develop tests for an app you can download and build on your own in Android Studio. It is available at this link. Alternatively, you can download the app.
Setting Up and Installing the Android Studio Emulator
Selecting an operating system for automated Android testing is not especially critical. You can use Windows, Linux, or MacOS.
To start testing, we will need the Android SDK and Android Studio installed. We will have to create an emulator. We will only dwell a short time on this aspect here because the guide to configuring an emulator is well set out and provides essential details in the official documentation.
Setting Up Appium
Once we have set up Android Studio, it's time for us to set up Appium. Open the Appium UI we installed earlier on. We have to enter the address 0.0.0.0 in the Host field and 4723 in the Port field.
Next, go to Edit Configurations and specify the ANDROID_HOME and JAVA_HOME paths, as shown in the screenshot below, and then click Save and Restart:
Note that your paths may differ from those shown in the screenshot, at least by offering different users.
Next, go to the Advanced tab in Appium and check to make sure that your settings in the Server Address and Server Port fields are the same as those in the screenshot below:
After setting up all the required fields, click the Start Server button and make sure that Appium Server is running:
We don't need to do anything with it now; let's leave the Appium server running.
Setting Up the Appium Inspector and Locator Search
For our tests to understand which interface items we will interact with, we need to know how these elements can be accessed. To do this, run the Appium Inspector and specify Remote Host: 0.0.0.0, as well as Remote Path /wd/hub, as shown in the screenshot below:
Now let's move on to Capability. Capability is a set of parameters and values to start an Appium session. The set's information describes the "features" you require when running the emulator — for example, a certain mobile operating system or a certain version of the device. Capability is represented as key-value pairs; the values can be of any valid JSON type.
{
"appium:platformName": "Android,"
"appium:deviceName": "autoschool10",
"appium:app": "/Users/s.yakovlev/Desktop/login.apk"
}
In the variable appium:platformName
, we specify the Android platform, whereas in the variable appium:deviceName
, we specify the name of the Android emulator we have created, in our case this is "autoschool10
," as well as the path to the .apk file.
Let's save the specified capabilities and name this preset as loginAndroidApp
. The name doesn't matter, but it should make sense and be understandable to you.
The saved capabilities are accessible in the Saved Capability tab:
Now we only have to open Android Studio, run the emulator we created earlier, and click Start Session in the Appium Inspector.
This launches the Appium Inspector, displaying a screenshot on the left side and an item tree on the right.
We will write three tests:
- The first one will check whether the Login Name is displayed in the tab bar,
- The second test will check the favorable authorization scenario,
- And the third test will check the adverse authorization scenario.
To do this, we need to know the paths to these items. Let's start with the tab bar. Click on the Login name, and you will see that the properties of this item are displayed on the right side:
Let's write out the id of this item which we will need later: com.example.login:id/toolbar
Do the same with the Email and Password fields. The ID for the Email field will be com.example.login:id/username
and com.example.login:id/password
for the Password field.
Also, let's find out the id for the SIGN IN OR REGISTER button:
Its id will be com.example.login:id/login
.
If the user enters the following valid email data: admin@admin.ru and the password: 1234, the text below will be displayed: Welcome ! user. Let's join the data in the fields and check to make sure:
But, as you can see, nothing has changed in the inspector, whereas we need to find out the id of this text field. That's okay; let's just hit the Refresh button.
And take a new screenshot. We have a new screenshot pulled up, and we can now find out the id of this text field com.example.login:id/succestext
:
If an invalid password or email is entered into the emulator, an authorization error message will be displayed along with the appropriate text.
You may have already noticed that this error message has the same id, but if you look closely enough, you will see that the error text in the text field is entirely different. That's precisely what we need.
Now that we have written out all the ids of the items, let's get down to writing the tests.
Configuring the Libraries
We will be developing tests in IntelliJ IDEA. To do this, start IDEA and create a directory, for example, on the desktop and name it androidAutomation.
After we have created a default project, let's connect our libraries to it:
For your convenience, we have gathered all the libraries in one place.
Open the project we have already created and create the libs directory:
Next, place the downloaded libraries in our directory:
Now let's go into our project, under Project Structure, and add our libraries from the libs folder:
We have added all the libraries we need for our work-related purposes, and now we have to create a directory where our app will be located. Create the apps directory and drag our app into it:
Developing the Tests
Now let's go to the leading - Java directory and create our first class, called LoginTest
, where we will be writing our tests:
To successfully run the test, we need the setUp()
, tearDown()
methods, and the test itself. Let's write the setUp()
method, in which we will write all the required parameters for the test run, specifically:
platformName
: The OS version of our emulator, which is Android in our case; deviceName
: The name of the emulator which is displayed in Android Studio, autoschool10
being the name in our case (the name of the emulator can be anything, but it’s best to make sure that it’s not written in Cyrillic); platformVersion
: The version of our OS - Android 10; automationName
: We automate with Appium; appPackage
: The package of our test app com.example.login
; appActivity
: The activity, i.e., the screen that will be launched. There is only one screen in our app, and this activity is called .ui.login.LoginActivity
; app
: Here, we specify the full path to our app, in this case, it is "/Users/{user_name}/Desktop/androidAutomation/apks/login.apk.
"
We use this line to connect the Android driver (Appium Android Driver is the test automation tool for Android devices. The driver is part of the Appium mobile test automation tool):
driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
Android.uiautomator.testrunner.UiAutomatorTestCase
, placed on the Android device, opens a SocketServer
on port 4723. This server receives commands, converts them into appropriate Android UI Automator commands, and runs them on the devices.
The complete code listing is shown below:
public void setUp() throws Exception
{
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("platformName", "Android");
capabilities.setCapability("deviceName", "autoschool10");
capabilities.setCapability("platformVersion", "10");
capabilities.setCapability("automationName", "Appium");
capabilities.setCapability("appPackage", "com.example.login");
capabilities.setCapability("appActivity", ".ui.login.LoginActivity");
capabilities.setCapability("app",
"/Users/s.yakovlev/Desktop/androidAutomation/apks/login.apk");
driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
}
Let's terminate the driver in the tearDown()
method:
public void tearDown()
{
driver.quit();
}
Next, let's add the annotation @before
— is executed before the test, @After
— is executed after the test, @Test
— is added for each test.
All we now have to do is write the tests themselves. We will write just three tests. The first will check to ascertain whether the Login screen is open. The second test will check the authorization (with valid data), and the third logical test will review the approval with invalid data.
The Tab Bar Test
Let's write a positive screen title test. We will declare a variable called screenTitle
in which we will save our element. We will search for this element by id
to write findElementById
. To make sure that the element (item) really exists, let's call the isDisplayed()
method.
@Test
public void checkTitleScreen()
{
WebElement screenTitle = driver.findElementById("com.example.login:id/toolbar");
screenTitle.isDisplayed();
}
Authorization Test for Valid Data
Let's move on to writing an authorization test with valid data. The sequence of steps will be the same. We set the variables emailInput
and passInput
, in which we save our previously found elements.
To populate a field with data, it must first be clicked on. There is the click()
method to do this, whereas the sendKeys()
method will be used to enter the transferred data we want to insert in our field.
@Test
public void validRegistrationTest()
{
WebElement emailInput = driver.findElementById("com.example.login:id/username");
emailInput.isDisplayed();
emailInput.click();
emailInput.sendKeys("admin@admin.ru");
WebElement passInput = driver.findElementById("com.example.login:id/password");
passInput.isDisplayed();
passInput.click();
passInput.sendKeys("1234");
WebElement signInButton = driver.findElementById("com.example.login:id/login");
signInButton.isDisplayed();
signInButton.click();
WebElement successAuthText = driver.findElementByXPath
("//*[contains(@text, 'Welcome ! user')]");
successAuthText.isDisplayed();
}
Note that we are looking for items by id
, whereas for the text "Welcome ! user
", we are doing a search by XPath. This is the second option for finding locators. XPath is used to navigate through elements and attributes. The following construction findElementByXPath("//[contains@text, 'Welcome ! user')]");
is used to search text anywhere on the screen. The contains()
method doesn't need all the readers to identify an item correctly; just part of the text is enough, but this part of the text must be unique.
Authorization Test for Invalid Data
Similarly, let's write an authorization test for the situation when the data is invalid:
@Test
public void validRegistrationTest()
{
WebElement emailInput = driver.findElementById("com.example.login:id/username");
emailInput.isDisplayed();
emailInput.click();
emailInput.sendKeys("user@user.ru");
WebElement passInput = driver.findElementById("com.example.login:id/password");
passInput.isDisplayed();
passInput.click();
passInput.sendKeys("1111");
WebElement signInButton = driver.findElementById("com.example.login:id/login");
signInButton.isDisplayed();
signInButton.click();
WebElement successAuthText = driver.findElementByXPath
("//*[contains(@text, 'Login failed')]");
successAuthText.isDisplayed();
}
Our tests are now written and ready to be run. To run all the tests, click on the green triangle next to the LoginTest
class:
Important: Before the launch, ensure that you have the Appium Server GUI running and that the emulator is also running.
Our tests are running, and we can observe the emulator to see how the process is going - the fields are populated with data; if the test is successful, it's marked with a green checkmark.
Once the run is completed, we can check out the default report. It could be more informative, but it still shows that our tests are successful.
Conclusion
So we have looked at how to write tests in Java for an Android mobile app. Of course, these tests are far from perfect. Projects usually involve writing entire frameworks and using the PageObject
Pattern. But our task here was to write our first test, and you can get started without using the PageObject
Pattern.
History
- 12th April, 2023: Initial version