How does implicit wait of Protractor interact with

2019-02-09 10:42发布

问题:

The misunderstanding happens when implicit wait is less than explicit:

var timeOut = 5000;
var search = element(by.xpath(`//*[@name='qwer']`));
browser.manage().timeouts().implicitlyWait(4000);
browser.ignoreSynchronization = true;

describe('Protractor Test', function () {
    beforeEach(function () {
        browser.get('https://www.google.com.ua');
    });
    it('EC', function () {
        console.log('START');
        // browser.sleep(timeOut);
        browser.wait(protractor.ExpectedConditions.presenceOf(search), timeOut);
    });
});

Overall time: 8.613 seconds. Set implicitlyWait a second lower: 3000 and result is 6.865 seconds. How does it work under the hood? Big thanks in advance!

回答1:

Thats nice question. A lot of good QA automation guys broke their heads with this.

Implicit waits

This is special hidden automatic wait, on each driver.findElement(...). Original webdriver (js, python, java) throws NoSuchElementException if element cannot be found in page DOM structure. This kind of wait will be done before EVERY driver.findElement, no matter what kind of locator do you use. When implicit wait timed out, NoSuchElementException will be thrown outside findElement function.

Enabling implicit wait

By default implicit wait timeout is set to 0. browser.manage().timeouts().implicitlyWait(3000) makes webdriver automatically try/catch this exception, and retry to find this element. If 3 seconds (timeout) passed, and element is still not present in DOM - only then you are getting NoSuchElementException.

When it is good:

Your page modify DOM structure (99.999% of website) and some elements still not in the DOM, but appear within 1-3 seconds. To not make explicit waits, and reduce amount of code - you can try to set implicit wait timeout.

When it is bad: You want to test that element is not present in the DOM. This kind of wait is added before every .findElement call, so when you are trying to assert like this:

expect($('NON-EXIST-ELEMENT').isPresent()).toBeFalsy()

Your implicitWait still working here. First you will wait for 3 seconds to element to be present, then exception will be thrown, and caught by isPresent() function, that will return false in this case (what we actually asserting). So you are waiting for 3 extra seconds! It makes sense to set implicitWait(0) and then set it back after asserting element is not present (which might be really annoying).

Conclusion Implicit waits are good, when you understand how it is works. I recommend to not set implicit wait more than 1-5 seconds (you should define own value for each website). Also if you plan to assert lot of not-present elements - reset implicit wait to 0, and then set it back.

Explicit waits

This kind of waiting that you should call by yourself, but it much more flexible than implicit waits. In protractorjs, when you need to wait for something, you must call browser.wait(). It accepts predicate function (function that will return only true/false, no exceptions). Webdriver will poll this function until timeout occurs (you specify it as second param). Also you can specify error message that you want to throw as third parameter.

Obviously, that in web automation you wait for some element conditions most of the time. For this guys have created collection of predicate functions. This functions calls ExpectedConditions, and will return true/false for element that was passed to them.

browser.wait(ExpectedConditions.visibilityOf($('NON-EXISTING-ELEMENT')), 3000, 'error message')

When it is good: When you have to wait for some tricky conditions of your elements. You can easily define own conditions that you want to wait, specify custom timeout and so on. Use before manipulating with elements that might not be ready yet.

When it is bad: When you try to help you by combining browser.sleep(), implicit waits and explicit waits together. browser.sleep() is bad by default, in 99% of cases you can replace it with browser.wait() with provided conditions, or write your own condition.

Much more fun happens when you have your implicit wait set, and you trying to call explicit wait. Imagine: browser.manage().timeouts().implicitlyWait(10000) browser.wait(EC.stalenessOf($('NON-EXIST-ELEMENT')), 5000) //waiting for 5 seconds for element to disappear

What happens here: Wait function calls stalenessOf() function for your element. Inside it, driver.findElement() got called. Implicit wait don't let this function to throw error immediately, and pools webpage for 10 seconds until implicit wait timeout happens, and we are getting NoSuchElementException. Exception happens, and execution returns to wait function, 10 seconds are passed already! Wait is terminated with TimeOutException, because it was scheduled only for 5 seconds. We are getting error with wait time much longer that expected.

Also keep in mind that JS is async, and cannot guarantee exact wait time because of Event Loop. Usually this makes waiting not exact - 5200 ms instead 5000 (as example). This is absolutely different story :)


What happens in your example

implicit timeout - 4000 milliseconds.

explicit timeout - 5000 milliseconds.

  1. Wait started. First time calling predicate function - presenceOf()
  2. Internally predicate calls original webdriverjs function - driver.findElement(By.xpath('//*[@name='qwer']'))
  3. Since implicit wait is set, we are waiting for it before throw error.
  4. 4000 milliseconds of implicit element waiting passed. Only now we are returning error to predicate function.
  5. Predicate function catch error, and returns false instead
  6. Since we still have 1000 milliseconds before timeout of explicit wait - calling predicate function again.
  7. Implicit wait started again. 4000 milliseconds later - throwing error back to predicate function
  8. Predicate returns false
  9. Wait function got false, and our explicit wait is out of time - in ideal case - it would be about 8000 milliseconds, but also be aware about async calls, so real time would be more
  10. Wait throws error - jasminejs catch error, and fails test

I hope this will help!



回答2:

That's a great question and these unpredictable wait times when using a combination of implicit and explicit waits has been warned & stated in Selenium documentation. browser.wait() is an explicit wait and you are using that in combination with a implicit wait - browser.manage().timeouts().implicitlyWait()

Waiting is having the automated task execution elapse a certain amount of time before continuing with the next step. You should choose to use Explicit Waits or Implicit Waits.

WARNING: Do not mix implicit and explicit waits. Doing so can cause unpredictable wait times. For example setting an implicit wait of 10 seconds and an explicit wait of 15 seconds, could cause a timeout to occur after 20 seconds.



回答3:

The answer is: both waits wait in parallel. Implicit wait polls 4 seconds and returns failure after which explicit wait has waited only 4 seconds and has 1 more to go. This reissues implicit wait again and it polls another 4 seconds and fails again. After 8 seconds (2 attempts of implicit wait) explicit wait of 5 seconds also timed out and we finally we have our error after wasting 3 unexpected seconds. For example in case of Implicit set to 8 and Explicit set to 17 we will wait for 8*3 = 24 seconds. Note that script execution takes some time so if it's slow next iteration might not start.