Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cypress test: is .contains() equivalent to should('contain')?

Is this: cy.get('[name=planSelect]').contains(dummyPlan)

equivalent to this: cy.get('[name=planSelect]').should('contain', dummyPlan)

And if so, which is preferred? The first is more of an implicit assertion, but it's shorter and cleaner to my mind.

Follow-up question: After looking around to see how best to select elements for e2e testing I found that the Cypress docs recommend using data-cy attributes. Is there a reason this would be better than just adding name attributes to the markup? Should name only be used for forms fields?

like image 926
redOctober13 Avatar asked Apr 26 '18 12:04

redOctober13


People also ask

How do you use contain in Cypress?

contains() is chained off of a command that yielded the <button> , Cypress will look inside of the <button> for the new content. In other words, Cypress will look inside of the <button> containing "Delete User" for the content: "Yes, Delete!", which is not what we intended.

How do you write if conditions in Cypress?

Conditionally check whether an element has certain text: get('body'). then(($body) => { // synchronously ask for the body's text // and do something based on whether it includes // another string if ($body. text(). includes('some string')) { // yup found it cy.

How do I get an element's text contents in Cypress?

How do I get an element's text contents? Cypress commands yield jQuery objects, so you can call methods on them. If the text contains a non-breaking space entity &nbsp; then use the Unicode character \u00a0 instead of &nbsp; . Tip: watch the Confirming the text with non breaking space entity video.

How do you find the value of an element in Cypress?

If you would like to test/assert the value of an input element, just use . should('have. value', 'input value you test for') .


2 Answers

The result on your cypress test will be the same if the element with name=planSelect does not contain dummyPlan, that is, the test will fail at this point.

The difference between them is that in the first form, using contains(), you're actually trying to select an element, and the result of cy.get(...).contains() will yield this expected DOM element, allowing for further chaining of methods, like:

cy.get('[name=planSelect]').contains(dummyPlan).click();

In the second form you are making an explicit assertion to verify that dummyPlan exists within the other element, using the Chai chainer contain.

It is a subtle difference and the result is the same, but I would recommend you to use cy.get('[name=planSelect]').contains(dummyPlan) only in case you would like to chain some other method after contains, and use the second form if you want to explicitly assert that this element exists. Logically speaking, the first would represent a generic test failure (cypress tried to find an element that wasn't there) and the second represents an explicit assertion failure (element should contain dummyPlan but it does not).

As for your second question, name is a valid HTML attribute and using it for your tests can lead to confusion if the attribute is being used in its original function (to name input fields) or if the attribute is there just for testing purposes. I would recommend you to use cy-name as the documentation suggests because this way you avoid this ambiguity and make it clear that this attribute cy-name is only there for testing purposes.

Furhtermore, on some situations you might decide to strip all cy-name from your code before sending it to production (during the build process, using some webpack plugin, like string-replace-loader). You would not be able to do the same if using just name because you would also remove the required input name, if there was some inputs in your code.

like image 103
Guilherme Lemmi Avatar answered Oct 18 '22 07:10

Guilherme Lemmi


Answer

  • .contains(selector, content) is the best selector; it retries element selection AND allows text matching (not just <tag> .class #id [attributes])

  • .should() is just an assertion and only the assertion is retried (not the element selection)

.should('exist') is implied unless you specify your own -- this is how they allowed .should('not.exist')


Tangent

Browsers support XPath 1.0 which is a pretty cool but obscure way to make complex queries based on DOM tree traversal. There's a contains predicate function:

//*[         contains(normalize-space(.), 'The quick brown fox jumped over the lazy dog.') ]
   [not(.//*[contains(normalize-space(.), 'The quick brown fox jumped over the lazy dog.') ])]

This searches from root of the document for any node that contains the text and doesn't contain a descendant node which contains the text.

You can test it in the console with the Chrome $x() shortcut or this polyfill (and helper):

getLowestDomNodesByText("The quick brown fox jumped over the lazy dog.")

function getLowestDomNodesByText (text) {
  return x(`//*[contains(normalize-space(.), '${text}')][not(.//*[contains(normalize-space(.), '${text}') ])]`);
};

function x (expression) {
  const results = new XPathEvaluator().evaluate(expression, document);

  const nodes = [];
  let node = null;
  while (node = results.iterateNext()) {
    nodes.push(node);
  }

  return nodes;
}

If you need even more performance, you can use a TreeWalker with NodeFilter.SHOW_TEXT as seen in this chrome extension I've worked on for a long time

like image 5
neaumusic Avatar answered Oct 18 '22 05:10

neaumusic