Skip to main content

Running Cypress Tests on Pull Requests

Prerequisites
  • Kubernetes cluster set up that is connected to Signadot. See quickstart for details.
  • HotRod application installed in the cluster. See quickstart for details.

Overview

End-to-end (E2E) tests are often run in staging environments after code merges. However, this can delay finding issues until late in the process. Signadot enables you to shift these tests to the Pull Request (PR) stage by leveraging Kubernetes and isolated Sandboxes. This approach allows for quicker feedback and helps catch bugs early.

In this tutorial, we’ll use Cypress with Signadot to run E2E tests on PRs, isolating each change for precise validation before merging.

App & Test Setup

Here, we’ll use the HotROD application—a simple ride-sharing app. It includes four main services: frontend, location, driver, and route. Each service handles a specific part of the app’s functionality, allowing users to request rides, check locations, and receive estimated times of arrival.

Our focus will be on the frontend service. We’ll set up Cypress tests that interact with this service, ensuring that core user actions, like requesting a ride, work correctly. This setup helps verify the app’s critical paths so you can confidently merge new code without breaking essential functions.

The codes used in this tutorial are available on the HotRod repository. You can clone the repository to follow along.

Cypress Test

We'll proceed with the following Cypress test. The test simulates a user's interaction with the HotRod application by requesting a ride and verifying that the application responds correctly.

hotrod/cypress/e2e/hotrod-e2e.cy.js
describe('HotRod E2E Spec', () => {
it('should request a ride successfully', () => {
// Request a ride
cy.requestRide('1', '123');

// Verify that a driver has been assigned
cy.contains(/Driver (.*) arriving in (.*)./).should('be.visible');

// Check that the route is being resolved
cy.contains('Resolving routes').should('be.visible');
});
});

In this test:

  • We use the custom command cy.requestRide('1', '123') to simulate a user requesting a ride from location 1 to location 123.
  • After requesting a ride, we verify that the application displays a message indicating that a driver is arriving.
  • We also check that the application shows "Resolving routes" to confirm that the route calculation is in progress.

This test focuses on verifying the core functionality of requesting a ride and ensuring that the user receives the expected feedback from the application.

Adding a Custom Command

To streamline our testing process, we created a custom command requestRide(), for requesting rides:

hotrod/cypress/support/commands.js
// Request a HotRod ride
Cypress.Commands.add('requestRide', (from, to) => {
var frontendURL = '<http://frontend>.' + Cypress.env('HOTROD_NAMESPACE') + ':8080';

cy.visit(frontendURL);
cy.get(':nth-child(1) > .chakra-select__wrapper > .chakra-select').select(from);
cy.get(':nth-child(3) > .chakra-select__wrapper > .chakra-select').select(to);
cy.get('.chakra-button').click();
})

This command makes requesting a ride simpler by encapsulating the necessary steps into one reusable function. It intercepts requests to inject the SIGNADOT_ROUTING_KEY header, directing traffic to the appropriate Sandbox for testing.

The command navigates to the frontend service URL, selects the dropdown options, and submits the ride request.

We’ll import this custom command in the cypress/support/e2e.js file,

hotrod/cypress/support/e2e.js
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// <https://on.cypress.io/configuration>
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')

This setup consolidates commands for easier maintenance, allowing you to modify them in one place if needed.

Configuring Cypress

Now, let’s configure Cypress with environment variables in cypress.config.js to enable flexible testing across different environments:

hotrod/cypress.config.js
const { defineConfig } = require("cypress");

module.exports = defineConfig({
e2e: {
video: true,
experimentalStudio: true,
env: {
HOTROD_NAMESPACE: '',
SIGNADOT_ROUTING_KEY: '',
},
setupNodeEvents(on, config) {
// implement node event listeners here
},
baseUrl: "http://frontend.hotrod.svc:8080"
},
});

In this configuration:

  • baseUrl: Setting the baseUrl to http://frontend.hotrod.svc:8080 tells Cypress to use this URL as the base for all cy.visit() commands. This URL is the in-cluster DNS address for the HotRod frontend service within Kubernetes.
  • Environment Variables: The env object includes variables like HOTROD_NAMESPACE and SIGNADOT_ROUTING_KEY that help Cypress tests adapt to different environments and Sandboxes without changing the test code.

Injecting Routing Context

To effectively test changes within a Sandbox, we’ll need to ensure that Cypress directs requests to the correct environment. Signadot uses a routing key to manage this, and we’ll inject the SIGNADOT_ROUTING_KEY into our tests to help isolate each test run to its designated Sandbox.

In the custom command cy.requestRide, we have an intercept to inject the routing key:

hotrod/cypress/support/commands.js
// Request a HotRod ride
Cypress.Commands.add('requestRide', (from, to) => {
var frontendURL = '<http://frontend>.' + Cypress.env('HOTROD_NAMESPACE') + ':8080';
// inject routing key
cy.intercept(frontendURL + '/*', (req) => {
req.headers['baggage'] += ',sd-routing-key=' + Cypress.env('SIGNADOT_ROUTING_KEY');
})

cy.visit(frontendURL);
cy.get(':nth-child(1) > .chakra-select__wrapper > .chakra-select').select(from);
cy.get(':nth-child(3) > .chakra-select__wrapper > .chakra-select').select(to);
cy.get('.chakra-button').click();
})

Here, we inject the SIGNADOT_ROUTING_KEY into the request header. This key tells Signadot which Sandbox to route the request to, ensuring it aligns with the changes associated with the PR.

We can control the routing dynamically by setting SIGNADOT_ROUTING_KEY as an environment variable in cypress.config.js. This means we don't need to hardcode the routing context into individual tests. It makes it easier to run tests in different Sandboxes or environments without changing your test code.

Testing Using Signadot

With the Cypress tests set up, we can run them in our Kubernetes cluster using Signadot. To do this, let’s create a Job Runner Group (JRG), which manages the test jobs within the cluster, allowing us to execute tests on demand.

Setting Up the Job Runner Group (JRG)

Let’s create a Job Runner Group configuration file. Save it as .signadot/testing/cypress-runner.yaml:

hotrod/.signadot/testing/cypress-runner.yaml
name: cypress
spec:
cluster: "@{cluster}"
labels:
env: "@{env}"
namespace: signadot-tests
image: cypress/included:latest
jobTimeout: 30m
scaling:
manual:
desiredPods: 1

Apply the configuration to the cluster by running:

signadot jrg apply \
-f .signadot/testing/cypress-runner.yaml \
--set cluster=<your-cluster-name>

Replace <your-cluster-name> with the name of your Kubernetes cluster. This command deploys the Job Runner Group, preparing it to run Cypress tests on PRs.

Defining the Test Jobs

Next, let’s define a job to run the hotrod-e2e.cy.js Cypress test. Create a new job configuration file named .signadot/testing/e2e-tests-job.yaml:

spec:
namePrefix: hotrod-cypress-e2e
runnerGroup: cypress
script: |
#!/bin/bash
set -e

# Clone the git repo
echo "Cloning signadot repo"
git clone --single-branch -b "@{branch}" \
https://github.com/signadot/hotrod.git

# Run cypress test
cd hotrod
export CYPRESS_SIGNADOT_ROUTING_KEY=$SIGNADOT_ROUTING_KEY
npx cypress run

routingContext:
sandbox: "@{sandbox}"
uploadArtifact:
- path: hotrod/cypress/videos/hotrod-demo.cy.js.mp4
- path: hotrod/cypress/videos/hotrod-e2e.cy.js.mp4

In this configuration:

  • namePrefix provides a prefix for the job's name.
  • runnerGroup assigns the job to the Job Runner Group cypress.
  • script contains the steps to clone the HotRod repository, set environment variables, and run the Cypress tests.
  • uploadArtifact specifies files to save after the test.

The SIGNADOT_ROUTING_KEY variable is automatically provided by Signadot when the job is executed. When you submit a job using the signadot job submit command, Signadot creates a unique routing key associated with the sandbox (or baseline) specified in the job's routingContext. This routing key is then made available to the job's execution environment as an environment variable named SIGNADOT_ROUTING_KEY.

In the job script above, we export CYPRESS_SIGNADOT_ROUTING_KEY and assign it the value of $SIGNADOT_ROUTING_KEY. This makes the routing key available to Cypress via Cypress.env('SIGNADOT_ROUTING_KEY'), which is used in the custom command to inject the routing key into HTTP requests.

Running Tests on the Baseline

Let's run the Cypress tests on the baseline (without Sandbox). This will validate our app's stable version before introducing PR changes. Submit the job using the following command:

signadot job submit \
-f .signadot/testing/e2e-tests-job.yaml \
--set sandbox="" \
--set branch="main" \
--attach

By setting sandbox="", we instruct the job to run on the baseline (no Sandbox). This helps ensure the app is fully functional in its current state.

Running Tests on a Sandbox

To validate changes from a Pull Request, let’s run tests within an isolated Sandbox. For example, if we modify the driver service, let’s configure a Sandbox to use a custom image for testing. Create the Sandbox configuration in .signadot/testing/sandbox.yaml:

name: new-driver
spec:
description: Test the updated driver service
cluster: "@{cluster}"
forks:
- forkOf:
kind: Deployment
namespace: hotrod-istio
name: driver
customizations:
images:
- image: >-
signadot/hotrod:8b99a5b2ef04c4219e42f3409cd72066279fd0e4-linux-amd64
container: hotrod

Click the button below to open and run this spec on Create Sandbox UI.

Once the Sandbox is created, submit the test job with:

signadot job submit \
-f .signadot/testing/e2e-tests-job.yaml \
--set sandbox=new-driver \
--set branch="main" \
--attach

This command runs the Cypress tests in the isolated environment provided by the Sandbox. If the driver service changes cause issues, we’ll identify it before reaching the main application. This enables us to get rapid feedback and ensure quick fixes.

Conclusion

In this tutorial, we learned how to use Signadot Sandboxes to run Cypress tests on Pull Requests. This enables us to catch issues early by testing isolated changes within Kubernetes. The approach helps streamline development and ensures smoother, more reliable releases.

Integrating this workflow into your CI/CD pipeline will automate testing at the PR stage. It will improve code quality and speed up development. For more information on configuring Signadot and Cypress, refer to the Signadot Documentation and the Cypress Documentation.