Adding entries to an open Excel spreadsheet during runtime

When running the API test for several thousand entries, I noted that matching up an invalid request with the correct source data could be a needle in the haystack affair. Instead, it would be better to record any errors right alongside the source data in the XLSX spreadsheet file. That can be done by writing to a new cell during runtime.

The process is only a couple of additional lines of code.

To start, a new file needs to be opened using:

FileOutputStream outFile = new FileOutputStream(new File("//Postman Testing//Data Files//pricing.xlsx")

Data can then be written to the sheet using, createCell(5).setCellValue():
The .createCell(5) refers to column 5 in the spreadsheet. I'm using columns 0-4 for source data.

sheet.getRow(loop).createCell(5).setCellValue("ERROR: Failure Message on pricing request " + loop + " " + failure_message)

At the end, all data needs to be written to the workbook:


And like other files, it needs to be closed when finished:

During runtime, if an error is encountered, it is written inline with the source data. When the pricing.xlsx file is opened, the error message and data used to replicate the error is all in one place.

Other articles of interest:

Create a new and random UUID for an API call

When working with an API call, it may be necessary to create a new and valid UUID. This can actually be accomplished in a single line of code.

def order_id = UUID.randomUUID().toString()

This can be sent to the API object as a variable

mydate = new Date()
def order_id = UUID.randomUUID().toString()

//Initialize Order
initialize_order = WS.sendRequest(findTestObject('Initialize Order',
    [('order_id') : order_id,
    ('date') : mydate]))

Other articles of interest:

A quick way to get the duration of a test using Groovy

From a previous test I worked on, I wanted to point out how to record the execution time of a test. It uses the TimeCategory and TimeDuration libraries. Since my API test doesn't run within a Test Suite, it doesn't record the execution time, but it's still possible to get that information using code similar to the following.

import groovy.time.TimeCategory 
import groovy.time.TimeDuration

def timeStart = new Date()

//Test Code goes between the Start and Stop definitions

def timeStop = new Date()
TimeDuration duration = TimeCategory.minus(timeStop, timeStart)

log.logWarning("Execution Time: " + duration)

Other articles of interest:

Data Driven API Testing with Katalon using Spreadsheet Data

In expanding my use of Katalon Studio, I have started working on automated API testing. Like Postman, you can set up a manual API test to confirm a request/response combination. But it's possible to take that further and not only send multiple API requests, but validate the response.

For a really good tutorial on putting together the API test, take a look at this video. It steps through creating the API object and configuring parameters.

Katalon Studio API Testing

Following up from that, the code listed below creates a data driven API test wherein it reads data from an XLSX spreadsheeting using the POI model.

The purpose of the test is to send a proper request for an inventory item and confirm the price of the item is correct. The spreadsheet contains the inventory data and the price of the item.

Additionally, the test records the Response Time and the Status Code of the request. Within Postman this would display as 398 ms 200 OK.

The request is built using data from the spreadsheet and is sent to the API object using the WS.sendRequest command.

Determining the number of rows in the spreadsheet is handled by:
int rowCount = sheet.getLastRowNum()

Reading data from the spreadsheet is done by reading a cell value and assigning it to a variable:
int customer_number=sheet.getRow(loop).getCell(0).getNumericCellValue()
String uom=sheet.getRow(loop).getCell(3).getStringCellValue();

The response time is recorded as:

The status of the response is:

The response is parsed using the JsonSlurper
def api_response = slurper.parseText(api_request.getResponseBodyContent())

The different response items are found using the following method:
String itemPrice = api_response.pricing.tiers[0].price

Parsing the JSON correctly took a couple of tries as I was using pricing.tiers.price to traverse the hierarchy rather than seeing it as an array and using tiers[0].price. Using a site like http://jsonparseronline.com/ got things sorted out.

For output, the test records several pieces of information:
The total number of requests
The number of pricing errors along with status code errors
A finally tally of the success rate

Using the code below, 100 requests can be processed in less than 20 seconds
1500 items can be processed in 5 minutes

This test will continue to expand to validate more data points, but at the moment, it's a solid gauge of whether the API is working, the response time, and accuracy.

import groovy.json.JsonSlurper as JsonSlurper

import org.apache.poi.xssf.usermodel.XSSFCell
import org.apache.poi.xssf.usermodel.XSSFRow
import org.apache.poi.xssf.usermodel.XSSFSheet
import org.apache.poi.xssf.usermodel.XSSFWorkbook

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import com.kms.katalon.core.logging.KeywordLogger as KeywordLogger

import groovy.time.TimeCategory 
import groovy.time.TimeDuration

KeywordLogger log = new KeywordLogger()

def timeStart = new Date()
int responseTimeError, pricingError, statusCodeError, priceAvailableError, messageFailureError=0
int api_response_time
float successRate

def slurper = new JsonSlurper()

FileInputStream file = new FileInputStream ("//Postman Testing//Data Files//pricing.xlsx")
XSSFWorkbook workbook = new XSSFWorkbook(file);
//XSSFSheet sheet = workbook.getSheetAt(1);
XSSFSheet sheet = workbook.getSheet("pricing-100-items");
int rowCount = sheet.getLastRowNum()

'Read data from excel'
for (loop = 1; loop <=rowCount; loop++) {
    //Assign spreadsheet columns to variables
    int customer_number=sheet.getRow(loop).getCell(0).getNumericCellValue()
    int branch_number=sheet.getRow(loop).getCell(1).getNumericCellValue()
    String tier_code=sheet.getRow(loop).getCell(2).getStringCellValue();
    String uom=sheet.getRow(loop).getCell(3).getStringCellValue();
    String item_price=sheet.getRow(loop).getCell(4).getNumericCellValue();
    //Send API Request
    api_request = WS.sendRequest(findTestObject('Pricing Request', 
        [('customer_number') : customer_number, 
        ('branch_number') : branch_number, 
        ('tier_code') : tier_code, 
        ('uom') : uom]))
    //Store Response time
    //If the Status of the request is 200 (OK) process the response
    if (api_request.getStatusCode()==200){
        def api_response = slurper.parseText(api_request.getResponseBodyContent())
        //Store the item price
        String itemPrice = api_response.pricing.tiers[0].price
        //Verify there is no failure message
        String failure_message=api_response.pricing.tiers[0].failure_messages
        if (failure_message!='[]'){
            log.logError("ERROR: Failure Message on pricing request " + loop + " " + failure_message)
        //Verify that Price Available is true within the response
        String price_available = api_response.pricing.tiers[0].price_available
        if (price_available!="true"){
            log.logError("ERROR: Price Available error on pricing request " + loop + " " + price_available)
        //Display pricing details
        log.logWarning('Tier Code: ' + tier_code + ' <---> ' + 'Item Price: ' + itemPrice + ' <---> ' + 'Expected Price: ' + item_price + ' <---> ' + 'Response time: ' + api_response_time)
        if (itemPrice!=item_price){
            log.logError("ERROR: The returned price for request " + loop + " does not match the expected price")
    } else {
        log.logError("ERROR: There was an error with the pricing request " + loop + ". Error code: " + api_request.getStatusCode())
    if (api_response_time>10000){
        log.logError("The server response time is higher than expected with a time of: " + api_response_time)

def timeStop = new Date()
TimeDuration duration = TimeCategory.minus(timeStop, timeStart)

log.logWarning("<--- API Pricing Request Results --->")
log.logWarning(rowCount + " items priced:")
log.logWarning("There were " + responseTimeError + " requests with a higher than average response time")
log.logWarning("There were " + pricingError + " requests with a different price than expected")
log.logWarning("There were " + statusCodeError + " requests with an unexpected status code")
log.logWarning("There were " + priceAvailableError + " requests with a Price Available error")
log.logWarning("There were " + messageFailureError + " requests with a Message Failure error")
successRate = ((rowCount - (Integer.valueOf(pricingError) + Integer.valueOf(statusCodeError))) / rowCount) * 100
log.logWarning("Success rate: " + successRate.round(2) +"%" )
log.logWarning("Execution Time: " + duration)


Other articles of interest:

Determine if a checkbox has been checked/selected

On the surface it seems like an easy test, check if a checkbox has been selected and perform an action. After trying it out, the process doesn't follow the usual pattern.

My first attempt looked like this:

    True action
    False action

That works, but throws an exception when the condition is false. And that looks really bad in the log files.

The correct way to handle the situation is to add the FailureHandling option so you don't get the error message.

In the code below, the status of the checkbox is determined. If it's already checked, enter the search criteria into the input field. If the checkbox isn't selected, enter the search criteria AND check the checkbox.

For my situation, I need that box to be checked. It would be just as easy to add the code that unchecks it or does something else. But the main action happens within the IF statement and the FailureHandling.OPTIONAL. This allows the processing to continue and code flows normally.

if (WebUI.verifyElementChecked(findTestObject('Object Location/checkbox-My Branch Only'),10,FailureHandling.OPTIONAL)){
    WebUI.setText(findTestObject('Object Location/input-Inventory Search Field'), inventorySearch)

} else {
    WebUI.setText(findTestObject('Object Location/input-Inventory Search Field'), inventorySearch)
    WebUI.click(findTestObject('Object Location/checkbox-My Branch Only'))

Other articles of interest:

Recent Comments