2025December

Testing in Umami codebase - Part 1.1

Inspired by BulletProof React, I applied its codebase architecture concepts to the Umami codebase.

This article focuses only on the testing strategies used in Umami codebase.

Prerequisite

  1. Testing in Umami codebase — Part 1.0

Approach

The approach we take is simple:

  1. Check the test script in package.json

  2. Understand the Cypress configuration

  3. Reviews tests in the cypress folder.

In this part 1.1, we review the website.cy.ts.

Test script in package.json

At L47, you will find the below script in package.json:

    "test": "jest",
    "cypress-open": "cypress open cypress run",
    "cypress-run": "cypress run cypress run",

Looks like they also have use jest.

Cypress config

In the umami/cypress.config.ts, you will find the below code:

import { defineConfig } from 'cypress';
 
export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
  },
  // default username / password on init
  env: {
    umami_user: 'admin',
    umami_password: 'umami',
    umami_user_id: '41e2b680-648e-4b09-bcd7-3e2b10c06264',
  },
});

Learn more about the Cypress Configuration File.

website.cy.ts

In this file, I found 3 test cases:

  1. Add a website

  2. Edit a website

  3. Delete a website

Add a website 

it('Add a website', () => {
    // add website
    cy.visit('/settings/websites');
    cy.getDataTest('button-website-add').click();
    cy.contains(/Add website/i).should('be.visible');
    cy.getDataTest('input-name').find('input').as('inputUsername').click();
    cy.getDataTest('input-name').find('input').type('Add test', { delay: 0 });
    cy.getDataTest('input-domain').find('input').click();
    cy.getDataTest('input-domain').find('input').type('addtest.com', { delay: 0 });
    cy.getDataTest('button-submit').click();
    cy.get('td[label="Name"]').should('contain.text', 'Add test');
    cy.get('td[label="Domain"]').should('contain.text', 'addtest.com');
 
    // clean-up data
    cy.getDataTest('link-button-edit').first().click();
    cy.contains(/Details/i).should('be.visible');
    cy.getDataTest('text-field-websiteId')
      .find('input')
      .then($input => {
        const websiteId = $input[0].value;
        cy.deleteWebsite(websiteId);
      });
    cy.visit('/settings/websites');
    cy.contains(/Add test/i).should('not.exist');
  });

Picked this code from Add a website test case.

Edit a website

it('Edit a website', () => {
    // prep data
    cy.addWebsite('Update test', 'updatetest.com');
    cy.visit('/settings/websites');
 
    // edit website
    cy.getDataTest('link-button-edit').first().click();
    cy.contains(/Details/i).should('be.visible');
    cy.getDataTest('input-name').find('input').click();
    cy.getDataTest('input-name').find('input').clear();
    cy.getDataTest('input-name').find('input').type('Updated website', { delay: 0 });
    cy.getDataTest('input-domain').find('input').click();
    cy.getDataTest('input-domain').find('input').clear();
    cy.getDataTest('input-domain').find('input').type('updatedwebsite.com', { delay: 0 });
    cy.getDataTest('button-submit').click({ force: true });
    cy.getDataTest('input-name').find('input').should('have.value', 'Updated website');
    cy.getDataTest('input-domain').find('input').should('have.value', 'updatedwebsite.com');
 
    // verify tracking script
    cy.get('div')
      .contains(/Tracking code/i)
      .click();
    cy.get('textarea').should('contain.text', Cypress.config().baseUrl + '/script.js');
 
    // clean-up data
    cy.get('div')
      .contains(/Details/i)
      .click();
    cy.contains(/Details/i).should('be.visible');
    cy.getDataTest('text-field-websiteId')
      .find('input')
      .then($input => {
        const websiteId = $input[0].value;
        cy.deleteWebsite(websiteId);
      });
    cy.visit('/settings/websites');
    cy.contains(/Add test/i).should('not.exist');
  });

Picked this code from Edit a website test case.

Delete a website

it('Delete a website', () => {
  // prep data
  cy.addWebsite('Delete test', 'deletetest.com');
  cy.visit('/settings/websites');
 
  // delete website
  cy.getDataTest('link-button-edit').first().click();
  cy.contains(/Data/i).should('be.visible');
  cy.get('div').contains(/Data/i).click();
  cy.contains(/All website data will be deleted./i).should('be.visible');
  cy.getDataTest('button-delete').click();
  cy.contains(/Type DELETE in the box below to confirm./i).should('be.visible');
  cy.get('input[name="confirm"').type('DELETE');
  cy.get('button[type="submit"]').click();
  cy.contains(/Delete test/i).should('not.exist');
});

Picked this code from Delete a website test case.

About me:

Hey, my name is Ramu Narasinga. I study codebase architecture in large open-source projects.

Email: ramu.narasinga@gmail.com

I spent 200+ hours analyzing Supabase, shadcn/ui, LobeChat. Found the patterns that separate AI slop from production code. Stop refactoring AI slop. Start with proven patterns. Check out production-grade projects at thinkthroo.com

References:

  1. https://github.com/umami-software/umami/blob/master/package.json

  2. https://github.com/umami-software/umami/blob/master/cypress/e2e/website.cy.ts

  3. https://github.com/umami-software/umami/blob/master/cypress.config.ts

We use cookies
We use cookies to ensure you get the best experience on our website. For more information on how we use cookies, please see our cookie policy.

By clicking "Accept", you agree to our use of cookies.

Learn more