How to select a drop down using protractor? - protractor

Can any one help me on how to select a drop down in protractor.
Page Object code
function selectDropdownbyNum(element, optionNum) {
if (optionNum) {
element.all(by.tagName('option')).then(function(options) {
browser.sleep('5000');
options[optionNum].click();
console.log('Desired value selected');
});
}
}
var pageName= function(){
this.selectTier = async function(){
var Tiers = element(by.xpath(/*element value*/));
console.log('select silver method');
browser.sleep(5000);
selectDropdownbyNum(Tiers,2);
console.log('value selected');
};
};
module.exports = new pageName();
And Spec is as follows
it('select Silver Tier',async function(){
browser.ignoreSynchronization = true;
console.log('Executing silver tier selection test case');
await pageName.selectTier()
});
I have tried the above code. I am able to print all the values of the drop down, but am unable to click.
Is their any mistake in the above code.I can print the 'Desired value selected'. But value was not selected

May this will help you for selecting option
element(by.cssContainingText('option','Option value')).click();
or
element(by.id('id')).sendKeys("Values from option");
this worked for me

Try:
var Tiers = element(by.xpath(dropDownValue));
Tiers.click();
selectDropdownbyNum(element, optionNum) {
if (optionNum) {
element.all(by.tagName('option')).then(function(options) {
options[optionNum].click();
});
}
}
selectDropdownbyNum(Tiers,4)
Note:
avoid using Xpath example use :
element(by.css('select[formcontrolname="any value according to situation"]'));

I haven't tested it, but I suppose it's because of the nested promise you are using inside the for loop. The nature of the promise is to be async, and the for loop is synchronous, which results in the loop complete whyle the very first promise items[i].getText().then get's resolved and that's why your click didn't succeed. If you don't need to know the option names, then just remove the nested promise items[i].getText() and just execute the click in the loop.

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!

Protractor element handling

I have a question regarding how protractor handles the locating of elements.
I am using page-objects just like I did in Webdriver.
The big difference with Webdriver is that locating the element only happens when a function is called on that element.
When using page-objects, it is advised to instantiate your objects before your tests. But then I was wondering, if you instantiate your object and the page changes, what happens to the state of the elements?
I shall demonstrate with an example
it('Change service', function() {
servicePage.clickChangeService();
serviceForm.selectService(1);
serviceForm.save();
expect(servicePage.getService()).toMatch('\bNo service\b');
});
When debugging servicePage.getService() returns undefined.
Is this because serviceForm is another page and the state of servicePage has been changed?
This is my pageobject:
var servicePage = function() {
this.changeServiceLink = element(by.id('serviceLink'));
this.service = element(by.id('service'));
this.clickChangeService = function() {
this.changeServiceLink.click();
};
this.getService = function() {
return this.service.getAttribute('value');
};
};
module.exports = servicePage;
Thank you in advance.
Regards
Essentially, element() is an 'elementFinder' which doesn't do any work unless you call some action like getAttribute().
So you can think of element(by.id('service')) as a placeholder.
When you want to actually find the element and do some action, then you combine it like element(by.id('service')).getAttribute('value'), but this in itself isn't the value that you are looking for, it's a promise to get the value. You can read all about how to deal with promises elsewhere.
The other thing that protractor does specifically is to patch in a waitForAngular() when it applies an action so that it will wait for any outstanding http calls and timeouts before actually going out to find the element and apply the action. So when you call .getAttribute() it really looks like
return browser.waitForAngular().then(function() {
return element(by.id('service')).getAttribute('value');
});
So, in your example, if your angular pages aren't set up correctly or depending on the controls you are using, you might be trying to get the value before the page has settled with the new value in the element.
To debug your example you should be doing something like
it('Change service', function() {
servicePage.getService().then(function(originalService) {
console.log('originalService: ' + originalService);
});
servicePage.clickChangeService();
serviceForm.selectService(1);
serviceForm.save();
servicePage.getService().then(function(newService) {
console.log('newService: ' + newService);
});
expect(servicePage.getService()).toMatch('\bNo service\b');
});
The other thing that I'm seeing is that your pageObject appears to be a constructor when you could just use an object instead:
// name this file servicePage.js, and use as 'var servicePage = require('./servicePage.js');'
module.exports = {
changeServiceLink: element(by.id('serviceLink')),
service: element(by.id('service')),
clickChangeService: function() {
this.changeServiceLink.click();
},
getService: function() {
return this.service.getAttribute('value');
}
};
Otherwise you would have to do something like module.exports = new servicePage(); or instantiate it in your test file.
When you navigate another page, the web elements will be clear, that you selected. So you have to select again. You can select all elements that is in a page of HTML. You can click that you see. So the protactor + Selenium can decide what is displayed.
You have a mistake in your code, try this:
expect(servicePage.getService()).toMatch('\bNo service\b');

Protractor how to wait for options

I have code like this:
element(by.model("roleSelection.role")).element(by.cssContainingText('option', newRole)).click();//.then(function() {console.log('role click')})//;
where the options is loaded via a call to the server.
I can wait for the first element by doing this
browser.wait(function() {
return browser.isElementPresent(by.model("roleSelection.role")).then(function(present){
return present;
});}, 8000);
and it seems to work. But how can I wait until the "sub-element" is clickable.
I have tried this
browser.wait(function() {
return browser.isElementPresent(by.model("roleSelection.role")).then(function(present){
if (present) {
var elm = element(by.model("roleSelection.role"));
return elm.isElementPresent(by.cssContainingText('option', newRole)).then(function(subpresent) {
return subpresent;
});
}
}); }, 8000);
Have you tried clickable? Something along these lines
var EC = protractor.ExpectedConditions;
var select = element(by.model("roleSelection.role"))
var isClickable = EC.elementToBeClickable(select);
browser.wait(isClickable,5000); //now options should have been loaded by now
Well, try to this: https://angular.github.io/protractor/#/api?view=ExpectedConditions.prototype.elementToBeClickable
But, Please keep in mind, Protractor is suitable for angular webpages and interactions, and animations. For example ng-animate. So, it is not sure to working for example jquery, or other animates.
In this way:
onPrepare: function () {
// disable animations when testing to speed things up
var disableNgAnimate = function () {
angular.module('disableNgAnimate', []).run(function ($animate) {
$animate.enabled(false);
});
};
browser.addMockModule('disableNgAnimate', disableNgAnimate);
}
Or you can switch in script way in browser.executeScript().
Please see this link. It works only jquery animations.
If you not have animate problems. Use setTimeout() JS function.

Protractor - How to obtain the new URL

I am new to Protractor (and Javascript by the way), and I am writing some tests to practice. My goal so far is to check that when I click on the home button of a website, the redirection leads me correctly to the expected address.
I have written this:
var HomeTopBanner = function() {
this.homeUrl = browser.params.homePageObject.homeUrl;
this.topBanner = element(by.css('.navbar-inner'));
this.homeButton = this.topBanner.element(by.css('.icon-home'));
}
describe('Home button', function(){
var homeTopBanner = new HomeTopBanner();
var newUrl = '';
it('clicks on the Home button', function(){
homeTopBanner.homeButton.click();
browser.getCurrentUrl().then(function storeNewUrl(url) {
newUrl = url;
});
})
it('checks that the home button leads to the homepage', function(){
expect(newUrl).toEqual(homeTopBanner.homeUrl);
})
});
This works, but my question is:
Why do I need to separate the "GetCurrentUrl" and the "expect(newUrl)" parts? I would prefer to have both of them in the same spec, but if I do that, during the comparison of the expect, newUrl=''
I assume this is related to browser.getCurrentUrl() being a promise, but is there a better way to do it?
Yes, getCurrentUrl returns a promise with the url in the form of a string as explained in the protractor api docs. You have to wait until the url is returned in order to use it. Now in order to combine both the specs you can write your expect statement inside the function that getCurrentUrl returns as shown below and there is no need of using a newUrl variable too if you want -
it('clicks on the Home button', function(){
homeTopBanner.homeButton.click();
browser.getCurrentUrl().then(function(url) {
expect(url).toEqual(homeTopBanner.homeUrl);
});
})
There could also be another issue when after the click action the previous url is being captured due to the fact that protractor is async and fast. In that case you can write your getCurrentUrl() function inside the promise that click() function returns. Here's an example of it -
it('clicks on the Home button', function(){
homeTopBanner.homeButton.click().then(function(){
browser.getCurrentUrl().then(function(url) {
expect(url).toEqual(homeTopBanner.homeUrl);
});
});
})
Hope this helps.

infinite scroll manual trigger callback

I'm using Infinite Scroll plugin in a (I know it is unrecommended http://isotope.metafizzy.co/docs/help.html#infinite_scroll_with_filtering_or_sorting), Infinite Scroll + Isotipe Filtering combination.
Now sometimes happend that after i run my filter, if I get an empty list i manually trigger infinite scroll to load more elements.
$('.items').isotope({ filter: filter }, function( $items ) {
var id = this.attr('class'),
len = $items.length;
if (len == 0){getElement();}
});
Here is my function that load elements, but it seems that the callback is not working.
function getElement(){
$('.items').infinitescroll('retrieve',function(items){
console.log('callback');
console.log(items);
});
}
Unfortunally Infinite Scroll documentation is not the best for manual trigger (it suggest a not-working way to call it - $(document).trigger('retrieve.infscr'); i found the solution here: infinite scroll manual trigger) so I'm a little bit stucked here.
Any suggestion?
I know it is a very old post. But I find it over Google as I searched for the self problem. But I also find the solution for this. Perhaps it helps somebody...
You must put the callback not to the main function. Because manual function only push the main function.
jQuery(document).ready(function($) {
var $infinitecontainer = $(".infinite-content").infinitescroll({
navSelector: ".nav-links",
nextSelector: ".nav-links a:first",
itemSelector: ".infinite-post",
errorCallback: function(){ $(".inf-more-but").css("display", "none") }
}, function() { // callback
alert("Manual click load finished");
});
$(window).unbind(".infscr");
$(".inf-more-but").click(function(e){
e.preventDefault();
var infinite_scroll = $(".infinite-content").infinitescroll("retrieve");
return false;
});