Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cy.get return invalid jquery element

Here is a snip of my test code:

cy.get('div[data-component-data-id=301602] h2:first')
  .should(($el) => {
    expect($el).to.have.text('dress')
  })

and cypress complains about the assertion:

CypressError: Timed out retrying: You attempted to make a chai-jQuery assertion on an object that is neither a DOM object or a jQuery object.

The chai-jQuery assertion you used was:

  > text

The invalid subject you asserted on was:

  > Object{3}

To use chai-jQuery assertions your subject must be valid.

This can sometimes happen if a previous assertion changed the subject.

So I have to change expect($el).to.have.text('dress') to expect($el[0]).to.have.text('dress'), then the complain dismissed and test passed.

I debug the assertion a bit, turns out $el[0] is also a jquery element.

Here is a snapshot about $el and $el[0]:

enter image description here

So my question is: Isn't cy.get equivalent to jquery $? Why should I have to get the first element from $el? Why $el[0] is also a jquery element?

Thanks in advance.

EDIT:

It turns out that this error related to the application code when it includes zepto library. I have raised an issue in the Cypress Github repository which you can track the progress.

like image 570
L_K Avatar asked Feb 21 '20 10:02

L_K


2 Answers

I'm not quite sure what you are looking for at this stage, but have found a way to reset the effect of the zepto library.

To summarize,

  • zepto is a jquery-compatible library that takes over the $ global on your app window ref

    $ = function(selector, context) {
    return zepto.init(selector, context)
    }

  • the result is that cy commands like .get() and .wrap() yield a zepto-wrapped form of the jquery result that's not compatible with chai expect() without some destructuring.

Experimenting with Cypress.$ I found this is still referencing jquery propper, for example

const h2 = Cypress.$('h2')

returns a jquery object not a zepto object, so we can reset the app global $ from Cypress.$.

it('title display', () => {

  cy.visit('index.html')  // zepto takes effect here

  // Reset $
  const win = cy.state('window');
  win.$ = Cypress.$;

  cy.get('h2')
    .should($el => {
      expect($el).to.have.text('dress')  // passes
    })
})

The reset code could be incorporated into an overwrite of the cy.visit() command to make it less pervasive.

Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
  return originalFn(url, options).then(_ => {
    const win = cy.state('window')
    win.$ = Cypress.$
  })
})
...

it('title display', () => {

  cy.visit('index.html')

  cy.get('h2')
    .should($el => {
      expect($el).to.have.text('dress')  // passes
    })
})

NOTE This affects the way zepto works in the app.


Work around that leaves Zepto functional

This version of the cy.visit() overwrite will leave the Zepto library functional, but allow Cypress to .should() to receive the correct jQuery object.

Essentially we add our own proxy (on top of zepto's proxy) and examine the type of the selector on each call.

Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
  return originalFn(url, options).then(_ => {
    const win = cy.state('window')
    const zepto_$ = win.$;
    win.$ = function(selector, context) {
      return typeof selector === 'string' 
        ? zepto_$(selector, context) 
        : Cypress.$(selector, context);
    }
  })
})

Use this html fragment to test it. If the two logs are identical, zepto is not affected.

<body>
  <h2>dress</h2>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/zepto/1.2.0/zepto.min.js"></script>

  <script>
    console.log('from app, call #1', $('h2'))
    setTimeout(() => {
      console.log('from app, call #2', $('h2'))
    }, 1000)
  </script>

</body>
like image 127
Richard Matsen Avatar answered Nov 09 '22 16:11

Richard Matsen


I feel like using .and("contain", "dress") would solve your issue.

EDIT :

I've tried running a snippet similar to yours on my machine. Using should didn't seem to have the expected results and I encountered the same jquery weird behavior. However, when using then, it works like a charm. $el and $el[0] both return the jquery element normally

cy.get("h1.h2:first").then(($el) => {
    cy.log($el)
    cy.log($el[0])
    expect($el).to.have.text('measure')
    expect($el[0]).to.have.text('measure')
})
like image 2
Jordan Kowal Avatar answered Nov 09 '22 18:11

Jordan Kowal