Protractor not waiting for modal - protractor

I have a modal that shows up in a protractor test. When I run the test case solo, it works perfectly, but when I run it as part of the larger file (currently 10 cases, some lengthy) it slows things down and, as a result, the modal is slower to open and close. The chain effect is that, when I try to click on a button on the page, it crashes since the modal (which was supposed to be closed by now) is still there and blocking the button.
How can I properly sense when the modal is open/closed so that I can work around this?
Thanks!
(Also, this is an AngularJS application)

These are my helper functions for manual waiting:
static async forElementAbsent(element) {
return await new Promise<any>((resolve) => {
browser.driver.wait(ExpectedConditions.stalenessOf(element), 10000,
`Timed out waiting for element to be absent`).then(() => {
resolve();
});
});
}
static async forElementPresent(element) {
return await new Promise<any>((resolve) => {
browser.driver.wait(ExpectedConditions.presenceOf(element), 10000,
`Timed out waiting for element to be present`).then(() => {
resolve();
});
});
}

In our tests, we are waiting for modals to be displayed manually. We have helper functions such as
export async function forCondition(condition: () => boolean | PromiseLike<boolean>, timeout = 20000): Promise<boolean> {
try {
return await browser.wait(condition, timeout);
} catch (err) {
return false;
}
}
Given function waits for a condition to fullfill. So in your particular case you would have to find out the css selector of the modal which is displayed.
Let's say the selector is by.css('.modal.visible'), then we would have to write something like the following if we wanted to wait for the modal to be displayed:
t.it('modal should be displayed', async function() {
// wait for modal to be displayed
var result = await forCondition(function() {
return element(by.css('.modal.visible')).isDisplayed();
});
expect(result).toBeTruthy();
});

Related

Protractor tests are inconsistent even with browser.wait and helper functions

I am looking for some advice. I have been using protractor for a few weeks and just cannot get my tests to be consistent unless I use browser.sleep. I have tried helper functions as well as browser.wait(expectedCondition). I have reduced browser.sleep immensely, but protractor still just goes way to fast. I can never successfully run multiple tests unless I have a few browser.sleeps just so protractor can relax for a second. Here is an example:
The Test I need to select a user, delete that user and wait for a success message. Then I click the same user and click the activate button.
Outcome: Unless I have browser.sleep, my success messages do not even appear after deletion/activation. The tests fail because protractor is moving way too fast. Even with the expected conditions. My main problem is that protractor moves to fast for the angular web page. I have tried ifCLickable or isDisplayed but they do not fix the issue entirely. Here is the code:
async deleteUser() {
await sendClick(this.getNewUser());
await sendClick(this.getDelete());
await waitTillPresent(this.getDeleteConfirm());
await sendClick(this.getDeleteConfirm());
await waitTillPresent(this.getSuccessMsg())
expect(await page.getSuccessMsg().isDisplayed()).toBeTruthy();
}
async activateUser() {
await sendClick(this.getNewUser());
await waitTillPresent(this.getEditBtn())
await sendClick(this.getActive());
await waitTillPresent(this.getSuccessMsg())
expect(await page.getSuccessMsg().isDisplayed()).toBeTruthy();
}
Functions:
export async function sendClick(element: ElementFinder): Promise<boolean> {
try {
if(!await element.isDisplayed()) {
return false;
}
await browser.executeScript('arguments[0].click();', await element.getWebElement());
return true;
}
catch (err) {
return false;
}
}`
export async function waitTillPresent (element: ElementFinder, timeout: number = 10000) {
return browser.wait(() => {
return element.isPresent();
}, timeout);
}
My Question: Am I handling this correctly? Is there a better to ensure my tests are consistent? Before these tests, I visit a non-angular webpage. So I have to include the line browser.waitForAngularEnabled(false)
Does this mess with the async nature of angular? Thank you.
I worked the last few months on our e2e test suite to make it stable. I did not believe it's possible but I made it using correct wait functions and sometimes browser.sleep() as a last resort.
You have a correct approach for waiting for elements. But there are 2 problems regarding your implementation:
1) The function waitTillPresent() does exactly what its name stands for. But if you only wait until the element is present on the page it does not mean it's clickable or displayed. An element can be hidden and at the same time still be present. Please rename waitTillPresent() to waitTillDisplayed() and change it as follows:
export async function waitTillDisplayed(element: ElementFinder, timeout: number = 20000): Promise<boolean> {
let result = await browser.wait(() => element.isPresent(), timeout);
if(!result) {
return false;
}
return await browser.wait(element.isDisplayed(), timeout);
}
2) You should exceed the default timeout. Set it a bit higher like 20 to 25 seconds. Just play with it.
Unfortunately, I don't know how browser.waitForAngularEnabled(false) changes test behavior. We do not use it :)
Note:
These functions are all exactly the same:
function foo() { return 'hello world'; }
var foo = () => { return 'hello world'; };
var foo = () => 'hello world';
Play with arrow functions, it's syntactic sugar.
Cheers and gl!

wait for http request to complete in protractor

I am trying to wait for spinner to disappear and then for my steps to execute but nothing is working for me.
browser.wait(function () {
return this.spinner.isDisplayed().then(function (result) {
return !result;});}, 20000);
and i even tried with
browser.wait(function () {
return !browser.isElementPresent(this.spinner);}, 20000);
even with below method
browser.sleep(1000);
this.spinner.isPresent().then(function (result) {
if (result === true) {
var EC = protractor.ExpectedConditions;
browser.wait(EC.invisibilityOf(this.spinner), 10000);}});
then only thing that works is
browse.sleep(10000);
i don't want to use sleep in my code. can anyone help me with how to wait for complete http request to complete and then process with testing
you should consider using Expected Conditions since they return true/false based on current conditions
http://www.protractortest.org/#/api?view=ProtractorExpectedConditions.prototype.invisibilityOf
so your test case would become:
browser.wait(EC.invisibilityOf(this.spinner),20000).then(function(){
...continue test, spinner gone
});
UPDATE
in order to use done, you would generally pass this cb into your it() function. This means your test could look like
describe("example describe",function(){
it("should be an example only", function(done){
request.get("www.google.com",function(res){
//done with async request, now call done
done();
})
})
});
Since your entire code isn't posted up here, you should have something similar to:
it("should wait for spinner to go bye-bye",function(done){
browser.wait(EC.invisibilityOf(this.spinner),20000).then(function(){
done()
});
});

Waiting for Ionic Loading dialogs with Protractor

There are similar questions (linked below) but none solves this problem. I'm writing Protractor tests for an Ionic Project. I need to execute tests at times when an Ionic Loading dialog appears and disappears.
I've created a repo with the bare bones of the app and the tests that need to be made. Solve this and you solve the problem (I describe the problem below): https://github.com/TmanTman/StackoverflowQ. Just adapt the path to your Chrome for your system in conf.js.
To simulate an asynchronous Ionic Loading dialog I just add this to the controller in a blank Ionic project:
$interval( function() {
$ionicLoading.show({
template: 'Async ionicLoading',
duration: 5000
});
}, 5000 , 1);
})
I need to get protractor to wait for the dialog to appear, do some tests, wait for the dialog to disappear, and then do some more tests. My latest attempt in my test file is:
it('should only test when ionicLoading appears', function() {
browser.wait(function(){
return element(by.css('.loading-container.visible.active')).isPresent();
}, 10000);
var ionicLoadingText = element(by.css('.loading-container.visible.active')).getText();
expect(ionicLoadingText).toEqual('Async IonicLoading');
})
it('should only test once ionicLoading disappears', function() {
browser.wait(function() {
var deferred = protractor.promise.defer();
var q = element(by.css('.loading-container.visible.active')).isPresent()
q.then( function (isPresent) {
deferred.fulfill(!isPresent);
});
return deferred.promise;
});
expect(1).toEqual(1);
})
I'm trying to avoid using synchronous sleep function, as my code is highly asynchronous. I've tried countless variations but I can't get it to work. Links I've used for info includes:
Protractor blocking wait
Asynchronous Testing with Protractor's ControlFlow
Protractor wait for isElementPresent and click on waited element fails
The problem is two-fold:
1) From what I can deduce, the duration property of the $ionicLoading method is implemented with a timeout function. Protractor does not work well with $timeout. So instead of using the duration property, the $ionicLoading dialog can be hidden with a $interval call (adapting the code from the question):
$interval( function() {
$ionicLoading.show({
template: 'Async IonicLoading'
});
$interval( function() {
$ionicLoading.hide();
}, 5000, 1)
}, 5000 , 1);
2) The code to detect the asynchronous change is the following:
it('should only test when ionicLoading appears', function() {
browser.wait(function() {
var deferred = protractor.promise.defer();
var q = element(by.css('.loading-container.visible.active')).isPresent()
q.then( function (isPresent) {
deferred.fulfill(isPresent);
});
return deferred.promise;
}, 10000);
var ionicLoadingText = element(by.css('.loading-container.visible.active')).getText();
expect(ionicLoadingText).toEqual('Async IonicLoading');
})
it('should only test once ionicLoading disappears', function() {
browser.wait(function() {
var deferred = protractor.promise.defer();
var q = element(by.css('.loading-container.visible.active')).isPresent()
q.then( function (isPresent) {
deferred.fulfill(!isPresent);
});
return deferred.promise;
}, 10000);
expect(1).toEqual(1);
})
Then both tests pass.

How to I find an element with protractor?

It seems if I use element(by.css('#id')); protractor sometimes finds it, sometimes doesn't. Depends if the DOM is settled down or not. I've gone and some this:
getElementsByCss: function (cssVal) {
// return element.all((by.css(cssVal)));
return self.wait(function () {
return element.all((by.css(cssVal))).then(function (els) {
return els;
}, function () {
return false; // Don't fail, we want to retry
});
}, GET_ELEMENT_TIMEOUT, 'Elements not found by css: ' + cssVal);
},
which plays a game of retrying to get the element for some timeout period. We use 5 seconds. This seems to work, most of the time. When running locally we have no issues.
When we run on the cloud (sauce labs) they have exceedingly slow VMs. We still get random issues. Unfortunately these games have to be played everywhere, for example:
getText: function (el) {
return self.wait(function () {
return el.getText();
}, 1000, 'Could not get element text.');
},
or
expectBulletin: function () {
var el, text;
return self.wait(function () {
// Find the bulleting element, make sure it's visible and grab its text.
// If any of those three fail, try all of them again.
return self.getElementByCss('.bulletin')
.then(function (elm) {
el = elm;
return self.isElementVisible(el);
}, function () {
return false;
})
.then(function () {
return self.getText(el).then(function (text) {
return text;
}, function () {
return false;
});
}, function () {
return false;
});
}, 10000, 'Count not get bulletin text.')
.then(function (result) {
text = result;
return self.executeScript('arguments[0].click();', el.getWebElement());
})
.then(function () {
return self.isElementNotVisible(el);
})
.then(function () {
return text;
});
},
all the self.waits are just a browser.driver.wait wrapper...
wait: function (fn, timeout, msg) {
return browser.driver.wait(fn, timeout, msg);
},
This feels like a big pack of bandaids and its not working all the time. expectBulletin works 99% of the time locally, but when run remotely on the slow VMs, it works about 50% of the time. Sometimes the text comes back blank, or issues about clicking an invisible element or just not finding the bulletin element.
Is there any better way to do this?
it seems if I use element(by.css('#id')); protractor sometimes finds
it, sometimes doesn't.
You know that it returns a promise right? If you want to get the absolute value of an element, you have to handle its promise first.
Protractor waits for the page to be ready before doing any assertions. If your page is "ready" but some data is missing, there is something wrong with your angular application.

How do I know that I'm still on the correct page when an async callback returns?

I'm building a Metro app using the single-page navigation model. On one of my pages I start an async ajax request that fetches some information. When the request returns I want to insert the received information into the displayed page.
For example:
WinJS.UI.Pages.define("/showstuff.html", {
processed: function (element, options) {
WinJS.xhr(...).done(function (result) {
element.querySelector('#target').innerText = result.responseText;
});
}
};
But how do I know that the user hasn't navigated away from the page in the meantime? It doesn't make sense to try to insert the text on a different page, so how can I make sure that the page that was loading when the request started is still active?
You can compare the pages URI with the current WinJS.Navigation.location to check if you are still on the page. You can use Windows.Foundation.Uri to pull the path from the pages URI to do this.
WinJS.UI.Pages.define("/showstuff.html", {
processed: function (element, options) {
var page = this;
WinJS.xhr(...).done(function (result) {
if (new Windows.Foundation.Uri(page.uri).path !== WinJS.Navigation.location)
return;
element.querySelector('#target').innerText = result.responseText;
});
}
};
I couldn't find an official way to do this, so I implemented a workaround.
WinJS.Navigation provides events that are fired on navigation. I used the navigating event to build a simple class that keeps track of page views:
var PageViewManager = WinJS.Class.define(
function () {
this.current = 0;
WinJS.Navigation.addEventListener('navigating',
this._handleNavigating.bind(this));
}, {
_handleNavigating: function (eventInfo) {
this.current++;
}
});
Application.pageViews = new PageViewManager();
The class increments a counter each time the user starts a new navigation.
With that counter, the Ajax request can check if any navigation occurred and react accordingly:
WinJS.UI.Pages.define("/showstuff.html", {
processed: function (element, options) {
var pageview = Application.pageViews.current;
WinJS.xhr(...).done(function (result) {
if (Application.pageViews.current != pageview)
return;
element.querySelector('#target').innerText = result.responseText;
});
}
};