Reputation:
Because of - imo - poor page design, I've found myself having problems verify the visibility or non-existance of one or more elements on a page.
The problem is that some of the elements does not exist, while some of them have CSS property display:none. But the existing test code checks for not.exist, which makes the test fail. But I cannot change to not.be.visible, since then it will fail on the other elements.
So: Is it possible to do an OR in an assertion? Somthing like
cy.get('blabla').should('not.be.visible').or.cy.get('blabla').should('not.exist');
The above line compiles, but yields an undefined on the second part, so it doesn't work.
Here's the code:
(I don't consider the code architecture important - the question is basically the OR thing.)
page.sjekkAtDellaanFelterVises(2, 2, [
DellaanFelter.formaal,
DellaanFelter.opprinneligLaanebelop,
DellaanFelter.utbetalingsdato,
DellaanFelter.restlaanInnfridd,
]);
public sjekkAtDellaanFelterVisesALT(sakRad: number, delLanRad: number, felter: DellaanFelter[]) {
this.sjekkFelter(felter, DellaanFelter, (felt: string) => this.delLanAccordionBody(sakRad, delLanRad).get(this.e2e(felt)));
}
@ts-ignore
public sjekkFelterALT<T, E extends Node = HTMLElement>(felter: T[], enumType, lookupFn: (felt: string) => Chainable<JQuery<E>>) {
this.valuesOfEnum(enumType).forEach(felt => {
this.sjekkFelt(felt, felter, enumType, lookupFn);
});
}
// @ts-ignore enumType fungerer fint i praksis ...
public sjekkFeltALT<T, E extends Node = HTMLElement>(felt: string, felter: T[], enumType, lookupFn: (felt: string) => Chainable<JQuery<E>>) {
if (felter.some(feltSomSkalVises => enumType[feltSomSkalVises] == felt)) {
lookupFn(felt).should('be.visible');
} else {
lookupFn(felt).should('not.exist');
}
}
Or is the solution to try and check if the elements exists first, then if they do, check the visibility?
Upvotes: 8
Views: 46213
Reputation: 1120
Upvotes: 1
Reputation: 215
You can use cypress-if
plugin for elegant syntax. Follow your cy.get()
with .if()
combined with the visibility assertion, then .else()
combined with existence assertion.
For example, if I have a single <div>
on the page but it's not visible
<div style="display:none">a</div>
Both these tests will pass
cy.get('div')
.if()
.should('not.be.visible') // passes
.else()
.should('not.exist') // skipped
cy.get('span')
.if()
.should('not.be.visible') // skipped
.else()
.should('not.exist') // passes
Upvotes: 11
Reputation: 2111
cy.get('my-selector').should($el => {
expect($el.length == 0 || !$el.is(":visible"), "does not exist or is not visible").to.be.true;
})
Edit: @“-1” Sorry, I think there was a bug (should be !$el.is(":visible")
not !$el.is("visible")
). Fixed it now.
Upvotes: -1
Reputation: 39
Hopefully this will help some of you. I've been working with Cypress for a while now and found these particular custom commands to be pretty useful. I hope they help you too. ( Check for visibility utilizes the checkExistence command as well. You can just use the cy.isVisible() command and it will automatically check if it's at least in the DOM before continuing ).
P.S. These commands are still being tweaked - be nice :)
Command for checking existence
/**
* @param {String} errorMessage The error message you want to throw for the function if no arg is used
*/
const isRequired = (errorMessage = '--- Parameter is required! ---') => { throw new Error(errorMessage); };
/**
* @description Check if an element, found through xpath selector, exists or not in the DOM
* @param {String} xpath Required. Xpath to pass into the function to check if it exists in the DOM
* @param {Object} ifFound Message to pass to cy.log() if found. Default message provided
* @param {String} ifNotFound Message to pass to cy.log() if not found. Default message provided
* @returns Boolean. True if found, False if not found
* @example cy.checkExistence("xpath here", "Found it!", "Did not find it!").then(result=>{if(result==true){ // do something } else { // do something else }})
*/
Cypress.Commands.add('checkExistence',(xpath=isRequired(), ifFound, ifNotFound )=>{
return cy.window().then((win) => {
return (function(){
let result = win.document.evaluate(xpath, win.document.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
if(result!=null && result!=undefined){
if ( ifFound != undefined ) {
cy.log( `_** ${ifFound} **_` );
}
return cy.wrap( true );
} else {
if ( ifNotFound != undefined ) {
cy.log( `_** ${ifNotFound} **_` );
}
return cy.wrap( false );
}
})();
});
})
Command for checking visibility
/**
* @param {String} errorMessage The error message you want to throw for the function if no arg is used
*/
const isRequired = (errorMessage = '--- Parameter is required! ---') => { throw new Error(errorMessage); };
/**
* @description Check to see if an element is visible or not, using xpath selector or JQuery element
* @param {String} Xpath Xpath string
* @param {Boolean} waitForVisibility Optional. False by default. Set to true to wait for element to become visible.
* @param {Number} timeout Optional. Timeout for waitForVisibility param.
* @returns Boolean. True if visible, False if not visible
* @example cy.isVisible("//div").then(result=>{})
*/
Cypress.Commands.add('isVisible', (Xpath=isRequired(), waitForVisibility=false, timeout)=>{
cy.checkExistence(Xpath).then(result=>{
if(result==true){
cy.xpath(Xpath).then($element => {
if ($element.is(':visible')){
return cy.wrap(true)
} else {
if(waitForVisibility===true){
if(!timeout){
cy.logSpecial('wave',3, `Must provide value for timeout. Recieved ${timeout}`, true)
} else {
cy.log(`Waiting for element to become visible within ${timeout / 1000} seconds...`, true).then(()=>{
let accrued;
let interval = 250;
(function retry(){
if ($element.is(':visible')){
return cy.wrap(true)
} else {
accrued = accrued + interval;
if(accrued>=timeout){
cy.log(`Timeout waiting for element to become visible. Waited ${timeout / 1000} seconds.`)
return cy.wrap(false)
} else {
cy.wait(interval)
cy.wrap(retry())
}
}
})();
})
}
} else {
return cy.wrap(false)
}
}
})
} else {
cy.log(`Element does not exist in the DOM. Skipping visibility check...`).then(()=>{
if(throwError==true){
throw new Error (`Element of xpath ${Xpath} was not visible.`)
}
return cy.wrap(false)
})
}
})
})
Upvotes: 1
Reputation: 4555
I was facing the same problem, with some modals being destroyed (i.e. removed from the DOM) on close and others being just hidden.
I found a way to kinda emulate an or
by adding the visibility check as a filter to the selection, then asserting non-existence:
cy.get('my-selector').filter(':visible').should('not.exist')
The error messages in case of failure are not as self-explanatory ("expected :visible
to not exist") and you have to read the log a bit further to understand. If you don't need the separation between selector and filter you can combine the both to make get a nicer error message ("expected my-selector:visible
to not exist"):
cy.get('my-selector:visible').should('not.exist')
Upvotes: 6
Reputation: 7145
The tl;dr is that there isn't going to be a simple solution here -- Cypress' get
command has assertions, so you can't easily catch or eat those exceptions. If you try to get an element that doesn't exist, Cypress will have a failed assertion. Unfortunately, the best case would be to have deterministic behavior for each assertion.
More info on why Cypress behaves this way here.
I think your best case for doing this would be to write a custom Chai assertion, but I don't have any experience in doing anything like that. Here is Chai's documentation on doing so.
If you wanted to simplify your code, but knew which elements should not exist and which elements should not be visible, you could write a custom command to handle that.
Cypress.Commands.add('notExistOrNotVisible', (selector, isNotExist) => {
cy.get(selector).should(isNotExist ? 'not.exist' : 'not.be.visible');
});
cy.notExistOrNotVisible('foo', true); // asserts that `foo` does not exist
cy.notExistOrNotVisible('bar', false); // asserts that `bar` is not visible
I arbitrarily made not exist the positive case, but you could switch that and the logic in the should
.
Upvotes: 0