user9347168
user9347168

Reputation:

Cypress should not.exist or not.be.visible

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

Answers (6)

Drew Cordano
Drew Cordano

Reputation: 1120

  • Use .should('not.be.visible') for elements that exist in the DOM but would not be visible to a fully sighted person.
  • Use .should('not.exist') for elements that do not exist in the DOM, regardless of their visibility.

Upvotes: 1

Pugachev
Pugachev

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

Marcus
Marcus

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

Austin
Austin

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

Malte Hartwig
Malte Hartwig

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

agoff
agoff

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

Related Questions