0

I'm writing a test using Selenium and JavaScript. I'm new to both, and also new to functional programming and promises. I'm trying to create a function that needs to do 3 things:

  1. Click on an input
  2. Clear the input
  3. SendKeys to input

My current function does not work:

    var clearAndSendKeys = function(driver, elementIdentifier, sendKeys) {
        var returnValue;
        driver.findElement(elementIdentifier).then(function(inputField){
            inputField.click().then(function() {
                inputField.clear().then(function() {
                    returnValue = inputField.sendKeys(sendKeys);
                });                 
            });                 
        });
        return returnValue;
    }

The function would then be called as for example:

    clearAndSendKeys(driver, webdriver.By.id('date_field'), '14.09.2015').then(function(){
        //Do stuff
    });

I expected the variable returnValue to contain the promise from sendKeys. However the function clearAndSendKeys returns the undefined variable before sendKeys is ran. I assume this is because returnValue was never defined as a promise, and so the program does not know that it needs to wait for sendKeys.

How can I make my function clearAndSendKeys return the promise from sendKeys? I'd rather avoid having to add a callback to the clearAndSendKeys function.

Edit: Removed .then({return data}) from the code as this was a typo.

EJS
  • 1,011
  • 5
  • 14
  • 28
  • you need to assign returnValue to the promise, first line should be `var returnValue = driver.findElement(...`, – Saar Sep 14 '15 at 13:06
  • http://jsfiddle.net/arunpjohny/s2b0v8nu/1/ - use callbacks – Arun P Johny Sep 14 '15 at 13:06
  • Hi Arun, why should he use callbacks when trying to use promises? – Saar Sep 14 '15 at 13:08
  • are any/all of those functions ( `.findElement()` `.click()` `.clear()` and `.sendKeys()` ) even asynchronous? I'm guessing `sendKeys` at least returns a promise, do any of the others? – Jaromanda X Sep 14 '15 at 13:37

2 Answers2

4

You have to return each promise from the .then callback:

var clearAndSendKeys = function(driver, elementIdentifier, sendKeys) {
    return driver.findElement(elementIdentifier).then(function(inputField){
        return inputField.click().then(function() {
            return inputField.clear().then(function() {
                return inputField.sendKeys(sendKeys);
            });                 
        });                 
    });
}

The promise returned by .then will resolve to the same value as the value returned from the callback.


See Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference for why your current code does not work. Promises are asynchronous.

Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
4

First of all its probably not the best idea to nest promises, completely defeating their main purpose of eliminating callback hell. then callback can return Thenable object that allows to create nice chains of async operations.

In this case you just need to store reference to input field available as a result of the first async operation in the scope of the main function and then create chain of async operations that can be returned from this function.

var clearAndSendKeys = function(driver, elementIdentifier, sendKeys) {
    var inputFieldRef;
    return driver.findElement(elementIdentifier)
        .then(function(inputField){
            inputFieldRef = inputField;
            return inputField.click();
        }).then(function() {
            return inputFieldRef.clear();
        }).then(function() {
            return inputFieldRef.sendKeys(sendKeys);
        });
}
Stubb0rn
  • 1,269
  • 12
  • 17
  • That global `inputFieldRef` variable is a particularly ugly way of [accessing previous promise results in a .then() chain](http://stackoverflow.com/q/28250680/1048572) though. – Bergi Sep 14 '15 at 13:57
  • Better than the pyramid of of death below!! @Bergi - I for one am interested in how to overcome this ugliness, but can't really see how anything in the link you posted is any more elegant – Jaromanda X Sep 14 '15 at 14:04
  • @JaromandaX: Well ES7 async/await is *definitely* more elegant :-) Regardless, I don't think a second level of nesting (more is not necessary) is that bad, and it could easily be `driver.findElement(…).then(function(inputField) { return inputField.click().then(inputField.clear.bind(inputField)).then(inputField.sendKeys.bind(inputField, sendKeys)); })` – Bergi Sep 14 '15 at 14:09
  • @Bergi - thanks for that. I agree, that's a good compromise between pyramid and outer scope var (not necessarily global) – Jaromanda X Sep 14 '15 at 14:16