Angular and Cypress: data-cy attributes

Cypress is an End-to-End JavaScript testing framework. And, unlike others, it sits inside the browser, alongside your application. This allows you to access objects from inside your application in your integration tests.

This article does not cover the setup nor the introduction of Cypress, it covers on what you can do after the tests have been written and put into production.

You might have used the data-cy HTML 5 attributes in your templates, if not, it’s highly recommended that you do. This simplifies selecting your elements during testing as opposed to selecting via class names (.class), id (#id) or via element hierarchy (div > article > ul > li).


Consider the following elementary Angular app;

The app launches with ‘nothing’ in the article element, the content changes when either button is clicked.

The Cypress tests are easy to write;

Though the data-cy attribute simplifies the Cypress tests, the attributes remain when the app is pushed from Dev to Test to Production. The following reasons might persuade you to consider hiding these attributes:

  • Functionality discovery
  • Malicious reverse engineering (bot-creation)
  • Pollution of test code in production

Competing parties could scrape your HTML for data-cy attributes and try to discover hidden functionality in your app. If the actor is more hostile, he/she could reverse engineer your app and create a bot that, for example, creates a bunch of users via the registration process of your app.

The last motivation is my favorite, the notion of having meaningless code in production, does not resonate well with me. So I’ve been looking into removing it from the generated HTML using Angular. From what I’ve discovered, there are two different ways of doing so.


data-cy.directive.ts

The first is the least invasive, if we would consider the data-cy attribute to be an Angular attribute directive, we could hook into that and just remove the attribute when it’s supposed to be rendered.

While this method does not require a lot of effort and seemlessly hooks into your existing application, the condition is evaluated at runtime. So any cunning actor could just flip the bit in production and have it reverted, exposing your data-cy attributes.


angular-cli-builders

Since Angular 6, there’s the possibility to hook into the compilation process using builders. The angular-cli-builders package does this for you and allows for adding a custom webpack configuration, including all the benefits you can imagine from a vanilla webpack setup.

The goal here, is to create a webpack config that checks each HTML file and runs a loader that will strip the data-cy attribute using a Regular Expression.

There are several files that need to be modified in order for this to work, starting with the package.json.

npm i angular-cli-builders --save-dev

This installs the package and modifies the package.json.

The angular.json embeds the project’s workspace and decides which transformations are used, which builders are used, and much more.

Consider lines 14 through 21, this selects the angular-cli-builder’s custom webpack browser and configures a file called webpack.extra.js to be appended to the existing internal Angular webpack configuration.

"builder": "angular-cli-builders:custom-webpack-browser",
"options": {
"customWebpackConfig": {
"path": "./webpack.extra.js",
"mergeStrategies": {
"externals": "append"
}
},

Line 55 allows you to hook into the serve process as well, leveraging the build configuration.

"builder": "angular-cli-builders:generic-dev-server",

The webpack.extra.js file needs to be created at the root of your project.

Here we can see the test for HTML files and the usage of the data-cy-loader, which is resolved using the data-cy-loader.js file.

This file tests the incoming source (HTML) for the existence of the [data-cy] attribute and replaces it with an empty string, removing it before it is even bundled.

This brings more advantages to the table, even unlimited advantages, since we can now freely choose whatever we want to do during the bundling process.

For example:

  • Generate a timestamp or watermark on each generated HTML page.
  • Prepend each JavaScript file with a certain line.
  • Ensure all debugger statements are purged from the source

Custom webpack plugins make this approach even more extensive.

If you’re wondering why we can’t switch upon the environment, this is because this variable is not yet available, since deciding the builder happens too early in the pipeline. If this were to change, or any other solution is found, I will update this article.

Which approach should you be using?

The methods described above hook into different timeslots of your application, via a directive at runtime, or during compile time. As I see it, these are three paths you could take.

Do nothing, consider this article to be purely informative and nothing else. Maybe your app is only used internally? Maybe you don’t mind the pollution of attributes throughout your codebase in production. This is fine.

Implement the directive and provide your application with a small wall of obfuscation. Maybe you’re using Angular 5 or earlier, or you don’t want to replace the default builder.

Replace the default builder with angular-cli-builders. This approach has the downside of having to decide at compile time to remove all data attributes.

If you’re using a Continuous integration tool to build your app, you might want to compile, unit test, lint, run and Cypress-test your application in an integration environment using an empty webpack.extra.js file first, and re-compile using the original webpack.extra.js file in a test environment as a second step. You could replace the file using the ‘fs’ tool via npm on the server.

npm run replace-webpack

In this article we’ve looking into data-cy attributes and how it benefits you when writing Cypress tests. We’ve addressed two techniques on removing these attributes from your application in production environments and what the disadvantages of both approaches are.

You can find all code in the datacy repository on GitHub.

Enjoy.

Keep in mind that the Cypress tests fail, as intended, the data-cy attributes are removed from the source during compilation.