1

Imagine I have the following simple html:

const inEl = document.querySelector("input")
const buttonEl = document.querySelector("button")


inEl.oninput = (() => {
  buttonEl.remove()
  setTimeout(() => {
    document.body.appendChild(buttonEl)
  }, 100)
})
.b {
  background: blue;
}
<input>
<button>weee</button>

As you can see, as someone types in the input, the button is temporarily removed from the DOM. I'd like to add a cypress test that checks that the button is NOT removed from the dom (so it would need to fail for the above scenario).

It seems simple enough, but because cypress is so good at waiting for things to appear, I'm not totally sure how to write this test.

It feels like what I need is a way to throw an error if a cypress command does pass. Something like

cy.get("input").type("hello")
cy.get("button").should("not.exist") //if this passes then throw an error!

Any help on how to do this seemingly simple thing would be appreciated. Thanks!

tnrich
  • 8,006
  • 8
  • 38
  • 59

2 Answers2

0

https://docs.cypress.io/guides/core-concepts/retry-ability#Disable-retry

You can set the timeout to 0 to disable retry

so you could add

cy.get("button", { timeout: 0 }).should("not.exist")

to make sure the button does not flicker.

tgreen
  • 1,720
  • 1
  • 16
  • 28
0

One possibility is to spy on the remove method

let spy;
cy.get("button").then($button => {
  spy = cy.spy($button[0], 'remove')
})

cy.get("input").type("hello")
  .should(() => expect(spy).to.not.have.been.called)

If you try to do visibility or existence checks you run the risk of false results, because that script will run quite quickly.

If you want to do so, you could control the clock

// This passes if the remove/append runs

cy.clock()
cy.visit(...)

cy.get("input").type("h") // one letter only
cy.tick(20)                           // 10ms is default delay in .type()
cy.get('button').should('not.exist')  // clock is frozen part way through setTimeout
cy.tick(100)
cy.get('button').should('exist')      // clock has moved past setTimeout completion
// This checks the remove/append does not run

cy.clock()
cy.visit(...)

cy.get("input").type("h") // one letter only
cy.tick(20)
cy.get('button').should('exist')  // fails here if the button is removed
cy.tick(100)
cy.get('button').should('exist')     
Fody
  • 23,754
  • 3
  • 20
  • 37
  • thanks for the spy idea. I'll try that out! I'm not sure if react calls the .remove() method on individual dom nodes or if it removes elements in some other way. And, if it is, is remove() called on all the nodes or just the parent node? In my real life case there are usually several layers of wrapper components. – tnrich Jan 25 '22 at 20:56
  • Yeah, I had an idea that remove/append was representative (react virtual DOM probably doing something more complicated). That's why I added the clock example which aims to test observed behaviour rather than internal implementation. The first clock example is kind of the failing test in TTD and the 2nd is the refactor to make it green. Even cy.clock is a bit dependent on internal implementation, as it only works if setTimeout is used internally, which is doubtful for a react re-render. – Fody Jan 25 '22 at 21:24
  • A further note on react, the JSX is always returned from component, and JSX is compiled down to `React.createElement()` - so I'm wondering if re-render always creates a new instance of the element (rather than just re-appending the same element instance as per your example). If so, it may be useful to check the element's detached status, similar to the code [here](https://stackoverflow.com/a/70842715/16997707). – Fody Jan 25 '22 at 21:35