6

Using the protractor-cucumber-framework, I'm trying to click a button a hundred times in one When-step. However, doing this would result in a timeout with the default 5000ms timeout value. I'd rather not change this default using:

var config = function() {
    this.setDefaultTimeout(60*1000);
};

module.exports = config;

This works, but I would rather set the timeout for that single step like so:

this.When(/^I click on the "([^"]*)" button$/, {timeout: 60*1000}, function(text, callback)
{
    // Click the button 100 times
    var button = element(by.partialButtonText('Widget'));
    for(j = 0; j < i; j++) {
        button.click();
    }
    callback();
});

According to the cucumber-js readme this should work, but still results in:

Error: Step timed out after 5000 milliseconds
    at Timer.listOnTimeout (timer.js:92:15)

Any ideas on why this doesn't work?

EDIT: It did work. However, I was using it in the wrong step. Calling click() a hundred times doesn't take so long. It times out on the step after it:

this.Then(/^a new widget is created$/, {timeout: 60 * 1000}, function(callback) {
    // Check if 100 widgets are created
});

Can anyone explain now why the long timeout is necessary in the step after all the calls to click? Is there a more elegant way to have cucumber wait for the buttonclicks to finish?

Jusser
  • 69
  • 2
  • 6
  • The documentation you linked to shows the `Given` function, not `When`. I'm not a cucumber expert, but in the little bit of digging I've done I haven't been able to find anything that indicates that `When` will accept the timeout in the manner you are trying to use. – CodingGorilla Mar 28 '16 at 14:06
  • I edited my question. It does work but not in the way I expected. I think someone with more cucumber expertise is needed to answer my question as I'm still confused why it works the way it does. – Jusser Mar 28 '16 at 14:24

1 Answers1

3

The reason why the timeout doesn't work for you as expected is that your callback() is fired early, even before the completion of the first click(). This is because the click()s are asynchronous and are added to protractor's controlFlow (they get queued to be fired one after the other), but the callback() is not. There are a couple of options to avoid this.

Option #1

You should make sure that the callback() is only fired once all the promises returned by click()s are fulfilled.

You can collect all the promises returned and call your callback() once all of them are resolved. This is what you would want to do if you did not know of the controlFlow:

this.When(/^I click on the "([^"]*)" button$/, {timeout: 60*1000}, function(text, callback)
{
    // Click the button 100 times
    var button = element(by.partialButtonText('Widget'));
    var promises = [];

    for (var i = 0; i < 101; i++) {
        promises.push(button.click());
    }

    protractor.promise.all(promises).then(callback);
});

Option #2

But things can get much easier if you just stash your callback() onto the controlFlow queue like this:

this.When(/^I click on the "([^"]*)" button$/, {timeout: 60*1000}, function(text, callback)
{
    // Click the button 100 times
    var button = element(by.partialButtonText('Widget'));
    for(var i = 0; i < 101; i++) {
        button.click();
    }

    browser.controlFlow().execute(callback);
});

As you can see, when working with Protractor you should make use of its controlFlow to avoid writing asynchronous (looking) code.

The Victor
  • 319
  • 3
  • 13