How can I wait for a condition?
Asked Answered
R

9

51

I'm new on protractor, and I'm trying to implement an e2e test. I don't know if this is the right way to do this, but... The page that I want to test is not a full angular page based, so... I'm having some trouble.

On my first spec I have:

describe('should open contact page', function() {
var ptor = protractor.getInstance();

beforeEach(function(){

   var Login = require('./util/Login');
   new Login(ptor);
});

I have created this Login class, but after login I want to open the contact page, but protractor immediately try to find element before the page is fully loaded.

I've tried to use:

browser.driver.wait(function() {

    expect(browser.findElement(by.xpath("//a[@href='#/contacts']")).isDisplayed());
    ptor.findElement(by.xpath("//a[@href='#/contacts']")).click();

});

But it doesn't work... it always try to find the element before the page loads. I tried this one too:

browser.driver.wait(function() {
    expect(ptor.isElementPresent(by.xpath("//a[@href='#/contacts']")));          
    ptor.findElement(by.xpath("//a[@href='#/contacts']")).click();
});

I'm able to do that using browser.sleep(); but I don't think that is a good option. Any idea? On my login class I have:

ptor.ignoreSynchronization = true;

How can I wait for this @href='#/contacts before protractor tries to click on it?

Recursion answered 27/2, 2014 at 15:2 Comment(33)
I guess your should run the tests after the page is loaded. You can set that in your test runner, config file, etc... I think. Why don't you use karma for client side testing?Carat
#17071022 Hmm karma not recommended with protractor...Carat
The easiest workaround to put your whole describe into a callback for the ready event, but I don't know this system and the test runner your are using, so maybe it won't work...Carat
I'm using jasmine, on my config I'm using defaultTimeoutInterval: 50000 and allScriptsTimeout: 50000.Recursion
Using async tests does not work? htmlgoodies.com/beyond/javascript/…Carat
I am not sure, so you think the problem is that the test runs before the ready event? I don't think that is possible.Carat
Ohh I see now, so the problem is with the protractor.Carat
Am I sure, that this is a similar problem? #12188169Carat
I don't think so. I'm using chrome driver and... my test run just like they should... but every time that my page reloads, I have to put a driver.sleep(); otherwise my findElement is not be able to find the specified element. I just wanna know if there is any other option for this sleep... something more... dynamic.Recursion
Strange, does it need only a tick or a longer time? Are you your that these tests should not be async?Carat
I found that there is a protractor/jasminewd driver to ease async testing, but I am confused about your problem :DCarat
I should try to use this protractor before any other comment. Can you send me a small example code which does not work?Carat
Ok, here's the problem... as I said before, the page is not a full angular page based. On my beforeEach I'm logging in the system. After the login, on my page I have a black loading screen, and then.. after all the page is loaded, the black screen disappear and the page appears. The problem is, after the beforeEach function is executed, protactor immediately execute my first "it", but at this point of the test, the page isn't fully loaded, so it can't find the element. My homepage is a login page, so i MUST login before do anything.Recursion
So... how can i "wait" between login and the page load without using browser.sleep()?Recursion
I'll paste here what i'm trying to do. Just a sec..Recursion
Did you try to force the promise to be solved using: browser.findElement(by.xpath("//a[@href='#/contacts']")).isDisplayed().then(function(){// do click() and other actions}; ?Cardiac
Try to put ptor = protractor.getInstance(); under the beforeEach or it. I checked about 5-10 code, but nobody sets its value in the describe. Ofc. this is just a guess... I am still learning how to use that test environment... blog.busymachines.com/frontend/angularjs/testing/2013/10/28/…Carat
@Cardiac According to this: engineering.wingify.com/posts/… Protractor automatically solves the promises.Carat
@user5968: Use the github.com/angular/protractor/blob/master/jasminewd/index.js jasmine wd driver. It automatically wraps jasmine functions for async testing. After that you can add a 3rd timeout parameter for each tests it(title, callback, msec) function, so you don't have to write the timeout manually...Carat
But.. I guess when ptor.ignoreSynchronization = true; it don't solve the promises by itself.Recursion
Nah that's a good question :-) I know this system only for 1 hour...Carat
I guess this tutorial will be helpful too: engineering.wingify.com/posts/…Carat
Btw. if you are at the beginning of your e2e tests, I'd rather use karma with phantomjs. It is easy to setup karma, and has great support. But that's my opinion... :-)Carat
Does karma works with angular? And it has support for sauce labs?Recursion
Yes... it works, i'll take a look on it. But, if you have any idea on how to solve this problem on protector, I'll be very grateful.Recursion
I am sure it works with angular, I saw about 50 times that...Carat
It is out of my league. I mean I don't think I'll ever write e2e tests or angular projects... I currently use backbone and I don't test dom elements, just the models... Writing automated tests for UI is wasting resources I think, but that's my opinion...Carat
On my case that's not a waste of resource. We kind of sell "things" to the government, and before we can sell, we have to do a kind of "concept test", that is a test to know if the system it's on accord with their expectation. So... this kind of test that I have to do almost everyday, would be very useful if I could automate them.Recursion
Ye I understand. I'm sorry I cannot help more... Maybe somebody else...Carat
Thanks anyway, I hope I'll find out.Recursion
I have a video for you about application architecture: youtube.com/watch?v=WpkDN78P884 It has a part about why testing View is not so important, but anyways the whole presentation is interesting... Enjoy!Carat
Somebody recommended me the nightwatch.js in this problem domain. (I hope I used the right words, sometimes I am unsure of my English.) I think you should check that framework, it is much cleaner, than protractor.Carat
protractor.getInstance(); is not a functionRoca
W
25

I had the same problem you were having for the longest time while using protractor. In my e2e test I start in a non angular app, then get into an angular portion, then get back out to a non angular portion. Made things tricky. The key is to understand promises and how they work. Here's some examples of my real world code in a functioning e2e test. Hoping this gives you an idea of how to structure your tests. Probably some bad practice in this code, please feel free to improve upon this, but I know that it works, maybe not the best way.

To get to angular I use

var ptor;
var events = require('events');
var eventEmitter = new events.EventEmitter();
var secondClick = require('./second-click');

beforeEach(function () {
    browser.driver.get('http://localhost:8080/');
},10000);

it("should start the test", function () {
    describe("starting", function () {
        it("should find the  link and start the test", function(){
            var elementToFind = by.linkText('Start'); //what element we are looking for
            browser.driver.isElementPresent(elementToFind).then(function(isPresent){
                expect(isPresent).toBe(true); //the test, kind of redundant but it helps pass or fail
                browser.driver.findElement(elementToFind).then(function(start){
                    start.click().then(function(){ //once we've found the element and its on the page click it!! :) 
                        ptor = protractor.getInstance(); //pass down protractor and the events to other files so we can emit events
                        secondClick(eventEmitter, ptor); //this is your callback to keep going on to other actions or test in another file
                    });
                });
            });
        });
    });
},60000);

While in angular this code works

 describe("type in a message ", function(){
        it("should find and type in a random message", function(){
            var elementToFind = by.css('form textarea.limited');
            browser.driver.isElementPresent(elementToFind).then(function(isPresent){
                element(elementToFind).sendKeys(randomSentence).then(function(){
                    console.log("typed in random message");
                    continueOn();
                });
            });
        });
    },15000);

After exiting angular

browser.driver.wait(function(){
   console.log("polling for a firstName to appear");
   return    browser.driver.isElementPresent(by.name('firstName')).then(function(el){
         return el === true;
       });
     }).
   then(function(){
       somefunctionToExecute()
    });

Hope that gives some guidance and helps you out!

Warfold answered 27/2, 2014 at 23:19 Comment(2)
I guess you still do not know promises very well. If you return from within first .then, you can chain other .then calls so that you get a flat structure.Pico
I wish someone would have told me that a year ago when I wrote this ha! Thank you @AliMotevallian I haven't written anything with the newer version of protractor, I'd like to revisit it and clean up the code for the new versions and hopefully make it a lot simpler.Warfold
M
52

Protractor 1.7.0 has also introduced a new feature: Expected Conditions.

There are several predefined conditions to explicitly wait for. In case you want to wait for an element to become present:

var EC = protractor.ExpectedConditions;

var e = element(by.id('xyz'));
browser.wait(EC.presenceOf(e), 10000);

expect(e.isPresent()).toBeTruthy();

See also:

Mydriasis answered 23/2, 2015 at 19:18 Comment(0)
R
34

I finally find out...

   var waitLoading = by.css('#loading.loader-state-hidden');

   browser.wait(function() {
       return ptor.isElementPresent(waitLoading);
   }, 8000);

   expect(ptor.isElementPresent(waitLoading)).toBeTruthy();

   var openContact = by.xpath("//a[@href='#/contacts']");
   element(openContact).click();

With this protractor could wait for that element until it loading page disappears. Thanks for those who tried to help XD.

Recursion answered 28/2, 2014 at 19:10 Comment(1)
I was looking to use this to test a custom angular flash message content. But protractor seems unable to get the corresponding DOM element. I resolved the issue by using selenium driver instead of protractor (e.g. use browser.driver instead of ptor in the above code). This is not a direct answer to the question but I though it might be helpful.Monanthous
W
25

I had the same problem you were having for the longest time while using protractor. In my e2e test I start in a non angular app, then get into an angular portion, then get back out to a non angular portion. Made things tricky. The key is to understand promises and how they work. Here's some examples of my real world code in a functioning e2e test. Hoping this gives you an idea of how to structure your tests. Probably some bad practice in this code, please feel free to improve upon this, but I know that it works, maybe not the best way.

To get to angular I use

var ptor;
var events = require('events');
var eventEmitter = new events.EventEmitter();
var secondClick = require('./second-click');

beforeEach(function () {
    browser.driver.get('http://localhost:8080/');
},10000);

it("should start the test", function () {
    describe("starting", function () {
        it("should find the  link and start the test", function(){
            var elementToFind = by.linkText('Start'); //what element we are looking for
            browser.driver.isElementPresent(elementToFind).then(function(isPresent){
                expect(isPresent).toBe(true); //the test, kind of redundant but it helps pass or fail
                browser.driver.findElement(elementToFind).then(function(start){
                    start.click().then(function(){ //once we've found the element and its on the page click it!! :) 
                        ptor = protractor.getInstance(); //pass down protractor and the events to other files so we can emit events
                        secondClick(eventEmitter, ptor); //this is your callback to keep going on to other actions or test in another file
                    });
                });
            });
        });
    });
},60000);

While in angular this code works

 describe("type in a message ", function(){
        it("should find and type in a random message", function(){
            var elementToFind = by.css('form textarea.limited');
            browser.driver.isElementPresent(elementToFind).then(function(isPresent){
                element(elementToFind).sendKeys(randomSentence).then(function(){
                    console.log("typed in random message");
                    continueOn();
                });
            });
        });
    },15000);

After exiting angular

browser.driver.wait(function(){
   console.log("polling for a firstName to appear");
   return    browser.driver.isElementPresent(by.name('firstName')).then(function(el){
         return el === true;
       });
     }).
   then(function(){
       somefunctionToExecute()
    });

Hope that gives some guidance and helps you out!

Warfold answered 27/2, 2014 at 23:19 Comment(2)
I guess you still do not know promises very well. If you return from within first .then, you can chain other .then calls so that you get a flat structure.Pico
I wish someone would have told me that a year ago when I wrote this ha! Thank you @AliMotevallian I haven't written anything with the newer version of protractor, I'd like to revisit it and clean up the code for the new versions and hopefully make it a lot simpler.Warfold
H
9
browser.driver.wait(function() {
    return browser.driver.isElementPresent(by.xpath("//a[@href='#/contacts']"));
});

This works for me too (without the timeout param)..

for more information, see http://angular.github.io/protractor/#/api?view=webdriver.WebDriver.prototype.wait

Hawfinch answered 26/9, 2014 at 4:38 Comment(1)
This one worked well with Protractor version 2.5.1 on a non-Angular siteParlormaid
P
1

Thanks to answers above, this was my simplified and updated usage

function waitFor (selector) {
  return browser.wait(function () {
    return browser.isElementPresent(by.css(selector));
  }, 50000);
}
Pule answered 18/1, 2016 at 14:28 Comment(0)
G
0

Have you tried putting the ng-app in the <html> tag (assuming this part of code is under your control)? This solved a lot of initialization timing problems for me.

Graupel answered 25/3, 2016 at 8:3 Comment(0)
P
0

Best way to use wait conditions in protractor that helps to show proper error message to particular element if test case failed

const EC = ExpectedConditions;
const ele = element(by.xpath(your xpath));

return browser.wait(EC.visibilityOf(ele),9000,'element not found').then(() => {
            ele.click();
         });
Passifloraceous answered 3/10, 2018 at 8:7 Comment(0)
B
0

I'm surprised that nobody has added this solution. Basically, if you are using modal dialogues you often get an element visible and available to click but not being clickable due to the modal dialogue being in front of it. This happens because protractor moves faster than angular and is ready to click the next element while angular is still closing the modal.

I suggest using

public async clickElementBug(elementLocator: Locator) {
const elem = await element(elementLocator);
await browser.wait(
  async function() {
    try {
      await elem.click();
      return true;
    } catch (error) {
      return false;
    }
  },
  this.TIMEOUT_MILLIS,
  'Clicking of element failed: ' + elem
);

}

Bergin answered 13/12, 2018 at 0:48 Comment(0)
M
0

browser.wait may sound too ordinary, but it's not!

browser.wait is the way to go. Just pass a function to it that would have a condition which to wait for. For example wait until there is no loading animation on the page

let $animation = $$('.loading');

await browser.wait(
  async () => (await animation.count()) === 0, // function; if returns true it stops waiting; can wait for anything in the world if you get creative with it
  5000, // timeout
  `message on timeout` // comment on error
);

Make sure to use await

You can also use existing library called ExpectedConditions that has lots of predefined conditions to wait for

You can't imagine what you can do with it...

A few of my favorite ones:

wait until the number of browser's tab's is 2

// wait until the number of browser's tab's is 2
await browser.wait(
  async () => {
    let tabCount = await browser.getAllWindowHandles();
    return tabCount.length === 2;
  },
  5000,
  'the url didnt open in a new window'
);

wait until the loading animation is gone for at last 750ms

// wait until the loading animation is gone for at last 750ms
await browser.wait(
  async () => (await this.$$loadAnimations.count()) === 0 && !(await browser.sleep(750)) && (await this.$$loadAnimations.count()) === 0,
  5000,
  `waiting timeout`
);

wait for ANY number of elements to be present

// wait for any number of elements to be present
async waitForElements($elem, timeout = 120000, start = +new Date()) {
    let conditions = [];

    for (let i = 0; i < $elem.length; i++) {
        conditions.push(ExpectedConditions.presenceOf($elem[i]));
    }

    await browser.wait(
        ExpectedConditions.and(...conditions), 
        remainingTimeout(timeout, start), 
        `wait for all elements`
    );
}

// and use

await waitForElements([
  $usernameField, 
  $passwordFiend, 
  $submitButton
])
Meitner answered 9/2, 2021 at 1:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.