2

I'm trying to write a function (using WebdriverJS lib) that iterates through a list of elements, checks the names and build an xpath locator that corresponds to that name. I simplified xpath locators here, so don't pay attention.

The issues I'm facing here are: 1) Calling this function returns undefined. As far as I understand, this is because the return statement is not in its place, but: 2) Placing it in the correct place where a synchronous code would normally work, doesn't work for async promises, hence calling this function will return the same undefined, but because the return statement fires before the "driver.findElement" statement.

How should I use the return statement here, if I want to get createdTask variable as a result of calling this function?

var findCreatedTask = function() {

    var createdTask;
    driver.findElements(By.xpath("//div[@id='Tasks_Tab']")).then(function(tasks) {

        for (var index = 1; index <= tasks.length; index++) {
            driver.findElement(By.xpath("//div[@id='Tasks_Tab'][" + index + "]//div[@class='task-title']")).getText().then(function(taskTitle) {
                if (taskTitle == "testName") {
                    createdTask = "//div[@id='Tasks_Tab'][" + index + "]";
                    return createdTask;
                }
            });
        }
    });
};
Mike Trent
  • 23
  • 1
  • 5

3 Answers3

1

You will not be able to return the value that you want from this function because when this function returns, the value is not defined yet.

This is not a problem that you try to return the value in the wrong place, but that you try to access it at the wrong time.

You have two options: you can either return a promise from this function, or this function can take a callback that would be called when the value is available.

Examples

This is not tested but should give you an idea on how to think about it.

Promise

Version with promise:

var findCreatedTask = function (callback) {

    var createdTask;
    return new Promise(function (resolve, reject) {

        driver.findElements(By.xpath("//div[@id='Tasks_Tab']")).then(function(tasks) {

            for (let index = 1; index <= tasks.length && !createdTask; index++) {
                driver.findElement(By.xpath("//div[@id='Tasks_Tab'][" + index + "]//div[@class='task-title']")).getText().then(function(taskTitle) {
                    if (taskTitle == "testName") {
                        createdTask = "//div[@id='Tasks_Tab'][" + index + "]";
                        resolve(createdTask);
                    }
                });
            }
        });
    });
};

and then you call it with:

findCreatedTask().then(function (createdTask) {
  // you have your createdTask here
});

Callback

Version with callback:

var findCreatedTask = function (callback) {

    var createdTask;
    driver.findElements(By.xpath("//div[@id='Tasks_Tab']")).then(function(tasks) {

        for (let index = 1; index <= tasks.length && !createdTask; index++) {
            driver.findElement(By.xpath("//div[@id='Tasks_Tab'][" + index + "]//div[@class='task-title']")).getText().then(function(taskTitle) {
                if (taskTitle == "testName") {
                    createdTask = "//div[@id='Tasks_Tab'][" + index + "]";
                    callback(null, createdTask);
                }
            });
        }
    });
};

and then you call it with:

findCreatedTask(function (err, createdTask) {
  // you have your createdTask here
});

More info

You can read some other answers that explain how promises and callbacks work if you're interested to know ore about it:

Community
  • 1
  • 1
rsp
  • 107,747
  • 29
  • 201
  • 177
  • If there's an error in one of the nested promises will it not get swallowed? – Dave Briand Nov 24 '16 at 12:17
  • 1
    rsp, you are incrementing the `index` with a loop, so you can't use it in the resolution of `getText`. In both of your examples the `index` in `createdTask` will always be equal to `tasks.length + 1`. – Florent B. Nov 24 '16 at 13:09
  • 1
    @FlorentB. Very good point, I focused on getting the value from async call and didn't notice that problem with the code from the question. Thanks for pointing it out, I updated the answer. – rsp Nov 24 '16 at 15:14
1

Here you go, I cleaned it up a bit. This will actually return an error if one is experienced in the nested promises:

var findCreatedTask = function() {
  var Promise = require('bluebird');
  var createdTask;
  return driver.findElements(By.xpath("//div[@id='Tasks_Tab']"))
    .then(function(tasks) {
      return Promise.map(tasks, function(task){
        return driver.findElement(By.xpath("//div[@id='Tasks_Tab'][" + index + "]//div[@class='task-title']")).getText()
      }).then(function(taskTitles){
        for (let i = 0; i < taskTitles.length; i++){
          if(taskTitles[i] === 'testName'){
            createdTask = "//div[@id='Tasks_Tab'][" + i + "]";
            return createdTask;
          }
        }
      });
  });
};

You call it using

findCreatedTask.then(function(res){
   //do your thing
}).catch(function(err){
   console.error(err.stack);
});
Dave Briand
  • 1,684
  • 1
  • 16
  • 16
1

You could first get all the texts with promise.map and then get the position with indexOf :

var map = webdriver.promise.map;

var findCreatedTask = function() {
    var elems = driver.findElements(By.xpath("//div[@id='Tasks_Tab']//div[@class='task-title']"));
    return map(elems, elem => elem.getText()).then(titles => {
      var position = titles.indexOf("testName") + 1;
      return "//div[@id='Tasks_Tab'][" + position + "]";
    });
}
Florent B.
  • 41,537
  • 7
  • 86
  • 101
  • Thanks! How can I use the value afterwards? Unfortunately, the code below returns undefined instead of the desired locator, even though console.log shows a correct value: var createdTask; findCreatedTask().then(function(locator) {createdTask = locator}); driver.wait(until.elementLocated(By.xpath(createdTask + "//div[@class='task-title']"))) – Mike Trent Nov 24 '16 at 15:50
  • You shouldn't use the result in a waiter since the position could match another element. Try to include the text in your XPath instead: `driver.wait(until.elementLocated(By.xpath("//div[@id='Tasks_Tab']//div[@class='task-title'][normalize-space()='testName']")))`. – Florent B. Nov 24 '16 at 16:11