E2E Testing Applications
As the complexity of your Superblocks apps grows, manually testing to verify everything is working as expected becomes harder and harder to achieve. PMs and QA need to spend more time testing user flows and edge cases to make sure your end-users have a bug free experience.
End-to-end (E2E) testing can help with this, ensuring that every part of your app works seamlessly together from your user's perspective. By validating user workflows within your app, E2E testing can help identify issues early and enhance the reliability and stability of your apps.
This guide walks through how to write E2E tests in Superblocks and integrate them with CI to help your team ship changes faster and with more confidence knowing their changes are bug free.
Install
To get started writing end-to-end tests for your application, navigate to your app's git repo used for Source Control or create a new local directory for your tests.
mkdir -p superblocks/tests
In these examples, we'll show how to write tests using Cypress. Use the following instructions to install the required dependencies:
Install Cypress via your preferred package manager. This will install Cypress locally as a dev dependency. For full instructions see docs on Installing Cypress.
- npm
- Yarn
- pnpm
npm install cypress --save-dev
yarn add cypress --dev
pnpm add cypress --save-dev
Now you can open Cypress using one of the following commands, depending on your package manager.
- npm
- Yarn
- pnpm
npx cypress open
yarn dlx cypress open
pnpm dlx cypress open
While you can use the full path to the Cypress executable each time, it's recommended that you add Cypress commands to the scripts field in your package.json
file.
{
"scripts": {
"cy:open": "cypress open",
"cy:run": "cypress run"
}
}
In the Launchpark, select E2E Testing as your test type. Click Continue to accept the quick configuration and choose a browser to use when running tests.
For best compatibility with Superblocks, we recommend using Chrome.
Writing your first test
Now that you've successfully installed your test framework, you're ready to write your first test!
For this test, we'll use a simple sales Application. On the app's home page we see some simple sales metrics. Users can choose to display anywhere from 6 to 18 months worth of data. They can also navigate to other pages of the app including Projects, Sales, Invoices, and more.
Configure Cypress
Before writing E2E tests, configure your chosen framework so that:
- Your tests can log in to Superblocks using either Username/Password, Google, or SSO
- After logging in, you can navigate to your app
- Finally, your test can select the Superblocks iframe of the app to easily interact with the app
Configure Cypress
Update your Cypress config to include the following:
import { defineConfig } from "cypress";
export default defineConfig({
defaultBrowser: 'chrome',
defaultCommandTimeout: 15000,
includeShadowDom: true,
chromeWebSecurity: false,
modifyObstructiveCode: false,
viewportWidth: 1400,
waitForAnimations: true,
e2e: {
// Superblocks US region
baseUrl: 'https://app.superblocks.com',
// Superblocks EU region
// baseUrl: 'https://eu.superblocks.com',
},
env: {
access_token_prefix: '@@auth0spajs@@',
// Deployed vs Preview mode App URLS
previewRoute: 'applications/preview',
deployedRoute: 'applications',
// Superblocks View Mode used for testing
environment: 'preview'
}
});
Set Superblocks app credentials
To have access to test user credentials within our tests we need to configure the Cypress environment variables:
{
"SUPERBLOCKS_USERNAME": "<YOUR_SUPERBLOCKS_USERNAME>",
"SUPERBLOCKS_PASSWORD": "<YOUR_SUPERBLOCKS_PASSWORD>"
}
If your tests are in a git repo, make sure to add cypress.env.json
to your .gitignore
file to make sure your secrets aren't committed.
Add login
command
Next, we'll write a login command to log in to Superblocks. This command will use cy.origin()
to:
- Navigate to the Superblocks log in page
- Input user credentials
- Sign in and redirect back to Superblocks
- Cache the results with
cy.session()
const login = (username: string, password: string) => {
// Superblocks home page redirects to Superblocks login
cy.visit(Cypress.config('baseUrl'));
// Login
cy.get('input[name="username"]').type(username);
cy.contains('button[value=default]', 'Continue').click().wait(2000);
cy.get('input[name="password"]').type(password, { log: false });
cy.contains('button[value=default]', 'Continue').click();
// Ensure Superblocks has redirected back to home page
cy.url().should('equal', Cypress.config('baseUrl'));
}
Cypress.Commands.add('login', (nextUrl: string) => {
const log = Cypress.log({
displayName: 'LOGIN',
message: '🔐 Authenticating',
autoEnd: false
});
log.snapshot('before');
cy.session(
`superblocks-${Cypress.env('SUPERBLOCKS_USERNAME')}`,
() => {
login(
Cypress.env('SUPERBLOCKS_USERNAME'),
Cypress.env('SUPERBLOCKS_PASSWORD')
);
}, {
validate: () => {
cy.wrap(Object.keys(localStorage))
.invoke('find', (k) => k.startsWith(Cypress.env('access_token_prefix')))
.should('exist');
}
}
);
log.snapshot('after');
log.end();
});
Add getSBApp
command
Superblocks apps are all wrapped in a sandboxed iframe. To test your app, you'll need to interact with DOM elements inside an iframe. Add the following custom command to your cypress/support/commands.ts
file so you can easily get the iframe in your tests.
Cypress.Commands.add('getApp', () => {
return cy.get('iframe[data-test="sb-iframe"]', { log: false })
.its('0.contentDocument.body', { log: false }).should('not.be.empty')
.then(cy.wrap);
})
(Optional) Add goTo
command
For convenience, you can optionally add a goTo
command that will load either your deployed or pre-production app based on the current environment.
Cypress.Commands.add('goTo', (appId: string, route?: string) => {
const branch = Cypress.env('branch') || 'main';
let url = Cypress.env('baseUrl');
if (Cypress.env('environment') === 'deployed') {
url = `${url}/${Cypress.env('deployedRoute')}/${appId}/${route || null}`;
} else {
url = `${url}/${Cypress.env('previewRoute')}/${appId}/${route || null}?branch=${branch}`;
}
cy.visit(url).wait(2000);
});
For more information on testing code in development see Running tests on a branch.
Add a test file
Now that our framework is configured, we can start writing our test. For our first test we'll simply test that we can load the application and that it shows the current user's name.
Follow the instructions for your framework to create this test.
Start by creating a test file at cypress/e2e/sample.cy.js
. You'll end up with a project that looks like:
.
├── .gitignore
├── package.json
├── cypress.config.ts
├── cypress.env.json
└── cypress
├── downloads
├── e2e
│ └── sample.cy.ts
├── fixtures
└── support
Replace the contents of the test file with the code below. Make sure you update the code to include your application's ID.
const APP_ID = "<YOUR_APPS_ID>"
describe('My First Test', () => {
beforeEach(() => {
cy.login();
cy.visit(`${Cypress.${APP_ID}`).wait(2000);
})
it('loads homepage welcome', function () {
let username = 'Test User';
// Test that app loads with welcome message for current user
cy.getApp().contains(`Welcome, ${username.split(" ")[0]}`).should('be.visible');
})
})
Run your test
Finally, run the test by going to the Specs page in the Cypress UI. Click on the sample.cy.ts
to start running your test.
In the Command Log you'll see Cypress display our first test passing!
E2E Test Examples
Now that we have a basic test running, we can start creating more complex tests. When testing your Superblocks app, you'll likely want to do a few basic things, including:
- Take actions: click button, open dropdown, select a date, etc
- Enter input: fill out a form, search in a table, etc
The following sections will cover how each of these can be achieved when writing tests for Superblocks.
Taking actions
In your E2E tests, you'll likely find yourself wanting to interact with many components on the page. The most simple interaction with be clicking on buttons, links, etc to open views/perform actions.
To interact with Superblocks components in E2E tests, you'll need to select them. Superblocks components all contain data attributes that you can use to easily find them in the app. The naming convention of most data selectors is:
[data-test="widget-<COMPONENT_NAME>"]
Once you've selected a component, you can then select DOM elements within it.
As an example, let's go back to our test application. It has navigation buttons to move between pages. Let's write a test to check that when I click the "Projects" nav item, I'm routed to the /projects
URL.
To accomplish this we'll:
- Select the nav link for the Project page (
[data-test="widget-NavLink_Projects"]
) - Select the
<a>
tag within the component - Click on the link
- Assert that the URL route was updated to
/projects
The code for this simple tests looks like:
describe('Navigation Test', () => {
beforeEach(() => {
cy.login();
cy.goTo(APP_ID);
})
it('projects link should go to projects page', () => {
// Select the left-nav for Projects and click
cy.getApp().find('[data-test="widget-NavLink_Projects"] a').click();
// Wait for page to load
cy.wait(1000);
// Assert URL contains `/projects`
cy.url().should('include', '/projects');
})
})
Entering input
Similar to clicking on an item, you can interact with input fields in your app. This can be useful to test form validation, or API behavior given a certain set of inputs provided in the UI.
To enter input into a field, just select it using the [data-test="widget-<COMPONENT-NAME>"]
pattern and use the type()
function to provide your input.
For example, let's write a test that checks that when I enter an invalid email in an email input field, an error is displayed and the Submit button of my form is disabled. To accomplish this, we'll:
- Select the Email input field using the selected
[data-test="widget-EmailInput1_Form1"]
- Use the
type()
method to type in an invalid email - Assert that a error message is displayed saying the email needs to be valid
- Select the form's submit button (
[data-test="widget-SubmitButton_Form1"]
) and assert that it's disabled
The code looks like this:
describe('New User Registration Form Test', () => {
beforeEach(() => {
cy.login();
cy.goTo(APP_ID, 'register');
})
it('should error on invalid email address', () => {
// Select the form's Email input field
cy.getApp().find('[data-test="widget-EmailInput1_Form1"]').type('invalid email address');
// Assert error is displayed to user
cy.getApp().contains('Must be a valid email address');
// Assert form button is disabled
cy.getApp().find('[data-test="widget-SubmitButton_Form1"] button').should('be.disabled');
})
})
Advanced testing topics
Running tests on a branch
So far this guide has demonstrated how to run E2E tests using the Preview URL of you app's main
branch. You can also update you configuration to run tests against the changes on a branch for apps using Source Control.
To test against a branch, update your package.json
as follows:
{
"scripts": {
"cy:open": "cypress open --env branch=$(git rev-parse --abbrev-ref HEAD)",
"cy:run": "cypress run --env branch=$(git rev-parse --abbrev-ref HEAD)",
}
}
Now when you run cy:open
or cy:run
, Cypress will load the Branch Preview URL for the branch you have checked out locally.
This requires using the goTo
command we introduced earlier
Testing deployed app
Similar to testing changes on a branch, you can also update you package.json
to provide a script to run tests against the deployed version of your app.
To do this, add the following scripts to your package.json
:
{
"scripts": {
...
"cy:open:deployed": "cypress open --env environment=deployed",
"cy:run:deployed": "cypress run --env environment=deployed"
}
}
Now when you run cy:open:deployed
, Cypress will load the deployed version of your app when using the goTo
command we introduced earlier.
Integrating with CI
You can easily run your E2E tests as a part of your CI/CD flow to make sure you app is working as expected before deploying changes. To configure your CI/CD to run tests, following the instructions below.
If your application is in Source Control, make sure any code changes have synced with Superblocks before your tests run.
- GitHub
- GitLab
For the full action to sync changes to Superblocks, see docs on Configuring GitHub Action CI
name: Sync to Superblocks & Run Tests
on: [push]
jobs:
superblocks-sync:
...
cypress-run:
runs-on: ubuntu-latest
needs: superblocks-sync
steps:
- name: checkout
uses: actions/checkout@v4
- name: cypress run
uses: cypress-io/github-action@v6
with:
start: npm run cy:run
For the full action to sync changes to Superblocks, see docs on Configuring GitLab Pipeline
stages:
- sync
- test
...
test:
image: cypress/browsers:22.12.0
stage: test
script:
- npm run cy:run