A smart solution for dependant tests, conditional test execution on runtime and individual test-bound hooks and many more..
In the realm of test automation, ensuring that end-to-end tests are both efficient and maintainable is a continuous challenge. While Cypress has revolutionized the way we approach testing, there are scenarios where we wish our tests could be more… SMART!
Introducing the Cypress Smart Tests plugin
The plugin cypress-smart-tests is designed to elevate your test execution by introducing advanced control mechanisms directly into your Cypress tests. This plugin will solve common pain points in E2E test orchestration by:
- Letting you define dependencies between tests
- Running tests conditionally based on runtime logic
- Enabling
before/after
hooks per test block
Okie dokie! Let’s explore how to install, configure, and use it effectively..
Setup and Configure
First things first, initiate a project and install Cypress. It does not matter whether it is JavaScript or TypeScript. Then install the plugin as a dev dependency:
npm install — save-dev cypress-smart-tests
Whenever you are writing a Spec file, import the helpers to your file:
import { cytest, defineTestDependencies, configure, cyVariables } from 'cypress-smart-tests';
Now, instead of using the usual it()
block, use cytest()
— a drop-in replacement with smart capabilities ❤
Features
Conditional Test Execution
If you’ve been in the test automation realm for sometime, you already know that you will come across lots of scenarios where you have to execute your test based on various conditions. Either based on environment variables/feature or runtime conditions. Some of these scenarios could be:
- test only on mobile viewports
- run only if a flag is enabled
It doesn’t matter whatever the condition is, you can define your condition with the cytest() block and execute your test based on the conditions met. Check this out:
Problem solved! 😉 Let’s move on to the next pain-point..
Dependant Tests
Well well well.. I know! The “Best practice is to avoid dependant tests!”, BUT we all know that sometimes — at least rarely we get to situation where we have to write dependant tests. So, this plugin is gonna solve that problem too.
First you have to define your test dependencies like this:
Hold on! Let’s consider kind of a complex scenario. We have 6 tests in our Spec file:
- Test2 & Test3 depends on Test 1
- Test5 depends only on Test4
- Test6 is an independant test
Still you can achieve this with the plugin like this:
When test execution happens, it behaves this way:
- If
Test1
fails: Execution of the tests Test2 & Test3 will be skipped! - If
Test4
fails: Execution of the test Test5 will be skipped! -
Test6
will be executed no matter what happens with the other tests
What do we achieve through this? You might be thinking so WHAT? This makes a big difference, if you have dependant tests like this and you execute the tests in the traditional way, you will waste a lot of time! WHY? Because when the parent test fails, the dependant tests will obviously fail because the pre-condition of those are not met! Yet the dependant tests execute (with all the Cypress retries and default waiting times) and waste the time. Time is Gold!
So it’s important to fail-fast or skip-fast ❤ Here is a silly sample code with dependencies and tests:
import { cytest, defineTestDependencies, configure, resetState } from 'cypress-smart-tests';
describe('Cypress Smart Tests Plugin - Dependencies', () => {
beforeEach(() => {
resetState();
});
context('Simple Dependent Test Execution', () => {
beforeEach(() => {
// Configure failFast mode
configure({ failFast: true });
// Define dependencies
defineTestDependencies({
'Critical Test': ['Subsequent Test 1', 'Subsequent Test 2'],
});
});
cytest('Critical Test', () => {
// This test will fail
cy.wrap(false).should('be.true');
});
cytest('Subsequent Test 1', () => {
cy.log('This test should be skipped in failFast mode');
cy.wrap(true).should('be.true');
});
cytest('Subsequent Test 2', () => {
cy.log('This test should also be skipped in failFast mode');
cy.wrap(true).should('be.true');
});
cytest('Subsequent Test 3', () => {
cy.log('This test should not be skipped in failFast mode');
cy.wrap(true).should('be.true');
});
});
context('Complex Dependent Test Execution', () => {
beforeEach(() => {
// Define dependencies
defineTestDependencies({
'Critical Test': ['Subsequent Test 1', 'Subsequent Test 2'],
'Other Critical Test': ['Subsequent Test 4'],
});
});
cytest('Critical Test', () => {
// This test will fail
cy.wrap(false).should('be.true');
});
cytest('Subsequent Test 1', () => {
cy.log('This test should be skipped in failFast mode');
cy.wrap(true).should('be.true');
});
cytest('Subsequent Test 2', () => {
cy.log('This test should also be skipped in failFast mode');
cy.wrap(true).should('be.true');
});
cytest('Subsequent Test 3', () => {
cy.log('This test should not be skipped in failFast mode');
cy.wrap(true).should('be.true');
});
cytest('Other Critical Test', () => {
// This test will fail
cy.wrap(false).should('be.true');
});
cytest('Subsequent Test 4', () => {
cy.log('This test should be skipped in failFast mode');
cy.wrap(true).should('be.true');
});
cytest('Subsequent Test 6', () => {
cy.log('This test should not be skipped in failFast mode');
cy.wrap(true).should('be.true');
});
});
});
Individual Test Hooks
While global hooks are a staple in testing frameworks including Cypress, there are instances where individual tests require specific setup or cleanup operations. The Smart Tests Plugin allows you to define custom hooks for individual tests while having the global hooks! Yes, you read it right!
I’ll take a simple scenario and provide you the code for it. Let’s assume If you’re seeding a test DB for a specific test case:
Persistent Variables
Speaking of variables, it’s a very common scenario we want to share variable values between tests. But by default they reset after the execution of the it()
block. The cypress-smart-tests
plugin provides you with a solution to define and persist variables across tests (not across Spec files). You can store and retrieve test variables across cytest()
blocks like this:
Here is a full code snippet where variables are defined and used across tests:
import { cytest, cyVariable, cyVariables, resetState } from 'cypress-smart-tests';
describe('Cypress Smart Tests Plugin - Persistent Variables', () => {
beforeEach(() => {
resetState();
});
context('Basic Variable Usage', () => {
// Set up a variable before tests
before(() => {
cyVariable('testVar', 'initial value');
});
cytest('Set and get a variable', () => {
// Set a variable
cyVariable('username', 'testuser');
// Get the variable
const username = cyVariable('username');
// Verify the variable was set correctly
expect(username).to.equal('testuser');
});
cytest('Variable persists across tests', () => {
// Get the variable set in the previous test
const username = cyVariable('username');
// Verify the variable still has the same value
expect(username).to.equal('testuser');
// Update the variable
cyVariable('username', 'updateduser');
// Verify the update worked
expect(cyVariable('username')).to.equal('updateduser');
});
cytest('Variables can store different types', () => {
// Store a number
cyVariable('count', 42);
expect(cyVariable('count')).to.equal(42);
// Store an object
const user = { id: 1, name: 'Test User', active: true };
cyVariable('user', user);
expect(cyVariable('user')).to.deep.equal(user);
// Store an array
const items = ['item1', 'item2', 'item3'];
cyVariable('items', items);
expect(cyVariable('items')).to.deep.equal(items);
// Store a boolean
cyVariable('isActive', true);
expect(cyVariable('isActive')).to.be.true;
});
cytest('Variable set in before hook is available', () => {
// Get the variable set in the before hook
const testVar = cyVariable('testVar');
// Verify the variable has the expected value
expect(testVar).to.equal('initial value');
});
});
context('Multiple Variables Management', () => {
before(() => {
// Reset variables to ensure a clean state
resetState(true);
});
cytest('Add and get variables', () => {
// Add variables
cyVariables().add('username', 'testuser');
cyVariables().add('userId', 123);
cyVariables().add('userPreferences', { theme: 'dark', language: 'en' });
// Get variables
const username = cyVariables().get('username');
const userId = cyVariables().get('userId');
const userPreferences = cyVariables().get('userPreferences');
// Verify variables were set correctly
expect(username).to.equal('testuser');
expect(userId).to.equal(123);
expect(userPreferences).to.deep.equal({ theme: 'dark', language: 'en' });
});
cytest('Check if variables exist', () => {
// Check existing variables
expect(cyVariables().has('username')).to.be.true;
expect(cyVariables().has('userId')).to.be.true;
expect(cyVariables().has('userPreferences')).to.be.true;
// Check non-existing variable
expect(cyVariables().has('nonExistingVar')).to.be.false;
});
cytest('Get all variables', () => {
// Get all variables
const allVariables = cyVariables().getAll();
// Verify all variables are returned
expect(allVariables).to.deep.equal({
username: 'testuser',
userId: 123,
userPreferences: { theme: 'dark', language: 'en' }
});
});
cytest('Remove a variable', () => {
// Remove a variable
cyVariables().remove('username');
// Verify the variable was removed
expect(cyVariables().has('username')).to.be.false;
// Other variables should still exist
expect(cyVariables().has('userId')).to.be.true;
expect(cyVariables().has('userPreferences')).to.be.true;
});
cytest('Clear all variables', () => {
// Clear all variables
cyVariables().clear();
// Verify all variables were cleared
expect(cyVariables().getAll()).to.deep.equal({});
expect(cyVariables().has('userId')).to.be.false;
expect(cyVariables().has('userPreferences')).to.be.false;
});
});
context('Variables with resetState', () => {
before(() => {
// Set up some variables
cyVariable('testVar1', 'value1');
cyVariable('testVar2', 'value2');
});
cytest('Variables persist after normal resetState', () => {
// Reset state without resetting variables
resetState();
// Variables should still exist
expect(cyVariable('testVar1')).to.equal('value1');
expect(cyVariable('testVar2')).to.equal('value2');
});
cytest('Variables are cleared with resetState(true)', () => {
// Reset state and variables
resetState(true);
// Variables should be cleared
expect(cyVariable('testVar1')).to.be.undefined;
expect(cyVariable('testVar2')).to.be.undefined;
});
});
context('Practical Examples', () => {
before(() => {
// Reset variables to ensure a clean state
resetState(true);
});
cytest('Store user credentials for reuse', () => {
// Store user credentials
cyVariables().add('credentials', {
username: 'testuser',
password: 'password123'
});
// Verify credentials were stored
const credentials = cyVariables().get('credentials');
expect(credentials.username).to.equal('testuser');
expect(credentials.password).to.equal('password123');
// Simulate login
cy.log(`Logging in with username: ${credentials.username}`);
});
cytest('Use stored credentials in another test', () => {
// Get credentials from previous test
const credentials = cyVariables().get('credentials');
// Verify credentials are still available
expect(credentials).to.not.be.undefined;
expect(credentials.username).to.equal('testuser');
// Simulate using credentials
cy.log(`Using stored credentials for user: ${credentials.username}`);
});
cytest('Store and update test data', () => {
// Store initial test data
cyVariable('testData', { id: 1, status: 'pending' });
// Simulate updating the data
const testData = cyVariable('testData');
testData.status = 'completed';
cyVariable('testData', testData);
// Verify the update
expect(cyVariable('testData').status).to.equal('completed');
});
});
});
Keep in mind, that the variables are not persistent across Spec files. If you want variables that persist across Spec files, I highly recommend using the plugin ‘cypress-plugin-store’ by Lasitha Wijenayake!
Final Thoughts
The cypress-smart-testsplugin is a simple plugin I developed in my free-time ❤ This empowers you to write more smart and efficient tests by introducing mechanisms for defining dependencies, conditional execution, and custom hooks. By integrating this plugin into your Cypress test suite, you can enhance the maintainability and effectiveness of your end-to-end tests, ensuring that they remain robust and relevant as your application evolves.
Here is a full-demo code: https://github.com/s-chathuranga-j/cypress-smart-tests-demo-code
Let me know how it works for you and feel free to contribute or suggest improvements!
GitHub repo: https://github.com/s-chathuranga-j/cypress-smart-tests
Top comments (0)