2

I have a 'select' element in a UI component from which I need to retrieve the selected option (if any). As a beginner in both JavaScript and protractor, I am having trouble figuring out how to accomplish this without a bunch of nested promises:

I have two locators -- one for the selector's current selection and one for all the options:

selector = element(by.model("something.someId"));
this.selectorOptions = element.all(by.repeater("repeat in someOptions | orderBy:'name'"));

getSelectedOption = function () {
    return this.selector.getText().then( function (selectionText) {
        return this.selectorOptions.filter(function (option) {
            option.getText().then(function (optionText) {
                if(optionText === selectionText) {
                    option.getAttribute("value").then(function (value) {
                        // Some logic here which uses the value to return an pojo representing the selection
                    })
                }
            })
        })
    })
};

The above is just awful and I am sure this can be done better. I have looked at a lot of examples, but I haven't found one that involves dealing with nested promises which need to take parameters and then do something conditional based on the value, so I am having difficultly applying them to my situation, mostly because I don't really feel comfortable with asynchronous programming yet. How can I take the mess above and refactor it into something that isn't a nested callback hell?

Selena
  • 2,208
  • 8
  • 29
  • 49
  • `filter` doesn't even really work with promises? – Bergi May 23 '17 at 15:35
  • It doesn't? Well, that complicates things for me. – Selena May 23 '17 at 15:44
  • yeah actually you are using the protractor `filter` method, which can be called in the way you did : http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.filter – quirimmo May 23 '17 at 15:45
  • @quirimmo Ah, thanks, I thought of `Array`s `filter`. But even in protractor, you need to return a boolean somewhere. – Bergi May 23 '17 at 15:46
  • @Bergi oh sure this is totally true and another issue :D – quirimmo May 23 '17 at 15:48
  • Can you not make a wrapper to make it use promises. Something along these lines: return new Promise((resolve, reject) => {... – Amiga500 May 23 '17 at 15:49
  • 1
    but considering how she used that, maybe she was looking for protractor `each` method, not filter: http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.each – quirimmo May 23 '17 at 15:49
  • @Wexoni protractor has his promises. `protractor.promise` . And they are already promises. She could just return the statements instead of wrapping a promise with another promise again – quirimmo May 23 '17 at 15:51
  • 2
    @Wexoni [No](http://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)?! – Bergi May 23 '17 at 16:16
  • lol thx @Bergi I replied without knowing that there was also an article about it as antipattern :D – quirimmo May 23 '17 at 16:20
  • My line of thought was if something is not returning promise, why not make it promisable and use it in that manner. I had no knowledge about the protractor. – Amiga500 May 23 '17 at 16:25
  • how @Bergi shown you, your idea is to wrap something that is already promisable with something else promisable. Did you get the point? it doesn't depend on protractor, it would be the same with plain javascript – quirimmo May 23 '17 at 16:47

1 Answers1

0

Maybe playing a little bit with promises, protractor, arguments and bind you could get it quite cleaner. Then you are using the protractor filter method which needs a boolean to be returned, in order to filter your values. But, from the way you used it, maybe you were looking for each(): http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.each

I didn't have any chance to test the following code, so it may most probably not work :D

selector = element(by.model("something.someId"));
this.selectorOptions = element.all(by.repeater("repeat in someOptions | orderBy:'name'"));

getSelectedOption = function () {
  return this.selector.getText().then(firstText.bind(this));
};

function firstText(text) {
  return this.selectorOptions.filter(filterSelector.bind(this, text));
}

function filterSelector(text, option) {
  return option.getText().then(optionText.bind(this, text, option));
}

function optionText(text, option, optionText) {
  if(optionText === text) {
    return option.getAttribute("value").then(someLogic);
  }
}

function someLogic(value) {
  console.log(value);
  // value should be your value
  // Some logic here which uses the value to return an pojo representing the selection
  // return true or false, filter is still waiting for a boolean...
}

Another version just using arguments without function parameters. Specially follow the arguments which got printed, to see if the order is correct:

selector = element(by.model("something.someId"));
this.selectorOptions = element.all(by.repeater("repeat in someOptions | orderBy:'name'"));

getSelectedOption = function () {
    return this.selector.getText().then(firstText.bind(this));
};

function firstText() {
  console.log(arguments);
  // arguments[0] should be your selectionText
    return this.selectorOptions.filter(filterSelector.bind(this, arguments[0]));
}

function filterSelector() {
  console.log(arguments);
  // arguments[0] should be your previous selectionText 
  // arguments[1] should be your option
    return arguments[1].getText().then(optionText.bind(this, arguments[0], arguments[1]));
}

function optionText() {
  console.log(arguments);
  // arguments[0] should be your optionText
  // arguments[1] should be your selectionText
  // arguments[2] should be your option
    if(arguments[0] === arguments[1]) {
    return arguments[2].getAttribute("value").then(someLogic);
  }
}

function someLogic(value) {
  console.log(value);
  // value should be your value
    // Some logic here which uses the value to return an pojo representing the selection
  // return true or false, filter is still waiting for a boolean...
}
quirimmo
  • 9,800
  • 3
  • 30
  • 45
  • In filterSelector(): this line 'option.getText()' <=== What is option? Where would this be declared? My IDE is happy with everything else, but this is unresolved. – Selena May 23 '17 at 16:43
  • sorry option is arguments[1] as commented above. Updating the answer – quirimmo May 23 '17 at 16:44
  • Why not simply name the arguments? – DonovanM May 23 '17 at 17:15
  • @DonovanM yeah, for not lovers of arguments the other version should work too. Added it above – quirimmo May 23 '17 at 17:27
  • @Selena I added a version above without using arguments, I don't know if the previous one worked but you can take a look also to the other one if you prefer that one – quirimmo May 23 '17 at 17:28
  • @quirimmo Still trying to test it. Getting tangled up in finding the right way to ensure that the page UI components are actually loaded before trying to do anything. – Selena May 23 '17 at 17:40
  • @Selena `var EC = protractor.ExpectedConditions; var defaultTimeout = 5000; browser.wait(EC.presenceOf(myElem), defaultTimeout, 'Element not present until the timeout has been reached');` – quirimmo May 23 '17 at 17:42
  • @quirimmo I tried that, but the program just blazes along and doesn't wait on the expected conditions to yield true. – Selena May 23 '17 at 18:09
  • can you paste me the code that is not working please? – quirimmo May 23 '17 at 18:11
  • isLoaded = function () { return ExpectedConditions.and(... a few expected conditions which are visibility checks); } – Selena May 23 '17 at 18:20
  • And then: load = function () { browser.wait(isLoaded(), 30000)}; return this; – Selena May 23 '17 at 18:21
  • Youcannot chain multiple expectations in that way. Try to start with just one and increase the timeout to 30 seconds, copying my code and passing just one protractor element, you should see the browser wait. For the element. You can check it providing an element with a wrong selector and it should timeout – quirimmo May 23 '17 at 18:27
  • Protractor's API doc implies that you can: http://www.protractortest.org/#/api?view=ProtractorExpectedConditions.prototype.and – Selena May 23 '17 at 18:29
  • Your solution works. I can use this to avoid deeply nested promises. Thanks! – Selena May 23 '17 at 19:34
  • I am happy it helped and I am happy your battle with protractor is going fine :) – quirimmo May 23 '17 at 19:35
  • LOL. I'm willing myself not to run back screaming back to Java Selenium. I. Will. Master. This. Tool. – Selena May 23 '17 at 20:10