Utilizing data attributes in E2E testing for optimal results

We have decided to transition from using tag and classname selectors to data attributes in Cypress for our E2E testing. This change is intended to make the selectors more robust and less prone to breaking.

Specifically, Cypress recommends using data-cy, data-test, or data-testid as data attribute prefixes.

One of the challenges we face is selecting specific rows and columns from a table. For example:

<!-- difficult-to-test markup example -->
<table class='users-table'>
<thead>
  <th>Name</th>
  <th>Email</th>
  <th>Phone</th>
<thead>
<tbody>
  <tr>
    <td>Bob Fish</td>
    <td><a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a1c3cec3e1c7c8d2c98fc2ce">[email protected]</a></td>
    <td>123-123-1234</td>
  </tr>
    <td>Shaggy Rogers</td>
    <td><a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="3b48535a5c7b5642484f5e494252555815585456">[email protected]</a></td>
    <td>509-123-1235</td>
  </tr>
<tbody>
</table>

To implement the recommended use of data-test, an approach like this could be taken:

<table data-test='users-table'>
  ...
<tbody>
  <tr data-test='user-id-1'>
    <td data-test='name-col'>...
    <td data-test='email-col'>...
    <td data-test='phone-col'>...

This structure allows for targeted selection based on values, such as:

cy.contains('[data-test="users-table"] [data-test="name-col"]', user.name).should('be.visible')

Or with a more precise method:

cy.get(`[data-test="users-table"] [data-test="user-id-${user.id}"]`).within(() => {
    cy.get('[data-test="name-col"]').should('have.text', user.name)
    cy.get('[data-test="email-col"]').should('have.text', user.email)
    cy.get('[data-test="phone-col"]').should('have.text', user.phone)
  })

In considering semantic markup principles, an alternative approach using custom data attributes like data-entity and data-col is explored:

<table data-entity='users'>
  ...
<tbody>
  <tr data-entity-id='1'>
    <td data-col='name'>...
    <td data-col='email'>...
    <td data-col='phone'>...
  </tr>
  <tr data-entity-id='2'> ...

While this eliminates assembling attribute values like user-id-1, it requires creating a consistent set of custom data- attributes (data-entity, data-col, etc.)

The question remains: what is the objective way to utilize data attributes effectively without being subjective? We acknowledge that software development often involves tradeoffs and nuances.

On a related note, exploring tools like Cypress Testing Library may offer a solution by retrieving elements based on roles or labels, yet certain aspects of markup may still pose challenges unless extensive use of ARIA role attributes is implemented, potentially conflicting with W3C standards.

Answer №1

In my extensive experience handling large test suites within enterprise settings, I have encountered and navigated through various challenges related to this particular issue multiple times. The decision-making process involves numerous trade-offs, and there is no definitive "one-size-fits-all" solution.

Utilizing Data Attributes

The practice of separating selectors from internal DOM attributes, such as the class attribute, is a sound approach.

Data attributes can play a role in addressing this issue. However, it's essential to be aware of the potential drawbacks before fully committing to them. While they offer benefits like decoupling test code from application-layer implementation details (e.g., class), there are also considerations to keep in mind.

I have adopted conventions like using a data-cy-component to identify the type of component represented by a specific DOM element, along with component-specific attributes associated with that component. For instance:

<tbody>
  <tr data-cy-component="row" data-cy-row-id="1">
    <td data-cy-component="cell" data-cy-cell-column="name">...
    <td data-cy-component="cell" data-cy-cell-column="email">...
    <td data-cy-component="cell" data-cy-cell-column="phone">...

This approach offers:

  • A cleaner alternative from a purist standpoint.
  • Enhanced extendability by allowing the addition of more test attributes without disrupting existing selectors.
  • Eradication of ambiguity, especially when dealing with special characters like -.

However, there are downsides to consider:

  • Increased complexity for application developers in ensuring the correct combinations of attributes. Any oversight could lead to errors in selection criteria.
  • Temptation for users to not just select on these attributes but also read from them and assert values, which may not align with the actual user interactions.
  • Addition of another layer of implementation details, potentially causing misalignment between what is selected and what is actually visible to the user.

Prioritizing Accessible Selectors

Cypress Testing Library advocates for utilizing selectors based on DOM data that exists for accessibility reasons—one that is considered "public" or visible. Its recommendations emphasize the importance of selecting appropriate elements.

By focusing on visible text, aria roles, and their attributes, you can benefit from:

  • Testing against what is deemed a public interface, typically involving visible text instead of relying solely on data attributes.
  • Identifying accessibility issues if selecting elements becomes challenging, prompting necessary improvements in site accessibility.
  • Minimizing complexity associated with managing data attribute conventions by leveraging well-defined aria attributes.
  • Gaining insights into accessibility gaps whenever selectors fail, highlighting areas for improvement.
  • Prompting critical thinking about user interactions, particularly in scenarios where differentiation is crucial.
  • Leveraging existing libraries and design systems that integrate aria attributes seamlessly, enhancing overall accessibility.

While test attributes are not discouraged, Cypress Testing Library recommends limiting usage to a single data-testid attribute, discouraging complex compound solutions unless absolutely necessary due to the effectiveness of accessible selectors.

In practical terms, incorporating proper aria markup is paramount in achieving desired outcomes:

<table aria-label="Users table">
<thead>
   <tr>
     <th id="name-column">Name</th>
     <th id="email-column">Email</th>
     <th id="phone-column">Phone</th>
   </tr>
</thead>
<tbody>
  <tr>
    <td aria-describedby="name-column">...
    <td aria-describedby="email-column">...
    <td aria-describedby="phone-column">...
  </tr>
  <tr> ...

To execute various tasks effectively:

cy.findByRole('table', {name: "Users table"}).within(() => {

   // Obtaining a visible row by its unique ID (email)
   cy.findByRole('cell', {description: 'Email', name: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail=”65160d040225081c161100171c0c0b064b060a08”>[email protected]</a>'})
      .should('exist)
      .parent()
      .within(() => {
         cy.findByRole('cell', {description: 'Name'}) // Retrieving the name cell for the same row
           .should('have.text', 'Shaggy Rogers') 
       })
})

Note that handling tables presents distinct challenges compared to simpler interactions such as button clicks, necessitating the establishment of common commands.

Addressing your mentioned concern:

but there would still be tons of markup that would not be covered, unless maybe I started throwing role= on everything which seems like a dirty hack and probably against ARIA or some other W3C standard.

It is vital to assign a role only to elements accurately reflecting their intended purpose. If warranted, utilization of aria attributes should suffice for most cases. Consider adding meaningful aria-label or aria-describedby when necessary, keeping in line with accessibility standards.

While introducing new markup may seem unconventional, adhering to established standards ensures compliance and longevity. Remember, selectors represent just one facet of a comprehensive testing framework, prompting thoughtful considerations and driving improved test quality.

Based on my experience, adopting an accessible selector strategy enhances test reliability and encourages meticulous thought processes.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

A see-through section with a border that fades in transparency

Can anyone help me with creating a box with a unique design using CSS only? I want the box to have a rounded border that starts at 80% opacity at the top and gradually fades to 20% opacity at the bottom. Additionally, the border should start at 80% opaque ...

Definition of JSON Schema attribute: Field schema not supported for field... : Type of field is undefined

I am in the process of creating a form that is based on a JSON schema and utilizing the react-jsonschema-form component for ReactJS. The purpose of this form is to allow users to input various settings (such as CSS/HTML selectors) that will be used to ext ...

Tips for keeping MUI Autocomplete open even when the input field loses focus

I am currently working with a MUI autocomplete feature that includes a list of items and an edit icon next to each one. The edit icon allows the user to change the name of the option by rerendering it as a textfield. However, I have encountered an issue wh ...

What is the best way to add a media query to a <style> element?

I have implemented Skrollr for my Parallax animations, allowing me to use Parallax keyframes without writing them inline. To make the plugin utilize external stylesheets for the keyframes, it uses AJAX to locate the stylesheets imported via <link>. ...

Passing data from a card component to a tab component in ReactJS

Just starting out with react and facing an issue. I want to transfer props from the child component to the parent component tabs that include favorite tabs. My plan was to pass the values through the handleClickOpen method where I click the favorites icon. ...

Aligning HTML headers in a horizontal manner

Can anyone help me out here? I have a div containing 4 elements that are currently stacked on top of each other. I want them to be aligned horizontally instead. The div's ID is #ninesixty. Thank you in advance! HTML <header> <div id="ni ...

Obtaining only a portion of the text when copying and editing it

I have a React application where I am attempting to copy text from an HTML element, modify it, and then send it back to the user. I have been successful in achieving this, but I am facing an issue where even if I select only a portion of the text, I still ...

Is there a method to instruct crawlers to overlook specific sections of a document?

I understand that there are various methods to control the access of crawlers/spiders to documents such as robots.txt, meta tags, link attributes, etc. However, in my particular case, I am looking to exclude only a specific portion of a document. This por ...

What could be causing the issue where only one of my videos plays when hovered over using UseRef?

I'm currently working on a project where I have a row of thumbnails that are supposed to play a video when hovered over and stop when the mouse moves out of the thumbnail. However, I've encountered an issue where only the last thumbnail plays its ...

Error: Incorrect Path for Dynamic Import

Recently, I've been trying to dynamically load locale files based on the locale code provided by Next.js. Unfortunately, every time I attempt a dynamic import, an error surfaces and it seems like the import path is incorrect: Unable to load translatio ...

Merge web address when form is sent

I'm currently working on a search application using the Django framework and the Yelp API as my backend, which is functioning properly. However, I am facing some challenges with the frontend development, specifically integrating Leaflet.js. My search ...

Trick for adjusting padding on top and bottom for responsive blocks of varying widths

Hello there, I've been working on a project where I need to create a responsive grid of cards, like news articles, that maintain their proportions even when the browser's width changes. To achieve this, I decided to use an aspect-ratio hack by s ...

Managing the AJAX response from a remote CGI script

I'm currently working on a project that involves handling the response of a CGI script located on a remote machine within a PHP generated HTML page on an apache server. The challenge I am facing relates to user authentication and account creation for ...

Selenium Assistance: I'm encountering a scenario where on a webpage, two elements share the same Xpath, making it difficult to differentiate them based on even

At index [1], both elements are identified, but at index [2], nothing is identified. The key difference between the two is that one has display:none, and the other has display:block. However, their involvement in determining these fields is minimal due to ...

The calculator I designed using HTML, CSS, and JavaScript is experiencing difficulty adjusting to various screen sizes

I recently built a calculator using HTML, CSS, and JavaScript. It works perfectly on PC or big screens, but when viewed on smaller devices like phones or tablets, the display gets cut off and does not adjust properly. Here are some example pictures for ref ...

Encountering Nested CSS bugs while implementing Tailwind in a Next.js/React project

Just diving into the world of Next.js/React. I recently went through the Tailwind CSS for Next.js tutorial and successfully integrated Tailwind into my project by following these steps: npm install -D tailwindcss postcss autoprefixer npx tailwindcss init - ...

Update the fuelux treeview after uploading a file

Currently, I am utilizing the jquery fileupload plugin to facilitate the process of uploading a file and then using the data from said file to populate a fuelux treeview. I have configured an ajax call for handling the file data where the information is fe ...

Creating a Seamless Bond Between JavaScript and HTML

I've been struggling to find a helpful and straightforward solution to a simple problem. On my web page, I have five image placeholders that should be filled with one of nine random pictures. To achieve this, I wrote a JavaScript code that generates r ...

Display the div when scrolling downwards beyond 800px

Is there a way to display a hidden section when scrolling down the page, specifically after reaching a point 800px from the top? I currently have an example code that may need some modifications to achieve this desired effect. UPDATE: [Additionally, when ...

What is the best way to configure the loading state of my spinner?

When a user clicks to navigate to the articles page, I want to display a spinner while waiting for the articles data to be fetched and displayed. There is a slight delay after the click, hence the need for the spinner. I have a custom spinner component ca ...