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!
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 aimplicit wait - browser.manage().timeouts().implicitlyWait()
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.
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 EVERYdriver.findElement
, no matter what kind of locator do you use. When implicit wait timed out, NoSuchElementException will be thrown outsidefindElement
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 setimplicitWait(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 withbrowser.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.
presenceOf()
driver.findElement(By.xpath('//*[@name='qwer']'))
I hope this will help!