10

I'm writing a Protractor test that has to wait for an element attribute to have a non-empty value and then I want to return that value to the caller function. This has proven to be more difficult to write than I expected!

I am able to correctly schedule a browser.wait() command to wait for the element attribute to have a non-empty value and I have verified that this value is in fact what I am expecting to get inside the callback function, but for some reason, I am not able to return that value outside of the callback function and onto the rest of the test code.

Here is how my code looks like:

function test() {
    var item = getItem();
    console.log(item);
}

function getItem() {
    var item;
    browser.wait(function() {
        return element(by.id('element-id')).getAttribute('attribute-name').then(function(value) {
            item = value;
            // console.log(item);
            return value !== '';
        });
    });
    return item;
}

I can tell that the order of execution is not as I expect it to be, because when I uncomment the console.log() call inside the callback function, I see the expected value printed out. However, the same call in the test() function prints 'undefined'.

What is going on here? What am I missing? How can I get the attribute value out of the callback function properly?

I appreciate your help.

exbuddha
  • 603
  • 1
  • 7
  • 19

2 Answers2

14

I would not combine the wait and the getting attribute parts - logically these are two separate things, keep them separate:

browser.wait(function() {
    return element(by.id('element-id')).getAttribute("attribute").then(function(value) {
        item = value;
        // console.log(item);
        return value !== '';
    });
});

element(by.id('element-id')).getAttribute("attribute").then(function (value) {
    console.log(value);
});

Note that, you may simplify the wait condition this way:

var EC = protractor.ExpectedConditions;
var elm = $('#element-id[attribute="expected value"]');

browser.wait(EC.presenceOf(elm), 5000);
elm.getAttribute("attribute").then(function (value) {
    console.log(value);
});

Just FYI, you may have solved your current problem with the deferred:

function test() {
    getItem().then(function (value) {
        console.log(value);
    });
}

function getItem() {
    var item = protractor.promise.defer();
    browser.wait(function() {
        return element(by.id('element-id')).getAttribute('attribute').then(function(value) {
            var result = value !== '';
            if (result) {
                item.fulfill(value);
            }
            return result;
        });
    });
    return item.promise;
}
Community
  • 1
  • 1
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • 1
    Thanks! With some minor corrections, the last approach works in my case. I was hoping to be able to return the item value straight to the caller function but apparently this is not the right way of thinking when it comes to promises in asynchronous calls. The last line should be `return item.promise;`. – exbuddha Jan 23 '16 at 16:28
2

After doing some more reading about how protractor works with promises and schedules/registers them with the control flow, I found an easier work-around close to the first solution @alecxe provided. Here it goes:

function test() {
  var item = getItem().then(function(item) {
    console.log(item);
  });
}

function getItem() {
  return browser.wait(function() {
    return element(by.id('element-id')).getAttribute('attribute-name').then(function(value) {
      return value;
    });
  });
}

Since browser.wait() returns a promise itself, it can be chained with another then() inside the caller and this way the right order of execution is guaranteed.

exbuddha
  • 603
  • 1
  • 7
  • 19