Software Development

How To Avoid Anti-Patterns In Cypress

No matter how advanced your automation technology is, anti-patterns represent standard bad practices, code smells, and pitfalls. In this case, when creating automated tests, teams can experience a range of difficulties that compound to create inefficient, incomplete, and hard-to-maintain tests.

In addition, to provide high-quality, cost efficiency, and on-time delivery, it’s essential to recognize the signs that your tests aren’t performing optimally. In this Cypress testing tutorial on avoiding anti-patterns in Cypress, we will discuss some anti-patterns that we believe should be avoided using Cypress test automation framework for the most relevant testing experience and end product.

Let’s get started!

Fragile Locators

Have you experienced fragile locators in your test scripts? Any slight change on the UI would fail the tests, and it will require some maintenance work to update the specific locator.

Imagine a dynamic CSS, XPath, Id, Class, Tag selectors; it could be tough to target elements changing every time the web page loads.

Let’s imagine an input field

<input class="gLFyf gsfi" jsaction="paste:puy29d;" maxlength="2048" name="q" type="text" aria-autocomplete="both" aria-haspopup="false" autocapitalize="off" autocomplete="off" autocorrect="off" autofocus="" role="combobox" spellcheck="false" title="Search" value="" aria-label="Search" data-ved="0ahUKEwjj1t2JiY32AhVQkmoFHTMfD48Q39UDCAQ">

The easiest way to approach could be using:

cy.get('.a4bIc')

But, that wouldn’t be the best approach as this is a fragile selector; instead, we should add test attributes to our elements, and the example above it should be like the following:

<input class="gLFyf gsfi" jsaction="paste:puy29d;" maxlength="2048" name="q" type="text" aria-autocomplete="both" aria-haspopup="false" autocapitalize="off" autocomplete="off" autocorrect="off" autofocus="" role="combobox" spellcheck="false" title="Search" value="" aria-label="Search" data-ved="0ahUKEwjj1t2JiY32AhVQkmoFHTMfD48Q39UDCAQ" data-test="submit">

Now, we can use our locator in a better way:

cy.get('[data-test=submit]')

The test-cy attribute will not change from CSS style or JS behavioral changes; additionally, it clarifies to everyone that this element is used directly by the test code.

Also read – How to Find HTML Elements Using Cypress Locators

Overcomplexity, chains in Cypress

We always want to use elegant solutions and apply a better approach in our test automation code, but always remember the KISS principle; if you are not aware, KISS is an acronym for Keep It Simple, Stupid. This principle says about making your code simple. It will help if you avoid unnecessary complexity. A simple code is easier to maintain and easier to understand.

cy.get(nice.selector-unique').type('This is a great blog post.')

Chaining the .type() onto the cy.get(), telling it to type into the subject yielded from the cy.get() command, which will be a DOM element.

it('Another example of Command chains', () => {
  cy.visit('https://www.awesomesite.com') 

cy
  // Find the element with id any-link'
  .get('#any-link')
  .as('myElement') // sets the alias
  .click()

cy.get('@myElement') // re-queries the DOM as before (only if necessary)
  .click()

})

Using Aliases, let us reuse our DOM queries for faster tests when the element is still in the DOM. Furthermore, it automatically handles re-querying the DOM when it is not immediately found in the DOM. It is beneficial when dealing with front-end frameworks that do a lot of re-rendering.

Another cool thing about Cypress is the built-in retry function available in too many commands; there is a default timeout set to 4000 milliseconds, which you can modify in your cypress.json file. If you come from Selenium, you probably know it as a fluent wait. In this case, nothing extra needs to be added to our tests to make them wait fluently.

Also read – Cypress Vs Selenium: Which is Better in 2022?

Inefficient waits, avoiding waits in Cypress

Perhaps, you want to debug your tests, or you need to halt your tests for a couple of seconds to see the desired state. But, in Cypress, when you use .wait() and do nothing for a couple of seconds, how long do we need to wait? We understand sometimes our test scripts are going so fast that we need to consider hard waits and tell Cypress to reduce speed. But remember using waits in our code is regarded as an anti-pattern in Cypress.

cy.get('#any-link')
    .wait(5000) 
    .click()

Instead, we can use timeout per command; the timeouts can be modified per command, which will affect all default assertions and any assertions chained after that command.

cy.get('#any-link', {timeout: 5000})
    .should('be.visible')
    .click()

Also, you can wait for basically anything by passing a callback function into the .should() command. It will use the built-in retry logic and wait for the procedure to pass. As a recap in our example above, Cypress will wait 5 seconds for the element to exist in the DOM and wait another 5 seconds for the same element to be visible.

Optimizing DRY, custom commands in Cypress

If you are not aware of custom commands in Cypress, it is an excellent time to avoid reinventing the wheel and reuse those available custom commands. Cypress comes with its API for creating custom commands and overwriting existing commands, for example, cypress-downloadfile:

You can install the module:

npm install cypress-downloadfile

Then, add the following line to cypress/support/commands.js:

require('cypress-downloadfile/lib/downloadFileCommand')

Add the following lines to cypress/plugins/index.js

const {downloadFile} = require('cypress-downloadfile/lib/addPlugin')
module.exports = (on, config) => {
  on('task', {downloadFile})
}

After we can start using the custom command the following way:

cy.downloadFile('https://my_blog.pdf', 'downloads', 'blogpost.pdf')

As mentioned before, we can reuse most of the custom commands, but Cypress allows us to modify the behavior of these custom commands; this is really useful to optimize our code and use the DRY principle.

Cypress.Commands.overwrite('download', (url, directory) => {})

Let’s try not to overcomplicate things and reuse custom commands in our tests.

In the below Cypress testing tutorial video, you’ll understand how to create custom commands in Cypress.

ou can also Subscribe to the LambdaTest YouTube Channel and stay updated with the latest tutorials around automated browser testing, Selenium testing, CI/CD, and more.

Behavior Driven Development in Cypress

Behavior-driven development is a software engineering practice developed to deliver high-quality products by focusing on the business value or behavior of the system. The main difference is BDD focuses on the behavior/ business value. In contrast, TDD focuses on tests which sometimes leads developers to become too detail-focused compared to the business focus/value. However, it’s important to understand the TDD vs BDD difference before choosing a suitable framework.

Cucumber is a tool that supports BDD; anyone can write specifications in plain English using Gherkin. It is as simple as adding a test from the business value point of view, as I like to call business-centric test automation.

Let’s start adding the cucumber plugin using npm to our current Cypress project. Please follow the below video for Cypress installation:

Please install the plugin

pre class=”brush:js”>
npm install –save-dev cypress-cucumber-preprocessor

The following dependency with the latest version will be added to your package.json of the project. At the time of writing this recipe, the version of cypress-cucumber-preprocessor is 4.1.4

pre class=”brush:js”>
devDependencies”: { “cypress-cucumber-preprocessor”: “^4.1.4” }

To make it work, we would need to add it to cypress plugins as part of Cypress Configuration under cypress/plugins/index.js

pre class=”brush:js”>
const cucumber = required(‘cypress-cucumber-preprocessor’).default

module.exports = (on, config) => {
on(‘file:preprocessor’, cucumber())

}

Next, we need to add cosmiconfig configuration to the package.json. Cosmiconfig searches and loads the required configuration of the project. In this case, we are defining to locate the step definitions by setting up the below property.

pre class=”brush:js”>
“cypress-cucumber-preprocessor”: {
“nonGlobalStepDefinitions”: true
}

Let’s create a new folder under Cypress -> Integration directory as ‘cucumber-test’ and then create a new feature, “Home.feature.”

pre class=”brush:js”>
Feature: Home / Landing Page

Scenario: Navigating to E-commerce Store
Given I open home page
Then I should see Homepage

For step definition location, let’s create a folder named “home.”

Let’s create a step definition file ‘homeSteps.js.”

pre class=”brush:js”>
import { Given } from “cypress-cucumber-preprocessor/steps”;

Given(‘I open home page’, () => {
cy.visit(‘https://ecommerce-playground.lambdatest.io/’)
})

Then(‘I should see Homepage’, () => {
cy.get(‘#search’).should(‘be.visible’)
})

The folder structure should be as the following:

Now, let’s run using the following command in the terminal or command console to execute the test:

pre class=”brush:js”>
npx cypress open

On Cypress Test Runner, select ‘home.feature’

And you should see the results correctly:

Also readNow Run Your Cypress Tests on LambdaTest

Cucumber Expressions

In the “.feature” file, cypress-cucumber-pre-processor uses the same expression as the cucumber expressions to link Gherkin to step definition. This can be achieved either by Cucumber expressions or regular expressions. The advantage of these is that they are more humanly readable than regular expressions. The following is how a Gherkin Step is translated in Step Definition.

Gherkin Step

pre class=”brush:js”>
Given I open home page

Step Definition translation

pre class=”brush:js”>
Given(‘I open home page’, () => {
cy.visit(url)
})

Gherkin Keywords

Keywords are used to give structure & meaning to the executable specifications. For example, every specification or a feature file starts one of the keywords as:

  • FEATURE
  • SCENARIO
  • SCENARIO OUTLINE
  • BACKGROUND

Examples:

pre class=”brush:js”>
Feature: Background Example

Background: Given greeting has been set

Scenario: example #1
When name is “John”
Then greetings equals “Hello John”

Scenario: example #2
When name is “Mary”
Then greetings equals “Hello Mary”

Step definition will look like the following:

pre class=”brush:js”>
let myName, greeting, finalString;

Given(“greeting has been set”, () => {
greeting = ‘Hello ‘
})

When(“name is {string}”, setName => {
myName = setName
finalString = greeting.concat(myName)
})

Then(“greetings equals {string}”, expectedValue => { expect(finalString).to.equal(expectedValue)
})

Take this certification to showcase your expertise with end-to-end testing using Cypress automation framework and stay one step ahead.

Here’s a short glimpse of the Cypress 101 certification from LambdaTest:

Gherkin Functions

Gherkin uses functions to execute the specifications/ features files. These functions are used to translate the specification to executable tasks with the help of expressions or regular expressions.

  • GIVEN represents pre-conditions in the executable specification. It is a pre-step to the specification or test.
  • WHEN represents the condition in the executable specification. It is a condition to the specification or test.
  • THEN represents post-condition in the executable specification. It is the post-condition to the specification or test. There could be single or multiple steps as part of Then (post-condition).
  • AND step is to extend pre-step, conditions, and post-condition (s). It can be used with all the functions (Given, When & Then).
  • BUT is helpful to define the negative step in the scenario. Same like AND step, BUT also supports the Given/When/Then step.

How do you write a feature statement?

Follow the following principles to build greatness in features.

  • Keep it short and to the point.
  • Make your benefits measurable.
  • Emphasize the advantages.

The principles are not mandatory like Gherkin Keywords or Function, but they communicate the vision or goal of the feature.

Feature and Steps Definition

Here, we can find our Feature File and our Step Definition file.File: search.feature

File: searchSteps.js

Code Walkthrough

Let’s start defining our Page file, here we can include our locators and URLs:searchPage.js

pre class=”brush:js”>
const URL = “https://ecommerce-playground.lambdatest.io/”
const search = “#search”

class SearchPage{
static visitPage(){
cy.visit(URL)
}

static searchPrd(name){
cy.get(search)
.type(name+'{enter}’)
}

}

export default SearchPage

We defined the URL, in this case, LambdaTest Playground, and our search locator, as we can see in the following two methods:

pre class=”brush:js”>
class SearchPage{
static visitPage(){
cy.visit(URL)
}

static searchPrd(name){
cy.get(search)
.type(name+'{enter}’)
}

Now, let’s take a look into our Step Definition file searchSteps.js.

pre class=”brush:js”>
import { Given, When, Then } from “cypress-cucumber-preprocessor/steps”
import SearchPage from ‘./searchPage’

First, we need to import our Cucumber plugin previously installed and our SearchPage file; this file includes our selectors and methods.

Now we need to define our Given, When, and Then steps; let’s start with the “Given” one:

pre class=”brush:js”>
Given(‘I open home page’, () => {
SearchPage.visitPage()
})

As we can notice, we are calling our SearchPage method to perform a “cy.visit”.

Related to our “When” definition, we take advantage of dynamic parameters coming from our feature file. Therefore, it is helpful to get the actual search value from the feature file.

pre class=”brush:js”>
Scenario: Navigating to E-commerce and Search Mac Devices
Given I open home page
When I search for “Mac”
Then I should see Product Available

pre class=”brush:js”>
When(‘I search for {string}’, Mac => {
SearchPage.searchPrd(Mac)

})

As we notice, we are distinguishing from a search returning any Product vs. a search returning no Products; it is helpful as our feature can include negative scenarios.

pre class=”brush:js”>
Then(‘I should see Product Available’, () => {
cy.get(`div:nth-of-type(1) > .product-thumb`)
.should(‘be.visible’)
})

Then(‘I should Not see Product Available’, () => {
cy.get(`div:nth-of-type(1) > .product-thumb`)
.should(‘not.exist’)
})

pre class=”brush:js”>
Scenario: Navigating to E-commerce and Search Mac Devices
Given I open home page
When I search for “Mac”
Then I should see Product Available

Scenario: Navigating to E-commerce and Search Monitor Devices
Given I open home page
When I search for “Printers”
Then I should Not see Product Available

Scenario: Navigating to E-commerce and Search Samsung Devices
Given I open home page
When I search for “Samsung”
Then I should see Product Available

And here is the test execution, which indicates that our BDD approach is working:

How to run Regression on Cypress Cloud Grid

We can use a Cloud Cypress Grid like LambdaTest, which provides automated cross browser testing on 40+ browsers and operating systems, and Cypress parallel testing to expedite the test execution in a scalable way. In addition, it will help improve our overall test coverage by resulting in better product quality as we can cover different combinations using the same test scripts.

To get started with Cypress testing, you have to install LambdaTest Cypress CLI on your machine. Trigger the following command to install the same:

pre class=”brush:js”>
npm install -g lambdatest-cypress-cli

After installation is completed, set up the configuration using the below command:

pre class=”brush:js”>
lambdatest-cypress init

Once the command is completed, lambdatest-config.json is created in the project folder. Next, enter the LambdaTest credentials from the LambdaTest Profile Section.

pre class=”brush:js”>
lambdatest_auth”: {
“username”: “<Your LambdaTest username>”,
“access_key”: “<Your LambdaTest access key>”

Here is how you can configure the required browser & OS combinations in lambdatest-config.json:

pre class=”brush:js”>
{
“lambdatest_auth”: {
“username”: “”,
“access_key”: “”
},
“browsers”: [
{
“browser”: “MicrosoftEdge”,
“platform”: “Windows 10”,
“versions”: [
“latest”
]
},
{
“browser”: “Chrome”,
“platform”: “Windows 10”,
“versions”: [
“latest”
]
},
{
“browser”: “Firefox”,
“platform”: “macOS Big Sur”,
“versions”: [
“latest”
]
},
{
“browser”: “Firefox”,
“platform”: “Windows 10”,
“versions”: [
“latest”
]
}
],

The run_settings section in the JSON file contains the desired Cypress test suite capabilities, including Cypress_version, build_name, number of parallel sessions, etc.

pre class=”brush:js”>
{
run_settings”: {
“Cypress_config_file”: “Cypress.json”,
“build_name”: “build-cucumber-testing”,
“parallels”: 5,
“specs”: “./Cypress/integration/cucumber-tests/*.feature”,
“ignore_files”: “”,
“feature_file_suppport”: false
},

Tunnel_settings in the JSON file lets you connect your local system with LambdaTest servers via an SSH-based integration tunnel. Once this tunnel is established, you can test locally hosted pages on all the browsers currently supported by Cypress on LambdaTest.

pre class=”brush:js”>
“tunnel_settings”: {
“tunnel”: false,
“tunnelName”: null
}

Now that the setup is ready, it’s time to run the tests; remember that our run_settings file displays the parallels field as five once we trigger our execution in parallel without any extra parameter.

pre class=”brush:js”>
lambdatest-cypress run

Shown below is the test execution status from the LambdaTest Automation Dashboard.

To view test performance metrics, navigate to the LambdaTest Analytics Dashboard. The Test Overview will provide a snapshot of tests consistent with stable behavior. Meanwhile, the Test Summary will display the total number of tests passed or failed, as well as any completed tests and pending tests.

Also readHow to Perform Cypress Testing at Scale with LambdaTest

Final Thoughts

The presented list was not supposed to be exhaustive since there are good resources and smells/patterns in Parts II and III of xUnit Test Patterns. In this Cypress e2e testing tutorial on avoiding anti-patterns in Cypress, I focused on the BDD approach as our Test Automation must be customer-centric and easy to follow from our non-technical stakeholders.

Happy Bug Hunting with Cypress!

Published on Java Code Geeks with permission by Enrique DeCoss, partner at our JCG program. See the original article here: How To Avoid Anti-Patterns In Cypress

Opinions expressed by Java Code Geeks contributors are their own.

Enrique DeCoss

Enrique DeCoss, Senior Quality Assurance Manager at FICO is an industry leader in quality strategy with 16+ of experience implementing automation tools. Enrique has a strong background in Testing Tools, API testing strategies, performance testing, Linux OS and testing techniques. Enrique loves to share his in-depth knowledge in competencies including, Selenium, JavaScript, Python, ML testing tools, cloud computing, agile methodologies, and people Management.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button