This question is closely related to the solutions given in this question
In my test script, I need to go to login script and need to logout in case the browser logs in automatically in the app. So following the solutions provided in the question How to create a condition in protractor for when an element exists or not, I have created this script:
beforeEach(function () {
browser.driver.manage().window().maximize();
browser.get(globalVariables.loginMain);
globalVariables.User_Menu_Dropdown.isDisplayed().then(function(Login_Menu) {
if (Login_Menu) {
globalVariables.User_Menu_Dropdown.click();
browser.wait(globalVariables.until.presenceOf(globalVariables.logOut_Button), 3000, 'The Logout menu too long to appear in the DOM');
globalVariables.logOut_Button.click();
browser.wait(globalVariables.until.presenceOf(globalVariables.Email_Input_box), 3000, 'The User Input box too long to appear in the DOM');
} else {
console.log("the app is on the login page")//do nothing
}
});
But when I run the script, I still get the following error
"Failed: No element found using locator: By(css selector, img[class="img-thumb-xs mr-1 align-middle"])". What am I doing wrong here? What is the best approach to achieve it?
you can use the ExpectedConditions in your case.
var EC = protractor.ExpectedConditions;
// Waits for the element with id 'abc' to be no longer visible on the dom.
browser.wait(EC.invisibilityOf($('#abc')), 5000);
or you can use the not condition which will lead to the same result
var EC = protractor.ExpectedConditions;
// Waits for the element with id 'abc' to be no longer visible on the dom.
browser.wait(EC.not(EC.visibilityOf($('#abc'))), 5000);
Related
I'm trying to click on '0 - Bootstrapping' under Tutorial after going to https://docs.angularjs.org/tutorial, but Protractor is not waiting for the page to fully load, so I get an Error
Failed: Cannot read property 'click' of undefined
I tried manually waiting for the page to load using
browser.driver.sleep(10000);
but I still get the same Error.
conf.js
exports.config = {
// seleniumAddress: 'http://localhost:4444/wd/hub',
getPageTimeout : 12000000,
capabilities: {
'browserName': 'chrome'
},
specs: ['todo-spec.js'],
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 1440000,
}
};
todo-spec.js
describe('angularjs homepage', function () {
it('should greet the named user', function () {
var dropdown = element.all(by.css('.dropdown')).first(),
dropdownToggle = dropdown.element(by.css('.dropdown-toggle')),
dropdownMenu = dropdown.element(by.css('.dropdown-menu')),
menuItem = dropdownMenu.all(by.tagName('li')).first();
browser.get('http://www.angularjs.org');
dropdownToggle.click();
menuItem.click();
expect(browser.getCurrentUrl()).toEqual('https://docs.angularjs.org/tutorial');
//waiting 10 seconds for the page to fully load
browser.driver.sleep(10000);
browser.waitForAngular();
});
it('', async function () {
/* tried waiting for the element to be present
var EC = protractor.ExpectedConditions;
var e = element.all(by.css('.nav-index-listing'))[0];
browser.wait(EC.presenceOf(e), 10000);
Failed: Cannot read property 'isPresent' of undefined
*/
//clicking '0 - Bootstrapping' under Tutorial
element.all(by.css('.nav-index-listing'))[0].click();
expect(element(by.cssContainingText('.pln', 'npm install')).isEnabled()).toBe(true);
// Failed: Cannot read property 'click' of undefined
});
});
Edit
Replaced
element.all(by.css('.nav-index-listing'))[0].click();
expect(element(by.cssContainingText('.pln', 'npm install')).isEnabled()).toBe(true);
by
element.all(by.css('.nav-index-listing')).get(0).click();
browser.wait(protractor.ExpectedConditions.urlIs("https://docs.angularjs.org/tutorial/step_00"), 5000);
expect(browser.getCurrentUrl()).toEqual('https://docs.angularjs.org/tutorial/step_00');
expect(element(by.cssContainingText('.pln', 'npm install')).isEnabled()).toBe(true);
And got an Error
Failed: Wait timed out after 5001ms
If I remove browser.wait I get
Expected 'https://docs.angularjs.org/tutorial' to equal 'https://docs.angularjs.org/tutorial/step_00'.
First of all browser.driver.sleep(10000) is not required. For page load, you can use protractor.ExpectedConditions. It will reduce unnecessary waiting for 10 seconds, if the page is already loaded.
Secondly, there is a minor mistake in your code which is the root cause of your problem.
Change this line
element.all(by.css('.nav-index-listing'))[0].click();
To this
element.all(by.css('.nav-index-listing')).get(0).click();
UPDATE:
element.all(by.css('.nav-index-listing')) will only give you li elements which will pass the test. But clicking it, will not lead you to the desired page.
Change it to:
element.all(by.css('.nav-index-listing a')).get(0).click();
In this case, [0] will not work since the returned value is not an array. It is of type ElementArrayFinder. So, that's why .get(0) is required.
It will be better if you check for below conditions too, for your test.
browser.wait(EC.urlIs("https://docs.angularjs.org/tutorial/step_00"), 5000);
expect(browser.getCurrentUrl()).toEqual('https://docs.angularjs.org/tutorial/step_00');
These conditions checks whether your current URL is https://docs.angularjs.org/tutorial/step_00 or not.
Try using waitForAngualarEnabled(true) instead of expected wait which makes the protractor to wait until all the angular elements are loaded and add await in every line of your test.
I created a basic test with protractor to click on a element and on the new page check if given element exists, but Protractor does not seem to wait and it runs the assertion just after the click but before the new page loads. The element I am looking for is available on both pages, so protractor sees the element on the old page, before the new page loads. Can someone please tell me what am I doing wrong?
it('should check when new page is loaded', function () {
button.click().then(function (){
return expect(newElement).to.exist;
});
First of all ,add "getPageTimeout" variable to your Protractor configuration file.
If you are not using it already.This is to set global page timeout based on your average page load time in your application.
conf.js
getPageTimeout: 120000,//change it based on your app response time
If it does not help even then verify the page title of next page(assuming its different from previous page) before checking the actual element you are looking for.
it('should check when new page is loaded', function () {
button.click().then(function (){
browser.getCurrentUrl();
browser.getTitle().then(function (title) {
expect(title).toEqual('Next Page Title');
});
return expect(newElement.isDisplayed()).toBeTruthy();
});
Even if that does not help you may use 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();
You can use below method in Configuration file
jasmineNodeOpts: {
showColors: true,
includeStackTrace: true,
defaultTimeoutInterval: 1440000
},
And In Spec file you can customise the wait time according to page and object visibility
browser.sleep(20000);
I hope above method will work fine in all the cases if you customise the wait time Properly.
My test steps to test a logout flow are,
1) click a logout button
2) wait for url change to login.html
3) wait for login page text fields to be loaded.
Code looks like,
//wait for logout menu/button
browser.wait(function(){
return element(by.buttonText('Log out')).isPresent()
})
element(by.buttonText('Log out')).click()
//wait for url to change to login.html
browser.wait(function(){
return browser.getCurrentUrl().then(function(url){
return url.indexOf("login") != -1
})
})
//wait for login page text boxes
browser.wait(function(){
return element(by.css('[type=text]')).isPresent()
})
This makes my code lengthy since, i am wrapping each action inside a browser.wait call.
Is there a way i can avoid browser.wait. I tried adding,
browser.manage().timeouts().implicitlyWait(5000)
But then, i get No element found using locator: by.buttonText("Log out") error.
Protractor has inbuilt ExpectedConditions checks, you don't have to write a custom function yourself all the time. Here's how to wait for an element to be visible -
var EC = protractor.ExpectedConditions;
browser.wait(EC.presenceOf(element(by.buttonText('Log out'))), 10000); //Checks only if element is present in DOM
browser.wait(EC.visibilityOf(element(by.buttonText('Log out'))), 10000); //Checks if element is present in DOM and visible to user on page
You can also create a custom function as shown in another answer. Hope this helps.
If you are doing the same thing more than once, it tends to help to make a helper function for it. Add to your helper file something like:
this.waiterFunc = function(element){
browser.wait(function() {
return element.isPresent();
})
};
then in your main class you can do things like:
helper.waiterFunc(element(by.buttonText('Log out')));
Im not sure about avoiding browser.wait as its an integral step in the process of waiting for the elements to be accessible at a particular time. If you dont wait, then you are going to start getting nasty "element not clickable at time x" errors
I'm on my third day working with Protractor and I'm constantly hitting bric walls in regards to waiting around for pages to load and elements to appear. This test case in particular has grown ugly and I would like to solve the issues without having to rely on sleeps.
I am currently "outside of the land of AngularJS"
it('it should reflect in both the field and the title when the personnel name is changed', function() {
var inputField, personnelHeader, personnelName;
personnelName = element(By.css(".overlay.editnameoverlay")).click();
personnelHeader = element(By.id("personnel_name_header"));
inputField = element(By.css("input[name='newvalue']"));
inputField.clear();
inputField.sendKeys("Test 123");
element(By.css("input[name='ok_button']")).click();
// browser.driver.sleep(2000); This test only works with this sleep added
browser.wait(function() {
console.log("Waiting for header to change...");
return personnelHeader.getText().then(function(text) {
return text === "Test 123";
});
}, 5000);
return expect(personnelHeader.getText()).toBe(personnelName.getText());
});
So the test here changes the name in an input field. submits it and waits for the changes to become reflected in the header of the modal. The problem is that without the browser.driver.sleep(2000) I get an error saying
Stacktrace:
StaleElementReferenceError: stale element reference: element is not attached to the page document
How do I go about solving this in this particular case?
From the documentation for Expect Conditions:
var EC = protractor.ExpectedConditions;
// Waits for the element with id 'abc' to contain the text 'foo'.
browser.wait(EC.textToBePresentInElement($('#abc'), 'foo'), 5000);
When you use Protractor to test for non-angular pages you're on your own regarding waiting for elements to be ready for interaction.
StaleElementReferenceError is probably the most useless selenium error, it happens when the element got removed from the DOM but is still cached somehow, I also suffered this problem when started with Protractor and even tried to convince it should be automatically retried Protractor-side.
The solution for me is to always explicitly wait for an element to appear on the page using a custom function waitReady() that browser.wait for elements ready, i.e: waits for the element to be ready for interaction:
expect($('#login_field').waitReady()).toBeTruthy();
First integrate this snippet in your code: https://gist.github.com/elgalu/2939aad2b2e31418c1bb
Not only the custom waitReady() waits for the element but it also swallows any unrelated useless webdriver error like StaleElementReferenceError and will simply retry up until finding the element or it will timeout.
So waitReady() each element before interacting, i.e. before clear() or sendKeys() or click() ...
// TODO: Move to Page Objects module
var personnelNameElm = $(".overlay.editnameoverlay");
var personnelHeaderElm = $("#personnel_name_header");
var inputFieldElm = $("input[name='newvalue']");
var okBtnElm = $("input[name='ok_button']");
it('it should reflect in both the field and the title when the ' +
'personnel name is changed', function() {
expect(personnelNameElm.waitReady()).toBeTruthy();
personnelNameElm.click();
expect(inputFieldElm.waitReady()).toBeTruthy();
inputFieldElm.clear().sendKeys("Test 123");
expect(okBtnElm.waitReady()).toBeTruthy();
okBtnElm.click();
browser.wait(function() {
console.log("Waiting for header to change...");
// Using waitReady() before getText() avoids Stale element errors
return personnelHeaderElm.waitReady().then(function() {
return personnelHeaderElm.getText().then(function(text) {
return text === "Test 123";
});
});
}, 5000);
expect(personnelHeaderElm.getText()).toEqual(personnelNameElm.getText());
});
I am new to Protractor. I think I have this down when dealing with an Angular page, but can't figure it out for a non-Angular page. Any help would be appreciated.
describe('Search', function() {
it('should click Search button and wait for results', function() {
browser.driver.findElement(by.id('search')).click();
});
});
Testing non-angular pages with Protractor can be tricky regarding waiting for stuff.
I suggest you upgrade Protractor to latest (1.5.0 as of now), use a custom function waitReady() that browser.wait for elements ready and rewrite your test like below. Note you can put everything within 1 spec if you like so.
// TODO: use page objects
var searchBtnElm = $('#search'); // use element(by.id('search')) if you prefer
it('waits for the elements present and visible (non-angular)', function() {
expect(searchBtnElm.waitReady()).toBeTruthy();
});
it('should click Search button', function() {
searchBtnElm.click();
});
it('wait for more results', function() {
// keep using waitReady() before interacting with the elements
// and before performing expectations on them
});
More details of why waitReady here.
Note: remember to set ignore synchronization for testing a non-angular page:
browser.ignoreSynchronization = true;
You can set it before browser.get the non-angular page.
I've suggested setting a high implicit wait in the past, e.g.
browser.manage().timeouts().implicitlyWait(5000);
That hack allows to you avoid waitReady and keep using the standard
expect(searchBtnElm.isPresent()).toBeTruthy();
But has an ugly disadvantage when testing for elements NOT present, i.e. when testing for absent or non visible elements in which case it will wait 5 seconds (5000ms) in vane, e.g. when doing
expect(someNonExistingElm.isPresent()).toBeFalsy();
Figured this out. I simply added the code below, after the click method:
describe('Search', function() {
it('should click Search button and wait for results', function() {
browser.driver.findElement(by.id('search')).click();
dvr.wait(function() {
return dvr.isElementPresent(by.xpath(
'/html/body/div/div[4]/div/div[2]/div/div/div/span'));
}, 20000);
});
});
Another Neat approach is to use "Expected Conditions" inside browser.wait - something like this:
var EC = protractor.ExpectedConditions;
var search = element(by.id('search'))
browser.wait(EC.visibilityOf(search), 2000).then(function(){
search.click()
})
You can get more details here: https://angular.github.io/protractor/#/api?view=ExpectedConditions
In protractor there are two types terms for on the page. isPresent ask if the element is exists on the page. isDisplayed asks if the element is visible. If you are waiting for a page to load you need to wait for isDisplayed, but that will error if it is not present, so wait for isPresent first. I use a function to wait for an element.
function waitForElement(el, waitTimeoutMilliseconds){
return browser.wait(function() { return el.isPresent(); }, waitTimeoutMilliseconds)
.then(function(){
return browser.wait(function() { return el.isDisplayed(); }, waitTimeoutMilliseconds);
});
}
Then just call that function in your test.
describe('Search', function() {
it('should click Search button and wait for results', function() {
var el = element(by.id('search'));
waitForElement(el, 5000);
el.click();
});
});