0

I am working on an e2e test using protractor. I have a search field that I would like to test. I would like to make sure that every time I search for a keyword that is being shown in the table, the search will filter out everything else.

To achieve this I am using two for loops: the first loop, loops through the items that are being displayed. It grabs the items and types it in the search field. The table display dynamically updates. The second loop, loops through the updated display table to ensure the value of the field matches what we have put in the search box.

My problem is that the second for loop ( inside a second promise) uses the result from the first promise: "testingSuit". "testingSuit" is a fixed value by the time the second promise is executed, as it is not in a nested loop.

How can I use the result of one promise in another nested promise?

describe('Test Suit Search', function () {        
    it('Testing Hearts suits', function () {


        return element.all(by.className('view')).count().then(function(rowCount) {
            //for every suit that we have in the view do:
            for (i=0 ; i< rowCount; i++)
            {

                // first clear the suit search field and 
                element.all(by.className('suitSearch')).get(0).clear();
                //get the suit value
                var testingSuit = element(by.exactRepeater("card in cards").row(i).column("card.suit")).getText();

                // then insert the value in the search field 
                element.all(by.className('suitSearch')).get(0).sendKeys(testingSuit);

                //loop through the results:
                element.all(by.binding('card.suit')).count().then(function(resultRowCount ){
                    for ( j=0; j<resultRowCount; j++)
                    {
                        expect(testingSuit).toEqual(element.all(by.binding('card.suit')).get(j).getText());

                    }
                    return;
                })


            }
            return;
        })
    });

});

Here is the HTML code:

<select ng-model="orderProp">
<option class="option" value="suit">Suit</option>
<option class="option" value="numOrd">Number</option>
</select>
Number search: <input type="text" ng-model="searchTerm.card.number">
search: <input class="suitSearch" type="text" ng-model="searchTerm.card.suit">
<table>

<tr><th><th><th>Number</th><th>Suit</th></tr>
<tr ng-repeat="card in cards | orderBy:orderProp |  filter:searchTerm.card ">
    <td><a class="view"                    href="#/home/number/{{card.number}}/suit/{{card.suit}}">View</a></td>
    <td><a class="delete" href="#/delete/number/{{card.number}}/suit/{{card.suit}}">Delete</a></td>
    <td>{{card.number}}</td>
    <td class="blah" >{{card.suit}}</td>
</tr>


  • I don't completely understand what your issue is. You say: *My problem is that the second for loop ( inside a second promise) uses the result from the first promise: "testingSuit". "testingSuit" is a fixed value by the time the second promise is executed, as it is not in a nested loop.* Are you wanting `testingSuit` to vary with `j` in the inner loop? – Joel Lee Oct 13 '16 at 22:35
  • When the second loop is executed it is as if the code was back-to-back and note nested. So `testingSuit` has the last value it was assigned at the time the first one finished execution. I believe this has to do with how promises work. But I am not sure how to work around that. – bonitoo2005 Oct 13 '16 at 22:45
  • OK, I understand the issue now. Writing up answer. Has to do with how closures work, and the fact that with promises your code is asynchronous, – Joel Lee Oct 13 '16 at 23:02
  • @Bergi - You are right: I should have referenced the previous question, for a fuller explanation of closures. But, although the question here is similar, it is not an "exact duplicate". The OP apparently didn't understand something about how promises (as stated in comments) to realize this was a "create now - execute later" situation. – Joel Lee Oct 14 '16 at 19:25

1 Answers1

0

The problem here is that that the anonymous function below is inside of then:

element.all(by.binding('card.suit')).count().then(function(resultRowCount ){
    for ( j=0; j<resultRowCount; j++) {
            expect(testingSuit).toEqual(element.all(by.binding('card.suit')).get(j).getText());
    }
    // return; // this return is redundant
});

When the first line here executes, the then function executes immediately, and associates the anonymous function that is being passed to it, which will be executed later (assuming the promise is resolved, rather than rejected). That is, the anonymous function is created, but not executed yet. When the anonymous function does eventually execute, it uses the whatever the value of testingSuit is at the time the function executes, not at the time the function was created.

A common way to solve this kind of problem is to use an IIFE (Imediately Invoked Function Execution), like this:

(function(suit) {
    element.all(... for (j=0 ... expect(suit) ...  /
 })(testingSuit);

That is, the second line here is the entire code block for element.all, but replace testingSuit with the introduced parameter suit. This defines an anonymous function that you then execute immediately, passing to it the value of testingSuit.

The reason this works is that you the function you are passing to then no longer references the outer variable testingSuit, but rather a new variable `suit' that still holds the value that was passed at the time the function was created. The "held" value is in a closure.

To see this, output both suit and testingSuit with console.log, right before executing your expect.

Joel Lee
  • 3,656
  • 1
  • 18
  • 21