How can I wait for a condition? - javascript

I'm new on protractor, and I'm trying to implement an e2e test.
I don't know if this is the right way to do this, but...
The page that I want to test is not a full angular page based, so... I'm having some trouble.
On my first spec I have:
describe('should open contact page', function() {
var ptor = protractor.getInstance();
beforeEach(function(){
var Login = require('./util/Login');
new Login(ptor);
});
I have created this Login class, but after login I want to open the contact page, but protractor immediately try to find element before the page is fully loaded.
I've tried to use:
browser.driver.wait(function() {
expect(browser.findElement(by.xpath("//a[#href='#/contacts']")).isDisplayed());
ptor.findElement(by.xpath("//a[#href='#/contacts']")).click();
});
But it doesn't work... it always try to find the element before the page loads.
I tried this one too:
browser.driver.wait(function() {
expect(ptor.isElementPresent(by.xpath("//a[#href='#/contacts']")));
ptor.findElement(by.xpath("//a[#href='#/contacts']")).click();
});
I'm able to do that using browser.sleep(); but I don't think that is a good option. Any idea? On my login class I have:
ptor.ignoreSynchronization = true;
How can I wait for this #href='#/contacts before protractor tries to click on it?

Protractor 1.7.0 has also introduced a new feature: Expected Conditions.
There are several predefined conditions to explicitly wait for. In case you want to wait for an element to become present:
var EC = protractor.ExpectedConditions;
var e = element(by.id('xyz'));
browser.wait(EC.presenceOf(e), 10000);
expect(e.isPresent()).toBeTruthy();
See also:
Expected conditions in protractor

I finally find out...
var waitLoading = by.css('#loading.loader-state-hidden');
browser.wait(function() {
return ptor.isElementPresent(waitLoading);
}, 8000);
expect(ptor.isElementPresent(waitLoading)).toBeTruthy();
var openContact = by.xpath("//a[#href='#/contacts']");
element(openContact).click();
With this protractor could wait for that element until it loading page disappears.
Thanks for those who tried to help XD.

I had the same problem you were having for the longest time while using protractor. In my e2e test I start in a non angular app, then get into an angular portion, then get back out to a non angular portion. Made things tricky. The key is to understand promises and how they work. Here's some examples of my real world code in a functioning e2e test. Hoping this gives you an idea of how to structure your tests. Probably some bad practice in this code, please feel free to improve upon this, but I know that it works, maybe not the best way.
To get to angular I use
var ptor;
var events = require('events');
var eventEmitter = new events.EventEmitter();
var secondClick = require('./second-click');
beforeEach(function () {
browser.driver.get('http://localhost:8080/');
},10000);
it("should start the test", function () {
describe("starting", function () {
it("should find the link and start the test", function(){
var elementToFind = by.linkText('Start'); //what element we are looking for
browser.driver.isElementPresent(elementToFind).then(function(isPresent){
expect(isPresent).toBe(true); //the test, kind of redundant but it helps pass or fail
browser.driver.findElement(elementToFind).then(function(start){
start.click().then(function(){ //once we've found the element and its on the page click it!! :)
ptor = protractor.getInstance(); //pass down protractor and the events to other files so we can emit events
secondClick(eventEmitter, ptor); //this is your callback to keep going on to other actions or test in another file
});
});
});
});
});
},60000);
While in angular this code works
describe("type in a message ", function(){
it("should find and type in a random message", function(){
var elementToFind = by.css('form textarea.limited');
browser.driver.isElementPresent(elementToFind).then(function(isPresent){
element(elementToFind).sendKeys(randomSentence).then(function(){
console.log("typed in random message");
continueOn();
});
});
});
},15000);
After exiting angular
browser.driver.wait(function(){
console.log("polling for a firstName to appear");
return browser.driver.isElementPresent(by.name('firstName')).then(function(el){
return el === true;
});
}).
then(function(){
somefunctionToExecute()
});
Hope that gives some guidance and helps you out!

browser.driver.wait(function() {
return browser.driver.isElementPresent(by.xpath("//a[#href='#/contacts']"));
});
This works for me too (without the timeout param)..
for more information, see http://angular.github.io/protractor/#/api?view=webdriver.WebDriver.prototype.wait

Thanks to answers above, this was my simplified and updated usage
function waitFor (selector) {
return browser.wait(function () {
return browser.isElementPresent(by.css(selector));
}, 50000);
}

Have you tried putting the ng-app in the <html> tag (assuming this part of code is under your control)? This solved a lot of initialization timing problems for me.

Best way to use wait conditions in protractor that helps to show proper error message to particular element if test case failed
const EC = ExpectedConditions;
const ele = element(by.xpath(your xpath));
return browser.wait(EC.visibilityOf(ele),9000,'element not found').then(() => {
ele.click();
});

I'm surprised that nobody has added this solution. Basically, if you are using modal dialogues you often get an element visible and available to click but not being clickable due to the modal dialogue being in front of it. This happens because protractor moves faster than angular and is ready to click the next element while angular is still closing the modal.
I suggest using
public async clickElementBug(elementLocator: Locator) {
const elem = await element(elementLocator);
await browser.wait(
async function() {
try {
await elem.click();
return true;
} catch (error) {
return false;
}
},
this.TIMEOUT_MILLIS,
'Clicking of element failed: ' + elem
);
}

browser.wait may sound too ordinary, but it's not!
browser.wait is the way to go. Just pass a function to it that would have a condition which to wait for. For example wait until there is no loading animation on the page
let $animation = $$('.loading');
await browser.wait(
async () => (await animation.count()) === 0, // function; if returns true it stops waiting; can wait for anything in the world if you get creative with it
5000, // timeout
`message on timeout` // comment on error
);
Make sure to use await
You can also use existing library called ExpectedConditions that has lots of predefined conditions to wait for
You can't imagine what you can do with it...
A few of my favorite ones:
wait until the number of browser's tab's is 2
// wait until the number of browser's tab's is 2
await browser.wait(
async () => {
let tabCount = await browser.getAllWindowHandles();
return tabCount.length === 2;
},
5000,
'the url didnt open in a new window'
);
wait until the loading animation is gone for at last 750ms
// wait until the loading animation is gone for at last 750ms
await browser.wait(
async () => (await this.$$loadAnimations.count()) === 0 && !(await browser.sleep(750)) && (await this.$$loadAnimations.count()) === 0,
5000,
`waiting timeout`
);
wait for ANY number of elements to be present
// wait for any number of elements to be present
async waitForElements($elem, timeout = 120000, start = +new Date()) {
let conditions = [];
for (let i = 0; i < $elem.length; i++) {
conditions.push(ExpectedConditions.presenceOf($elem[i]));
}
await browser.wait(
ExpectedConditions.and(...conditions),
remainingTimeout(timeout, start),
`wait for all elements`
);
}
// and use
await waitForElements([
$usernameField,
$passwordFiend,
$submitButton
])

Related

Cypress - mocking window property

I have a code that uses window.foo.abc as a condition to display something.
I want to test this functionality with cypress and I want to mock this value to be false and true.
How can I do that?
I've tried
before(function() {
Cypress.on('window:before:load', win => {
win.foo.abc = true;
});
and
cy.window().then(win => {
window.foo.abc = true;
});
with no success.
How can I mock this value?
thanks 🙏
This code is incorrect,
Cypress.on('window:before:load', win => {
window.foo.abc = true;
});
It should be
Cypress.on('window:before:load', win => {
win.foo.abc = true;
});
You don't have to use it in before(), but it should be at the top of the spec.
But I suspect it still won't work after correcting, most likely the app resets foo to a new object during loading, i.e during cy.visit()
You can use the 2nd block
cy.visit('...') // visit before changing
cy.window().then(win => {
win.foo.abc = true; // correct the syntax here as well
})

Unable to locate MatSnackBar element in protractor e2e test

I am not able to locate MatSnackBar element using protractor.
This is how I display snack bar.
const snackBarRef = this.snackBar.open('Book added.', 'Undo', {
duration: 300000
});
This is e2e test.
const snackBar = element(by.tagName('simple-snack-bar'));
browser.wait(ExpectedConditions.visibilityOf(snackBar), 30000);
element(by.tagName('simple-snack-bar')).getText().then(function (val) {
console.log(val);
expect(val).toEqual('Book added');
});
Test code fails with this error.
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Thanks for your message, #dplavcic.
The reason that my code didn't work is that snackbar was out of angular root. So I have to use browser.driver.findElement() instead of element(), and finally have solved the issue.
Something like:
public getSnackBar(): promise.Promise<string> {
const snackbar = browser.driver.wait(until.elementLocated(By.tagName('simple-snack-bar')), 10000);
return snackbar.getText();
}```
The time out is from the browser.wait of 30 second, so the application cannot find the elemnt with in that time . so increase the time out as :
add below property to your config file:
jasmineNodeOpts: {defaultTimeoutInterval: 40000}
About element not found
make sure elemnt is not inside iframe or shadown , if so , then switch to parent before accessing it
use promise chaining properly or use await instead
it('should find an element by text input model', async function() {
await browser.get('app/index.html#/form');
var username =element(by.model('username'));
await username.clear();
await username.sendKeys('Jane Doe');
var name = element(by.binding('username'));
expect(await name.getText()).toEqual('Jane Doe');
});

Cypress request wait by default?

I need Cypress to wait for any xhr requests to complete by default before performing any operations. Is there any way to make this as a default or any other alternatives because the application I am testing is slow and makes a lot of api calls?
Edit: By writing a single statement for every api request is getting messy and unnecessary work. Need a way to make this easier.
If what you want is to wait for a specific xhr you can do it making use of cy.route(). I use this in some scenarios and it is really useful. The general steps to use it are:
cy.server()
cy.route('GET','**/api/my-call/**').as('myXHR');
Do things in the UI such as clicking on a button that will trigger such api calls
cy.wait(#myXHR)
This way if such call isn't triggered your test will fail. You can find extensive documentation about this here
Found something that works for me here https://github.com/PinkyJie/cypress-auto-stub-example
Look for cy.waitUntilAllAPIFinished
I partialy solve the problem adding a waitAll command and ovewrite route command in support folder:
const routeCallArr = [];
Cypress.Commands.overwrite('route', (route, ...params) => {
const localRoute = route(...params);
if (localRoute.alias === undefined) return;
localRoute.onRequest = function() {
routeCallArr.push({alias: `#${localRoute.alias}`, starTime: Date.now()});
}
localRoute.onResponse = function() {
clearCall(`#${localRoute.alias}`);
}
})
const waitAll = (timeOut = 50000, options = {verbose: false, waitNested: false}) => {
const filterRouteCallArr = [];
const date = Date.now();
for (const routeCall of routeCallArr) {
if ((date - routeCall.starTime) > timeOut) continue;
filterRouteCallArr.push(routeCall.alias);
}
if (options.verbose ){
console.table(routeCallArr.map(routeCall => ({
deltaTime: date - routeCall.starTime,
alias: routeCall.alias,
starTime: routeCall.starTime,
})));
console.log(routeCallArr, filterRouteCallArr)
};
routeCallArr.length = [];
if (filterRouteCallArr.length > 0) {
const waiter = cy.wait(filterRouteCallArr, {timeout: timeOut});
options.waitNested && waiter.then(() => {
if (routeCallArr.length > 0) {
waitAll(timeOut, options);
}
});
}
}
Cypress.Commands.add('waitAll', waitAll)
And in the test instead of use cy.wait(['#call01',..., '#callN']); I use cy.waitAll();
The problem with this implementation came when have nested calls in a relative separate time interval from original calls. In that case you can use a recursive wait cy.waitAll(50000, {waitNested: true});

Protractor Test Scripts are executing faster than the page load

I am automating an angular js website which has a login functionality. All I want to Click on sign in link and enter username and password. But Somehow my script are executing really fast than the page load. Please advice me on how I can handle this:
My Login Page object is:
'use strict'
// Normal Login
require('../page-objects/loginPage.js');
var customerPortalHome = function () {
this.clickSignIn = function () {
browser.sleep(5000);
element(by.linkText('Sign In')).click();
browser.waitForAngular();
browser.sleep(2000);
return require('./loginPage.js');
}
}
module.exports = new customerPortalHome();
My Test Spec is;
var co = require('co');
var UI = require('./ui.js');
var ui = new UI();
var CustomerPage = require('../page-objects/customerPortalHome.js')
describe(" Smoke Test Login to the application", function () {
it("test", co.wrap(function* () {
var EC = protractor.ExpectedConditions;
browser.get(ui.createStartLink());
expect(browser.getTitle()).toContain("Portal");
// Verify if user is able to Login into the application
var loginPage = CustomerPage.clickSignIn();
loginPage.switchToFrame('account-sdk');
var reportPage = loginPage.clickLogin('$$$$$#gmail.com', '$$$$$');
expect(browser.getCurrentUrl()).toContain('reports');
reportPage.clickSignOut();
expect(browser.getCurrentUrl()).toContain("?signout");
browser.sleep(800);
}));
});
Whenever I execute the test The browser opens for a sec and then closes.
My Onprepare method looks like this:
beforeLaunch: function () {
return new Promise(function (resolve) {
reporter.beforeLaunch(resolve);
});
},
onPrepare: function () {
browser.manage().timeouts().implicitlyWait(5000);
afterEach(co.wrap(function* () {
var remote = require('protractor/node_modules/selenium-webdriver/remote');
browser.driver.setFileDetector(new remote.FileDetector());
}));
Using browser sleep is never a good idea the best thing to do is to wait for an element and use the then function to do so.
element(by.xpath("xpath")).click().then(function(){
var list = element(by.id('id'));
var until = protractor.ExpectedConditions;
browser.wait(until.presenceOf(list), 80000, 'Message: took too long');
});
browser.wait(protractor.ExpectedConditions.visibilityOf($$('.desk-sidebar > li').get(num-1)), 60000);
Usually,I use this wait.
Are you using an ignoreSynchronisation without putting the false somewhere in your page objects helpers?
Be careful, the login can sometimes break the waitForAngular when there are a lot of redirections. I ended up using a dirty sleep to wait the page to be loaded when logging (no other solution were found, ignoreSync, EC for a change of url and wait for an element were not working solutions).
You should also share the error you get.

Check title of multiple pages in Selenium

I'm currently using WebDriver.js to run some automated testing on Browserstack. My goal is to take all the hrefs in a given div, open them and check the title of the page where the link is pointing to. To do that I'm doing the following:
for (var i = 0; i < hrefs.length; i++) {
var href = hrefs[i],
site_name = sites_names[i];
driver.get(href);
driver.getTitle().then(function(title) {
assert.equal(title, site_name);
});
}
My problem is that get and getTitle are both asynchronous methods and thus when I'm calling getTitle the page is already changed and thus the assertion is failing. What is the best pattern/solution for this kind of sitations?
If I am reading your question correctly, your assertions are failing because the assert happens before the page is completely loaded in the browser? I think you require a "wait" logic around your getTitle call as mentioned here
var webdriver = require('selenium-webdriver');
var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.chrome()).build();
driver.get('http://www.google.com');
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.name('btnG')).click();
driver.wait(function() {
return driver.getTitle().then(function(title) {
return title === 'webdriver - Google Search';
});
}, 1000);
driver.quit();
If you want to just check the title of all the links present in you page you can change your driver to
WebDriver driver = new HtmlUnitDriver();
This is just a good practice.... not mandatory (to increase the speed of execution). After checking the title you can change your driver back to the original.
Use the below code to iterate valid href one by one and assert it accordingly
List<WebElement> allLinks = driver.findElements(By.tagName("a")); // use tagname according to the need
for (WebElement link : allLinks) {
if (link.getAttribute("href") != null && link.getText().isEmpty()==false) { //again modify the condition here accordingly
driver.get(link.getAttribute("href"));
driver.getTitle().then(function(title) {
assert.equal(title, site_name);
});
}
}

Categories

Resources