Determine if a Spreadsheet Cell contains Numeric or Text data

While working with some text data from a spreadsheet, I needed to read different kinds of SKU data. In some cases it would be a number such as 1313, while in others it might be a mix of text and numbers such as OU812.

Reding the value is normally done with .getNumericCellValue() or .getNumericCellValue(), but what happens when you don't know what the next value would be?

To overcome that, it's possible to the use .getCellType() and adjust the read based on type. Using some previous code, reading values from a spreadsheet and populating a Global List would look like the following:

for (loop = 1; loop <=rowCount; loop++) {
    //Assign spreadsheet columns to variables
    String cellType=sheet.getRow(loop).getCell(0).getCellType()
    //Determine if the cell contains Text or Numeric data
    if (cellType=="1"){
        String cellTextValue=sheet.getRow(loop).getCell(0).getStringCellValue()
        GlobalVariable.inventorySKU[loop]=cellTextValue
    }else{
        long cellIntValue=sheet.getRow(loop).getCell(0).getNumericCellValue()
        GlobalVariable.inventorySKU[loop]=cellIntValue
    }
}

In this case, 1 is String and 0 is Integer or Numeric. Based on the length of the SKU, I used long to store the value.

Additionally, in order to use the Integer value within a form, it needs to be converted to a String using:
String.valueOf(GlobalVariable.inventorySKU[loop])) such as:

WebUI.setText(findTestObject('Object/input-SKU'), String.valueOf(GlobalVariable.inventorySKU[loop]))

Apache POI – Cells

Other articles of interest:

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:

workbook.write(outFile)

And like other files, it needs to be closed when finished:
outFile.close()

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:
api_response_time=api_request.getWaitingTime()

The status of the response is:
api_request.getStatusCode()

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
    api_response_time=api_request.getWaitingTime()
    
    //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)
            messageFailureError++
        }
        
        //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)
            priceAvailableError++
        }
        
        //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")
            pricingError++
        }
    } else {
        log.logError("ERROR: There was an error with the pricing request " + loop + ". Error code: " + api_request.getStatusCode())
        statusCodeError++
    }
    
    if (api_response_time>10000){
        log.logError("The server response time is higher than expected with a time of: " + api_response_time)
        responseTimeError++
    }
}

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)

file.close();

Other articles of interest:

Recent Comments

  • How To Disable the Quicken Registration Prompt (26)
    • Denise Defoor: I have a Quicken SE Version 6 .. My computer crashed a few weeks ago and I finally got it up and running. Now Quicken is telling me I must registered it. What? I have had this program for 20 years. How can I disable the...
    • Greg: For me, holding the *LEFT* CTL + Shift then clicking Online, One Step Update worked. I originally tried holding the right CTL + Shift, and it didn’t work. I’m using Quicken 2006, so I don’t know if it will work...
    • Joe SR>: My monthly income is deposited into my Credit Union account. I use debit whenever possible. I write checks manually and mail them. I use Quicken 2012 off-line only. I have entered all my money and investment accounts. I...
    • Prtet: Never say never….every time I swear I will never use Quicken again, I discover that there are still no viable alternatives. Amazing that there is no decent personal finance software.
    • Joe D.: Holding CTRL + Shift keys and selecting On Line | One Step Update from the main menu worked for my Quicken 2004. I’m grateful that you wrote a synopsis (“Simply put, …”) just beneath the link to the blog...
    • Susan Long: I bought my quicken disc in the beginning and it came with a registration number. I rang the helpline and they gave me the code to put in and talked me through it. It you downloaded your version then you don’t own it...
    • Peter: You might consider running your old version of Quicken on an ancient computer. This is what I have done for years. The newer versions are fraught with problems- criminal, in my opinion. One version made mathmatical errors when...
  • Parsing Strings in Katalon – Split, Substring and Readlines (4)
    • Anjana: Hey thanks for the response It is quite strange but initializing variable for delimiter and it worked String splitFormat = ‘-‘ WebUI.println(var_CampaignDura tion.toString().split(splitFor mat)[0])...
    • Don Pedro: I’m not really sure what the difference is, but I don’t think the “-“ in the split is the same as the one in the date. String var_CampaignDuration = “05/13/2019-06/07/2019&# 8221; String[] parsedDateRange =...
    • Anjana: Hi, I tried the same thing to split the string, but does not work for me, Could you plz point out whats wrong Variable var_CampaignDuration has value “05/13/2019-06/07/2019&# 8221; String[] parsedDateRange =...
    • Ellen: Thanks for sharing!! I like your contributions to Katalon topics.
  • How to Block games by Title and Tag on Steam (2)
    • Marcus861: I made an acc just to say: Same bro
    • JACK: Thanks, same just wanted to block anime games in my discover
  • Create a new and random UUID for an API call (1)
    • Anonymous: Hi ! I found this blog few weeks ago and it’s really helping me out with my tests! Thanks a lot for the Katalon tips and tricks ! Keep up the good work 🙂
  • Working with Dates and Date Formatting in Katalon Studio (6)
    • Ajoo: Thank you for the details. How do i remove leading “0” from dates. i.e. while formatting i receive 04/21/2019, but i need 4/21/2019. (same applies for date)