1

Having trouble to understand how exactly Protractor order of execution works..

If I am having PageObject:InvitePage

And order of execution is defined like this:

InvitePage.EnterUsername()
InvitePage.EnterPassword()
InvitePage.EnterEmail()
InvitePage.Invite();
InviteHelper.waitForEmail()
browser.go(invitationUrl)
...
expect(somecondition)

All page methods are returning protractor promise(for example browser.sendKeys for entering the password) waitForEmail also returns the promise that I have created using:

protractor.promise.defer()

Problem is that waitForEmail get executed first and methods after it don't wait for waitForEmail to finish, which I expected to be true by creating the promise using protractor method...anyway I found solution to it and it looks something like this:

lastMethodBeforeWaitForEmail.then(function(){
    browser.driver.wait(InvitationHelper.waitForEmail(userEmail))
       .then(function(recievedUrl){
         ...
         //methods that I want after
        expect(someCondition)
    });
  });

Pretty ugly don't you think?

Is there a way to do this one more nicely, any suggestions? And which part around async nature of protractor I didn't get? Am I missing something?

getInvitationEmail

var getInvitationEmail = function (emailAddress){
var deferred = protractor.promise.defer();
mailbox.getEmailsByRecipient(emailAddress, function(err, emails) {
  if (err) {
    console.log('>Fetch email - call rejected');
    deferred.reject(err);
  }else{
    console.log('>Email service fetched.')
    deferred.fulfill(emails);
  }
});

return deferred.promise;

};

and then waitForEmail

this.waitForEmail = function(email){
var deferred = protractor.promise.defer();
var timeout;
var interval = 3000;
var timePassed = 0;

var recursive = function () {
  var message = '>Checking for invitational email';
  if(timePassed>0) {
    message = message + ":" + timePassed/1000 + "s";
  }
  console.log(message);
  timePassed += interval;

  getInvitationEmail(email).then(function(data){
    if(data.length>0){
      var loginUrl = data[0].html.links[0].href;
      if(interval) clearTimeout(timeout);
      console.log(">Email retrieved.Fetching stopped.")
      deferred.fulfill(loginUrl);
    }else{
      console.log(">Still no email.");
    }
  });

  timeout = setTimeout(recursive,interval);
};

recursive();

return deferred.promise;

};

Marko
  • 1,874
  • 1
  • 21
  • 36

3 Answers3

3

In Protractor/WebDriverJS, there is that special mechanism called Control Flow, which is basically a queue of promises. If you have a "custom" promise, in order for it to be in the queue, you need to put it there:

flow = protractor.promise.controlFlow();
flow.await(InviteHelper.waitForEmail());

Or:

browser.controlFlow().wait(InviteHelper.waitForEmail());
Community
  • 1
  • 1
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • 1
    @Marko alright, could you please show what is inside the function? – alecxe Jun 09 '16 at 18:42
  • I have edited the question, added waitForEmail method – Marko Jun 09 '16 at 19:01
  • @Marko interesting, I have a similar use case in one of the projects. We have [this `getLastEmail` function](https://gist.github.com/alecxe/9f9c86901d121abf83735ddbcbc70119) and using it via `browser.controlFlow().wait(helpers.getLastEmail("topic")).then(function (email) { ... });`..works for us. Could you check if may be `setTimeout()` is what causing problems here? Thanks! – alecxe Jun 09 '16 at 20:05
0

One question and one remark.

  • Shouldn't you put other methods in the ControlFlow if you want to control the execution flow?
  • JavaScript engines add ; at the end of commands when needed, but it is always better to put them yourself.
  • 1) they are already in the flow, each of them returning protractor.promise, for example browser.sendKeys or element(by.model(...)) 2) I have disabled atom plugin that was warning me when I skip ; :D – Marko Jun 10 '16 at 13:53
0

In waitForEmail you have defined a promise, but you need to insert it into the controlFlow. As you may know, all of the normal webdriver actions click(), getText(), etc already know how to execute in the right order so you don't have to chain all your promises with .then every time.

... the bottom of your function should look like this
recursive();
return browser.controlFlow().execute(function() {
    return deferred.promise;
});

Your ugly solution lastMethodBeforeWaitForEmail.then(function() ... works because it is one way to make sure the waitForEmail promise is executed in the right order, but the above code is the prettiness that you are looking for.

martin770
  • 1,271
  • 11
  • 15