Selenium WebDriver – How To Design Business Workflows In Fluent Style

Overview:

We already have seen the page objects design in the fluent style here. Properly designed page objects are good. But our design is not just yet complete with that. We might need a proper technique to connect these pages to create a business work flow.

Lets see how we could create a business workflow – by using Java8 consumer objects.

Sample Application:

Lets consider an application which has below pages to register a new user. The user has to navigate all these screens to complete the business process.

fluent-workflow-001

As an automation engineer, you would have to create page objects for the above pages and then call them in the sequence to create the workflow.

All these page objects will have some dependencies like WebDriver instances which you need to pass. You also need to ensure that Page is loaded fully before interacting with the page. Adding these repetitive conditions makes the workflow process is less readable. Lets see how we could handle that!

Page Objects:

Lets assume that we have below page objects.

Abstract Page:

public abstract class Page {
    public abstract boolean isAt();
}

User Details Page:

public class UserDetailsPage extends Page{

    public void firstname(){
        System.out.println("entering firstname");
    }

    public void lastname(){
        System.out.println("entering lastname");
    }
    
    public void address(){
        System.out.println("entering address");
    }
    
    @Override
    public boolean isAt() {
        return true;
    }

}

Product Search Page:

public class ProductSearchPage extends Page {

    public void search(){
       System.out.println("searching for product");
    }
    
    public void choose(){
        System.out.println("found a product");
    }
    
    @Override
    public boolean isAt() {
        return true;
    }

}

Order Summary Page:

public class OrderSummaryPage extends Page{
    
    public void enterCC(){
        System.out.println("entering credit card details");
    }
    
    public void enterBillingAddress(){
        System.out.println("entering billing address");
    }

    @Override
    public boolean isAt() {
        return true;
    }

}

Order Confirmation Page:

public class OrderConfirmationPage extends Page{
    
    public void verify(){
        System.out.println("verifying the order confirmation page");
    }

    public void print(){
        System.out.println("printing the order confirmation page");
    }
    
    @Override
    public boolean isAt() {
        return true;
    }

}

Creating A Workflow:

We have created all the page objects required for the workflow. As you have noticed, all the methods are void. That is, they do not return anything. So basically we are not going design the page objects in the fluent style. Instead we would create the workflow in the fluent style as shown here.

public class RegistrationWorkflow {
    
  //This is static to give an entry point
    public static RegistrationWorkflow userDetailsPage(Consumer<UserDetailsPage> c){
        UserDetailsPage p = new UserDetailsPage();
        c.accept(p);
        return new RegistrationWorkflow();
    }

    public RegistrationWorkflow productSearchPage(Consumer<ProductSearchPage> c){
        ProductSearchPage p = new ProductSearchPage();
        this.waitForPageLoad(p);
        c.accept(p);
        return this;
    }

    public RegistrationWorkflow orderSummaryPage(Consumer<OrderSummaryPage> c){
        OrderSummaryPage p = new OrderSummaryPage();
        this.waitForPageLoad(p);
        c.accept(p);
        return this;
    }

    public void orderConfirmationPage(Consumer<OrderConfirmationPage> c){
        OrderConfirmationPage p = new OrderConfirmationPage();
        this.waitForPageLoad(p);
        c.accept(p);
    }

    //handle page sync techniques here
    private void waitForPageLoad(Page p){
        System.out.println("------------------------------");
        System.out.println("Waiting for " + p.toString() + " to load" );
        // wait till p.isAt() returns true with a timeout
    }
}

Test:

Once the workflow is created, rest is simple.  You need to call them in the sequence. Above workflow class should be responsible for creating and managing the reporting and logging functionalities.

RegistrationWorkflow.userDetailsPage(u -> {
    u.firstname();
    u.lastname();
    u.address();
}).productSearchPage(p -> {
    p.search();
    p.choose();
}).orderSummaryPage(o -> {
    o.enterCC();
    o.enterBillingAddress();
}).orderConfirmationPage(o -> {
    o.verify();
    o.print();
});

Test Output:

entering firstname
entering lastname
entering address
------------------------------
Waiting for ProductSearchPage to load
searching for product
found a product
------------------------------
Waiting for OrderSummaryPage to load
entering credit card details
entering billing address
------------------------------
Waiting for OrderConfirmationPage to load
verifying the order confirmation page
printing the order confirmation page

Summary:

Workflow classes would be helpful when you need to manage multiple page objects, WebDriver instance, Reporting, Logging etc and pass them to the subsequent page objects.  Workflow accepts a consumer object which gives more flexibility to the user. For ex:  You need to add some debug statement. You can add that without breaking the chain as shown here which was not easy for the Fluent style page objects!

RegistrationWorkflow.userDetailsPage(u -> {
    u.firstname();
    u.lastname();
    u.address();
}).productSearchPage(p -> {
    p.search();
    System.out.println("debugging"); //debug
    p.choose();
}).orderSummaryPage(o -> {
    o.enterCC();
    o.enterBillingAddress();
}).orderConfirmationPage(o -> {
    o.verify();
    o.print();
});

 

Happy Testing & Subscribe 🙂

 

 

Share This:

6 thoughts on “Selenium WebDriver – How To Design Business Workflows In Fluent Style

  1. Hi Vinoth…how can we use asserts in our test classes here.

    Secondly if I understand correctly the workflow is acting as our manual testcase here so if we have to come back to a previous would the below snippet work (assuming there is a back button on Product search page)

    RegistrationWorkflow.userDetailsPage(u -> {
    u.firstname();
    u.lastname();
    u.address();
    }).productSearchPage(p -> {
    p.search();
    p.choose();
    p.back();
    }).userDetailsPage(u 1-> {
    u1.firstname();
    })
    Kind regards
    Jatin

  2. This is great article. but quick questions. i think one of them was asked above, but no answer yet.
    1. Assertion:
    – don’t see any assertion. I guess you could place assertion statement inside verify function in the OrderConfirmationPage class. but isn’t it really worse idea to have Assertion statement inside the base class instead of test class?
    2. Code Structures: RegistrationWorkFlow class
    – Would you place this class under Test folder? I guess you could create BusinessWorkFlow package under test folder….

    Thanks.

    1. I would keep the workflows under the src/main/test in a different package. Assertions can be included just like the debug statements shown in this post. These are all just high level ideas. It is upto the reader to take it to the level whatever the want if they liked it.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.