Saturday, 25 February 2017

Assertion using Cucumber Data Table in Protractor using Async Each libarary


In this article, I will show how to use Async libarary in Cucumber protractor tests when working Cucumber data table.
 
What is Async Library?

Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript. Although originally designed for use with Node.js and installable via npm install --save async, it can also be used directly in the browser.
If you have seen Protractor APIs, most of the APIs are asynchronous. Hence we need Async like libraries to work efficiently.

Cucumber Data Table:

Data Table comes handy when you want to pass combinations of inputs to your step definitions
Given the following users exist:
  | name  | email                     |
  | Scott  | scott@gmail.com     |
  | John   | john@yahoo.com    |
  | Steve  | steve@gmail.com   |


Understanding the problem when using Cucumber Data table without Async : 
Assume, you want to test some objects exists on the page. You may want to pass data table in Cucumber scenario to assert multiple objects.
Suppose, I want to write Cucumber tests for my E-commerce application. 
I want to write a test for shopping cart check out behavior. When I add item to the shopping cart and finally I do check out from the application then I should able to see my shopping cart items are correctly populated.


Cucumber Scenario:

Scenario:  Verify shopping cart check out behavior
When I add some items into my cart
And I check out from the application
Then I should able to see following items in my cart
   |  item        |   price      | quantity   |
   |  shoes      |  200         |   2            |
   |  watch     |   500        |   1            |
   |  mobile   |    1000     |   1            |      


Now for above Cucumber scenario, I have corresponding Cucumber Step definitions.  We are interested in the last step which takes Data table as inputs.
I am following Page object pattern for writing my tests. If you are not familiar with it, you can check it out here

Page Object Pattern:
Within your web app's UI there are areas that your tests interact with. A Page Object simply models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.

Advantages of Page Object Pattern:
The Page Object Design Pattern provides the following advantages
1. There is a clean separation between test code and page specific code such as locators (or their use if you’re using a UI Map) and layout.
2. There is a single repository for the services or operations offered by the page rather than having these services scattered throughout the tests.

Step Definition Implementation without Async:

Following is the Step Definition code. 



1
2
3
4
5
6
7
8
this.Then(/^I should able to see following items in my cart$/, function(dataTable, callback) {
   for( let i = 0; i < dataTable.hashes().length; i++ ) {   
              
     let item = dataTable.hashes().[0].item;          
     myPage.isItemsExists(item).should.eventually.equals(true);
   }  
   callback(); 
 }




1
2
3
4
5
6
7
MyPage.js

this.itemsExists = function ( item )
{
//some protractor locator to check items exists on the page
// returns webdriver.protractor.Promise<boolean>
}

We have two input parameters as below –
    1.  dataTable :  We will get the data that passed below step definition. For reference see our cucumber scenario section defined above.
     2. Callback:  Once our test execution completes we need to notify back to CucumberJS that we are done with the test. So that based on result CucumberJS will mark test Pass or Fail.
If you have given some default time out for functions and your method is taking time more than default timeout then test will fail due function timeout issue.
 Now, I want to iterate on dataTable. DataTable.hashes() gives me list of rows.  For each row, I will check the given item exists on the UI page.
For assertion purpose, I am using Chai and Chai as Promised library.  You need to install this library on your environment. npm install --save chai-as-promised
Instead of manually wiring up your expectations to a promise's fulfilled and rejected handlers, we can directly use chai-as-promised for expecting result for a method which returns promise in the response.

Have you noticed any problem with the test?
Any idea, what is the problem?  
It will always pass in any situation. Ohh God, my manager will kill me for writing a test like this.
First question comes in mind, why it is always passing?  
There is one gotcha with our line number 7, we are invoking callback(). This will pass the control to the CucumberJs.
Is there any issue with line number 7? No, the problem lies in our implementation, we are iterating on items and calling an asynchronous method.
When line number 7 gets executed that time our line number 5 code still executing due to asynchronous implementation.
We should have called callback() once we are done with the all operations on items. How we will know that we have completed operations for all items?
That’s where Async library helps us smartly to do asynchronous implementation effectiverly.

Async libarary comes to rescue us:  

Here is our final solution using Async Each libarary.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
this.Then(/^I should able to see following items in my cart$/, function(dataTable, callback) {   

       async.each(dataTable.hashes(), (row, doneCallback) => {             
            let item = row.item;
            myPage.isItemsExists(item).then( (isExists) => {
                      if(isExists) {                           
                         doneCallback();
                      } else {
                         doneCallback(new Error('Item does not exists on the page  ' + item)) ;
                      }
             });                      

            }, (err) => {
             if (err) {
                  callback(err);
             } else {
                  callback();
             }
           });
  });


Let us understand, how Async.Each solves our problem.

We have passed three parameters to Async.each method –

1.     We are passing collection of items that we want to iterate

2.     Async function which will execute for each object on the collection. We are passing two parameters to this function,  current object of the collection and  callback

3.     Final Callback which will get invoked either our all objects operation completes or if we get any error while executing some operation. This callback is taking “err” as an input parameter which is an Error object.


In case of any error, “err” will have value and if there is no error  “err” will be null.

So, if you have notice unlike earlier implementation, our callback will be passed to CucumberJS once our all operations completes or in any case we get any error. 

Hence, this test will return correct result. 

Conclusion: 
 

I have shown that for asserting Cucumber data table where page object returns promise, it is needed to write smart test which will work for asynchronous implementation.

Using Async.each API, we can have flexibility and we can get callback exactly when all asynchronous operations gets completed. So that test results will correctly captured by the CucumberJS.

Async library is very powerful library, there are some different versions of each. Also, there are some other variations like async.map, async.every. If you are interested then you can look at the Async documentation: https://caolan.github.io/async/docs.html#each
 


6 comments:

  1. sorry, its to eventually.equal and the explanation is too simplified... needs more detail

    ReplyDelete
    Replies
    1. I have updated the tutorial, with additional details. Please let me know if you have any inputs.

      Delete
  2. very helpful article... thanks

    ReplyDelete
  3. Good explanation and examples are really helpful. Thanks

    ReplyDelete
  4. Thanks. Maybe it's solution for me. Let's check ;)

    ReplyDelete
  5. Wow this is really amazing post. Thanks for sharing the useful informative data. I appreciate your difficulty work. Keep blogging. Protractor Training in Electronic City

    ReplyDelete