https://cypress.io logo
What is better for E2E: testing library or data-te...
# i-need-help
b
I am building an automation framework using Cucumber, Page Object Model and Cypress, and I am not sure if it is better to use Data-testid or testing library, as Best Practice of Cypress recommends using data-testid, but on the other hand, a lot of people suggest using testing library, and the best options which are recommended by Testing Library are to use findByRole or findByText (instead of testid), it means, the opposite of Best Practice of Cypress. On the other hand, if I use testing library, I don’t understand what is the goal of cypress assertions like webElement.should('have.text', 'expectedText'); as this webElement must be caught using the text by findByRole or findByText, and if the text isn’t the expected the webElement won’t be caught, so I see the duplicity of actions using testing library. What is your recommendation? Thanks for the help
g
sigh, you can select things by role (it is just an attribute on html) or by text (use
cy.contains
command)
b
it doesn't answer my question, https://docs.cypress.io/guides/references/best-practices#Selecting-Elements and https://testing-library.com/docs/ are following the opposite idea, so what is the best?
g
The ideas are not really in opposite. You use attributes to select elements. If there is role attribute you can use it (without testing library). If there is data test id or data cy attribute, you can use that.
f
I recently did a presentation on "choosing the best selectors" at work the other day, I think it might help explain something here. So, we're going to need to break apart a couple things. There's selector queries and assertions. Those two are completely separate. Disclaimer: My terminology might not match Cypress docs, but it might help provide some insight. For example, the selector query
cy.contains('string')
selects anything that contains that string. It's not an assertion, it doesn't assert or check that something contains that string before trying to find it. The assertion is
.should('contain', "string")
So, separating those two topics ahead of time is important.
------------- If we take a button that has the following attributes
button [data-id="loginButton"] class="btn.login"
with the text "login" you can use any of the following to select that login button.
Copy code
cy.get('button').should('contain', "login")
This gets the object button, and asserts that one of the buttons on the page contains the text "login". It doesn't select that button, it just asserts that one of them contains that text.
Copy code
cy.get('button').contains("login").should('be.visible")
This asserts that the button that contains the text "login" is visible. It selects the exact button we want as long as no other buttons contain the string "login". This does get your correct button, but may be brittle in the future.
Copy code
cy.get('button.btn.login').should('be.visible")
This selects the login button by it's unique class. If that class ever changes or another element ever shares that class, it'll break text.
Copy code
cy.get('[data-id="loginButton"]').should('be.visible')
This gets the exact button we want by ID, and asserts it's visible. ID is often the best option for important DOM elements
Important to note: The best selectors are ones that will always stay the same, are shared by no other DOM elements, and will only ever exist once on a page. This is why
[data-id="loginButton"]
is so good, because it's a constant ID that you can always use and will always get the right thing. Classes can change, but that data ID should always remain the same. Next best is using a unique class, like
.btn.login
, if you know that element will be the only one that ever can be selected by that class. Lastly,
cy.contains()
is great, if you know that text will never change, and no other elements will contain that exact string. That's relying on a few things not to change, so it's better to go with something that you know will stay the same if there's any uncertainty.
So you can select something a ton of different ways, but some ways are much better than others and will be less brittle and subject to change
The direct answer to your question I think is https://docs.cypress.io/guides/references/best-practices#Selecting-Elements as a general practice, but what Bahmutov was trying to say is that while testing libraries are a useful tool, you can always select elements in your tests using the best practices described above
w
I can share my article about this if you are interested: https://css-tricks.com/front-end-test-element-locators/ I recommend using testing library for certain things like
findByLabelText
and in some cases
findByRole
, but I would do most element selection with
cy.contains()
when that is possible. Combining some of@fast-artist-45202's examples, I would do something like this:
cy.get('button[data-id="loginButton"]', 'Log In').should('be.visible')
Because I want to find the element with a certain data attribute, but it should also use a real button element have the expected text (using judgement about whether the text matters). You can get these things "for free" with
contains
, in a very clean, short way. I have post coming soon about this on the Cypress blog, I'll updated when it is posted. > If there is role attribute you can use it (without testing library). @gray-kilobyte-89541 I think people use
findByRole
not specifically expecting a role attribute to be added on the element, but to also include implicit roles that elements have. For example finding by role
link
would find anchor elements (with no
role
attr) or find a div that has the
role
attribute added and does navigation by JS or something. So the test doesn't care about how the role is created, only that the role is present. I still prefer the app to use real HTML in almost all cases, so I barely ever use
findByRole
but afaik we can't replicate it with CSS selectors (maybe there's some way to get an OR in there that I'm not thinking of). To that point though - Cypress Testing Library is useful if you are testing an app that has been written in a good accessible way and all those roles and labels etc are correctly associated with the proper elements. Often that's not the case and in many apps you will do just fine with lots of
cy.get('[data-test=login'])
.
> the opposite of Best Practice of Cypress. Also I want to make sure it's clear that this kind of testing (using cy.contains or even Cypress Testing Library) does not actually contradict the practices we recommend. See this section about Text Content: https://docs.cypress.io/guides/references/best-practices#Text-Content We do recommend data-* as the most safe because it ignores all possible other things that could change. But it says right in the docs: > Cypress loves the Testing Library project. We use Testing Library internally, and our philosophy aligns closely with Testing Library's ethos and approach to writing tests. We strongly endorse their best practices. But oof, it looks like we have a missing link in that section, or something else is funky, it's just kind of cut offi.
b
Okay, thanks a lot for your answers, they are very useful and interesting. That is exactly which i was looking for! 👏 one two things @wonderful-match-15836 : 1. Could you have the missing link of the Cypress and Testing Library section? Please, share with me if you had it 😄 2. As https://docs.cypress.io/guides/references/best-practices#Text-Content says, if the text is important for us, then we should use cy.contains(...) to get the element, so my question is when should we use should assertion to check that a expected text is the right? 🤔 Thanks a lot again!
f
---Edited to remove incorrect information, thanks bahmutov
g
This is incorrect.
cy.contains
retries if it does not find an element with matching text
f
Has this been the same in previous versions? I’m almost certain that it’s failed for me because of failing to retry in the past.
g
nope, it was always retrying and the existence assertion is built-in
w
I remember at one point how I seemed to never have certain problems other people used to have with Cypress, and I eventually realized that the free retries and reliability of cy.contains probably the cause, exactly as Gleb said - the built-in retrying and assertions. Let me check on that link, I feel like we don't have a specific section for that.
b
Talking about my question, any answer? > As https://docs.cypress.io/guides/references/best-practices#Text-Content says, if the text is important for us, then we should use cy.contains(...) to get the element, so my question is when should we use should assertion to check that a expected text is the right? 🤔