Showing posts with label Test Automation. Show all posts
Showing posts with label Test Automation. Show all posts

Saturday, August 24, 2024

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 accessible via Citrix (varies from Organization to Organization). Some products have stringent security access policies, and their product owners (Organizations) generally do not allow installing any test automation tools in their product's host server. They provide access to their products only through multi-channel Citrix layers. Example: Oracle Health EHR. These put a lot of limitation to the Test Architect/Principal Engineer/Test Manager, whose given task is to prepare test automation strategy for these types of product applications.

Strategy:

First step is to have a series of dialogues with the organization who owns the product to work out any possibility of getting the required test automation tools installed in the test server of the product. Due to complexity of multi channel Citrix layers, we may need to install some test automation agents at each layer. For example, UiPath remote runtime and OpenText UFT's Citrix Plugin. In case of Robot Framework, we need Python, PIP and all supported libraries to be installed on the Citrix server. Same applies to Test Complete tool.

Second step, if we are at this point, then definitely we did not get much support from the product's owner organization, so we need to rely on relatively unpopular test automation approach, which is Image Recognition. There are many trendy test automation tools that provide image recognition capability. Following are some important factors to consider:

1. Does the existing tools in my organization's inventory have image based testing capabilities?

2. Do we have any budget allocated to your project plan for procuring any new test automation tool? (i.e. Eggplant, a renowned tool for Image Based testing that comes with licensing. UiPath Computer Vision may look appealing, but it has license costs associated and you may need to override the default OCR library, in case the text search and extraction does not fit your need.)

3. Are there Open Source tools that can fit your existing Test Automation ecosystem? (For example: if your company's test automation ecosystem does not use Java or Python backed hybrid test automation frameworks, then you cannot easily integrate SikuliX or Open-CV).

Answer to this question will lead to:

a. How long does it take to build a test framework from scratch to use these open source libraries? 

b. Do we have the required skills within our existing talent pool?

c. Do we have any budget allocated to hire an expert (Contract / Permanent Role)?

d. How long will be the learning curve?

Third step, how are you going to build a repository of Ui Object's images? This is the most important question, since, many of the Oracle forms have common Ui Objects and if you follow kind of page object model (POM), then you will end up building a huge repository that will contain duplicate images. So, defining an image capture strategy is quintessential. Capture an Ui Object's image in such a way that the same could be reusable across the application. 

Fourth step, how are you going to identify the objects uniquely in the Ui? Sometimes, you may find, some Ui Object's are repetitive in the forms. Example, there could be two fields with label "First name", under two different sections on the same form. In these cases a conceptual strategy of an anchor comes into play. For example, we could use the section headers as unique anchors to define a reduced region (to the left, right, top or bottom from the anchor image) on the screen in which we should search for an image for uniqueness.

Final step, how we are going to sale our strategy to the project stakeholders? While creating the test automation strategy, always keep in mind, the amount of efforts invested on test automation should  reduce the manual test efforts gradually, once it is rolled out for usage. This ROI factor always determines the fate of test automation activities.

I hope this is helpful to you, please share your opinions in the comments section. Always refer to the official websites of the tools mentioned in this article.

Wednesday, May 1, 2024

Interview preparation series Java: Are you not getting interview and/or coding test invites?

Are you not getting interview and/or coding test invites? 

These days it is pretty common. So, first of all, I would advice, consider reviewing your CV/Resume. Kindly do not exaggerate listing your skills, by copying from others. The technical panels are smart to judge how much truth is in it. Focus on elaborating on your role, job duties and some of the key challenges that you may have faced in the projects that you have been a part of, your professional achievements and certifications. My advice to freshers is they should put focus on their academic projects, and challenges faced in executing them and the certifications.

Now, if you get an invite for interview/coding test with a very short notice, how much prepared do you think you are? 

If you are among a small set of lucky candidates who received the interview/coding test invites, you still are in a competition.Thus, you need smart preparation to compete fairly, which includes a lot of coding practice on a regular basis. Below are my recommendations for preparing well:

Java:

1. Thoroughly study and memorize the functions under java.lang.Math package. You may ask why? Because over past 10 years, most of the coding tests ask require you to solve either a mathematical or a numerical problem. So without having a solid command over Math package, you can't solve most of the problems.

2. Focus on Java 8 onward features and coding styles. These days Java 6 style coding is old-fashioned and the industry trends follow Functional programming, including asynchronous functions (lamda), bi-functions and its use in stream() operations. In some of the online coding tests, you will be asked to only write the missing part of the code in a method. If you are not familiar with the aforementioned concepts, then you may miss an opportunity to score, because sometimes the asks will be very simple, but by the time you realize it, the opportunity will turn into a lost opportunity.

3. Thoroughly study and memorize the functions under java.util.Stream package. You may ask why? Again, most coding tests moved to Java 8 and above syntax. So, the expectation is to solve problems using stream() operations. Practice using Array, Strings, charArray, collections, (i.e. List, Set) and map. The idea is to determine whether you are familiar with the java data-structure and collections framework or not, and if you can apply it whenever appropriate in a programming context. 

4. Bitwise operations, sometimes most neglected by the students and professionals, however Bitwise operations are very important to master if you are a Java developer or going to become one. Brush up the bitwise operations and practice the number conversions including Octal, Hexadecimal and Binary numbers, bitwise shift operations, XOR operations. This topic always finds its place either in the online coding test or in the interview process.

5. You might have learned about Data-Structures: Stack, Queue, Dequeue, LinkedList, Tree etc. Do you know when to use it to solve a programming problem? Have you tried using the built-in data-structures in Java in any of your academic or professional code? Being honest with yourself, if you feel you are not comfortable with applying Data-structures, then revisit the concepts, look out for programming practice problems and set out sometime everyday to do practice for at least an hour.

6. None of the programming is complete without its File operations, and Java is not an exception. So please read through java.io.File package, memorize the functions and its parameters, and understand when to use which technique to read and write to files. The interviewers are sometimes more interested in understanding how much you know about the file operation efficiencies.

7. Without DB connection, a standalone program does not deliver value. So, you need to master JDBC connection and Spring Boot alternatives. In the interviews, you may expect questions on DB operations with respect to microservices development. So, getting yourself familiar with MySQL, Postgre, MongoDB will help a lot.

8. Exception handling and debugging is an integral part of any programming language. So, you should be able to tackle questions around efficient ways of handling errors and exceptions, including raising custom exceptions.

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

Friday, August 18, 2023

How to build a test automation framework using Playwright, Spring boot, Java, Cucumber and Junit5

In this article, I will be guiding you step by step in creating a test automation framework using Playwright, Spring boot, Java, Cucumber and Junit5. Additionally, I will explain about Aspect Oriented Programming (AOP) for cross-cutting features (like logging) and using Lombok annotations for simplification and enhancing readability of the code.

What do you need before starting?

I always recommend to use latest tech to explore full potentials of various software's. Its absolutely fine if someone disagrees with this approach 😃

  • A 64 bit system with at least 4GB RAM (8 GB preferred).
  • MacOS - Monterey or Windows 10 and above
  • Java 11 or above installed
  • IntelliJ Community edition (latest version preferred) installed
  • Docker Desktop installed
How to create a Spring boot project?

The simplest way, is to visit this site: https://start.spring.io/, then select from the available options, enter value for Group, remember this will become your parent package. Based on your requirement, you may decide to enter Artifact, Name, Description.

Here, I have selected Project as "Maven". If you want to
 experiment with "Gradle-Groovy" you are most welcome, do leave a comment  and let me know if you faced any issue, it will help me gain some knowledge.

For Language, I have selected Java, as our objective is to write code using java. It is recommended to use latest Spring Boot version, as it will always have new features in it.

Packaging type "Jar" is selected by default, and I don't have any objective to change it, so left it as is. Java version also I left as is.

You might have noticed, there is an option to add dependencies:

This is a useful option, through which we can add a lot of needed dependencies for spring boot , which will be automatically added to the maven pom file, once we click on the "Generate" link.

I will be adding Lombok dependency as follows:

Click the "Add Dependencies" link, then select Lombok and press enter.








Now, you can see that Lombok has been selected as a dependency:






Now you are ready to create the project. Click "Generate" button. It will automatically download the project skeleton as a zip file. The file name will be as per the "Name" parameter which you have passed.

 











This is how the project folders will look like. Until unless, you are planning to create some Micro-services for your hands on with Playwright API testing, you may remove the "main" folder from the project, before importing it as a maven project in IntelliJ.

What maven dependencies do we need to add?
  • Cucumber-java
  • Cucumber-spring
  • Cucumber-junit-platform-engine
  • Playwright
  • junit-platform-suite
  • Commons-lang3
  • aspectjweaver
You can refer to the Maven POM file: POM

Now, I will explain how to create Playwright browser using factory design pattern. The code is available in my GitHub repository:   PlaywrightTestAutomationFramework

Step 1:

Create a package (folder), called configuration and park your Cucumber and Spring configuration classes in it.

import io.cucumber.spring.CucumberContextConfiguration;
import org.springframework.test.context.ContextConfiguration;

@CucumberContextConfiguration
@ContextConfiguration(classes={TestConfig.class})
public class CucumberContextConfig {

}
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("springboot.playwright.cucumber")
public class TestConfig {

}
Step 2:

Create a package (folder) to park all interfaces and classes that are responsible for creating and supplying Playwright bean. Next, create a Java Interface, which will supply the playwright browser factory bean:


Here, I have created some method definitions. 

setPlaywrightBrowser(String browserType) method will take the browser type as input, say Chrome/Firefox etc. and will initialize the Playwright browser context (native to Playwright).

getPlaywrightBrowser() method will return the playwright bean browser context.

setPlaywrightBrowserContext(), getPlaywrightBrowserContext(), setPlaywrightPage() and getPlaywrightPage() methods are created for support purposes, in case the user need to access these from the factory bean. setTracing(Boolean) and isTracingOptionSet() are also support methods, to turn the browser tracing on/off.

Now, create a class that will implement the interface:

The first line within the class is very important:  Playwright playwright = Playwright.create();

The rest of the code is self-explanatory, carefully read and understand how it Overrides the PlaywrightBrowser interface, created in Step 1 above.

import com.microsoft.playwright.*;
import java.util.Objects;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;

@SuppressWarnings({"unused","cast"})
public class PlaywrightBrowserSupplier implements PlaywrightBrowser {
Playwright playwright = Playwright.create();
Browser browser;
BrowserContext browserContext;
Page page;
//This boolean value is used for introducing Playwright tracing
Boolean isTracingSet=false;

public PlaywrightBrowserSupplier(String BrowserType, Optional<Boolean> tracingOption){
setPlaywrightBrowser(BrowserType);
//This method checks the optional tracingOption value and accordingly turns on
// Playwright browser tracing
setTracing(tracingOption.orElse(false));
}

/** This method will initialize the PlaywrightBrowser bean (Facade design pattern)
* with appropriate browser type
* Author: Susnigdha Chatterjee
*/
@Override
public void setPlaywrightBrowser(String browserType)
{
switch(browserType.toLowerCase()) {
case "chrome":
//Below line will launch chrome browser in non-headless mode
//browser = playwright.chromium().launch(new BrowserType.LaunchOptions()
// .setHeadless(false));
//If you wish to execute the code in Headless mode then please un comment
// below line*/
browser = playwright.chromium().launch();
break;
case "firefox":
browser = playwright.firefox().launch();
break;
}
setPlaywrightBrowserContext();
setPlaywrightPage();
}

@Override
public Browser getPlaywrightBrowser() {
return browser;
}

@Override
public void setPlaywrightBrowserContext() {
browserContext=getPlaywrightBrowser()
.newContext();
}

@Override
public BrowserContext getPlaywrightBrowserContext() {
return browserContext;}

@Override
public void setPlaywrightPage(){
page=getPlaywrightBrowserContext().waitForPage(()->getPlaywrightBrowserContext()
.newPage());
}

/**This method will help to retrieve the Playwright page from the PlaywrightBrowser bean
*/
@Override
public Page getPlaywrightPage(){
return page;
}

@Override
public void close() {
page.close();
browserContext.close();
playwright.close();
}

/**
* This method will be called from Hooks class, from cucumber After hook
* @author Susnigdha Chatterjee
* @return byte array which holds the screenshot
*/
@Override
public byte[] captureScreenshot(){
Path objPath = Paths.get("target/screenshots/Screenshot_"+ LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("ddMMyy_hhmmss"))+".png");
return page.screenshot(new Page.ScreenshotOptions().setPath(objPath)
.setFullPage(true));
}

/**
* This method will be called from Constructor to set the Playwright browser tracing on
* @author Susnigdha Chatterjee
* @return byte array which holds the screenshot
*/
@Override
public void setTracing(Boolean option){
if(option && !Objects.isNull(browserContext)){
browserContext.tracing().start(new Tracing.StartOptions()
.setSnapshots(true));
isTracingSet = true;
}
}
/**
* This method will be called from Hooks class's After annotation to decide if
* Playwright browser tracing needs to be turned off
* @author Susnigdha Chatterjee
* @return byte array which holds the screenshot
*/
@Override
public boolean isTracingOptionSet(){
return isTracingSet;
}
}

Finally. create a "Configuration" class, that will be responsible for creating the factory bean. Pay attention to the annotations used. @Configuration will inform Spring that it has Bean definitions. @Slf4j annotation comes from Lombok, which will use Spring's default Log Back mechanism to generate console logs. @SupressWarnings will suppress the listed unwanted warnings. @Bean annotation indicates that the method will create a bean and @ScenarioScope will indicate that this bean will be available across Cucumber scenarios. Rest of the code is self-explanatory, you need to read :) . Do leave a comment for any doubts.
import io.cucumber.spring.ScenarioScope;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Optional;

/**
* Using factory design pattern. The PlaywrightBrowser interface serves as the factory.
* PlaywrightBrowserSupplier implements the PlaywrightBrowser interface based on
* the browser type supplied using the browserType parameter of
* testconfigurations.properties file
*/

@Configuration
@Slf4j
@SuppressWarnings({"unused","cast"})
public class PlaywrightInitializer {
/** Using Facade design pattern to initialize Playwright page using
* custom built PlaywrightBrowser interface
* @author Susnigdha Chatterjee
*/
@Bean(name="PlaywrightBrowser", destroyMethod = "close")
@ScenarioScope
public PlaywrightBrowser init() {
//Creating the bean of type PlaywrightBrowser using its implementing
// class PlaywrightBrowserPage
log.info("Creating PlaywrightBrowser bean");
//Added new Optional parameter to the constructor method
return new PlaywrightBrowserSupplier(System.getProperty("browser"),
Optional.of(Boolean.valueOf(System.getProperty("tracing"))));
}
}
Step 3:

Create a package to park your Cucumber hooks, and place related classes in it. In this example class, I am attaching screenshots for each cucumber scenario, and also determining if we need to stop tracing.
import com.microsoft.playwright.Tracing;
import io.cucumber.java.Scenario;
import lombok.extern.slf4j.Slf4j;
import springboot.playwright.cucumber.playwright.PlaywrightBrowser;
import io.cucumber.java.After;

import java.nio.file.Paths;

@Slf4j
public class hooks {
PlaywrightBrowser playwrightBrowser;
hooks(PlaywrightBrowser browser) {
this.playwrightBrowser=browser;
}

@After
public void afterScenario(Scenario scenario)
{
//Capturing screenshot irrespective of whether the test pass or fail
//This approach will ensure there is always evidences for the tests outcome
scenario.attach(playwrightBrowser.captureScreenshot(),
"image/png","screenshot");
log.debug("Attaching full page screenshot, after the scenario");

if(playwrightBrowser.isTracingOptionSet()) {
playwrightBrowser.getPlaywrightBrowserContext().tracing()
.stop(new Tracing.StopOptions()
.setPath(Paths.get("target/traces.zip")));
}
}
}
Step 4:

Create a package to park your Cucumber Junit5 runners:
Pay attention to the annotations used. Also look into the setUp method, from this method we will set system properties, which will be used by the PlaywrightInitializer class, we created earlier.

import io.cucumber.java.Before;
import lombok.extern.slf4j.Slf4j;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;

@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("/features")
@ConfigurationParameter(key="cucumber.plugin",
value = "html:target/CucumberTestExecutionReport_Chrome.html")
@Slf4j
public class runnerChrome {
@Before
public void setUp() {
//You could pass these property values using application.properties file also.
//I am writing it in this way to make it easy to understand for those who have
// less technical knowledge
//and willing to learn and experiment more
System.setProperty("browser","chrome");
System.setProperty("tracing", String.valueOf(true));
log.info("Passing browser property to PlaywrightBrowser bean");
}
}
For cross browser execution, create another runner class and set the browser property to a different browser.

Step 5:

Create two package, one for Step Definitions and the other for PageObjects, then add the needed classes.

Step 6:

Create a package for AOP, and park related classes in it:

The @Aspect and @Component annotation makes this class as an aspect. The @Before annotation is defined with a point cut, that will attach to all the methods of pageObjects.PageObject class. The associated JoinPoint will then be used to log the method signatures. It describes how to create common logging. The @Around annotation is defined with  a point cut on Playwright's native package to capture and throw any exception, this is kept as an example to show that we can write custom exception handlers using AOP

import io.cucumber.spring.ScenarioScope;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Slf4j
@Component
@ScenarioScope
public class CommonLogger {
@Before("execution(* springboot.playwright.cucumber." +
"pageObjects.PageObject.*(..))")
public void putCommonLog(JoinPoint joinPoint) {
log.debug("Invoking this method from PageObject class instance: "
+ joinPoint.getSignature().getName());
}

@Around("execution(* com.microsoft.playwright.*.*(..))")
public Object exceptionHandlerWithReturnType(ProceedingJoinPoint joinPoint)
throws Throwable{
Object obj;
obj = joinPoint.proceed();
return obj;
}
}
If you have followed the contents so far and also checked out the associate GitHub repository, then I am sure you have learned step by step how to design a test automation framework, using Playwright, Spring boot, Java, Cucumber and Junit5.

Thursday, August 10, 2023

Why should Test Automation Engineers learn Python?

 Introduction:

This question could be bothering a lot of test automation strategists. Java, C#, Groovy and JavaScript’s are the major programming or scripting languages dominating the test automation space for long. I also explained how a programming languages are chosen for test automation in my previous blog. Checkout this link, here.

So, what’s special about Python? In short, Python is an interpreted language, supports majority of operating systems with .NET framework installed, supporting high level programming, including object-oriented programming and offers numerous choices of powerful libraries for AI and ML, which could be integrated into your automation framework to make it AI/ML enabled. Both PyTest and RobotFramework are very popular, easy to learn, powerful test automation frameworks built on Python. RobotFramework supports a wide range of automation capabilities, like UI, E2E, RPA, Salesforce, Workday, SAP, API and Mobile. PyTest backed frameworks are extensible using Python libraries.

The dilemma on Test Automation Engineers minds:

First is assertiveness about there won’t be any decline on Java/C#/JavaScript automation demands. For example: “I have been working with Java backed test automation from past 10 years, it won’t be easy to acquire Python skills and become proficient in it within few weeks” – such proposition is not always true. Because, you might have worked with only core-java with Selenium / Appium without Spring or Hibernate frameworks, you might have only used TestNg and not Junit, you might used Junit 4 but not Junit 5. You might not try Lombok annotations, running batches with Jsch and reading console logs, connecting to JDBC and executing SQL queries, using JMS for message queues, integration with Swager/pact, JSON/XML parsing. These all are related to Java and required for E2E automation, if you did not work in all these areas in your 10 years journey, and you are not confident on your proficiency in these areas, then you cannot justify your love and commitment with Java.

In today's world, the test automation demands are volatile, recently the curve is going up for C# and JavaScript backed test automation, and it’s going down for java. Python backed automation demand is at equilibrium.

Second, “the syntax is completely different and not easy to remember, not as friendly as java” – such proposition is also not always true. The basic of all programming languages remain same, if you create own mental barriers, then you will never be able to focus on the learning. The Generative AI, can write better code than most of us, if it can learn, then why can’t we?

Our future automation will include more of AI/ML:

As more and more organizations undertaking their AI/ML enablement journeys, our future automation work will involve a lot more testing around AI/ML training data models, predictable complex api’s, big data analytics and numerical analysis. Python is in forefront, and a language of choice for AI/ML programming. So, the more delay we do to appreciate these facts, we will be fallen behind and will repent missing out on the growth opportunities in our career.

Is Python easy to learn:

I would say yes, otherwise in India it should not feature in School level curriculum. From my personal perspective also, yes, it is easy to learn with an open mind, overcoming our preferred programming language biases and psychological resistances, focusing on important semantics which helps us learn by analogy.

Below example explains a simple python program to help you learn by analogy:

#Copy the code in a file and save with .py extension

#Ensure Python 3.11 is installed in your system

#To execute the file, use any command line tool, type: python <filename>.py

#At first, we are creating a class file, with two methods, "init" represent a parameterized constructor method, and "str" represent a special method which instructs how to print the class objects in output, similar to overriding "toString()" method in java. As you can see, there is no need to specify access modifiers, return type, semicolon, curly braces. Python follows strict indention, as long as you are adhering to it, everything works fine. The "brand" and "engineCapacity" are the instance variables.

class Car:

    def __init__(self, brand, engineCapacity):

        self.brand = brand

        self.engineCapacity = engineCapacity

    def __str__(self):

        return f"Brand = {self.brand} and Engine_Capacity = {self.engineCapacity}"

#The "main" method is equivalent to Java/C# main method, but lot simpler in its declaration. The below code is responsible for running the Python program

if __name__ == "__main__":

    #Creating two Car objects

    firstCar = Car("Maruti","1000CC")

    secondCar = Car("Lamborgini","2500CC")

    #Creating a list with the two Car objects created above

    cars= [firstCar, secondCar]

    #Loop construct is similar to "for each" in java,

    for x in cars:

        print(x)

    #The len(list/string) method returns the length of a list or string

    print("Total items in cars list: ", len(cars))

    #Python lists support pop method, which can remove an item from a list with the specified index

    cars.pop(1)

    print("Removed 2nd item from cars array using list pop method, so Lamborgini will no longer be listed")

    for x in cars:

        print(x)

        print("index of "+ str(x) +" is "+ str(cars.index(x)))

    print("Using list append method to add one car to the cars list, the item will be added at last")

    #We can append an item to the tail of a list using the append method

    cars.append(Car("Hyundai","1299CC"))

    for x in cars:

        print(x)

    print("Using list insert method to add one more car to the cars list, at index 1")

    #We can append an item to a given index in a list using the append insert method

    cars.insert(1,Car("Kia","1200CC"))

    print("Present items in cars list")

    for x in cars:

        print(x)

    #Below code iterates a string in a reverse method, there are more simple techniques available

    #to achieve the same

    ms = "Hello"

    for x in range(len(ms)-1, -1, -1):

        print(x)

        print(ms[x])

    #Below code removes duplicate numbers from the list

    my_list = [1, 2, 4, 4, 1, 4, 2, 6, 2, 9]
    for i in range(0,len(my_list)):
        for j in range(i+1,len(my_list)):
            if (j<len(my_list) and (i == my_list[j] or my_list[j] in my_list[0:j])):
                del my_list[j]
    print("The list with unique elements only:")
    print(my_list)

Summary:

In this blog, I just tried to establish a proposition, discussed various aspects around it and cited some  examples. Hope you will find the post useful and share with your friends. Do write comments to share your opinion, it matters most.

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 ...