5

(I have seen this SO discussion, but was not sure how to apply it to my case, so I’m asking a new question. Hope it’s not a duplicate)

I am testing a form written in Angular using Protractor with Cucumber.js.

So what I would like to do is to tell Protractor to go click on the title of a field (which is a link) then, when that field appears, enter some text in it, then move on to the title of the next field, and so on.

Here is my step in Cucumber:

When I fill the form with the following data
    | field            | content           |
    | First Name       | John              |
    | Last Name        | Doe               |
    | Address          | Some test address |
# and so forth

Here’s a half-hearted attempt at step definition:

this.When(/^I fill the form with the following data$/, function (table, callback) {
    data = table.hashes();
    # that gives me an array of objects such as this one:
    # [ { field: 'First Name', content: 'John' },...]

    for (var i = 0; i < data.length; i++){
        var el = element(by.cssContainingText('#my-form a', data[i].field));
          el.click().then(function(){
                var fieldEl = el.element(by.xpath("../.."))
                    .element(by.css('textarea'));
                fieldEl.sendKeys(data[i].content);
            });
        }
    };
    callback();
});

But of course, this isn't working, because even before Protractor has time to click on a field name and enter the necessary data into the field, the callback function is called, and Cucumber moves to the next step.

So my question is, how can I, using Protractor with Cucumber.js, write the step to insert data defined in the Cucumber table into the form fields? Is this feasible using a for loop?

Community
  • 1
  • 1
azangru
  • 2,644
  • 5
  • 34
  • 55

1 Answers1

4

Your loop is enqueuing promises, so the loop finishes before any "clicking" or sending of keys happens. You need to invoke callback after all the promises have resolved.

I see two solutions (I think). You could keep track of the promises in an array, and then use protractor.promise.all (see http://spin.atomicobject.com/2014/12/17/asynchronous-testing-protractor-angular/) to wait for the array of promises to finish. First save the promise in a var promises = [] array:

var p = el.click().then(function(){ ... });
promises.push(p)

Then outside the loop:

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

Or, you can rely on the ControlFlow to keep your promises ordered in the loop, and call the callback in the last iteration of the loop:

var p = fieldEl.sendKeys(data[i].content);
if (i === data.length - 1) { // beware: you want to check "i" inside the loop and not in a promise created in the loop.
     p.then(callback);
}

Despite all the text to the contrary, I cannot promise that either of these work. Hopefully they at least point you in the right direction.

P.T.
  • 24,557
  • 7
  • 64
  • 95
  • Thank you! I tried the first method that you suggested, and it mostly works. I can see in the browser that Protractor opens the correct fields and inserts the correct data into them, but then shuts down Selenium and fails the step, without explaining the reason. Don't know how to address that problem yet, but it seems to be another matter. Just wanted to point out that the Protractor’s API is called `promise` (as you correctly wrote the first time), not `promises`. Protractor would complain that it "Cannot call method 'all' of undefined" if you try to call `protractor.promises.all`. – azangru Apr 26 '15 at 18:20
  • Curious thing: Protractor/Cucumber *first* fails the step (which can be seen in the console), and *then* goes on clicking on the links and filling in the form fields. – azangru Apr 26 '15 at 18:28
  • out of curiosity, why use `protractor.promise.all` and not just `Promise.all` is there something special the protractor version adds to the mix>? – Chuck van der Linden Aug 25 '16 at 21:44
  • That worked in my case. Using loops inside a synchronous function is tricky and this one helped me complete my code. – Harisha K P Feb 11 '20 at 15:55