I'm trying to test if an element is visible using protractor. Here's what the element looks like:
<i class="icon-spinner icon-spin ng-hide" ng-show="saving"></i>
When in the chrome console, I can use this jQuery selector to test if the element is visible:
$('[ng-show=saving].icon-spin')
[
<i class="icon-spinner icon-spin ng-hide" ng-show="saving"></i>
]
> $('[ng-show=saving].icon-spin:visible')
[]
However, when I try to do the same in protractor, I get this error at runtime:
InvalidElementStateError:
invalid element state: Failed to execute 'querySelectorAll' on 'Document':
'[ng-show=saving].icon-spin:visible' is not a valid selector.
Why is this not valid? How can I check for visibility using protractor?
This should do it:
expect($('[ng-show=saving].icon-spin').isDisplayed()).toBe(true);
Remember protractor's $ isn't jQuery and :visible is not yet a part of available CSS selectors + pseudo-selectors
More info at https://stackoverflow.com/a/13388700/511069
The correct way for checking the visibility of an element with Protractor is to call the isDisplayed method. You should be careful though since isDisplayed does not return a boolean, but rather a promise providing the evaluated visibility. I've seen lots of code examples that use this method wrongly and therefore don't evaluate its actual visibility.
Example for getting the visibility of an element:
element(by.className('your-class-name')).isDisplayed().then(function (isVisible) {
if (isVisible) {
// element is visible
} else {
// element is not visible
}
});
However, you don't need this if you are just checking the visibility of the element (as opposed to getting it) because protractor patches Jasmine expect() so it always waits for promises to be resolved. See github.com/angular/jasminewd
So you can just do:
expect(element(by.className('your-class-name')).isDisplayed()).toBeTruthy();
Since you're using AngularJS to control the visibility of that element, you could also check its class attribute for ng-hide like this:
var spinner = element.by.css('i.icon-spin');
expect(spinner.getAttribute('class')).not.toMatch('ng-hide'); // expect element to be visible
I had a similar issue, in that I only wanted return elements that were visible in a page object. I found that I'm able to use the css :not. In the case of this issue, this should do you...
expect($('i.icon-spinner:not(.ng-hide)').isDisplayed()).toBeTruthy();
In the context of a page object, you can get ONLY those elements that are visible in this way as well. Eg. given a page with multiple items, where only some are visible, you can use:
this.visibileIcons = $$('i.icon:not(.ng-hide)');
This will return you all visible i.icons
If there are multiple elements in DOM with same class name. But only one of element is visible.
element.all(by.css('.text-input-input')).filter(function(ele){
return ele.isDisplayed();
}).then(function(filteredElement){
filteredElement[0].click();
});
In this example filter takes a collection of elements and returns a single visible element using isDisplayed().
This answer will be robust enough to work for elements that aren't on the page, therefore failing gracefully (not throwing an exception) if the selector failed to find the element.
const nameSelector = '[data-automation="name-input"]';
const nameInputIsDisplayed = () => {
return $$(nameSelector).count()
.then(count => count !== 0)
}
it('should be displayed', () => {
nameInputIsDisplayed().then(isDisplayed => {
expect(isDisplayed).toBeTruthy()
})
})
To wait for visibility
const EC = protractor.ExpectedConditions;
browser.wait(EC.visibilityOf(element(by.css('.icon-spinner icon-spin ng-hide')))).then(function() {
//do stuff
})
Xpath trick to only find visible elements
element(by.xpath('//i[not(contains(#style,"display:none")) and #class="icon-spinner icon-spin ng-hide"]))
element(by.className('your-class-name'))
.isDisplayed()
.then(function (isVisible) {
if (isVisible) { // element is visible
} else { // element is not visible
}
})
.catch(function(err){
console.error("Element is not found! ", err);
})
Here are the few code snippet which can be used for framework which use Typescript, protractor, jasmine
browser.wait(until.visibilityOf(OversightAutomationOR.lblContentModal), 3000, "Modal text is present");
// Asserting a text
OversightAutomationOR.lblContentModal.getText().then(text => {
this.assertEquals(text.toString().trim(), AdminPanelData.lblContentModal);
});
// Asserting an element
expect(OnboardingFormsOR.masterFormActionCloneBtn.isDisplayed()).to.eventually.equal(true
);
OnboardingFormsOR.customFormActionViewBtn.isDisplayed().then((isDisplayed) => {
expect(isDisplayed).to.equal(true);
});
// Asserting a form
formInfoSection.getText().then((text) => {
const vendorInformationCount = text[0].split("\n");
let found = false;
for (let i = 0; i < vendorInformationCount.length; i++) {
if (vendorInformationCount[i] === customLabel) {
found = true;
};
};
expect(found).to.equal(true);
});
Something to consider
.isDisplayed() assumes the element is present (exists in the DOM)
so if you do
expect($('[ng-show=saving]').isDisplayed()).toBe(true);
but the element is not present, then instead of graceful failed expectation, $('[ng-show=saving]').isDisplayed() will throw an error causing the rest of it block not executed
Solution
If you assume, the element you're checking may not be present for any reason on the page, then go with a safe way below
/**
* element is Present and is Displayed
* #param {ElementFinder} $element Locator of element
* #return {boolean}
*/
let isDisplayed = function ($element) {
return (await $element.isPresent()) && (await $element.isDisplayed())
}
and use
expect(await isDisplayed( $('[ng-show=saving]') )).toBe(true);
waitTillElementIsPresent(locator: Locator): promise.Promise<boolean>
{
const EC = protractor.ExpectedConditions;
return browser.wait(EC.visibilityOf(element(by.id('xyz')), browser.params.explicitWaitTime, 'Element taking too long to appear in the DOM');
}
const isDisplayed = await $('div').isDisplayed().then(null, err => false)
Related
I have the following function:
function focusIsNotInInput() {
// If the element currently in focus is of a certain type, then the key handler shouldn't run
var currentlyInFocus = $window.document.activeElement;
var blacklist = ['INPUT', 'TEXTAREA', 'BUTTON', 'SELECT', 'IFRAME', 'MD-OPTION'];
return !blacklist.some(function (nodeName) {
return nodeName === currentlyInFocus.nodeName;
});
}
And I need to mock that the element currently in focus is of one of the specified types, but can't get it to work.
I've tried injecting window, like this:
beforeEach(function() {
var $windowMock;
inject(function(_$window_) {
$windowMock = _$window_;
$windowMock.document.activeElement.nodeName = 'INPUT';
});
});
But when the code above runs, the active element is always still body. It's getting overwritten. I have also tried creating an element and setting focus on it:
var elementInFocus = $('<input>');
this.elem.append(elementInFocus);
elementInFocus.triggerHandler('focus');
elementInFocus.focus();
But it's the same, body is always in focus, what ever I do.
I had some trouble with this too, a possible solution (worked for me) is to add a spyOn(element, 'focus') -- here's a reference: How do I check if my element has been focussed in a unit test
My successful solution:
const htmlItem = fixture.nativeElement;
const searchBar = htmlItem.querySelector('.search-box');
let focusSpy = spyOn(searchBar, 'focus');
searchBar.focus();
expect(focusSpy).toHaveBeenCalled();
it('should show archived row profiles', function() {
$$('.custom-control-input').get(0).click()
$$('tr.archived')
.then(function(elements) {
console.log(elements);
})
})
But now how can I check to see if the elements are displayed?
You can also use below piece of code,
var isElementsDisplayed = $$('tr.archived').isDisplayed();
expect(isElementsDisplayed).not.toContain(false);
isDisplayed() method will return array of boolean values(true/false) based on element display status. you can make a expect statement to check if the result array should not contain value false which means that particular element is not displayed.
You can use isDisplayed.
$$('tr.archived').each(function(element, index) {
console.log(element);
expect(element.isDisplayed()).toBe(true);
});
I have a dropdown with elements that get disabled when conditions are met. In the test, I check them for being disabled, but all tests fail and always return the element state as enabled (Clearly incorrectly. I have ensured that this is not a timing issue - refreshed the page and gave ample wait time with browser sleep - the elements are clearly disabled on the screen). There is an anchor within a list item. Please see image:
I have tried checking both the list item and the anchor, like so:
var actionDropDownList = $$('[class="dropdown-menu"]').get(1);
var checkOutButtonState = actionDropDownList.all(by.tagName('li')).get(6);
actionsButton.click();
actionDropDownList.all(by.tagName('li')).count().then(function(count){
console.log('THE NUMBER OF ELEMENTS IN THE DROPDOWN IS...............................................................' + count);
}) //verify that I have the correct dropdown - yes
checkOutButtonState.isEnabled().then(function(isEnabled){
console.log('CHECKING checkOutButton BUTTON STATE: ' + isEnabled);
}) //log state - shows incorrectly
I have also tried checking the button itself for disabled state (the element below is what I tried checking instead of the list element):
var checkOutButton = $('[ng-click="item.statusId !== itemStatus.in || checkOut()"]');
This failed as well.
Not sure which one I should check and why both are failing. How do I correct this and get it to show that the disabled button is...well, disabled.
TEMPORARY ADD ON EDIT:
For simplicity's sake, I am trying:
var hasClass = function (element, cls) {
return element.getAttribute('class').then(function (classes) {
return classes.split(' ').indexOf(cls) !== -1;
});
var checkOutButtonState = actionDropDownList.all(by.tagName('li')).get(6);
expect(hasClass(checkOutButtonState, 'disabled')).toBe(true);
It still fails, however, despite the element clearly having the class. Alec - your solution throws "function is not defined," I am not sure if I need something else for it to see jasmine. Tried, but can't find anything wrong with it, not sure why I can't get it to work.
Edit:
If I run...since it only appears to have one class:
expect(checkOutButtonState.getAttribute('class')).toBe('disabled');
I get "expected 'ng-isolate-scope' to be 'disabled'"
In a quite similar situation I've ended up checking the presence of disabledclass:
expect(checkOutButtonState).toHaveClass("disabled");
Where toHaveClass() is a custom jasmine matcher:
beforeEach(function() {
jasmine.addMatchers({
toHaveClass: function() {
return {
compare: function(actual, expected) {
return {
pass: actual.getAttribute("class").then(function(classes) {
return classes.split(" ").indexOf(expected) !== -1;
})
};
}
};
},
});
});
My HTML structure is this:
<li ng-repeat="m in members">
<div class="col-name">{{m.name}}</div>
<div class="col-trash">
<div class="trash-button"></div>
</div>
</li>
What I want to be able to do is using protractor, click on the trash when m.name equals a specific value.
I've tried things like:
element.all(by.repeater('m in members')).count().then(function (number) {
for (var i = 0 ; i < number ; i++) {
var result = element.all(by.repeater('m in members').row(i));
result.get(0).element(by.binding('m.name')).getAttribute('value').then(function (name) {
if (name == 'John') {
result.get(0).element(by.className('trash-button')).click();
};
});
};
});
This seems like it should work, however, it seems like my function does not even run this.
I've also looked into promises and filters though have not been successful with those either. Keep getting errors.
var array = element.all(by.repeater('m in members'));
array.filter(function (guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (selected) {
selected.element(by.className('trash-button')).click()
}
All I would like to do is click on the corresponding trash when looking for a specific member list!
Any help would be much appreciated.
EDIT: suggested I use xpath once I find the correct element, which is fine, the problem is I cannot get the filter function's promise to let me use element(by.xpath...)
If I try:
var array = element.all(by.repeater('m in members'));
array.filter(function (guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (selected) {
selected.element(by.xpath('following-sibling::div/div')).click()
}
I get the error:
Failed: Object has no method 'element'
Figured it out. Following the Protractor filter guideline in the API reference and using alecxe's xpath recommendation, this code will click on any element you find after filtering it.
element.all(by.className('col-name')).filter(function (member, index) {
return member.getText().then(function (text) {
return member === 'John';
});
}).then( function (elements) {
elements[0].element(by.xpath('following-sibling::div/div/')).click();
});
You can change the xpath to select different stuff incase your HTML does not look like mine.
It looks like you can avoid searching by repeater, and use by.binding directly. Once you found the value you are interested in, get the following sibling and click it:
element.all(by.binding('m.name')).then(function(elements) {
elements.filter(function(guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (m) {
m.element(by.xpath('following-sibling::div/div')).click();
});
});
Or, a pure xpath approach could be:
element(by.xpath('//li/div[#class="col-name" and .="John"]/following-sibling::div/div')).click();
I'm trying out Protractor to e2e test Angular app and haven't figured out how to detect if an element has a specific class or not.
In my case, the test clicks on submit button and now I want to know if form[name="getoffer"] has class .ngDirty. What may be the solutions?
describe('Contact form', function() {
beforeEach(function(){
browser.get('http://localhost:9000');
element(by.linkText('Contact me')).click();
});
it('should fail form validation, all fields pristine', function() {
element(by.css('.form[name="getoffer"] input[type="submit"]')).click();
expect(element(by.name('getoffer'))).toHaveClass('ngDirty'); // <-- This line
});
});
One gotcha you have to look out for with using toMatch(), as in the accepted answer, is partial matches. For instance, let's say you have an element that might have the classes correct and incorrect, and you want to test that it has the class correct. If you were to use expect(element.getAttribute('class')).toMatch('correct'), that will return true even if the element has the incorrect class.
My suggestion:
If you want to only accept exact matches, you can create a helper method for it:
var hasClass = function (element, cls) {
return element.getAttribute('class').then(function (classes) {
return classes.split(' ').indexOf(cls) !== -1;
});
};
You can use it like this (taking advantage of the fact that expect automatically resolves promises in Protractor):
expect(hasClass(element(by.name('getoffer')), 'ngDirty')).toBe(true);
If you're using Protractor with Jasmine, you could use toMatch to match as a regular expression...
expect(element(by.name('getoffer')).getAttribute('class')).toMatch('ngDirty');
Also, note that toContain will match list items, if you need that.
Simplest is:
expect(element.getAttribute('class')).toContain("active");
Based on the answer from Sergey K, you could also add a custom matcher to do this:
(coffeescript)
beforeEach(()->
this.addMatchers({
toHaveClass: (expected)->
#message = ()->
"Expected #{#actual.locator_.value} to have class '#{expected}'"
#actual.getAttribute('class').then((classes)->
classes.split(' ').indexOf(expected) isnt -1
)
})
)
Then you can use it in tests like this:
expect($('div#ugly')).toHaveClass('beautiful')
And you'll get the following error if it doesn't:
Message:
Expected div#ugly to have class beautiful
Stacktrace:
Error: Expected div#ugly to have class 'beautiful'
Have you tried this?
const el = element(by.name('getoffer'));
expect(el.getAttribute('class')).toBe('ngDirty')
or a variation of the above...
I made this matcher, I had to wrap it in a promise and use 2 returns
this.addMatchers({
toHaveClass: function(a) {
return this.actual.getAttribute('class').then(function(cls){
var patt = new RegExp('(^|\\s)' + a + '(\\s|$)');
return patt.test(cls);
});
}
});
in my test i can now do stuf like this:
var myDivs = element.all(by.css('div.myClass'));
expect(myDivs.count()).toBe(3);
// test for class
expect(myDivs.get(0)).not.toHaveClass('active');
this also works when an element has multiple classes or when an element has no class attribute at all.
function checkHasClass (selector, class_name) {
// custom function returns true/false depending if selector has class name
// split classes for selector into a list
return $(selector).getAttribute('class').then(function(classes){
var classes = classes.split(' ');
if (classes.indexOf(class_name) > -1) return true;
return false;
});
}
This is how I do it at least, without the need to use the expect function. This function simply returns true if the class is inside the element and false if not. This also uses promises so you would use it like:
checkHasClass('#your-element', 'your-class').then(function(class_found){
if (class_found) console.log("Your element has that class");
});
Edit: I just realized this is essentially the same as the top answer
Here a Jasmine 1.3.x custom toHaveClass matcher with negation .not support plus wait up to 5 seconds (or whatever you specify).
Find the full custom matcher to be added on your onPrepare block in this gist
Sample usage:
it('test the class finder custom matcher', function() {
// These guys should pass OK given your user input
// element starts with an ng-invalid class:
expect($('#user_name')).toHaveClass('ng-invalid');
expect($('#user_name')).not.toHaveClass('ZZZ');
expect($('#user_name')).toNotHaveClass('ZZZ');
expect($('#user_name')).not.toNotHaveClass('ng-invalid');
// These guys should each fail:
expect($('#user_name')).toHaveClass('ZZZ');
expect($('#user_name')).not.toHaveClass('ng-invalid');
expect($('#user_name')).toNotHaveClass('ng-invalid');
expect($('#user_name')).not.toNotHaveClass('ZZZ');
});
One way to achieve this would be to use xpath and use contains()
Example:
var expectElementToHaveClass = function (className) {
var path = by.xpath("//div[contains(#class,'"+ className +"')]");
expect(element.all(path).count()).to.eventually.be.eq(1);
};
You can use the CSS parser to handle this by checking if an element with the given class exists:
expect(element(by.css('.form[name="getoffer"].ngDirty')).isPresent()).toBe(true);
Essentially, you're solving a few problems:
how to get class. class is an html attribute and thus can be retrieved by this command (await is the recommended way these days)
let class = await element.getAttribute('class')
Once you got the value of a class, you want to assert it
// for exact value
expect(class).toBe("active");
// for partial match
expect(class).toContain("active");
// or
expect(class.includes("active")).toBe(true);
// BUT, keep in mind
expect('male').toContain('male');
expect('female').toContain('male');
// BOTH pass