Andrew Yochum Andrew Yochum - 1 year ago 84
Javascript Question

Resolving promises in Protractor and Cucumber using Chai as Promised

Lately a colleague and I have had some disagreements on the "right" way to implement Cucumber step definitions using Protractor and Chai as Promised. Our contention comes from a mutual lack of understanding of precisely what is going with promise resolution in the context of Cucumber.

We're testing against an AngularJS application, so resolving promises and asynchronous behavior is a necessary evil. The biggest problem we've had is forcing synchronous test behavior and getting Cucumber to wait on promises between step definitions. In some cases, we've observed situations such that Cucumber seems to plow straight through step definitions before Webdriver even executes them. Our solutions to this problem vary...

Consider the hypothetical scenario:

Scenario: When a user logs in, they should see search form
Given a user exists in the system
When the user logs into the application
Then the search form should be displayed

Most of the confusion originates in the Then step. In this example the definition should assert that all fields for the search form exist on the page, meaning multiple isPresent() checks.

From the documentation and examples I was able to find, I felt the assertion should look something like this:

this.Then(/the search form should be displayed/, function(next) {
expect(element(by.button('Run Search')).isPresent());

However, my colleague contends that in order to satisfy promise resolution, you need to chain your expects with then() like this:

this.Then(/the search form should be displayed/, function(next) {
element(by.model('searchTerms')).isPresent().then(function(result) {

}).then(function() {
element(by.model('optionsBox')).isPresent().then(function(result) {

}).then(function() {
element(by.button('Run Search')).isPresent().then(function(result) {

The latter feels really wrong to me, but I don't really know if the former is correct either. The way I understand eventually() is that it works similarly to then(), in that it waits for the promise to resolve before moving on. I would expect the former example to wait on each expect() call in order, and then call next() through notify() in the final expect() to signal to cucumber to move on to the next step.

To add even more confusion, I've observed other colleagues write their expects like this:

expect(some_element) {
expect(some_other_element) {
expect(third_element) {

So the questions I think I'm alluding to are:

  • Is any of the above even kinda right?

  • What does eventually() really do? Does it force synchronous behavior like then()?

  • What does and.notify(next) really do? Is it different from calling next() inside of a then()?

  • Is there a best practices guide out there that we haven't found yet that gives more clarity on any of this?

Many thanks in advance.

Answer Source
  • Your feeling was correct, your colleague was wrong (though it was a reasonable mistake!). Protractor automatically waits for one WebDriver command to resolve before running a second. So in your second code block, element(by.button('Run Search')).isPresent() will not resolve until both element(by.model('optionsBox')).isPresent() and element(by.model('searchTerms')).isPresent() are done.
  • eventually resolves promises. An explanation is here:
  • I do not believe it's different from putting next() inside of then()
  • I do not believe there is a best practices guide. Cucumber is not a core focus of the Protractor team, and support for it is largely provided by the community on github. If you or someone you know would like to write a best practices guide, we (the protractor team) would welcome a PR!