Thursday, March 28, 2024

Image recognition based test automation - practicalities

In anticipation of readers time, keeping this post short and simple. Just sharing my experiences with image recognition based automation.

My first encounter with SikuliX was in early 2013. That time it was pretty new, and it used to serve the purpose during test automation, where there was no other means to mimic user actions on the application. The best part was, it used to recognise images based on similarity factor. Lot of time has passed since then, there have been significant development of OpenCv, and numerous new tools been to market.

My second encounter took place in 2017. I supposed to perform some visual testing and also need to validate some images within pdf files. I used Java and OpenCv to achieve the test objectives. OpenCv is used by SikuliX internally to recognize images. But one thing I must admit, learning curve of OpenCv with Java is much longer than with Python. For people without image recognition background, it is tough to master this tool in its raw form. I picked it up because I need to run these tests in headless mode and SikuliX didn't satisfy the criteria.

My third encounter with image recognition based automation was when using Test Complete tool in 2018. It was not as efficient as SikuliX, since the images were searched pixel-by-pixel. Given same test environment setup, it used to work fine, but with different screen resolutions the flakiness starts.

Most recently, I supposed automate an application running on Citrix, and I tried UiPath computer vision. They offers many cool features, like anchors, which works as referential identifiers. During script recording once you select an UI field, it will start suggesting anchors if it can't identify the image uniquely. It will automatically store the images in a repository, saves lot of manual efforts, compared to SikuliX and Test Complete. However, there are some drawbacks. Example, selecting a value from a multi-select listbox. You need to capture individual items, and it gets stored with its Region details. So, unlike SikuliX, you cannot identify an item based on text. So data parameterization is not possible in this case.

At last, I would say SikuliX still have the upper hand, in terms of its simplicity and ease of usage. I have been using the latest version, the actions are performed very fast than earlier, and often I need to put waits to slow it down.

Note: I professionally never used EggPlant tool, only used for training purposes long ago.

Saturday, September 23, 2023

Should we NOT do contract testing on API’s and Messages?

 

Welcome readers on this interesting article. If you are a software QA/SDET professional, and interested or involved in API testing, then the term “Contract Testing” may be familiar with you. Or, you may have heard about the term “PACT”, most famous tool for Contract Testing. Here, I am not going to include a definition of Contract testing, neither I will explain how to use “PACT” tool in detail, I will focus only on the important use cases where we can apply it.

Avoiding surprise chaos between teams:

If you are part of an organization who are heavily using micro-services architecture to design their reusable common business components, then you might have encountered chaotic scenarios when one department made some changes to a micro-service, either by mistake or to enhance it for some specific requirement, may have run their department specific unit and integration tests, and published the service (dev-deployment, not to production) without thoroughly assessing its impact on other departments who are consuming that service by using the reusable component. If the concerned team forgot to inform the other consumers (departments), then it can severely impact the development and testing of other departments, as they will be surprised by the unexpected change. If for some reason, the issue slips through other teams, it could create bigger problem in production.

So, this type of services is a prime candidate for Contract Testing. All consumers of these type of services should mutually define a set of consumer interactions in an open API contract, based on business requirements and user journeys that they are expecting from the provider service and keep a single source of truth. Ideally, this contract document should be placed under version controlling.

Integrating third party APIs or providing a consumable service to other businesses:

Let’s say, you have an online retailer website, and you want to integrate a third-party payment gateway. In this situation, you should seek a consumer contract from the third-party provider and ensure that you are interacting through the predefined interactions and providing right responses to the third-party payment gateway for their consumption. Additionally, you can create a consumer contract and share it with the third party, so that they can ensure that they are sending payment acknowledgements in conformance with the given contract.

Faster dev-integration testing of APIs and messages:

Contract testing (using PACT tool) once integrated in CI/CD pipeline will significantly shorten the build time for API’s and messages (MQ/Kafka etc.), as it replays the provider test for each service in dynamic stubs and perform conformance check against the consumer contracts. Thereby, it identifies the integration issues faster. Remember, leveraging contract testing should not give an excuse to avoid end-to-end functionality testing of the services or messages completely. Rather it will help in optimizing the end-to-end functionality testing, test designers can only focus on the business-critical services after running a risk assessment exercise.

Conclusion:

The ultimate need of contract testing is context dependent; it is beneficial for above use cases. These are some important considerations for Agile/Lean teams:

  • it is not a sensible decision to enforce contract testing for each service/message, rather it should be done for most critical/important services.
  •  training and adoption of contract testing should be done at the very beginning of the project, otherwise it will be challenging.
  •  Contracts generated by “PACT” tool should be reviewed by a duo of Business Analyst and a Technical SME to ensure that it specifies the consumer interactions in conformance with the business requirements. Without adequate review process, it may happen that the contract test passes but it does not conform to the business requirements.
You can now clone the below repository and start learning about how to do automated contract testing using Java, Pact, Junit and Spring Boot:

https://github.com/susnigdha1/LearnPactContractTesting 

Thank you for viewing this post.


Thursday, August 24, 2023

Interview preparation series: Java 8 Streams and BiFunction - scratch work reference

 Hello readers, in this blog, I am going to show some scratch work, based on the recent interview trends in Test Automation. These days most organizations are expecting people to code using Java 8 style, than Java 6 style, and there is nothing wrong in it, Java has been evolving and helping us optimizing our coding effort. In case, you are a test automation professional and yet to upgrade your coding style to Java 8, then this article will surely help you to break the ice.

No more boring texts to read :) , the complete program is self-explanatory. Major highlight is the use of generic type filter functions, Bi-functions, Optional's and generator functions.

package springboot.webdriver.cucumber;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Function;

public class Miscellaneous {

public static void main(String[] args) {
//Creating a linked list. Remember this data-structure does not
// allocate contiguous space like array-list. This linked
// list will be used throughout most part of the below code.
LinkedList<Integer> objSearchableLinkedList = new LinkedList<Integer>();
objSearchableLinkedList.add(5);
objSearchableLinkedList.add(1);
objSearchableLinkedList.add(4);
objSearchableLinkedList.add(3);
objSearchableLinkedList.add(2);
//Creating an object of this class, will be used to call
// non-static methods
Miscellaneous practiceLinkedList = new Miscellaneous();
//This map will be used to showcase some Java8 steam() and
// filter() concepts
Map<String,String> objMap = new HashMap<>();
objMap.put("Tamil Nadu","Chennai");
objMap.put("Karnataka","Bengaluru");
objMap.put("West Bengal","Kolkata");
//To diversify the examples creating this List<Integer>.
// Remember we are
//using List<T> interface object, rather than Array-List /
// Linked-List
//reason behind it is, later we can store array-list or
// linked-list vales in it
int[] pval= {11,20,21,22};
List<Integer> objAlist = Arrays.stream(pval).boxed().toList();

//The element() method always returns the HEAD or the first element
//of the LinkedList. The peek() method also returns the same
System.out.println("element() method returns the first element" +
" of the linked-list, peek() method also does the same "
+ objSearchableLinkedList.element());

//Find if a given number is present in the Linked List and return
// its index, in Java 6 style
System.out.println("The Linked-list is hardcoded with values" +
" between 1 to 5. Now enter a number to be searched" +
" in the linked-list: ");
//You need to enter the value in the execution prompt
//for the program to resume.
int input = new Scanner(System.in).nextInt();
System.out.println("Output returned by Java 6 style:");
practiceLinkedList.findInputInLinkedList(objSearchableLinkedList,
input);
System.out.println("Output returned by Java 8 style:");
//Below example will do the same in Java 8 style
practiceLinkedList.findInputInLinkedListJava8(
objSearchableLinkedList,
input);

//Sort and display the integer Linked-List values in Ascending order
System.out.println("Sorted values of Linked-List in ascending" +
" order: "
+ objSearchableLinkedList.element());
objSearchableLinkedList.sort(Comparator.
comparingInt(Integer::intValue));
objSearchableLinkedList.forEach(System.out::println);

//sort and display elements from a List of integer in reverse order
System.out.println("Sorted values of List in descending " +
"order/reverse: "
+ objSearchableLinkedList.element());
List<Integer> objList = objAlist.stream().sorted(Comparator.
reverseOrder())
.toList();
objList.forEach(System.out::println);

//Using BiFunction and generic filter method to find integers in
// a LinkedList whose values are greater than 2
List<Integer> result1 = practiceLinkedList
.filterList(objSearchableLinkedList,2,
practiceLinkedList::filterByValue);

System.out.println("BiFunction on Integer Linked-List output: "
+result1.toString());

//Using BiFunction and generic filter method to find a string in
// a Map values, where the string length is less than or equal
// to 7
List<String> result2 = practiceLinkedList.filterMap(
objMap,7,practiceLinkedList::filterByStringLength
);
result2.forEach(a->System.out.println("BiFunction on map" +
" output: "+a));

//Below is a use case for using Optional and using it with streams
String testString = new Random().nextInt(1,5)%2==0
?"hello":null; //Generating random string value
Optional<String> optString = Optional.ofNullable(testString);
assert(optString.isPresent());
optString.ifPresent(val -> System.out.println("Length of String: "
+val.length()));
Optional<String> optString2 = optString.stream().findFirst();
optString2.ifPresent(System.out::println);

//Below is use case for Generator function
List<Integer> objIntegerList = Arrays.asList(1,2,2); //input
//Below are generator functions, which we are going to chain together
Function<List<Integer>, Integer> function = (val)->val
.stream().map(X-> X*2)
.mapToInt(B->B).distinct().sum();
Function<Integer,Integer> function2 = (val1) -> val1 *10;
Function<Integer,Integer> function3 = (val2) -> val2*100;
//Chaining generator functions call
Integer result = function3.andThen(function2).apply
(function.apply(objIntegerList));
System.out.println("Result: " + result);

}

/**
* This method will be supplied as parameter in place of the
* BiFunction. The BiFunction will be responsible to apply
* this method to the inputs to the generic filterList method
* and will return the output
* @param val
* @param size
* @return
*/
private Integer filterByValue(Integer val, Integer size) {
if (val > size) {
return val;
} else {
return null;
}
}
//Another support method to be used as BiFunction parameter
private String filterByStringLength(String val, Integer size) {
if (val.length()<=size) {
return val;
} else {
return null;
}
}

/**
* This is a generic type implementation of a custom filter method.
* Why do we need one? Well, the stream().filter() methods can take
* only one variable as argument. If we need two variables as
* argument, then we need to use BiFunction<T, U, R> function().
* If you study the below method, the first argument takes List<T>,
* Second argument will determine the type by the passed value,
* and the third argument is a BiFunction.
* @param list1
* @param condition
* @param func
* @param <T>
* @param <U>
* @param <R>
* @return
*/
protected <T, U, R> List<R> filterList(List<T> list1,
U condition, BiFunction<T, U, R> func) {
//If you want to return different type of value,
//you can modify the return type of result here
List<R> result = new ArrayList<>();
for (T t : list1) {
//The apply(t,condition) method is a special method
// introduced by Java 8 Functional programming
R apply = func.apply(t, condition);
if (apply != null) {
result.add(apply);
}
}
return result;
}
//Another generic type function, which will be applied to a Map
protected <T, U, R> List<R> filterMap(Map<T,T> map1,
U condition, BiFunction<T, U, R> func) {
//If you want to return different type of value,
//you can modify the return type of result here
List<R> result = new ArrayList<>();
for (T t : map1.values()) {
//The apply(t,condition) method is a special method
// introduced by Java 8 Functional programming
R apply = func.apply(t, condition);
if (apply != null) {
result.add(apply);
}
}
return result;
}

//This method uses Java 6 style syntax to find and return an element
// from the linked list
public void findInputInLinkedList(LinkedList<Integer> objLinkedList,
Integer input)
{
ListIterator<Integer> objIterator= objLinkedList
.listIterator(0);
AtomicBoolean notFoundFlag = new AtomicBoolean();
notFoundFlag.set(true);
System.out.println("Remember Peek value in Linked List " +
" never changes, until a pollFirst() method is executed" +
" so, current peek always returns the Head:" +
objLinkedList.peek());
while(objIterator.hasNext()) {
var value = objIterator.next();
if(Objects.equals(value, input)) {
notFoundFlag.set(false);
System.out.println("input value present in the linked list: "
+ input + ", its index is: "+objLinkedList.
indexOf(value));
break;
}else{
notFoundFlag.set(true);
}
}
if(notFoundFlag.get()) {
System.out.println("input value is NOT present in the linked list");
}
}
//This method uses Java 8 style syntax to find and return element
//from the linked list
public void findInputInLinkedListJava8(LinkedList<Integer> objLinkedList,
Integer input)
{
//Atomic values are mutable, and accessible from Lamda functions/methods
AtomicInteger i = new AtomicInteger();
int indexOfElement = objLinkedList.stream()
.peek(val -> i.incrementAndGet())
.anyMatch(target -> Objects.equals(target, input)) ?
i.get() - 1 : -1;
if(indexOfElement >= 0)
System.out.println("input value present in the linked list: "
+ input + ", its index is: " + indexOfElement);
else {
System.out.println("input value is NOT present in the linked list");
}
}
}

Here are the outputs:

element() method returns the first element of the linked-list, peek() method also does the same 5
The Linked-list is hardcoded with values between 1 to 5. Now enter a number to be searched in the linked-list: 
4
Output returned by Java 6 style:
Remember Peek value in Linked List  never changes, until a pollFirst() method is executed so, current peek always returns the Head:5
input value present in the linked list: 4, its index is: 2
Output returned by Java 8 style:
input value present in the linked list: 4, its index is: 2
Sorted values of Linked-List in ascending order: 5
1
2
3
4
5
Sorted values of List in descending order/reverse: 1
22
21
20
11
BiFunction on Integer Linked-List output: [3, 4, 5]
BiFunction on map output: Chennai
BiFunction on map output: Kolkata
Length of String: 5
hello
Result: 6000

Process finished with exit code 0

Test Automation Strategy for Oracle Forms application running in Citrix servers

  Context : There are many product based applications developed using Oracle Forms and Java thick-client architecture, and most of them are ...