In this
article, I will show how to use Async library 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:
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
sorry, its to eventually.equal and the explanation is too simplified... needs more detail
ReplyDeleteI have updated the tutorial, with additional details. Please let me know if you have any inputs.
DeleteGood explanation and examples are really helpful. Thanks
ReplyDeleteThanks. Maybe it's solution for me. Let's check ;)
ReplyDelete