Reputation: 254
I have the following code:
const until = protractor.ExpectedConditions;
export class GenericPOClass {
waitForPagetoLoad(numberOfSecondsToWait: number = 30) { // note the default
(1) let elementToWaitFor = element(by.css('app-spec-mapper .stepper'));
(2) browser.wait(until.presenceOf(elementToWaitFor), numberOfSecondsToWait*1000);
(3) return expect(elementToWaitFor.isPresent()).toBeTruthy('Element stepper is not actually present.');
}
}
This is code called from a protractor/jasmine test to wait for my non-angular web page to load.
If I use all 3 lines, with a "default wait time of 30 seconds, I make it past this loading part of my test and move onto another section.
If I use just lines 1 & 3, I get the expectation error.
If I use line 2 as well with a number of seconds to wait of 0, it works anyway. The page is loaded, line 3 passes.
Is there some type of default wait time on browser.wait that overrides my wait time? Is there some freaky magic of asynchronicity that it works if observed (browser.wait calling the expected conditions is magic)?
Coming from a synchronous Java and TestNG background, I've been learning protractor and JavaScript as I go, writing automated test cases for my job and things like this without a formal class or knowledgeable person to ask keep tripping me up. I've been wrangling with getting waiting for things to load working for a week, please help me understand this black magic.
Protractor Version: 5.1.2
NodeJS: 6.11.2
IDE: IntelliJ WebStorm
Upvotes: 0
Views: 143
Reputation: 141
The black magic you speak of is a Promise. And your three seconds is wait UP TO three seconds, not wait EXACTLY three seconds.
First, let's confirm it really is a promise we're dealing with, then we'll see how a promise works.
Looking at the protractor source, you'll see that until.presenceOf()
1 returns ElementFinder.isPresent
:
presenceOf(elementFinder: ElementFinder): Function {
return elementFinder.isPresent.bind(elementFinder);
};
And ElementFinder.isPresent()
2 returns a Promise containing a boolean
isPresent(): wdpromise.Promise<boolean> {
return this.count().then((count) => {
return count > 0;
});
}
Ok, it's promise, but what how does it work? Here's a simplified version of what's happening in your test:
var wait = 0
// Count to three then change content to 'Times up'
var timeout = setTimeout(timesUp, wait)
function timesUp() {
document.body.innerHTML = 'Times up'
}
// make a new Promise and keep a reference of its resolve function
var resolve
var p = new Promise(function(resolveFunc){
resolve = resolveFunc
})
// when the Promise is resolved by calling resolve(), cancel the timer
p.then(cancelTimeout)
function cancelTimeout() {
clearTimeout(timeout)
document.body.innerHTML = 'Timeout cancelled'
}
// resolve the promise (like presenceOf finding the element)
resolve()
In the snippet, setTimeout asks the runtime to run timesUp()
zero seconds after the current execution finishes. But before the end of the execution, resolve()
is called, so we see 'Timeout cancelled', not 'Times up'.
In your test, the same happens. Your element is found before a future round of execution can fail the test and the test passes.
Actually, the 'execution' I referred to is called the 'event loop': https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#Event_loop
Hope that helps.
Upvotes: 1