Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protractor browser.wait doesn't work with element(by()) on Angular

For what reason(s) could this code fail (no element found)...

   element(by.id('loginButton')).click(); // triggers route change
   browser.wait(element(by.tagName('myComponent')).isPresent(), 10000, 'timeout');
   element(by.tagName('myComponent')).click();

...while this code works ?

   element(by.id('loginButton')).click(); // triggers route change
   const eC = protractor.ExpectedConditions;
   browser.wait(eC.visibilityOf(element(by.tagName('myComponent'))), 10000, 'timeout');
   element(by.tagName('myComponent')).click();

I'm working with Angular 5.2.5, Protractor 5.3.0 and Jasmine 2.8.0.

May be related: I could also have asked why I need to add a browser.wait() while element(by()) is supposed to be automatically added in the ControlFlow by Protractor, but there are already lots of related questions (here, here, there, there,...), with no clear answer unfortunately.

like image 445
ThCollignon Avatar asked Dec 24 '22 09:12

ThCollignon


2 Answers

There is a not-so obvious difference between the two . But the webdriver docs are clear about this.

eC.visibilityOf(...) - Returns a function. browser.wait() repeatedly evaluates functions until they return true.

isPresent() - Returns a promise. browser.wait() does not / cannot repeatedly evalute promises(!) browser.wait() will continue immediately when the promise resolves , regardless of whether it returns true or false.

If you want to use isPresent() you can wrap it in a function. This allows webdriver to call it over and over.

 browser.wait(() => element(by.tagName('myComponent')).isPresent(), 10000, 'timeout');

Works exactly as you expect.

like image 75
gawicks Avatar answered Dec 28 '22 12:12

gawicks


The two statements are not equivalent as such. I created a simple page like below

<html>
    <body>
        <div id="first_name">Tarun</div>
        <script type="text/javascript">
            var div = document.createElement('div');
            div.innerText = 'lalwani';
            div.id = 'last_name';
            setTimeout( () => document.body.appendChild(div),  3000);
        </script>
    </body>
</html>

And a simple test like below

describe('angularjs homepage todo list', function() {
    it('should add a todo', async function() {
        browser.waitForAngularEnabled(false);

        browser.get('http://0.0.0.0:8000');

        const eC = protractor.ExpectedConditions;

        browser.wait(element(by.id('last_name')).isPresent(), 10000, 'timeout');
    });
});

When you run you will find the output is

Started
...
1 spec, 0 failures
Finished in 0.617 seconds

Now if you change the code to

describe('angularjs homepage todo list', function() {
    it('should add a todo', async function() {
        browser.waitForAngularEnabled(false);

        browser.get('http://0.0.0.0:8000');

        const eC = protractor.ExpectedConditions;

        browser.wait(eC.visibilityOf(element(by.id('last_name'))), 10000, 'timeout');

    });
});

The output of the same is below

Started
...
1 spec, 0 failures
Finished in 3.398 seconds

As you can see the visibilityOf actually waited for the object to appear while the previous one didn't.

This is because the controlFlow will make isPresent get executed and return the promise returning value of true/false to the wait. While visibilityOf will return a function that the wait can check by calling again and again.

You can verify this by adding below in the test

console.log(typeof eC.visibilityOf(element(by.id('last_name'))))
console.log(typeof element(by.id('last_name')))

The output of same is

function
object

So the assumption your below two statements are same is wrong and that is why you don't get the correct results with the first one

browser.wait(element(by.tagName('myComponent')).isPresent(), 10000, 'timeout');
browser.wait(eC.visibilityOf(element(by.tagName('myComponent'))), 10000, 'timeout');
like image 34
Tarun Lalwani Avatar answered Dec 28 '22 12:12

Tarun Lalwani