Testing plays a key role during the development of an app, and if done properly, can save a lot of time. Manually repeating tests is costly and time consuming, that’s why test automation frameworks were created. Appium is one of the more popular test automation frameworks in the mobile world. It’s an open source framework for use with native, hybrid and mobile web apps and is very well documented.Appium is a powerful framework that enables you to write tests independently from your app. Tests can be written in Java, Objective-C, JavaScript (Node), PHP, Python, Ruby, C#, Clojure, or Perl with the Selenium WebDriver API and language-specific client libraries.
How to use Appium
Appium comes with three tools:
Appium server
Appium server is a HTTP REST API which is responsible for detecting and interacting with test devices. Appium supports both emulators and real devices. Nowadays Appium it’s integrated into Gitlab CI, AWS, Bitrise and other cloud applications. Using the API you will also be able to completely write your own integration if needed.Appium
Appium desktop
Desktop is an app for Mac, Windows, and Linux which gives you the power of the Appium automation server wrapped in a beautiful and flexible UI.
Appium Inspector
Appium inspector is a tool that can be used to inspect elements of a mobile app. When writing mobile tests you need to identify UI elements by certain ids and with Appium inspector you can find those ids without having the source code.
Once the Appium server is running you can interact with its API using one of the previously mentioned programming languages or using Appium inspector.
Hier is de Engelse vertaling van de alinea's, met behoud van de strakke lay-out en opbouw die perfect aansluit bij de richtlijnen uit image_5e4a3e.png:
Appium Desired Capabilities: What are they?
Before you start testing, you need to understand what Appium’s Desired Capabilities are. This is essentially a JSON document sent by the Appium client to the server as soon as a new automation session is requested. This document contains crucial data about how your session should run. Think of things like the device you want to use, which app needs to be launched, whether the app should be uninstalled beforehand, and which browser you want to test. Appium supports virtually all emulators, physical devices, and browsers.
Configuring for Android and iOS
For an Android emulator (such as a simulated Samsung Galaxy A40), you specify the platform data, the device name, and the UDID. Additionally, Android requires either a path to your APK file via the "app" property, or two specific properties that define the app package and the app starting activity (as defined in your AndroidManifest.xml).
With iOS, physical devices must first be provisioned. In the JSON configuration, you use specific properties for this, such as xcodeOrgId and xcodeSigningId. Instead of the bundleId, you can also use the "app" property here to point directly to an IPA file. A typical configuration looks like this:
JSON
{
"appium:platformName": "Android",
"appium:platformVersion": "11",
"appium:deviceName": "Samsung Galaxy A40",
"appium:udid": "******",
"appium:appPackage": "com.coffeeit.appium_example",
"appium:appActivity": "com.coffeeit.appium_example.MainActivity",
"appium:noReset": "false",
"appium:fullReset": "false"
}
What do "noReset" and "fullReset" do?
Beginning Appium users often get confused by the "noReset" and "fullReset" keys. These properties instruct Appium to either clear the app data or completely uninstall the app before a test starts. This is incredibly useful when you want to repeatedly test an app's onboarding flow from a clean slate, for example.
- If noReset is set to true, all app data will be cleared
- If fullReset is set to true, the app will be uninstalled and reinstalled (this is onlypossible if a path to an APK/IPA is provided)
I personally always clear the app data because you can’t rely on the state of an app. Clearing the app data will give a test higher assurance of passing successfully.Accessibility idsWhen writing tests we have to interact with UI elements. There are different methods to dothis. UI elements are identifiable by:
- Id
- Accessibility id
- xpathThe nested path of an element
- Class nameClass name of the element
- Tag name
- And various other Android/iOS related methods
The best way is to use accessibility ids. Simply because accessibility ids do not require codechanges and can be synchronized between multiple apps. So if you have an Android and aniOS app, depending on how much they differ, you will be able to use the same test for bothapps. Meaning you don’t need to create a separate test for Android and a different one foriOS. Furthermore, you can use if conditions in your code to perform iOS only or Android onlyUI operations.
Hier is het laatste deel van je tekst, op precies dezelfde wijze herschreven: compact, scanbaar met overzichtelijke koppen (H2, H3, H4), vetgedrukte kernwoorden en strakke code-blokken.
Writing Tests with Kotlin
Now that we understand Appium’s Desired Capabilities and have a basic understanding of how we can identify UI elements, we can start writing tests. I will be using Kotlin in this blog post.
Step 1: Import Dependencies
First, we need to import a few required dependencies into our project:
Kotlin
dependencies {
implementation("io.appium:java-client:8.0.0")
implementation("org.seleniumhq.selenium:selenium-java:4.1.4")
implementation("org.testng:testng:7.5")
testImplementation("org.testng:testng:7.5")
}
Step 2: Create the Test Class
For this example, I will be testing a simple tasks app that I created. Create a new class in src/test/kotlin called TodoTasksTest which looks like this:
Kotlin
import io.appium.java_client.AppiumBy
import io.appium.java_client.android.AndroidDriver
import org.openqa.selenium.WebDriver
import org.openqa.selenium.remote.DesiredCapabilities
import org.openqa.selenium.support.ui.ExpectedConditions
import org.openqa.selenium.support.ui.WebDriverWait
import org.testng.annotations.AfterTest
import org.testng.annotations.BeforeTest
import org.testng.annotations.Test
import java.net.URL
import java.time.Duration
class TodoTasksTest {
private lateinit var driver: WebDriver
private lateinit var waiter: WebDriverWait
@BeforeTest
fun setup() {
// Setup the desired capabilities
val capabilities = DesiredCapabilities()
capabilities.setCapability("platformName", "Android")
capabilities.setCapability("deviceName", "Samsung Galaxy A40")
capabilities.setCapability("platformVersion", "11")
capabilities.setCapability("udid", "******")
capabilities.setCapability("appPackage", "com.coffeeit.appium_example")
capabilities.setCapability("appActivity", "com.coffeeit.appium_example.MainActivity")
capabilities.setCapability("noReset", "false")
capabilities.setCapability("fullReset", "false")
driver = AndroidDriver(URL("http://127.0.0.1:4723/wd/hub"), capabilities)
waiter = WebDriverWait(driver, Duration.ofSeconds(10L))
}
@Test
fun testAddTask() {
// wait for the element to be visible and click on it
waiter.until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.accessibilityId("AddTask"))).click()
// if accessibility ids are not defined, we can identify elements in different ways
val elements = driver.findElements(AppiumBy.className("android.widget.EditText"))
elements[0].sendKeys("Some title")
elements[1].sendKeys("Some description")
// wait for the element to be visible and click on it
waiter.until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.accessibilityId("SaveTask"))).click()
}
@Test
fun testAddTaskBackButton() {
// wait for the element to be visible and click on it
waiter.until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.accessibilityId("AddTask"))).click()
// wait for the element to be visible and click on it
waiter.until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.accessibilityId("Back"))).click()
}
@AfterTest
fun teardown() {
driver.quit()
}
}
In the example above, we start by setting up our capabilities. After that, two tests are defined: one adds a new task with a title and description, while the other presses the back button when trying to add a task. In the final function, we terminate the automation session.
The longest step here is setting up the capabilities and preparing the test device. The tests themselves take just a few seconds.
Next time we want to test something, we simply start the Appium server, connect our device, and run our tests. However, what if you are not a developer? Can you still use Appium to write tests?
Introduction to TestProject
Appium is a free, open-source framework designed to automate testing as much as possible, and it does a pretty good job. However, you still need to be a developer to write tests.
Fortunately, commercial test automation frameworks offer online environments where you can test apps without writing code. You can set up test data, record tests, and integrate them into cloud solutions like GitLab CI or AWS.
In 2015, an open-source effort started to provide these premium features to the world for free. In April 2018, the first version of this online test environment was released under the name TestProject.
TestProject is a free, end-to-end test automation platform for web, mobile, and API testing that makes great use of Appium. It is a highly powerful platform that can be used completely free of charge.
Important Core Considerations
While TestProject claims to be open-source, only their SDK and plugins actually are. The online environment itself is closed-source. This means you cannot run a local instance of the online dashboard, and an active internet connection is mandatory to use it.
How to Use TestProject
Using TestProject is very straightforward:
- Register an account and log in.
- Follow the setup wizard and confirm your email.
- Set up the TestProject Agent. This agent runs an Appium server in the background and interacts directly with your test devices and emulators.
Once configured, you will land in the test environment where a default test project is automatically created to get you started.
Navigating the TestProject Dashboard
At the top left of the dashboard, you can see your current project and switch to others. The tabs below it handle your project-specific data:
Tests & Jobs
TestProject splits work into tests and jobs. Tests simply follow a specific flow of steps, while jobs are one or multiple tests attached to a specific device. Jobs can be triggered manually or scheduled.
Elements
In the Elements tab, you can pre-define UI elements. Pre-defining elements makes writing multiple tests much easier because you can simply reference them later without rewriting code.
Applications & Data Sources
- Applications: Define the specific apps you want to test.
- Data Sources: Store external files (like a CSV file) to pave the way for Data-Driven Tests and Jobs.
Parameters & Devices
- Parameters: Define project-level key/value parameters (like login credentials) to reuse them across tests.
- Devices: Authorize your testing hardware. When the agent recognizes a new device, a authorization popup will appear.
Advanced Test & Step Configuration
When you open a test, all related information is displayed. You can record steps natively (which requires a local TestProject agent to be running) or create steps manually.
Global vs. Step-Level Settings
The gear icon at the top left exposes the global test settings. Here, you can configure:
- How fast the test should execute.
- What happens if a step fails.
- Whether screenshots should be taken at each step.
While these apply globally, TestProject also allows you to override these rules on a step-level basis by expanding the advanced options inside any individual step.
Inside the Steps Tab
Each step in a test has a specific action that needs to be performed. TestProject offers a wide range of built-in options and supports Add-ons.
Add-ons are incredibly useful when you need to interact with native UI elements outside of your app, and they can be created and shared by anyone in the community.
Platform Reports and Integrations
Beautiful Reporting
TestProject can automatically generate beautiful, detailed test reports. These explain the test execution from start to finish, highlighting the exact test data used and actions performed.
Summary and full reports can easily be shared with stakeholders. If your test is configured to take screenshots at every step, those images will be directly linked inside the full report.
Third-Party Integrations
TestProject integrates natively with platforms like Sauce Labs and BrowserStack, and supports custom webhooks for success/failure notifications.
While TestProject agents can run inside Docker containers, note that Docker-based agents do not currently support local hardware device recognition.
API & GitLab CI Pipelines
TestProject features a massive API that allows you to easily trigger tests and jobs remotely. For example, you can write your own custom integration pipeline in GitLab CI for Android testing. This sequence executes a series of jobs and only exits the pipeline when all tasks pass successfully:
YAML
# Minimal OS image with Java and Android 31 build tools pre-installed
image: mreichelt/android:31
variables:
# Colors for terminal output
Color_Off: '\e[0m'
Red: '\e[0;31m'
Green: '\e[0;32m'
BRed: '\e[1;31m'
BGreen: '\e[1;32m'
BBlue: '\e[1;34m'
# TestProject variables
TP_API_URL: "https://api.testproject.io/v2"
TEST_RETRIES: 2
TP_API_KEY: "SOME_API_KEY"
TP_PROJECT_ID: "some_project_id"
TP_APP_ID: "some_app_id"
TP_APK_FILE_NAME: "app-debug.apk"
TP_JOBS: "job_id_1 job_id_2"
before_script:
- apt-get --quiet update --yes
- apt-get --quiet install jq --yes
stages:
- build
- upload
- test
# Build project
assembleDebug:
interruptible: true
stage: build
script:
- ./gradlew -Dorg.gradle.jvmargs=-Xmx2048m assembleDebug
artifacts:
untracked: true
paths:
- app/build/outputs/
# Upload the APK to TestProject
uploadAPK:
interruptible: true
stage: upload
script:
# Get AWS url from TestProject to upload to
- 'UPLOAD_DATA=$(curl -s -S -X GET "${TP_API_URL}/projects/${TP_PROJECT_ID}/applications/${TP_APP_ID}/file/upload-link" -H "accept: application/json" -H "Authorization: ${TP_API_KEY}")'
- echo $UPLOAD_DATA
# Parse the upload url
- UPLOAD_URL=$(echo $UPLOAD_DATA | jq -r ".url")
- UPLOAD_METHOD=$(echo $UPLOAD_DATA | jq -r ".method.Method")
- echo $UPLOAD_URL
- echo $UPLOAD_METHOD
# Upload the APK to the upload url
- curl "${UPLOAD_URL}" --upload-file app/build/outputs/apk/debug/app-debug.apk
# Publish the APK to the TestProject app
- 'UPLOAD_CONFIRM_DATA=$(curl -X POST "${TP_API_URL}/projects/${TP_PROJECT_ID}/applications/${TP_APP_ID}/file" -H "accept: application/json" -H "Authorization: ${TP_API_KEY}" -H "Content-Type: application/json" -d "{ \"fileName\": \"${TP_APK_FILE_NAME}\" }")'
.avif)



