SBKDeveloper
SBKDeveloper

Reputation: 693

protractor: how to wait for findElements?

I have a search field, when the user hits enter it has a table with a bunch of checkboxes. I need to wait for the results to load and then click the checkbox on the first row of the table.

This is separated into 2 test cases, the first executes the search, the 2nd clicks the checkbox in the first row of the results that does something else.

 // Test #2      
 it('should perform the Search', () => {
    po.searchAutocomplete.sendKeys('\n');
  });

 // Test #3
  it('3  : should Activate and Inactivate a Program', () => {
    const r = browser.driver.findElements(by.tagName('p-checkbox'));

    browser.driver.wait(EC.presenceOf(r[0]));

    r[0].click();

    browser.sleep(2000).then(() => {
    });

    r[0].click();  
  });

  // Test #4
  it('4  : should navigate to Create New', () => {
    po.createNewButton.click();
  });

So, there are sync problems here - my Test #2 runs successfully but then Test #3 starts running here immediately. If the search results are too slow then I get an error that the checkbox does not exist, if it returns in time it's fine. Also, sometimes the 2nd click() results in object not attached to current page because the "Create New" in Test #4 navigates to another page.

It looks like the r[0].clicks's promise is returning after the page navigates to another view with the po.createNewButton.click() causing the object not attached to current page error.

I need all these operations synchronously. I put in a browser.wait but that does not work, it's just ignored.

How can I fix this?

Edit - thanks for the suggestions below. Here is what I changed and got it working :)

In my app had a clock timer using Observable.interval in my header, in addition I had a timer that was pinging the server to get some data at a fixed interval. Since these are always running and will never complete it was causing all sorts of timeouts in Protractor with the element.all syntax which is why I switched to browser.driver.findElement syntax but due to the async nature of it (as identified by the poster below - thank you!) makes it difficult to code.

I wrapped the timers in the following code:

this.ngZone.runOutsideAngular(() => {   // Required to prevent interference with Protractor
  this.updateTimer = Observable.interval(UPDATE_INTERVAL).publish();
  this.updateTimer.subscribe(v => {
    this.doSomething();
  });
});

Now, I am able to use the element.all without it timing out!

Upvotes: 3

Views: 737

Answers (1)

Ashish Ranjan
Ashish Ranjan

Reputation: 12960

This is happening because you are using driver commands to fetch elements. Driver commands don't wait for angular asynchronous tasks to finish. for example, HTTP requests, data load in your case. (Protractor by default will wait for angular async tasks to get over before proceeding towards the next promise in queue).

Using driver commands will only make sense if you have some asynchronous activity always going on in your application. Such as a timer.

Instead of

browser.driver.findElements(by.tagName('p-checkbox'));

Use:

element.all(by.tagName('p-checkbox'));

Now, whenever you use browser.wait() then you have got to wait for the condition to be truthy and put rest of the code when the wait is over like:

browser.wait(EC.presenceOf(r[0])).then(() => {
   ...// wait is over. Rest of my code here.
})

Suggestion: Please make test its and fits independent of one another. What if the first it fails? Then the subsequent it will also fail?

EDIT: Reasons for tests getting timed out

In your comment, you said that you disabled the timer? Are you sure there is no other timer running? Protractor will probably show this type of message in two cases:

  1. There' a timer running in your app and protractor waited for it to finish but timed out.
  2. You have added a wait for checking presenseOf and element or isElement Clickable, etc using Browser wait, but you that element never appeared on the page you had added the wait in.

If there's a timer running: (Hadn't read your comments properly. Below is a solution if Point number 2 in the above list is false for you.)

If you are getting timed out then definitely there is some timer running in the background in your app. We usually have it in our applications for various reasons, one common reason is to log out a user from the application after some specific interval of inactivity in the application.

Now that you are getting timed out then you have two options.

  1. Use drivers commands.

  2. Disable angular wait and then use protractor commands.

    browser.waitForAngularEnabled(false/true).then(() => {.. rest of codes with specific checking of element's existance, their clickability, etc and then perform the actual opration. })

Both are equally cumbersome because now you have to wait for everything. You can't use protractors default wait for angular to finish page loads, HTTP requests, etc because if you use that then you will end up waiting forever until timeout.

What I have done in such cases is:

Add a way to the application so as to disable the running timer for a specific test user. We do protractor test cases using a specific test user and when that user logs in, that user has an option to disable the timer. (An extra dom element which when clicked, removes the timer). So just when the user is about the hit the login button, I disable the angular wait,(timer in my app starts only after login) then I do specific waits in the application until I see if the extra DOM element is present and isClickable, if true then I click it. Once clicked, I re-enable the angular wait. and after this, I can leverage upon the protractor to automatic wait for elements to be present. Hope this helps.

Upvotes: 4

Related Questions