Comprehensive REST APIs

Unlock advanced testing capabilities with the most extensive collection of REST API calls, enhancing your Appium scripts with seamless integration, dynamic device selection, and deep performance insights.
Leveraging HeadSpin REST APIs in Your Appium Script: A Comprehensive GuideLeveraging HeadSpin REST APIs in Your Appium Script: A Comprehensive Guide

How to Leverage the HeadSpin REST APIs in Your Appium Script

March 26, 2024
 by 
Jamie MooreJamie Moore
Jamie Moore

Introduction

One question that frequently arises is how to harness the capabilities of HeadSpin API calls within an Appium script. In this blog post, we'll explore the ways you can tap into the robust features and adaptability offered by the HeadSpin API to elevate the effectiveness of your Appium scripts.

Why Opt for HeadSpin API Calls in Your Appium Script?

HeadSpin boasts the industry's most comprehensive collection of REST API calls, offering a rich spectrum of functionalities crucial for enhancing your Appium scripts. These API calls are divided into three primary categories: Application APIs, Device APIs, and the unique Session APIs, exclusive to HeadSpin.

What exactly is a HeadSpin Session? It's a recording capturing the actions on a device while executing your Appium script, aptly termed a Performance Session. With HeadSpin's expansive range of API calls, you can delve deep into these sessions, querying and analyzing them to gain profound insights into your test execution.

Incorporating these API calls into your script enables you to seamlessly upload applications, install them on devices (while removing any previous versions), dynamically select devices for testing during execution, and meticulously scrutinize your recorded Performance Sessions. This integration significantly amplifies the capabilities of your script, empowering you with unparalleled control and understanding.

Read: A Step-by-Step Guide to Appium Test Automation

Getting Started

To begin harnessing API calls within an Appium script, you'll first require an Appium client. Throughout this blog, I'll provide examples in Python. However, you can easily adapt these calls to the programming language you're utilizing for your Appium client, be it Java, Ruby, C#, or others.

Furthermore, to experiment with these calls in your scripts, you'll need a valid HeadSpin account.

Choosing Your Workflow

With the extensive flexibility offered by HeadSpin APIs, determining the most effective workflow becomes crucial. One decision to make is how to install the application on the device for testing purposes. A popular approach involves utilizing a CI/CD tool like Jenkins, as outlined in detail in our blog post here. This method involves installing the application separately from your Appium script. Alternatively, you could opt to upload and install the application within the Appium script itself.

Traditional Appium scripts necessitate knowing the specific device for test execution, as device values must be specified in the Desired Capabilities. But what if you require greater flexibility, such as dynamically selecting devices during test execution? This is where the HeadSpin Device APIs come into play, providing the capability to view available devices and select one at runtime. Additionally, the HeadSpin Appium Load Balancer offers a dynamic device selection feature, which we'll delve into later.

When it comes to the Session APIs, it's essential to determine which ones will offer the most value to your organization. One option is creating session labels, a topic we'll explore further in the Session APIs section. 

HeadSpin API 

How do you get started? First, you need to make sure you have your HeadSpin API Token generated. To generate your API Token, login to your HeadSpin account and click on your name in the top right corner of the screen:

Select Profile

Then click on Settings

click on settings

Then, if there is no API Token already generated, you can click on + New Token to generate a new one:

click on new token

But what is even better, HeadSpin assists you with your API calls by already including your API Token in the API examples in the documentation. If you go to the API Reference documentation here without logging in first, you will see the example API calls with a variable for the API Token called <your_api_token>:

api token

However, if you are logged into your HeadSpin account, the API Token value is automatically filled in:

API Token value filled

This makes it extremely easy to copy and paste these commands into a command prompt for your API calls. However, we want to execute these calls in an Appium script, not by using curl, so we need to create these API calls properly and in the correct format. If you are unsure how to convert a curl command to your preferred language, there are a number of curl converter sites that you can find online. 

The HeadSpin API uses a Bearer Token for authentication, and this means that you will use your HeadSpin API Token as the Bearer Token. It’s easy to just put that value in a variable so that it’s easy to use it throughout your script. Here’s an example (as a reminder, all examples are in Python):


auth_token = 'a2564fa6141e4a1f8ru4e7046c13acac'

For Python, you’ll need to import a library to make API calls, and use the requests library:


import requests

The general format for a REST API call is:


requests.get(api_endpoint, headers={'Authorization': 'Bearer 
{}'.format(auth_token)})

The api_endpoint’ is the API URL to be used in the request, and you will notice how we’ve set the ‘headers’ to use the Bearer Token, which is the ‘auth_token’ value specified earlier.

Also read: Exploring Selenium/Appium Frameworks for Seamless Integration with HeadSpin

There is another component that you will need to use in a number of your API calls, and that is specifying some data that’s required for that API call. In that case, just create the appropriate object (be it json, parameter, or data) and include it in the request. In this example, I am creating a json object to set the status of the current session:


perf_data = {
	"session_id": driver.session_id,
	"status": status
}

And then I reference that object in my  API call, like this: 


requests.post(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)}, json=perf_data)

Review the HeadSpin API Documentation

You can get a head start by reviewing the documentation on the HeadSpin API Reference, at this page: https://headspin.io/docs

HeadSpin Docs - API Reference

As you can see, there are a number of API categories with the API calls listed below. 

For example, this is a list of API calls within the Session API section. 

session API docs

Let’s see how we can leverage these APIs in our Appium script. To start with, we need to upload the application to the HeadSpin platform and then find a device to install it on. Let’s discuss the Application APIs first, then we’ll talk about the Device APIs. 

Upload Application APIs

There are a number of methods you can use to upload your application to the HeadSpin platform and/or install it on a device. For example, as stated earlier, you could leverage your CI/CD tool to upload the application and then install it on a device. Additionally, Appium can specify the application file within the script itself to install on a device. These are all valid. However, I want to illustrate how you can also do all of these steps utilizing the HeadSpin APIs within your Appium script. 

The advantage of uploading your application to the HeadSpin platform (the assumption here is that you have access to the .apk or .ipa file) is the file is only uploaded once, and then it’s installed onto the devices directly from the HeadSpin platform. This method can offer tremendous savings on time and network traffic, depending on how large your application is. Additionally, when uploading to the HeadSpin platform, you can assign a metadata selector value to the application. What’s a metadata selector? It’s a way of uniquely identifying the application, similar to tags.  In fact, there are 17 metadata selectors that can uniquely identify the uploaded application. You can view those selectors on the Application Management documentation page here. Some selectors are automatically derived from the application itself, like the app_identifier, while others are user-provided, like build_type or version_tag. This gives you the flexibility to install an application onto a device based on the specific metadata selector. 

What is the HeadSpin API to upload an application? In the documentation, listed here, the example is:

api key

The reason we need to use the -F option for this curl command is that this is a multipart/form-data request, as explained in the documentation:

Request Body

As you can see, you have the option to add the metadata selector values to be assigned to the application. Once uploaded, the returned value is the unique app_id for that uploaded application. However, this upload command format is in curl, but we need to do it in a Python script. I can create the request like this:

In my script, I like to put the API endpoint URL in a variable:


api_endpoint = 'https://api-dev.headspin.io/v1/app/upload'

To do a multipart/form-data request in Python, we need to create a ‘files’ object. This is an example of the application that I want to upload. 


files = {
	'app': open('/Users/jamiemoore/Downloads/InsuranceCalculator.apk', 'rb')
	}

Then you can create the request:


response = requests.post(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)}, files=files)

To add values for the metadata selectors, just add the selector and its value in the files object:


files = {
	'app': open('/Users/jamiemoore/Downloads/InsuranceCalculator.apk',
	 'rb'),
	'metadata': (None, '{"version_tag": "ver1"}')
	}

You could easily add an additional selector: value pairs within the brackets { }

Why would you want to add a metadata selector? It provides a method to query or install an application based on that metadata selector value. We’ll cover this in a little more detail when we install the application on a device. 

This request will return a json object that contains the HeadSpin app_id for the uploaded application. You will need this value later in your script, so you’ll want to store it in a variable. 

The steps to do this are to convert the json object to a text object and then retrieve the app_id value.


app_upload_result = json.loads(response.text)

app_id = app_upload_result['app_id']

Device Selection APIs

Now that the application has been uploaded to the HeadSpin platform, it’s time to install it on a HeadSpin device. There are a number of ways to do this task, so it will depend on your preferred workflow process. I will highlight a few, but I will focus on leveraging the HeadSpin API to find and select a device (or multiple devices) to install the application. 

First, you can just explicitly state the specific device to use for the test. You simply add the udid/serial # within the Desired Capabilities (and other required values). Here’s an example:


CAPS = {
	'deviceName': 'SM-S911U',
	'udid': 'R5CT625E6DZ',
	'automationName': 'UiAutomator2',
	'platformName': 'Android',
	'appPackage': 'com.android.settings',
	'appActivity': 'com.android.settings.Settings'
}

Alternatively, you have the option to dynamically select a device at test execution time. There are two ways to do this. We’ll talk about the simpler method first, but I need to provide a little background information on the HeadSpin architecture. Each HeadSpin device is connected to a server that contains an Appium Server that services that device, up to eight devices per server. As a result, there could be differing Appium Server URLs that you would need to use for multiple devices. 

For example, the two devices below, both Android, are connected to different servers within my organization:

devices below

The Appium WebDriver URL for the Google Pixel 4L is: 


https://dev-us-sny-3.headspin.io:7031/v0/a2564fa61r6e4a1f82c3e7046c13acac/wd/hub

And the Appium WebDriver URL for the Samsung S22 is:


https://dev-us-sny-4.headspin.io:7017/v0/a2564fa61r6e4a1f82c3e7046c13acac/wd/hub

These are the values you use when creating your driver object (notice that your API Token is included in the URL which is why you conveniently don’t need to have a HeadSpin Username & Password to create a session). As you can see, the values are different because of the different server names. You would need to specify each of these values whenever you want to test on these devices. 

To help alleviate this situation, HeadSpin has what’s known as the Appium Load Balancer (ALB). You can find the documentation here.

In a nutshell, it provides a single WebDriver URL to use for all of your devices. For example, the two devices above could each use this WebDriver URL value:


https://appium-dev.headspin.io:443/v0/a2564fa61r6e4a1f82c3e7046c13acac/wd/hub

Using this Appium Load Balancer URL channel, ALL Appium commands are sent through a single point, and then the command is forwarded to the corresponding device. An additional advantage is that you can use a device metadata selector in the Desired Capabilities and let the Appium Load Balancer pick the first device that matches the selector value. 

If you place this value in your Desired Capabilities:


"headspin:selector": "device_skus:\"Galaxy S22\""

The Appium Load Balancer will select the very first available device which is a Galaxy S22, and execute the test on that device. This gives you flexibility to not have to have a specific device specified in your script. 

However, by using the Appium Load Balancer, an additional layer is added during the test execution. If you still want to dynamically select your device and use the device-specific WebDriver URL, you need to do that using the HeadSpin Device API. 

Let’s discuss how to achieve this method. There’s a HeadSpin API to list all of the devices and information (you must include at least one device metadata selector in the request):


curl -X GET https://a2564fa61r6e4a1f82c3e7046c13acac@api-dev.headspin.io/v0/devices/<device_selector>/information

There are two important values contained in the ‘information’ response that will let you know if a device is ready to use for testing. They are device_state and owner_email. There are 3 possible values for device_state: online, offline, and disconnected. Obviously, we are looking for online devices. Additionally, a device may be online, and it may be busy and locked by another user. When a device is in use, the owner_email value is populated. When the device is not in use,  owner_email is set to None

By using these selectors and filtering based on these values, you can create a tremendous amount of flexibility in your device selection at test execution time. And, if you’re using a framework, like TestNG, that allows you to thread out multiple test execution threads on multiple devices simultaneously, then you can really ramp up your test execution suite. 

This is a two-step process:

  1. Get a list of online devices (based on you selectors)
  2. From that list, make a sublist of devices that are not being used

Let’s take that curl request and convert it to Appium. 

First, we’ll create our API Endpoint URL. Since we don’t need a json object for this request, we can just add the selectors within the URL. 


api_endpoint = 'https://@api-dev.headspin.io/v0/devices/device_state:online+manufacturer:samsung+os_version=12/information'

In this example, I am getting a list of all of the online Samsung devices with OS version 12. I could certainly narrow that list down with additional selectors or, conversely, widen the list with fewer selectors. 

Include this api_endpoint, and the request looks like this:


response = requests.get(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)})

The object ‘response’ is a json object, so we need to convert it to a text object and then step through to find all of the devices that have owner_email = None.


device_result_text = json.loads(response.text)

for each in device_result_text['devices']:
	if each['owner_email'] == None:
		device_id = each['serial']
	else:
		print('No available devices')

If you wanted to create a list of all of the devices that match your criteria, then you would just add each device's serial number to a list to use later. In Python, create a list object: 


device_list[] 

Then, in the loop, add each device_id to the list with: 


device_list += [device_id]

The final loop would look like this:


for each in device_result_text['devices']:
	if each['owner_email'] == None:
		device_id = each['serial']
		device_list += [device_id]
	else:
		print('No available devices')

You would take the value in device_list[<index>] and use that in your Desired Capabilities to specify the device to use in your test. For example: 


'udid': device_list[0],

Then you could use the device_list[] and either thread out those device_ids to different test execution threads or use them to loop your test on each device sequentially. As I said, you have plenty of choices of how you want to construct your device list to use for testing. 

There is one more thing we need to do with this device list. And that is to determine which Appium WebDriver URL to use. As I mentioned earlier, using the HeadSpin Appium Load Balancer Webdriver URL makes it easy, and it’s the same for each and every device. But this blog is all about using the HeadSpin API, so we are not going to take the easy path! No, we are going to get the specific WebDriver URL for each device in our list. 

To do this,  we are going to leverage the HeadSpin Automation Config APIs for this task. You can read the documentation here. You can request the automation configuration information from any HeadSpin device. 

A sample curl command looks like this:

sample curl

This command returns the automation configuration information for every device in your organization. Luckily, as with the command to get the device information, you can include selectors in your command to narrow down the selected devices, for example:

automation configuration

However, to use this method, there is one more data field that we need to capture when we select our devices. In addition to the device_id, we will also need the device_address. Why would we need to keep the device_address? The return data set from the automation configuration is organized based on the device address of the devices. So, putting it all together, our new process is:

  1. Get a list of online devices
  2. From that list, create a sublist of devices not in use
  3. For each device in that sublist, retrieve the device-specific WebDriver URL

Therefore, we would modify our device selection code snippet to also capture the device addresses (in a list object called device_address_list[]), like this:


device_address_list = []

for each in device_result_text['devices']:
	if each['owner_email'] == None:
		device_id = each['serial']
		device_list += [device_id]
		device_address = each['device_address']
  	device_address_list += device_address
	else:
		print('No available devices')

Now that we have the device id and the device address, let’s retrieve the appropriate WebDriver URL.  

We need to translate the automation configuration API call to your language of choice. 

As before, we need to set the api_endpoint


api_endpoint = 'https://@api-dev.headspin.io/v0/devices/’ + device_list[0] + ‘/automation-config'

In this example, we are retrieving the data for only one specific device. You can certainly use a selector that returns multiple devices, but for the purpose of this blog, we will just retrieve the automation configuration for this single device. 


response = requests.get(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)})

Load the json object to text:


auto_config_text = json.loads(response.text)

This is the data that’s returned: 


{'R5CT625E6DZ@dev-us-sny-4-proxy-18-lin.headspin.io': 
{
	'control_url': 'https://dev-us-sny-4.headspin.io:19100', 
	'driver_url':    
	'https://dev-us-sny-4.headspin.io:7017/v0/{api_token}/wd/hub', 
	'lock_url': 'https://dev-us-sny-4.headspin.io:7017/v0/{api_token}/lock', 
	'unlock_url': 
	'https://dev-us-sny-4.headspin.io:7017/v0/{api_token}/unlock', 
	'host': 'dev-us-sny-4-proxy-18-lin.headspin.io', 
	'driver_lb_url': 
	'https://appium-dev.headspin.io:443/v0/{api_token}/wd/hub', 
	'device_location': {
		'country': 'us', 
		'country_readable': 'US', 
		'city': 'sny', 
		'city_readable': 'Sunnyvale', 
		'lat': 37.36883, 
		'lon': -122.0363496, 
		'carrier': 'T-Mobile Wi-Fi Calling'
		}, 
	'device_carrier': {
		'carrier': 'T-Mobile Wi-Fi Calling', 
		'network_type': 'WIFI', 
		'network_subtype': '', 
		'network_connected': True, 
		'phone_network': 'LTE'
		}, 
	'capabilities': {
		'automationName': 'uiautomator2', 
		'platformName': 'android', 
		'deviceName': 'SM-S901U', 
		'appPackage': 'com.android.settings', 
		'appActivity': 'com.android.settings.Settings', 
		'udid': 'R5CT625E6DZ'
		}, 
	'os': 'android', 
	'headspin:controlLock': True, 
	'headspin.controlLock': True, 
	'headspin:capture': True, 
	'headspin.capture': True
	}
}

To retrieve the device_url, which is the WebDriver URL for this specific device, you’ll need to base it off of the device_address and then place its 'device_url’ value in a variable. 


 webdriver_url = auto_config_text[device_address]['driver_url']

As you can see in the returned data, the driver_url value contains this value: {api_token}. Obviously, you’ll need to replace this with your valid API Token. Python makes this an easy task using the ‘replace’ command:


webdriver_url = webdriver_url.replace('{api_token}', auth_token)

Now my WebDriver URL has the correct information and is ready to use:


https://dev-us-sny-4.headspin.io:7017/v0/a2564fa6141e4a1f82c3e7046c13acac/wd/hub

I will use this value when creating my web driver:


driver = webdriver.Remote(webdriver_url, capabilities)

Application Installation APIs

All right, now that we’ve talked about the APIs for uploading the application and selecting devices, it’s time to talk about installing the application on the device itself. We have discussed this previously, and I know there are other methods to install the application on the device, but I want to discuss how you can do it with the HeadSpin APIs. 

But before we get into that, should we make sure that the application doesn’t already exist on the device? Once we’ve selected a device for testing, as a matter of practice, I would like to make sure that the application has been deleted so that I can do a fresh install. 

Luckily, you can use a metadata selector in the Delete API request, the documentation for that is here

Here’s an example of deleting an application on an Android device:

unintall app

At a minimum, you can use the app_indentifier selector in the api_endpoint and create a request as: 


api_endpoint = 'https://@api-dev.headspin.io/v1/apps/app_identifier:com.tricentis.insuranceCalculatorApp/latest/uninstall/' + device_list[0]'

response = requests.post(api_endpoint, headers={'Authorization': 'Bearer	{}'.format(auth_token)})

Now that the application has been deleted, let’s install the correct one that needs to be tested.

You have the option of doing it in a few ways. First, you can install the application directly onto the device without uploading it to the HeadSpin platform. This might be beneficial if you want to do a quick test on that version of the application before uploading it to the HeadSpin platform. The disadvantage is that by installing the application directly onto the device, each additional device would require an upload of the application, taking even more time and requiring more network resources. The second method is to install the application after uploading it to the HeadSpin platform. Not to confuse things, but even after you upload the application to the HeadSpin platform, you still have multiple ways to install the application on the device. Did I mention that the HeadSpin platform is extremely flexible? 😁

Let’s go over installing the application directly on the device since the other method provides a lot more flexibility. 

For Android, create a files object with your application and then the api_endpoint with the device_id you wish to use from our list.


files = open('/Users/jamiemoore/Downloads/InsuranceCalculator.apk','rb')

api_endpoint = 'https://api-dev.headspin.io/v0/adb/’ + device_list[0] + ‘/install'

For iOS, the api_endpoint is slightly different:


api_endpoint = 'https://api-dev.headspin.io/v0/idevice/’ + device_list[0] + ‘/installer/install'

Then execute the request: 


install_output = requests.post(api_endpoint, headers={'Authorization':	'Bearer {}'.format(auth_token)}, data=files)

If the installation is successful, you will get: <Response [200]>

This is a very explicit way of installing an application on a device, and, as mentioned, if you are testing on multiple devices, this method takes time and consumes network resources. 

If you have uploaded the application using the API mentioned earlier, then you have captured the unique app_id for that uploaded application. You have the option of using a HeadSpin-specific Desired Capability, headspin:app.id, to use that app_id, and the application will automatically be installed on the device. 


CAPS = {
	'headspin:app.id': app_id,
	'udid': device_list[0],
	'automationName': 'UiAutomator2',
	'platformName': 'Android',
}

The bonus of this method is that you do not have to specify the appPackage and appActivity (or bundleId for iOS) of the application in your Desired Capabilities. 

However, let’s go over how to leverage the APIs to install the application AFTER the application has been uploaded to the HeadSpin Platform, as described earlier. 

Because you can upload multiple versions of the same application to the HeadSpin platform, you have the flexibility to select the appropriate one using the metadata selectors. For example, for the application we uploaded previously, I could issue an install command to find the version of the application that has version_tag set to ver1. I don’t have to worry about specifying the app_id; just use a selector. I could always use HeadSpin to install the latest uploaded version regardless of the metadata selector. This would ensure that you are always testing on the latest uploaded version of the application. 

Since you can have different applications uploaded that can use the metadata selector and the same value, for example, there may be multiple applications that have a version_tag selector set to ver1; you can (and should!) include the app_identifier selector to explicitly specify the correct application to install. This is what we’ll do in our example. 

From the application I uploaded previously and set the version_tag to ver1, here’s how I would install it on the device specified by device_list[0]:


api_endpoint = 'https://@api-dev.headspin.io/v1/apps/app_identifier:com.tricentis.insuranceCalculatorApp+version_tag:ver1/latest/install/' + 	device_list[0] 

install_output = requests.post(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)})

Again, it all depends upon your process flow. The important thing to remember is that the HeadSpin Platform can support you no matter what your process flow is. 

Now that the correct version of the application has been installed on the device, we can add the appropriate values in the Appium script. 

Prepare Your Appium Script

First, we can modify the Desired Capabilities to use the device information contained in device_list:


CAPS = {
	'udid': device_list[0],
	'automationName': 'UiAutomator2',
	'platformName': 'Android',
	'appPackage': 'com.android.settings',
	'headspin:capture': 'True’
	'appActivity': 'com.android.settings.Settings'
}

(NOTE: I added an additional capability called ‘headspin:capture’ and set it to True. This will record the test execution and create a Performance Session. There are a number of HeadSpin-specific Desired Capabilities that you can use. The documentation for them is here

Second, use the value that we placed in webdriver_url in the driver creation statement, as shown previously.


driver = webdriver.Remote(webdriver_url, capabilities)

Then, you can start your application test. Now let’s discuss how we can add those session queries and interrogations into our script to take advantage of the power of the HeadSpin platform.

Session APIs

As mentioned in the Introduction section, HeadSpin has the ability to record your session and create what is known as a Performance Session. Each performance session has a unique ID that allows you to not only interrogate that session but also compare one session to another. There are a number of blogs and webinars on the HeadSpin website that go over these topics in greater detail. As a matter of fact, the blog located here goes over how to use the Session APIs both manually and in an automation script. 

You can also review the HeadSpin documentation on Performance Sessions here

There are three main types of Session APIs: 

  • Session APIs: read about them here
  • Session Annotation APIs: read about them here
  • Session Analysis APIs: read about them here

The Session APIs are primarily focused on managing the sessions, allowing you to see how many you have, deleting sessions, and other management-type tasks. We won’t be too focused on these calls for this blog.

The Session Annotation APIs allow you to, as the name suggests, annotate a session with things like tags, name, description, etc, as a way to uniquely identify a session. There are also API calls that allow you to create labels on the session. What is a label? They are described in the documentation here. Their purpose is primarily to allow you to create different types of questions about your session, for example, how long did a page take to load (a page load request), did an audio clip match what I was expecting? (an audio match request). We won’t go over every one of these in this blog, but we will point out how to add them to your script so that you can create as many as required to get a full understanding of how your application performed during the test. 

The Session Analysis APIs are focused on allowing you to track the status of the Session Annotations that you created…for example, has the audio match analysis been completed yet? You can read about these APIs here

Similar to our Decide On Your Process Flow discussion earlier, you’ll need to decide which annotations you want to include in your script. There’s no limit on how many, but you’ll want to focus on those that are most meaningful to you and your organization. 

From the documentation here, this is the list of Session Labels that you can create:

label type table

Most of these labels are time-based. For example, the page-load-request captures how long a page took to fully load. In the session, you have to mark a start time and an end time for the Analysis Engine to know the Start-Stop Boundaries within the session for the analysis. In your script, getting start and stop timestamps will be important. Along with the timestamps, you will need to capture the current session ID, as all Session API requests require that value. The basic format for all of these requests to add a label is:


/v0/sessions/{session_id}/label/add

You can capture the session_id by using this command:


session id = driver.session_id

And create your api_endpoint:


api_endpoint = f'https://api-dev.headspin.io/v0/sessions/' + session_id + '/label/add'

Now, we just use a json object to specify what type of label we want and other specific information required for that label request.

Let’s use a time series request as our example. You can refer to this webinar for more details about the additional label types. In this example, I want to create a time-series-request that tracks the Net CPU KPI throughout the entire session and will create a result if it goes over 10% at any time during the session. What is Net CPU? In the documentation here, it’s described as:


Net CPU Usage: The net CPU usage on the device. This value is system-wide CPU utilization as a percentage

Obviously, 10 is just an arbitrary number. Use whatever makes sense to you. Here’s the json object:


net_cpu_data = {
	"name": "Net CPU Above 10%",
	"label_type": "time-series-request",
	"category": "an optional category for the label",
	"ts_start": stringSearchStart,
	"ts_end": videoEndTime,
	"data": {"method": "range", "time_series_key": "net_cpu", "parameters": {"lower_limit": 10.0}},
	"pinned": True

Let's break down each entry:

  • name: This is the identifier for your request, and you can assign any name you prefer.
  • label_type: It must belong to one of the HeadSpin Label Types.
  • category: You have the option to assign a category to your request.
  • ts_start & ts_end: These timestamps are generated from your script. The initial timestamp is captured at the beginning of the script and stored in stringSearchStart, while the end of the session timestamp is stored in videoEndTime. The HeadSpin Analysis engine will then assess the Net CPU during this timespan within the session and display its findings on the Session Waterfall UI page.
  • data: In a time series request, you can specify different values relevant to this request type. Not all label types necessitate a data field.
  • pinned: Enabling this option will keep the request pinned on the Session Waterfall UI page.

To create the timestamps, first, capture the current time and then convert it into a string object.


startTime = time.time()
stringSearchStart = str(startTime)

sessionEndTime = time.time()
videoEndTime = str(sessionEndTime)

Once the json object is created, the request can be constructed: 


net_cpu_output = requests.post(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)}, json=net_cpu_data)

Again, you can capture these timestamps throughout the script. These two are at the beginning and end of the test. 

What are the results? When completed, you can review the Performance Session and see/analyze the results. Here’s a screenshot showing the Net CPU request and result from this example: 

CPU Usage

The top bar is the length of the request, the entire test run. The second bar graph is the result showing each time the Net CPU exceeded 10%. You can create a query like this for every KPI that HeadSpin captures. 

Putting It All Together

Just so you can see all of this together and not so broken up, these steps are the first part of my script before I create the Desired Capabilities.  


#Upload the app
files = {
    'app': open('/Users/jamiemoore/Downloads/InsuranceCalculator.apk', 'rb'),
    'metadata': (None, '{"version_tag": "ver1"}')
}
api_endpoint = 'https://api-dev.headspin.io/v1/app/upload'
response = requests.post(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)}, files=files)
app_upload_result = json.loads(response.text)
app_id = app_upload_result['app_id']


#Select a device
api_endpoint = 'https://@api-dev.headspin.io/v0/devices/device_state:online/information'
response = requests.get(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)})
device_result_text = json.loads(response.text)
device_list = []
device_address_list = []
for each in device_result_text['devices']:
    if each['owner_email'] == None:
            device_id = each['serial']
            device_list += [device_id]
            device_address = each['device_address']
            device_address_list += device_address
    else:
        print('No available devices')


#Make sure the app is deleted on the device
api_endpoint = 'https://@api-dev.headspin.io/v1/apps/app_identifier:com.tricentis.insuranceCalculatorApp/latest/uninstall/' + device_list[0]
response = requests.post(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)})


#Install the application on the device
api_endpoint = 'https://@api-dev.headspin.io/v1/apps/app_identifier:com.tricentis.insuranceCalculatorApp+version_tag:ver1/latest/install/' + device_list[0]
install_output = requests.post(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)})


#Get the WebDriver URL for the selected device
api_endpoint = 'https://@api-dev.headspin.io/v0/devices/serial:' + device_list[0] + '/automation-config'
response = requests.get(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)})
auto_config_text = json.loads(response.text)
webdriver_url = auto_config_text[device_address]['driver_url']
webdriver_url = webdriver_url.replace('{api_token}', auth_token)

Then I create the Desired Capabilities and the driver object: 


CAPS = {
  'udid': device_list[0],
  'autoAcceptAlerts': True,
  'automationName': 'UiAutomator2',
  'platformName': 'Android',
  'appPackage': 'com.tricentis.insuranceCalculatorApp',
  'appActivity': 'com.tricentis.mobile.MainActivity',
  'headspin:capture': 'True',
  'adbExecTimeout': '100000'
}
driver = webdriver.Remote(
  command_executor=webdriver_url,
  desired_capabilities=CAPS
)

Then, I execute the steps within my script, capturing the timestamps as needed.


startTime = time.time()
carButton = wait.until(EC.presence_of_element_located((MobileBy.ID, 'com.tricentis.insuranceCalculatorApp:id/Car')))

Finally, I capture the end time timestamp, and then I delete the uploaded application before I create my session labels at the end of the session: 


session_id = driver.session_id
sessionEndTime = time.time()
videoEndTime = str(sessionEndTime)

driver.execute_script('headspin:quitSession', {'status': 'passed'})


#Delete the Uploaded Application
api_endpoint = 'https://@api-dev.headspin.io/v1/app/' + app_id + '/delete'
delete_output = requests.delete(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)})


#Create the Net CPU Time Series Request
#Net CPU
net_cpu_data = {
    "name": "Net CPU Above 10%",
    "label_type": "time-series-request",
    "category": "an optional category for the label",
    "ts_start": stringSearchStart,
    "ts_end": videoEndTime,
    "data": {"method": "range", "time_series_key": "net_cpu", "parameters": {"lower_limit": 10.0}},
    "pinned": True
}
net_cpu_output = requests.post(api_endpoint, headers={'Authorization': 'Bearer {}'.format(auth_token)}, json=net_cpu_data)

Conclusion

In summary, leveraging the HeadSpin API Library alongside your Appium script empowers you to enhance your application testing process significantly. With this integration, you can seamlessly integrate unique selector values, dynamically choose devices, install the application across multiple devices, and crucially, establish session labels within your script to comprehensively assess application performance. This flexibility enables you to incorporate numerous session labels tailored to monitor and analyze key performance indicators vital for your organization's goals and objectives.

Share this

How to Leverage the HeadSpin REST APIs in Your Appium Script

4 Parts