Basic GitLab CI/CD & aqua workflow
This guide demonstrates integrating automated testing into your GitLab CI/CD pipeline, specifically using Maven project with JAVA/Playwright. However, the steps can be adapted for any test automation tool or framework to submit results to aqua.
Prerequisites
a GitLab project with CI/CD enabled
an aqua account to submit test results
an application or project configured with Maven project (e.g. using Visual Studi Code)
First, generate clean maven project or use existing one.
Make sure the following dependencies are added to the pom.xml file We will need Playwright, JUnit and JSON libraries.
Create a TestBase class to keep all general methods and implementations of the common logic and hooks: BeforeAll, BeforeEach, AfterAll, AfterEach, etc.
Create a TestCase class which extends the TestBase class and implements actual logic of a particular test. Here and further we assume that this class has an aqua Test Case instance so we will update its execution info in aqua web app. Test Case has one Step (Step 1) in aqua.
TestCase class implements simple actions: open www.wikipedia.org web site using playwright actions, do a few clicks there and check actual Web URL:
Using @AfterEach hook in TestBase class connect to aqua and get bearer token from REST GET request sent by playwright
The above token will be needed for POST request to create an aqua Test Case execution. We will use the following endpoint and JSON schema:
https://app.aqua-cloud.io/aquaWebNG/Help#tag/TestExecution/operation/TestExecution_Create
Example implementation in TestBase class for the above request looks as follows:
Copy static void createTCExecution(TestInfo tcInfo, String status){
Map<String, String> headers = new HashMap<>();
headers.put("authorization", "Bearer " + bearerToken);
headers.put("content-type", "application/json");
headers.put("accept", "application/json");
headers.put("Connection", "keep-alive");
APIRequestContext tcExecutionRequest = playwright.request().newContext(new APIRequest.NewContextOptions()
.setExtraHTTPHeaders(headers));
JSONArray finalArr = new JSONArray();
JSONObject json = new JSONObject();
int tcId = Integer.parseInt(tcInfo.getTags().iterator().next());
json.put("Guid", JSONObject.NULL);
json.put("TestCaseId", tcId);
json.put("TestCaseName", JSONObject.NULL);
json.put("Finalize", false);
json.put("ValueSetName", JSONObject.NULL);
json.put("TestScenarioInfo", JSONObject.NULL);
JSONArray stepsArr = new JSONArray();
JSONObject jsonSteps = new JSONObject();
jsonSteps.put("Index", 1);
jsonSteps.put("Name", "Step 1");
jsonSteps.put("StepType", "Step");
jsonSteps.put("Status", status);
jsonSteps.put("Description", JSONObject.NULL);
jsonSteps.put("ExpectedResults", JSONObject.NULL);
jsonSteps.put("ActualResults", JSONObject.NULL);
jsonSteps.put("ActualResultsLastUpdatedBy", JSONObject.NULL);
jsonSteps.put("ActualResultsLastUpdated","0001-01-01T00:00:00");
stepsArr.put(jsonSteps);
json.put("Steps", stepsArr);
json.put("TestedVersion", JSONObject.NULL);
json.put("Status", JSONObject.NULL);
JSONObject execDuration = new JSONObject();
execDuration.put("FieldValueType","TimeSpan");
execDuration.put("Text","20 second");
execDuration.put("Value",20);
execDuration.put("Unit","Second");
json.put("ExecutionDuration", execDuration);
json.put("AttachedLabels", new JSONArray());
json.put("CustomFields", new JSONArray());
json.put("Attachments", JSONObject.NULL);
json.put("TesterId", JSONObject.NULL);
json.put("ExecutionDate", JSONObject.NULL);
json.put("AttachedFiles", new JSONArray());
finalArr.put(json);
APIResponse tcExcutionResponse = tcExecutionRequest.post(appUrl + "api/TestExecution", RequestOptions.create().setData(finalArr.toString()));
assertTrue(tcExcutionResponse.ok());
}
In the above request we are passing “logResult” variable. It represents the actual JUnit test result (“Pass”, “Failed”, “NotRun”, etc.) To obtain this status from JUnit run we implement Test Watcher Interface in the TestBase Class:
In this class we should implement Interface Methods as below:
Copy @Override
public void testSuccessful(ExtensionContext context) {
System.out.println("Call Success extension");
logResult = "Pass";
}
@Override
public void testAborted(ExtensionContext context, Throwable cause) {
System.out.println("Call Abort extension");
logResult = "NotRun";
}
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
System.out.println("Call Fail extension");
logResult = "Failed";
}
We assume that Test Class name is based on actual aqua Test Case Id: e.g in the case above we updated aqua Test Case with Id=270.
This Id variable can be passed from Test Class by using TestInfo from org.junit.jupiter.api.TestInfo ;
After successful JUnit test execution we should see corresponding single Test Case execution in aqua
To be able to run the above Maven Project using GitLab CI/CD we need to create corresponding workflow file .gitlab-ci.yml. Simple Example of .gitlab-ci.yml script is shown below:
Copy image: maven:latest
stages:
- test
maven-build-job:
stage: test
script:
- pwd
- sleep 10
- cd aqua-demo/
- mvn compile
- mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps"
- mvn clean test
Once a GitLab test runner is configured and connected to your GitLab projects we can Run GitLab Pipeline using the above workflow:
Full TestBase class listing:
Copy package com.aqua;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.options.FormData;
import com.microsoft.playwright.options.RequestOptions;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;
import org.json.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashMap;
import java.util.Map;
public class TestBase implements TestWatcher
{
private static Playwright playwright;
private static Browser browser;
private static String appUrl = "https://aqua-auto-aqamasterpla.aqua-testing.com/aquawebng/";
private static String bearerToken;
private static APIRequestContext request;
private static BrowserContext context;
private static TestInfo testInfo;
private static String logResult;
Page page;
static void getBearerToken() {
Map<String, String> headers = new HashMap<>();
headers.put("content-type", "application/x-www-form-urlencoded");
request = playwright.request().newContext(new APIRequest.NewContextOptions()
.setExtraHTTPHeaders(headers));
APIResponse response = request.post(appUrl + "api/token", RequestOptions.create().setForm(
FormData.create()
.set("grant_type", "password")
.set("username","start")
.set("password","default")));
assertTrue(response.ok());
JsonObject j = new Gson().fromJson(response.text(), JsonObject.class);
bearerToken = j.get("access_token").getAsString();
}
static void createTCExecution(TestInfo tcInfo, String status){
Map<String, String> headers = new HashMap<>();
headers.put("authorization", "Bearer " + bearerToken);
headers.put("content-type", "application/json");
headers.put("accept", "application/json");
headers.put("Connection", "keep-alive");
APIRequestContext tcExecutionRequest = playwright.request().newContext(new APIRequest.NewContextOptions()
.setExtraHTTPHeaders(headers));
JSONArray finalArr = new JSONArray();
JSONObject json = new JSONObject();
int tcId = Integer.parseInt(tcInfo.getTags().iterator().next());
json.put("Guid", JSONObject.NULL);
json.put("TestCaseId", tcId);
json.put("TestCaseName", JSONObject.NULL);
json.put("Finalize", false);
json.put("ValueSetName", JSONObject.NULL);
json.put("TestScenarioInfo", JSONObject.NULL);
JSONArray stepsArr = new JSONArray();
JSONObject jsonSteps = new JSONObject();
jsonSteps.put("Index", 1);
jsonSteps.put("Name", "Step 1");
jsonSteps.put("StepType", "Step");
jsonSteps.put("Status", status);
jsonSteps.put("Description", JSONObject.NULL);
jsonSteps.put("ExpectedResults", JSONObject.NULL);
jsonSteps.put("ActualResults", JSONObject.NULL);
jsonSteps.put("ActualResultsLastUpdatedBy", JSONObject.NULL);
jsonSteps.put("ActualResultsLastUpdated","0001-01-01T00:00:00");
stepsArr.put(jsonSteps);
json.put("Steps", stepsArr);
json.put("TestedVersion", JSONObject.NULL);
json.put("Status", JSONObject.NULL);
JSONObject execDuration = new JSONObject();
execDuration.put("FieldValueType","TimeSpan");
execDuration.put("Text","20 second");
execDuration.put("Value",20);
execDuration.put("Unit","Second");
json.put("ExecutionDuration", execDuration);
json.put("AttachedLabels", new JSONArray());
json.put("CustomFields", new JSONArray());
json.put("Attachments", JSONObject.NULL);
json.put("TesterId", JSONObject.NULL);
json.put("ExecutionDate", JSONObject.NULL);
json.put("AttachedFiles", new JSONArray());
finalArr.put(json);
APIResponse tcExcutionResponse = tcExecutionRequest.post(appUrl + "api/TestExecution", RequestOptions.create().setData(finalArr.toString()));
assertTrue(tcExcutionResponse.ok());
}
@BeforeAll
static void berforeAll() {
playwright = Playwright.create();
browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setSlowMo(50));
}
@AfterAll
static void closeBrowser() {
createTCExecution(testInfo, logResult);
context.close();
playwright.close();
}
@BeforeEach
void createContextAndPage(TestInfo testInfo) {
TestBase.testInfo = testInfo;
context = browser.newContext();
page = context.newPage();
}
@Override
public void testSuccessful(ExtensionContext context) {
System.out.println("Call Success extension");
logResult = "Pass";
}
@Override
public void testAborted(ExtensionContext context, Throwable cause) {
System.out.println("Call Abort extension");
logResult = "NotRun";
}
@Override
public void testFailed(ExtensionContext context, Throwable cause) {
System.out.println("Call Fail extension");
logResult = "Failed";
}
@AfterEach
void closeContext() {
getBearerToken();
System.out.println("Aqua TC Id being executed: " + testInfo.getTags().iterator().next());
}
}
Full Test Class listing:
Copy package com.aqua;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(TestBase.class)
public class TestCase270 extends TestBase{
@Test
@Tag("270")
void shouldSearchWiki(TestInfo testInfo) {
page.navigate("https://www.wikipedia.org/");
page.locator("input[name=\"search\"]").click();
page.locator("input[name=\"search\"]").fill("playwright");
page.locator("input[name=\"search\"]").press("Enter");
assertEquals("https://en.wikipedia.org/wiki/Playwright", page.url());
System.out.println("Test Case executed");
}
}