Conditional Testing in Cypress: Tutorial
By Hamid Akhtar, Community Contributor - March 1, 2023
When you perform one action or a different one, you are using conditional testing, a basic programming pattern. It is also referred to as control flow. This is typically stated as If X, then Y, otherwise Z.
- The key to developing effective Cypress tests is to give Cypress as much information and data as possible while preventing it from sending new commands until your application has achieved the appropriate condition at which it must proceed.
- Conditional testing in Cypress, however, is a serious challenge since the test writers are frequently unclear about what the provided state will be.
- Every developer and QA engineer needs Cypress, the new benchmark in front-end testing.
- Importance of Conditional testing in Software Testing
- Setting up Cypress for Conditional Testing
- Using Conditional Statements in Cypress tests
- Best Practices for Conditional Statements in Cypress Tests
- Working with Conditional Logic in Cypress
- Scenario: Conditional Testing in Cypress for Navigation and Validation
- Test Suite: “Cypress Conditional Testing Demonstration”
- Test Setup: beforeEach Hook
- Test Case 2: “Navigate to InfoPage and Validate, if ProductPage is Absent (Else Path)”
Importance of Conditional testing in Software Testing
If the element exists, you want to do something, and if it doesn’t, you want to do something else. Despite how straightforward this question initially appears, answering it correctly involves much complexity. The goal of conditional testing is to test every condition or test in the source code thoroughly.
Consider a situation where your application might do two things you cannot control. In other words, you tried every tactic but, for whatever reason, could not predict how your application would behave in advance. Conditional testing makes it easy to test this in Cypress!
Setting up Cypress for Conditional Testing
Installing Cypress and configuring the environment
Installing all prerequisites first in our project is necessary. Refer to the Cypress test Automation tutorial first.
Create a new project folder and set up a new Cypress project after ensuring all the requirements are met.
- Create the folder anywhere on your computer, then launch Visual Studio Code by choosing File>Open Folder>Choose the file from the system>click Ok.
- The next step is to use the ‘npm init -y’ command in the terminal to generate the ‘package.json’ file in our project. This file initializes the npm project and serves as a dependency management file, allowing us to run npm commands directly on the project.
- The project name, version, description, keywords, author, license, and script section are all included in the package.json file. The script section is used to run the test scripts from the terminal.
- Install Cypress by using the ‘npm install cypress’ command to install the most recent version of Cypress or ‘npm install cypress@version number‘ to install the specific version of Cypress that is needed, for example, ‘npm install cypress@10.10‘ after creating the ‘package.json’ file.
Run the “npx cypress open” command to launch Cypress.
Once Cypress has been launched, select E2E Testing, click Continue, pick a browser, and then click Start Cypress E2E Testing in that browser.
After selecting “Start E2E Testing,” a Cypress runner appears, allowing us to create spec files with the “.cy.js” extension. A copy of this file will also be created in Visual Studio Code, where we can write your test cases.
Creating a basic Test Suite in Cypress
An array of test cases is known as a test suite, and it is used to run tests and record their outcomes. You can have a test suite for any of a software application’s fundamental features or a specific sort, like smoke, security test suites, etc. The Cypress approach to organizing numerous connected tests is known as ‘describe’. Every describe block is part of a group of tests.
Writing Test Cases with Conditional statements
Assume that when you visit your website, the content will vary depending on the A/B campaign your server chooses to transmit. It could depend on a user’s geolocation, IP address, time of day, location, or other hard-to-control circumstances.
How is it possible to create tests in this way?
Regulate which campaign is sent or offer a trustworthy way to identify it.
// navigating to the site to obtain session cookies cy.visit('https://bstackdemo.com/') // executing a request to fetch // details related to the user's campaign cy.request('https://bstackdemo.com/userDetails') .its('body.userSpecificCampaign') .then((obtainedCampaign) => { // implementing specific cypress test code // dependent on the retrieved campaign type return executeCampaignTests(obtainedCampaign) })
You might now ask your server to inform you the campaign you are currently in if it saves the campaign along with a session.
A session cookie that you can read off from your server may be another way to test this:
// Visiting the specified URL cy.visit('https://bstackdemo.com/') // Retrieving the cookie named 'userCampaignData' cy.getCookie('userCampaignData').then((extractedCampaign) => { // Executing the relevant campaign tests using the extracted data return runCampaignTests(extractedCampaign) })
Another viable tactic would be to embed data into the DOM directly, but to do it in a way that makes the data constantly accessible and searchable.
This would not function unless it were present constantly:
// Navigating to the HTML element cy.get('html') // Ensuring the HTML has an attribute named 'data-userCampaign' .should('have.attr', 'data-userCampaign') // Extracting the campaign data and running the corresponding tests .then((campaignData) => { return executeCampaignTesting(campaignData) })
Using Conditional Statements in Cypress tests
Common use cases for conditional testing in Cypress
- Users get a “welcome wizard,” but existing users don’t. Can you always close the wizard in case it appears and ignore it when it doesn’t?
- Can you undo unsuccessful Cypress commands, such as when cy.get() fails to find an element?
- Attempting to develop dynamic tests that react differently depending on the text on the page.
- What should you do differently depending on whether an element exists?
- How can you account for the A/B testing that your application conducts?
- You want to locate every <a> element automatically, and depending on which ones you locate, you want to verify that each link operates.
The basic goal of Cypress conditional testing is to conduct tests on an element with multiple potential outcomes, each of which may require you to act on a different outcome.
Best Practices for Conditional Statements in Cypress Tests
The foundation of Cypress framework is writing trustworthy tests. The key to creating effective tests is to give Cypress as much “state” and “facts” as you can while “guarding” it from issuing new commands until your application has achieved the state you want it to be to continue.
Understanding how your application behaves is essential to avoid writing flaky tests. For example,
- You cannot perform conditional testing on the DOM without server-side rendering and no asynchronous JavaScript.
- Another strategy is to use client-side JavaScript that does synchronous rendering alone.
- The fact that the test writers are unsure of the provided state when conducting conditional testing adds a significant challenge.
- Under certain circumstances, embedding a dynamic state reliably and consistently is the only reliable approach to having accurate tests.
Must Read: How to write Test Case in Cypress?
Working with Conditional Logic in Cypress
The following examples will help you understand conditional testing and how to use it in your test:
Case Study:
Step 1: Visit https://automationteststore.com/ as the first step.
Step 2: Select the Login button.
Step 3: If the option to “Register Account” is not checked, select it.
/// <reference types="cypress" /> // Describing the test suite describe('Initial Test Suite', function() { // Defining the test case it('User Account Creation Test', () => { // Navigating to the specified website cy.visit('https://bstackdemo.com/') // Clicking on the customer menu at the top of the page cy.get('#user_menu_top').click() // Checking the account registration option cy.get('#accountForm_accountregister').then(($inputElem) => { // Verifying if the account registration option is selected by default if ($inputElem.is(':checked')) { cy.log('Account registration option is selected by default') } else { // Selecting the account registration option if not selected cy.wrap($inputElem).click() } }) }) })
Here is a single test case in the test file mentioned above that will click on the registration option on the web page only if it is not already selected by default; otherwise, it will simply print a statement indicating that the option for account registration is already selected by default without carrying out any click action on the registration option.
To run the test file, you must first open the Cypress test runner using the ‘npx cypress open‘ command and then click the test file on the test runner. This will start the execution, and the test results will be displayed.
Using assertions with the conditional statement
The cypress-if plugin is where the child commands .if() and .else() originate. Any Cypress assertion can be used as the .if(assertion) condition; element existence is used by default. The test follows the “IF” path and bypasses the “ELSE” section of the command chain.
describe('Cypress Conditional Testing Demonstration', () => { // Common setup for each test beforeEach(() => { // Navigate to the designated website cy.visit('https://bstackdemo.com/') }) it('Navigate to ProductPage and Validate, if Present (If Path)', () => { // Assert the title of the page to be 'StackDemo' cy.title().should('eq', 'StackDemo') // Check the body of the page cy.get('body').then((bodyElement) => { // If ProductPage is found on the page, navigate to it if (bodyElement.find('[data-jsl10n="productPage.name"]').length > 0) { cy.get('[data-jsl10n="productPage.name"]').click() } // If ProductPage is not found, navigate to InfoPage else { cy.get('[data-jsl10n="infoPage.name"]').click() } }) // Validate the title of the navigated page cy.title().should('eq', 'ProductPage') }) it('Navigate to InfoPage and Validate, if ProductPage is Absent (Else Path)', () => { // Assert the title of the page to be 'StackDemo' cy.title().should('eq', 'StackDemo') // Check the body of the page cy.get('body').then((bodyElement) => { // If an incorrect locator is found, try to navigate to ProductPage (will not occur due to incorrect locator) if (bodyElement.find('nonExistentLocator').length > 0) { cy.get('[data-jsl10n="productPage.name"]').click() } // If the incorrect locator is not found, navigate to InfoPage else { cy.get('[data-jsl10n="infoPage.name"]').click() } }) // Validate the title of the navigated page cy.title().should('eq', 'InfoPage') }) })
Scenario: Conditional Testing in Cypress for Navigation and Validation
Test Suite: “Cypress Conditional Testing Demonstration”
Objective: The primary aim of this test suite is to validate the navigation and subsequent page title when interacting with different elements conditionally on the “https://bstackdemo.com/” website.
Test Setup: beforeEach Hook
- Purpose: To ensure that every test starts from the same initial state.
- Action: Navigate to “https://bstackdemo.com/” before executing each test case.
- Expected Outcome: The website should be loaded successfully before each test begins.
Test Case 1: “Navigate to ProductPage and Validate, if Present (If Path)”
Objective: To validate the navigation and page title when the element associated with “ProductPage” is present.
Steps:
- Title Validation: Ensure the initial page title is ‘StackDemo’. Element Check and Interaction:
- Condition Check: Determine if the element with data attribute [data-jsl10n=”productPage.name”] is present.
- Positive Path (If): If present, click on the element.
- Negative Path (Else): If absent, click on an alternative element with data attribute [data-jsl10n=”infoPage.name”].
- Post-Interaction Validation: Assert that the title of the navigated page should be ‘ProductPage’.
Test Case 2: “Navigate to InfoPage and Validate, if ProductPage is Absent (Else Path)”
Objective: To validate the navigation and page title when the element associated with “ProductPage” is absent, simulating a failure or negative path.
Steps:
- Title Validation: Confirm that the initial page title is ‘StackDemo’. Element Check and Interaction:
- Condition Check: Determine if an incorrect/non-existent locator is present to simulate the absence of the desired element.
- Positive Path (If): If the incorrect locator is found (unlikely), attempt to click on the element with [data-jsl10n=”productPage.name”].
- Negative Path (Else): If the incorrect locator is not found (likely), click on the element with [data-jsl10n=”infoPage.name”].
- Post-Interaction Validation: Assert that the title of the navigated page should be ‘InfoPage’.
Summary:
Through this test suite, we ensure that the application can handle dynamic navigation based on the presence or absence of elements, thereby validating the UI’s responsiveness and navigation logic under different scenarios. This is crucial for ensuring that users are directed correctly as per the available UI elements and that the corresponding pages are accurately represented by their titles, enhancing the reliability and user experience of the application.
cy.then
When the input box is not checked, you need a mechanism to click it.
it('Submits the Agreement Form', () => { // Navigating to the specified webpage cy.visit('https://bstackdemo.com/agreement.html') // Checking the agreement checkbox cy.get('#consent').then(($checkbox) => { // If the checkbox is already checked, log a message if ($checkbox.is(':checked')) { cy.log('User has already given consent') } // If not, click to check it else { cy.wrap($checkbox).click() } }) // Clicking the submit button to submit the form cy.get('button#submitForm').click() })
If the code is unclear to you, you must be familiar with the jQuery selectors and commands. You will also need to use the cy.wrap command to put the element back into a Cypress chain before using the .click() command.
Using loops and iterations in conditional testing
it('cy.each Halts Iteration Upon Returning false', () => { const veggies = ['carrots', 'potatoes', 'tomatoes', 'peppers'] cy.wrap(veggies) .each((veggie, index) => { console.log(index, veggie) if (index === 2) { return false // returning false should stop the iteration. // However, ensure to test and verify this behavior in your specific test environment. } cy.log('veggie', veggie) }) // cy.each yields the original subject // even if the iteration is stopped prematurely .should('equal', veggies) })
The snippet above will come to a stop when it determines that k == 2. You can manipulate and adjust the loop’s control per conditional logic.
Grouping test cases with conditional logic
With this.skip(), which can be applied conditionally based on, for example, an environment variable, you can dynamically skip a test through condition logic.
beforeEach(function() { // Retrieving the test filter from environment variables const filterCriteria = Cypress.env('FILTER_CRITERIA'); // If no filter is provided, proceed without filtering if (!filterCriteria) { return; } // Getting the full title of the current test const currentTestName = Cypress.mocha.getRunner().test.fullTitle(); // If the current test name does not include the filter criteria, skip the test if (!currentTestName.includes(filterCriteria)) { this.skip(); } })
Handling Exceptions and Errors in Conditional Testing
You should consider unsuccessful commands in Cypress to be similar to uncaught exceptions in server-side programming. In those circumstances, the system has changed to an unreliable state, making any attempt at recovery impossible. In contrast, you almost always choose to crash and log.
It is what Cypress is doing when it fails the test. Reporting the failure, bypassing any remaining commands in the test, and bailing out. But here, you need to assume for argument that Cypress did include error handling.
Enabling this would result in error recovery for every command, but only after the corresponding command timeout was reached. It would take a very long time to fail because timeouts begin at 4 seconds (and increase from there).
//! Note: Error handling in Cypress commands is not allowed. //! This code is purely for illustrative purposes function proceedWithoutWorries () { cy.get(...).should(...).click() } cy.get('#modal') .contains('Dismiss') .click().catch((error) => { // It's okay, perhaps the modal didn't exist // or something else... no big deal proceedWithoutWorries() }) .then(proceedWithoutWorries)
- In the best case, you had wasted at least 4 seconds waiting for the <#wizard> element to exist before we erred and moved on possibly.
- The <#wizard> was supposed to be rendered, but in the worst-case scenario, it didn’t render within the timeout you were allowed.
- Assume that this resulted from a timer in the queue, a WebSocket message, a waiting network request, or anything else.
- In this case, not only did you wait a long time for the< #wizard> element to appear, but it also probably led to an error farther down the line on other commands.
No matter what programming idioms are available to you, you cannot build 100% deterministic tests if you cannot determine the state of your application reliably.
Debugging and Troubleshooting Cypress Conditional Tests
1. Debugging conditional tests with Cypress’ debugger
A key consideration when selecting an automation framework is debugging. Let’s take the scenario when a tester has created ten lines of the test script, and it fails or throws an error. They must investigate the causes of its failure. Now you can easily create your first condition test with Cypress debugging successfully.
2. Using console logs and error messages for troubleshooting
On both the Cypress window console and the browser console, testers can print logs when using Cypress. Even printing the stack trace to the browser console is an option.
In Cypress, console logs can be used in one of two ways:
- cy.log() command
- console.log() by configuring cypress tasks
3. Analyzing test results and fixing failures
Debugging Cypress tests can be done in various ways, each with different levels of complexity. Each technique needs to undergo several testing rounds before testers can select one (or more) that best meets their skill set, available resources, and overall convenience.
By allowing test retries, Cypress Cloud can identify, report, and monitor flaky tests from your recorded Cypress test runs in your CI/CD workflow.
Organizing and methodically monitoring Cypress flaky tests can help you identify them as they happen, estimate their severity, and prioritize their fix.
Closing Notes
Applications written in JavaScript today are highly dynamic and mutable. Over time, the state and DOM undergo constant change. Conditional testing has the drawback of only being applicable when the state has stabilized. However, it is frequently impossible to tell when a situation is stable.
A change that occurs every 10 or even 100 milliseconds could escape your notice. However, a computer will have the ability to monitor those changes. Most of the time, you cannot use the state of the DOM to guide your conditional behavior. This results in flaky testing, which Cypress API is built to address at every stage.
While leveraging a real device cloud like BrowserStack, teams may take advantage of various advantages. Without external emulators, they will inevitably achieve the highest test coverage. Additionally, teams save a lot of the time and expense needed to build device labs.
For automated testing in an Agile environment, BrowserStack uses a cloud-based grid comprising more than 3000+ device-browser-OS combinations.