Testing Against Modern Web Frameworks with Cypress

Team Dialectica
Dialectica
Published in
5 min readApr 23, 2021

A quest of React and MUI, by Anastasios Haddad, Tech Lead at Dialectica

Thanks to the latest tools and technologies, the use of open-source libraries and frameworks may give developers a big advantage. Less code to write, more efficient software, as well as robust and secure implementations. However, there is a price to pay, as always. As all the magic happens behind the curtain, developers can barely change anything and there are strict rules and patterns to be followed.

I recently faced the challenge of having to use Cypress, in order to write automation tests for a web app that had been built with React and Material UI (MUI). The main difficulty lies in selecting the element you want to run the assertions on and successfully navigating through the HTML nodes.

React and MUI creates nested elements, which can be several levels deep and have dynamically created class names, ids, and attributes. Many elements are rendered outside the root element and manipulating a DOM like this turns out to be a brain teaser. Moreover, MUI builds custom elements that replace the standard HTML ones, like checkboxes which are actually nested divs implementing a checkbox’s functionality.

Pretty soon, I realized that pure Cypress code is inadequate in cases like this because you are forced to use code that jumps from child to parent and then to a sibling, etc. This approach is not human-friendly and, moreover, is vulnerable to code changes and to library or framework updates, alike.

In order to locate and manipulate all these wrapped elements, we are forced to use repetitive commands that are based on the current structure of a dynamically created DOM. Needless to say what the impact of the risk and the technical debt of a solution like this may turn out to be.

The Cypress React Selector plugin looked promising and might be a nice tool to work with but React uses generic props for the components which also have the same name. So it’s impossible to refer uniquely to them. In addition, after a short talk, I had with Dr. Gleb Bahmutov, Ph.D. -a “Distinguished Engineer” of the Cypress team- I realized that the combination of these technologies won’t be an easy journey, testing-wise.

Accessibility to the rescue! So, after many tries and ugly pieces of code (custom libraries and commands with logic and non-deterministic outcome), I concluded that using Cypress along with the Testing Library is the way to go.

With a little extra effort, you can use the findByRole command only in your code; something like findByRole(‘button’, {name: ‘Submit the form’}) will do the job most of the time.

You’ll need to add roles for every element, though. If none is specified, the default will be used, as dictated by the aria specification module [list of all roles]. Of course, you can override them with a custom one, but this is something strongly discouraged, which rarely turns out to be useful anyway.

When do you need to add roles to elements?

  • When the element doesn’t have a default role and its VALUE should be described to a ScreenReader. Thus, if an element is just a markup without a specific role you don’t have to add one.
  • When you want to alter the default role of an element (Quite rare case).

We have two categories of HTML elements, user-input elements, and presentation elements.

User-input elements like textbox, checkbox, radio, select natively have a role by default, but if you don’t use native elements, and create a custom component a role should be assigned. These elements also require a unique id and associated label or an aria label, if a label is not present (highly discouraged).

This is a textbox so no need to add a role here. The default is used.

Presentation elements like tables, rows, cells, lists, list items, fieldsets, navigations, menu items, tabs, dialogs, need a role to be specified most of the time, except when using native elements like <table>, <ul>, <ol>,‘<fieldset>.

For elements that contain other semantically relevant items like tables, fieldsets, navigations, dialogs, and lists, an aria-label should be also provided, in order to be able to identify them among elements with the same role. For example, a table that contains employees, their team, and their role should have an aria-label=“Employees”.

In the case of lists and tables which contain entries from data with associated ids (like employees, users, projects), a data-id attribute with the id in every row/list item can help better identify them.

Of course, there are also some special cases to consider.

A button’s name is retrieved from the text content of the element, but if the button only contains an image and no text, then an aria label will be needed.

The Rich text editor is a special case due to the implementation. Firstly, the textbox element can’t be accessed by findByRole without a hidden attribute in the options (for example findByRole(‘textbox’, {name: ‘insert brief’, hidden: true})). Secondly, because of the fact that it listens to the keystrokes and generates HTML code injected in the DOM, there is no element value to check and the generated HTML code is not identical to the one saved. So you can’t validate the user input.

This is a rich text editor. We see that it’s a div that acts as a text editor and how HTML code is wrapped around our two lines of text (“This is how a text is injected into the DOM”)

One must keep in mind that, what this library considers as visible, is what a screen reader does as well. This has the benefit of being able to cover tests with the same code with which you would use for a screen reader, as long as you follow the rules. Hence, if you decide to render a popover inline for example, that means that it will be included in the root element, inherit the rules, and will not be accessible by the Cypress/testing library even if it is visible.

The thing to bring home from this endeavor is that it seems that MUI has taken care of every piece of detail, with a robust and coherent logic, which one should avoid overriding at all cost, and that there is no doubt that Cypress is a vibrant project, under active development, which promises to give powerful testing abilities to developers of contemporary web applications, so as long as they have the discipline to adhere to the proper conventions.

Anastasios Haddad is Tech Lead at Dialectica responsible for assessing the software development process

Are you passionate about solving tech problems and building from scratch? Join our growing tech team now. Learn more here 💪

--

--

Team Dialectica
Dialectica

Dialectica is a leading B2B information services firm accelerating the shift toward a prosperous society by empowering better decision-making.