Integrating your automated tests within your CI flow can have a significant impact on your product’s overall quality and on releasing it faster. There are a lot of great tools that implement CI flows, such as: Jenkins, GitLab CI, TeamCity, Travis CI, Bamboo and much more. However, with these tools you will still need to incorporate additional tools (such as JUnit, NUnit, etc). Using TestProject’s API, you can easily overcome these obstacles.
In this article, we are going to write our own script that will execute our automated tests as part of our Continuous Integration process by triggering jobs in TestProject‘s platform. We will use JavaScript (ES6) to implement that.
Let’s take for example a scenario where we are developing an Android application, and naturally, we also have a set of tests and jobs that we are using to test the application’s functionality. Every time we release a new version of our application, we will want to run our tests and jobs as part of our CI build.
So, let’s get started!
Prerequisites
- Sign up to TestProject here. Don’t worry, it’s completely free!
- Login to TestProject and install the TestProject Agent (it incorporates all of the tools and drivers you need for your test automation, in one small desktop local component). Watch this short video to see how easily you can setup your Agent.
Note: If you are already using Jenkins, you can use TestProject’s built-in integration with Jenkins by using a simple plugin (Check out this tutorial for setting up your TestProject-Jenkins integration).
Tutorial Overview
- Step 1: Import JavaScript libraries for fetching API
- Step 2: Create global variables
- Step 3: Get a link to upload an application
- Step 4: Confirm the application upload
- Step 5: Create a job using TestProject
- Step 6: Trigger jobs as part of your CI build
- Summary
Step 1: Import JavaScript libraries for fetching API
In our script, we are going to use ES6 Promise, Fetch API and the file system of our machine. In order to do so, we need to import these three libraries:
- es6-promise: This is a polyfill of the ES6 Promise. The implementation is a subset of rsvp.js [Github repository].
- isomorphic-fetch: Fetch for node and Browserify. Built on top of GitHub’s WHATWG Fetch polyfill [Github repository].
- fs: The Node.js file system module that will allow us to work with the file system on our computer [Node.js documentation].
require('es6-promise').polyfill(); require('isomorphic-fetch'); const fs = require('fs'); const path = process.argv[6] const apkSize = fs.statSync(path).size; let readStream = fs.createReadStream(path);
Step 2: Create global variables
We need to create a few variables that we’ll use in our script:
1. newUrl: Will contain the new URL to which we will upload our new APK version.
2. appId: Our application’s ID on TestProject. Here is how you can get it:
3. projectId: Our project’s ID in TestProject:
4. Creare access token: We need to get an API key from the TestProject Developers page (You should use your own API key. The key in the following GIF will be deleted):
5. apiUrl: TestProject’s API URL: https://api.testproject.io/
At the end of step 2, our script should look like this:
let newURL = ""; const appId = process.argv[2]; const projectId = process.argv[3] const apiKey = process.argv[4] const apiUrl = "https://api.testproject.io/";
Step 3: Get a link to upload an application
In this step, we are going to create a request that will create upload link where we will upload the application file.
We’re going to use the JavaScrip Fetch API to send a GET request to the TestProject’s endpoint that will handle the request. Below is the object we need to create in order to define the headers and the method for our request:
const uploadUrlData = { headers: { "Authorization": apiKey }, method: "GET" };
Now we are going to implement the actual request:
fetch(apiUrl + `v2/projects/${projectId}/applications/${appId}/file/upload-link`, uploadUrlData) .then(result => { return result.json(); }) .then(data => { newURL = data.url; uploadAPK(); }) .catch(error => { console.log(error); });
You probably noticed that we called the function “uploadAPK()”, now we are going to implement it. First, we need to create an object that contains the headers, method and the actual application we’re about to upload:
const uploadAPKData = { headers: { "cache-control": "no-cache", "Content-length": apkSize }, method: "PUT", body: readStream };
Now we can create the uploadAPK() function:
async function uploadAPK() { fetch(newURL, uploadAPKData) .then(result => { confirmNewAPK(); }) .catch(error => { console.log(error); }); }
Step 4: Confirm the application upload
After we’ve uploaded our application in step 3 above, we have to send a PUT request to confirm the upload. In step 3 above, we called the function “confirmNewAPK()” that we haven’t implemented yet. Let’s implement it:
async function confirmNewAPK() { const newFileName = process.argv[5] const data = { headers: { "accept": "application/json", "Authorization": apiKey, "Content-Type": "application/json" }, body: JSON.stringify({ "fileName": newFileName }), method: "POST" } fetch(apiUrl + `v2/projects/${projectId}/applications/${appId}/file`, data) .then(result => { runJob(); }) .catch(error => { console.log(error); }); }
Step 5: Create a job using TestProject
Now, after we’ve successfully completed all of the above steps, we have a new version of our application in TestProject. Let’s create a job that will contain all of our test cases that we want to execute on every new application version that we’ll upload. A job can be scheduled to run at any time we want, we can add data sources to it (to create data-driven tests) and even get email notifications or use webhooks.
Click on “Add a job”:
Give this job a name and description (optional) and click “Next”:
Select the “Mobile” option and select the agent that will run this job (notice: the agent must be in “idle” status). Finally, click “Next”:
Select “Android” and then select your device connected (you can select as many devices as you’d like, and the job will run one after the other). Finally, click on “Next”:
Choose your execution time: On demand, one time or even schedule it to run recurrently. In this case, let’s choose on “On Demand” and then click “Finish”:
Now that we’ve created the job, let’s add test cases to it:
Step 6: Trigger your job as part of your CI build
Now that we have a job with all of the test cases we want to execute, the last thing we need to do in order to complete the CI process is to run the job on the new version of our application. Let’s implement the runJob() function that we called for in step 4 above:
async function runJob() { const jobId = process.argv[7]; const data = { headers: { "accept": "application/json", "Authorization": apiKey, "Content-Type": "application/json" }, method: "POST" }; fetch(apiUrl + `v2/projects/${projectId}/jobs/${jobId}/run`, data) .then(result => { console.log("The CI process completed successfully!"); }) .catch(error => { console.log(error); }); }
That’s it! We’ve successfully implemented a CI process where we have uploaded a new version of our application and executed tests on it.
You can find the complete script here (CIProcess.js):
require('es6-promise').polyfill(); require('isomorphic-fetch'); const fs = require('fs'); const path = process.argv[6] const apkSize = fs.statSync(path).size; let readStream = fs.createReadStream(path); let newURL = ""; const appId = process.argv[2]; const projectId = process.argv[3] const apiKey = process.argv[4] const apiUrl = "https://api.testproject.io/"; const uploadUrlData = { headers: { "Authorization": apiKey }, method: "GET" }; const uploadAPKData = { headers: { "cache-control": "no-cache", "Content-length": apkSize }, method: "PUT", body: readStream }; // Get an upload URL for an application fetch(apiUrl + `v2/projects/${projectId}/applications/${appId}/file/upload-link`, uploadUrlData) .then(result => { return result.json(); }) .then(data => { newURL = data.url; uploadAPK(); }) .catch(error => { console.log(error); }); // Upload the new APK to AWS S3 async function uploadAPK() { fetch(newURL, uploadAPKData) .then(result => { confirmNewAPK(); }) .catch(error => { console.log(error); }); } // Confirm the new file upload async function confirmNewAPK() { const newFileName = process.argv[5] const data = { headers: { "accept": "application/json", "Authorization": apiKey, "Content-Type": "application/json" }, body: JSON.stringify({ "fileName": newFileName }), method: "POST" } fetch(apiUrl + `v2/projects/${projectId}/applications/${appId}/file`, data) .then(result => { runJob(); }) .catch(error => { console.log(error); }); } // Runs a job in TestProject async function runJob() { const jobId = process.argv[7]; const data = { headers: { "accept": "application/json", "Authorization": apiKey, "Content-Type": "application/json" }, method: "POST" }; fetch(apiUrl + `v2/projects/${projectId}/jobs/${jobId}/run`, data) .then(result => { console.log("The CI process completed successfully!"); }) .catch(error => { console.log(error); }); }
To run this script you’ll need to type this in the terminal:
node CIProcess.js {appID} {projectID} {apiKey} {newFileName} {pathToFile} {jobID}
Summary
We’ve started this tutorial by uploading a new version of our application to TestProject. Then we executed a job with TestProject that contained all of the tests we wanted to execute on our application. The script we’ve created in this tutorial can run by typing a simple command that will implement your test automation as part of your CI using TestProject’s API.
Go ahead and try it out! Share your experience in the comments below ➡
This is awesome! I was trying to tease out the details to accomplish this and decided to search on the API endpoints. I used your example for file uploads rather than APKs and just for automatic updates of datasources rather than a CI pipeline, but it works beautifully for that. Thanks!