Automation

New Libraries to Import into Katalon Studio

Up to this point, we have added several new import statements to our projects. One was to use SendKeys, another for writing to the log file and another to mark a test as Failed or in Error. There’s also a few more coming as we get into using the WebDriver, so to keep things up to date, here is a list of import statements to add to your project.

import internal.GlobalVariable as GlobalVariable
//This section is needed to call the Selenium WebDriver
import org.openqa.selenium.WebDriver as WebDriver
import com.kms.katalon.core.webui.driver.DriverFactory as DriverFactory
import org.openqa.selenium.By as By
import org.openqa.selenium.WebElement as WebElement
import com.kms.katalon.core.annotation.Keyword
//This allows for the use of MarkFailed and MarkError
import com.kms.katalon.core.util.KeywordUtil
//This is for SendKeys
import org.openqa.selenium.Keys as Keys
//This is to write to the log file
import com.kms.katalon.core.logging.KeywordLogger as KeywordLogger

KeywordLogger log = new KeywordLogger()

Other articles of interest:

Marking tests as Passed, Failed or in Error using MarkFailed, MarkPassed, MarkError in Katalon Studio

We previously looked at the idea of writing information to the log files. This included the output of variable values and status messages. But, what if you need to stop the test and mark it as failed because the data you needed wasn’t there, or the information you did read indicates a more serious problem? Katalon Studio will mark a test as failed if the command fails to execute, but there is a way to fail the test because of other errors. Again, it takes a little digging to find the right command, but they certainly exist.

To start, an import statement is needed at the top of the project:

import com.kms.katalon.core.util.KeywordUtil

Once that’s in place, a test can be flagged in several ways using one of these commands:

KeywordUtil.markPassed
KeywordUtil.markFailed
KeywordUtil.markError
KeywordUtil.markWarning

* https://api-docs.katalon.com/studio/v4.6.0.2/api/com/kms/katalon/core/util/KeywordUtil.html

With these KeywordUtil commands, you have a great deal of flexibility in controlling the execution of your tests. For this small snippet, the two values I’m working with are written to the log file. I then use KeywordUtil to mark the test as passed or failed if those two values match or not.

log.logWarning('The All filter displays: ' + filterAllUsers)
log.logWarning('The number of users for Pagination displays: ' + footerAllUsers)
if(filterAllUsers==footerAllUsers){
 KeywordUtil.markPassed('SUCCESS: The Filter Results Matches the Pagination Results')
} else {
 KeywordUtil.markFailed('ERROR: The Filter Results Does NOT Match the Pagination Results')
}

The test itself will pass since there is no error in the code or in finding the elements on the page. However, if the values I’m comparing aren’t equal, I need to fail the test so I can look at it manually and determine the problem.

As another example, I need to check one of our sales dashboards. Due to some data inconsistencies during import, it’s possible for the month to display twice on the page, as in January, January, February, February.

Verifying these duplicate values won’t get the test to fail, the code executes correctly. But, now I can force it to error when I evaluate a duplicated month.

//Count the number of months in the table. If there are more than 12, there's an error with duplication
log.logWarning('Number of Months displayed in the Table: ' + TotalRowCount.size())
if (TotalRowCount.size() > 12){
 log.logWarning('There are more than 12 months. This is an error.')
 KeywordUtil.markError('ERROR: There are more than 12 months. This is an error.')
}

//If the above passes, such as an onboarded user, check that the text of the first month isn't the same as the second
//Compare the first two months, display the first three for visual comparison
monthName1 = driver.findElement(By.xpath("//*[@id='byMonth']/div/table/tbody/tr[1]/td[1]")).getText();
log.logWarning('Month:=' + monthName1)
monthName2 = driver.findElement(By.xpath("//*[@id='byMonth']/div/table/tbody/tr[2]/td[1]")).getText();
log.logWarning('Month:=' + monthName2)
monthName3 = driver.findElement(By.xpath("//*[@id='byMonth']/div/table/tbody/tr[3]/td[1]")).getText();
log.logWarning('Month:=' + monthName3)
if (monthName1==monthName2){
 log.logWarning('The two months sampled have the same text. The months appear to be duplicated.')
 KeywordUtil.markError('ERROR: The two months sampled have the same text. The months appear to be duplicated.') 
}

Again, I’m using both log.LogWarning and KeywordUtil.markError to output values and then mark the test as in error.

A test can be passed or failed for almost any reason you can control. As another example, here is a snippet of code that indicates whether a Contact was created successfully if the popup dialog indicates there was a success.

elementPresent=WebUI.waitForAlert(20)
if (elementPresent==true) {
 alertText = WebUI.getAlertText()
 log.logWarning('The title of the alert is:=' + alertText)
 if (alertText=='Successfully Created Contact'){
 KeywordUtil.markPassed('SUCCESS: Successfully Created the Prospect')
 WebUI.acceptAlert()
 } else {
 WebUI.acceptAlert()
 KeywordUtil.markFailed('ERROR: There was an error creating the Prospect')
 }
}

After the contact is created, the code waits for an alert to display. For that alert, read the text. If the text is “Successfully Created Contact” then obviously all is well. If it reads as something else, then a problem has occurred and the test will be marked as Failed so I can review it later.

It’s also possible to brute force end the execution of the test using the throw command. The following will stop the test in it’s tracks and log an error:

throw new AssertionError('ERROR: There was an error creating the Prospect')

As noted before, the count of Passed, Failed and Error tests is displayed on the Log Viewer.

Katalon offers a huge amount of flexibility with the inclusion of KeywordUtil.markFailed and KeywordUtil.markError. It’s now possible to indicate there is a problem even when all the commands execute successfully. Or, you can exit out gracefully if you know the information you just read would cause the rest of the test to fail. An ideal case would be reading the value $0, right before you perform a calculation.

Other articles of interest:

Output status messages and test information by writing to the Log File Viewer in Katalon Studio

One of my more heavily used functions in the Selenium IDE was writing to the LOG file. It was an excellent way of debugging code and displaying what values were read. It was also used to show which test was running and other status information. As a comparison, writing to the log file in the IDE looked like:

LOG.warn("-- Begin name of test Script  --");

LOG.warn("The calculated value is " + storedVars['variable'] + " --- The site value is " + storedVars['variable']);

Katalon has a similar log view, that looks like the following:

log-file

While Katalon Studio has a log file, several of them in fact, finding out how to write to them takes some digging. To get right to it, there is an import file and a keyword definition that need to be added to your Test Case.

The following import statement needs to be added:

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

At the end of the import statements, add the following line:

KeywordLogger log = new KeywordLogger()

Now the log file can be accessed with the following commands:

log.logError("")
log.logFailed("")
log.logInfo("")
log.logNotRun("")
log.logPassed("")
log.logWarning("")

For my tests, I write information I’m interested in into the Warning log. The Info log is far too busy and so is the Passed log. While not warnings, the Warning log gives me a visual output of what the test is reading so I can follow along. The log outputs text as well as the value of variables.

log.logWarning(‘The import date listed on the site is: ‘ + importDate)

Using the IF statement from before, I can output the decision of a conditional loop:

if (customerSalesPlan != ytdSalesPlanTotal) {
    log.logWarning('ERROR: The Budget Sales Plan Amount Does Not Equal the YTD Sales Plan Total')
} else {
    log.logWarning('SUCCESS: The Budget Sales Plan Amount Matches the YTD Sales Plan Total')
}

As the test case is running, I can see what sort of results are being generated and where problems may occur. If a variable comes back as blank or the wrong value, I can see that visually using the log files. I can also write custom messages.

The test itself may run just fine, meaning all the commands executed, but I can use the log files to show the values are in error or outside a given range.

Using the example above, I could change the script slightly to write information to the error log if the two value don’t match. The test executed correctly, but an error was encountered with the data.

The text of my message will now show in the Error log, in red, indicating the test completed, but manual investigation is needed.

I have to admit, just about every test I create uses log.logWarning. In many cases it can be as simple as:

log.logWarning('--- Begin Sales Plan Test ---')

This appears in the log file to show me which test is currently being executed. This information also show up in the Report generated after each Test Suite run. Since I display the value I read from the site and the value I calculated from the script I have confirmation as to what is being tested.

I find the log file helpful to display status information and for debugging. It’s a simple place to write information, but very powerful.

Other articles of interest:

Round and Round with the For..Next in Katalon Studio

Another big improvement with Katalon Studio is the looping mechanisms. The Selenium IDE could use loops with the addition of the SelBlocks plugin, but Katalon and Groovy support that natively and I use it frequently to read values from a table.

From the old BASIC days, the FOR loop is:

For A=1 to 5 (Step 1)

Print("hello world")

Next A

We have quite a few shortcuts and it now looks like this:

for (loop = 1; loop <=5; loop++) {

  println("hello world")

}

To get started, Katalon offers a way to set up a FOR loop in Manual view. If you click the down arrow next to Add, there is an option for Looping Statements and within there, For Loop Statement.

for-next

Choosing this option, creates a FOR loop that looks like:

for (def index : (0..0)) {

}

While either format is acceptable, I actually prefer:

for (loop = 1; loop <=5; loop++)

For my inexperienced mind, it’s a little easier to understand at a glance. It’s also the way SelBlocks did it, so it looks more natural to me.

Following that example, here is a loop that counts from 1 to 10 in order to read the 10 categories listed on a page. For each row it reads the Category name, the YTD Sales value and the YTD Plan value. Since there are always 10 categories, the boundary of loop is set at a specific number.

//Read the Product Category, the YTD Sales, YTD Plan from the table

for (loop = 1; loop <=10; loop++) {

 planCategory=WebUI.getText(findTestObject('Page_/Customer Search/Customer 2018 Plan Tab/Plan Tab Category', [('Variable') : loop]))

 planYTDSales=WebUI.getText(findTestObject('Page_/Customer Search/Customer 2018 Plan Tab/Plan Tab YTD Sales', [('Variable') : loop]))

 planYTDPlan=WebUI.getText(findTestObject('Page_/Customer Search/Customer 2018 Plan Tab/Plan Tab YTD Plan', [('Variable') : loop]))

 log.logWarning('Category: ' + planCategory + ' -:- ' + 'YTD Sales: ' + planYTDSales + ' -:- ' + 'YTD Plan: ' + planYTDPlan)

  }

The FOR loop can also be set to a variable, something determined based on information read from the site.

The code below is an example of the FOR loop being used to read values off the Sales Dashboard and sum them up. The loop uses a secondary counter (monthOfYear.toInteger() – 1) to determine how many iterations are needed. It then reads the Plan Value by passing in the value of loop to the Variable configured as part of the object. In this case, the variable represents the row we are reading.

As part of the loop, the $ and , are removed from the figure, so the values can be summed. This total is then checked against the total read from the site to make sure the calculation is correct.

Since the values being read are of type String the Integer.valueOf command is used to turn the values into Integers so they can be summed correctly.

for (loop = 1; loop <= (monthOfYear.toInteger() - 1); loop++) {

    tempText = WebUI.getText(findTestObject('Page_/Sales Dashboard/YTD Gross Profit Plan Value - By Month tab', [('Variable') : loop]))

    grossProfitMonth = tempText.replaceAll('[$,]', '')

 grossProfitPlanTotal = (Integer.valueOf(grossProfitPlanTotal) + Integer.valueOf(grossProfitMonth))

}

There are plenty of other uses for the FOR loop, but the basic structure is the same. For my cases, the loop is used to read a series of values from a table. Depending on where I place my Variable definition within the Objects XPATH definition, that can be reading down a column or reading across rows. It’s a simple command, but it provides a lot of power.

Other articles of interest:

Conditional Statements – IF .. ELSE IF in Katalon Studio

One of the big advantages of using Katalon Studio is the ability to use the underlying Groovy subsystem. And with that comes the means to do conditional branching, the ubiquitous IF and IF .. ELSE statement. This allows you to perform all sorts of actions based on what values are read from the site.

In my case, if a value is less than 0, it could mean the data import didn’t succeed. That can easily be expressed as:

if (salesPlan <= 0) {

    log.logWarning('ERROR: The Sales Plan Value is $0 or less. A problem may have occurred with the import')

}

The IF statement can be entered directly into the Script editor, but it can also be constructed using the Manual view. If you click the down arrow next to Add, there is an option for Decision-making Statements, and within there is IF, Else, and Else If.

if-then

This is very handy to get started the first couple of times, but the construct will become second nature pretty quickly. Since the above is a simple IF statement, the next step is the IF ELSE statement which look like:

if (salesPlan <= 0) {

    log.logWarning('ERROR: The Sales Plan Value is $0 or less. A problem may have occurred with the import')

} else {

 log.logWarning('SUCCESS: The import appears to have been successful')

}

That covers the idea of something being a success, or something being in error. It’s also possible to string statements together to cover multiple conditions. Below is a snippet of code that determines the first of a string. If it’s a vowel, the word, “AN” should be used instead of the letter, “A”.

if (prepWord==("A")) {

  if (adjWord.substring(0,1)=="A") {

  prepWord="An"

  }

  else if (adjWord.substring(0,1)=="E") {

  prepWord="An"

  }

  else if (adjWord.substring(0,1)=="I") {

  prepWord="An"

  }

  else if (adjWord.substring(0,1)=="O") {

  prepWord="An"

  }

  else if (adjWord.substring(0,1)=="U") {

  prepWord="An"

  }

}

A CASE statement probably would handled this better, but that’s an example for another day.

IF statements can also be nested. In this example, if the values don’t match, determine the margin of error. For my site, it’s not uncommon for figures to be by $1 or $2 due to rounding. This isn’t actually a problem and with an IF statement, it can be accounted for.

if (dailyGrossProfitPlan != siteDailyGrossProfit) {

 log.logWarning('The site and calculated values DO NOT match!')

 calcDifference=(dailyGrossProfitPlan-siteDailyGrossProfit)

 if (calcDifference<=5){

 log.logWarning('The difference is less than $5, most likely a rounding difference and is acceptable')

 } else {

 KeywordUtil.markFailed('ERROR: The difference between the site and the calculated value is greater than $5. Investigation is needed')

 }

}

The IF statement can also be used to determine if elements are visible on the page, and act accordingly. In a test case I recently put together, the Customer Profile can have several tabs listed – Contacts, Notes, Tasks, Quotes and Plan. However, the tabs display under certain conditions and may not always be visible. To start, I made an object with the XPATH for each tab. I then checked to see if the tab was visible. If it was, code related to that tab would run. If the tab wasn’t visible, the test would be skipped.

The code for that is listed below.

//Determine which of the tabs is visible and run the appropriate test

elementVisible=WebUI.verifyElementPresent(findTestObject('Page_/Customer Profile/tab-Contacts'), 10) - determine if the tab is visible on the page. The variable elementVisible will be TRUE if it exists or FALSE if it's not.

if (elementVisible==true){ - If the tab exists, execute the steps between the {}

log.logWarning('--- Contacts tab is available, running test ---')

WebUI.callTestCase(findTestCase('Customer Profile/Contacts'), [:], FailureHandling.STOP_ON_FAILURE) - Call out to another test case to verify items on the tab.

} - End of the IF statement

Finally, the operators used in an IF statement are as follows:

== equal

!= different (or not equal)

< less than

<= less than or equal

> greater than

>= greater than or equal

The IF statement is an incredibly powerful tool to interact with and adapt to the values read from a site. It can be used to determine if a value is too high or low, whether an element exists or not, can be used as part of string manipulation and nearly an endless variety of tests. This was pretty limited within the Selenium IDE and needed the SelBlocks plugin to work. Now, the full power of Groovy can be used to make tests infinitely flexible.

Other articles of interest:

Recent Comments