aboutsummaryrefslogtreecommitdiff
path: root/cypress
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-01-24 13:09:50 +0000
committerFuwn <[email protected]>2026-01-24 13:09:50 +0000
commit396acf3bbbe00a192cb0ea0a9ccf91b1d8d2850b (patch)
treeb9df4ca6a70db45cfffbae6fdd7252e20fb8e93c /cypress
downloadumami-396acf3bbbe00a192cb0ea0a9ccf91b1d8d2850b.tar.xz
umami-396acf3bbbe00a192cb0ea0a9ccf91b1d8d2850b.zip
Initial commitHEADmain
Created from https://vercel.com/new
Diffstat (limited to 'cypress')
-rw-r--r--cypress/docker-compose.yml52
-rw-r--r--cypress/e2e/api-team.cy.ts209
-rw-r--r--cypress/e2e/api-user.cy.ts125
-rw-r--r--cypress/e2e/api-website.cy.ts198
-rw-r--r--cypress/e2e/login.cy.ts36
-rw-r--r--cypress/e2e/user.cy.ts65
-rw-r--r--cypress/e2e/website.cy.ts89
-rw-r--r--cypress/fixtures/teams.json8
-rw-r--r--cypress/fixtures/users.json11
-rw-r--r--cypress/fixtures/websites.json10
-rw-r--r--cypress/support/e2e.ts123
-rw-r--r--cypress/support/index.d.ts56
-rw-r--r--cypress/tsconfig.json8
13 files changed, 990 insertions, 0 deletions
diff --git a/cypress/docker-compose.yml b/cypress/docker-compose.yml
new file mode 100644
index 0000000..01a47bd
--- /dev/null
+++ b/cypress/docker-compose.yml
@@ -0,0 +1,52 @@
+---
+version: '3'
+services:
+ umami:
+ build: ../
+ #image: ghcr.io/umami-software/umami:postgresql-latest
+ ports:
+ - '3000:3000'
+ environment:
+ DATABASE_URL: postgresql://umami:umami@db:5432/umami
+ DATABASE_TYPE: postgresql
+ APP_SECRET: replace-me-with-a-random-string
+ depends_on:
+ db:
+ condition: service_healthy
+ restart: always
+ healthcheck:
+ test: ['CMD-SHELL', 'curl http://localhost:3000/api/heartbeat']
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ db:
+ image: postgres:15-alpine
+ environment:
+ POSTGRES_DB: umami
+ POSTGRES_USER: umami
+ POSTGRES_PASSWORD: umami
+ volumes:
+ - umami-db-data:/var/lib/postgresql/data
+ restart: always
+ healthcheck:
+ test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ cypress:
+ image: 'cypress/included:13.6.0'
+ depends_on:
+ - umami
+ - db
+ environment:
+ - CYPRESS_baseUrl=http://umami:3000
+ - CYPRESS_umami_user=admin
+ - CYPRESS_umami_password=umami
+ volumes:
+ - ./tsconfig.json:/tsconfig.json
+ - ../cypress.config.ts:/cypress.config.ts
+ - ./:/cypress
+ - ../node_modules/:/node_modules
+ - ../src/lib/crypto.ts:/src/lib/crypto.ts
+volumes:
+ umami-db-data:
diff --git a/cypress/e2e/api-team.cy.ts b/cypress/e2e/api-team.cy.ts
new file mode 100644
index 0000000..2281113
--- /dev/null
+++ b/cypress/e2e/api-team.cy.ts
@@ -0,0 +1,209 @@
+describe('Team API tests', () => {
+ Cypress.session.clearAllSavedSessions();
+
+ let teamId;
+ let userId;
+
+ before(() => {
+ cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
+ cy.fixture('users').then(data => {
+ const userCreate = data.userCreate;
+ cy.request({
+ method: 'POST',
+ url: '/api/users',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: userCreate,
+ }).then(response => {
+ userId = response.body.id;
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('username', 'cypress1');
+ expect(response.body).to.have.property('role', 'user');
+ });
+ });
+ });
+
+ it('Creates a team.', () => {
+ cy.fixture('teams').then(data => {
+ const teamCreate = data.teamCreate;
+ cy.request({
+ method: 'POST',
+ url: '/api/teams',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: teamCreate,
+ }).then(response => {
+ teamId = response.body[0].id;
+ expect(response.status).to.eq(200);
+ expect(response.body[0]).to.have.property('name', 'cypress');
+ expect(response.body[1]).to.have.property('role', 'team-owner');
+ });
+ });
+ });
+
+ it('Gets a teams by ID.', () => {
+ cy.request({
+ method: 'GET',
+ url: `/api/teams/${teamId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('id', teamId);
+ });
+ });
+
+ it('Updates a team.', () => {
+ cy.fixture('teams').then(data => {
+ const teamUpdate = data.teamUpdate;
+ cy.request({
+ method: 'POST',
+ url: `/api/teams/${teamId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: teamUpdate,
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('id', teamId);
+ expect(response.body).to.have.property('name', 'cypressUpdate');
+ });
+ });
+ });
+
+ it('Get all users that belong to a team.', () => {
+ cy.request({
+ method: 'GET',
+ url: `/api/teams/${teamId}/users`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body.data[0]).to.have.property('id');
+ expect(response.body.data[0]).to.have.property('teamId');
+ expect(response.body.data[0]).to.have.property('userId');
+ expect(response.body.data[0]).to.have.property('user');
+ });
+ });
+
+ it('Get a user belonging to a team.', () => {
+ cy.request({
+ method: 'GET',
+ url: `/api/teams/${teamId}/users/${Cypress.env('umami_user_id')}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('teamId');
+ expect(response.body).to.have.property('userId');
+ expect(response.body).to.have.property('role');
+ });
+ });
+
+ it('Get all websites belonging to a team.', () => {
+ cy.request({
+ method: 'GET',
+ url: `/api/teams/${teamId}/websites`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('data');
+ });
+ });
+
+ it('Add a user to a team.', () => {
+ cy.request({
+ method: 'POST',
+ url: `/api/teams/${teamId}/users`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: {
+ userId,
+ role: 'team-member',
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('userId', userId);
+ expect(response.body).to.have.property('role', 'team-member');
+ });
+ });
+
+ it(`Update a user's role on a team.`, () => {
+ cy.request({
+ method: 'POST',
+ url: `/api/teams/${teamId}/users/${userId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: {
+ role: 'team-view-only',
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('userId', userId);
+ expect(response.body).to.have.property('role', 'team-view-only');
+ });
+ });
+
+ it(`Remove a user from a team.`, () => {
+ cy.request({
+ method: 'DELETE',
+ url: `/api/teams/${teamId}/users/${userId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ });
+ });
+
+ it('Deletes a team.', () => {
+ cy.request({
+ method: 'DELETE',
+ url: `/api/teams/${teamId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('ok', true);
+ });
+ });
+
+ // it('Gets all teams that belong to a user.', () => {
+ // cy.request({
+ // method: 'GET',
+ // url: `/api/users/${userId}/teams`,
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // Authorization: Cypress.env('authorization'),
+ // },
+ // }).then(response => {
+ // expect(response.status).to.eq(200);
+ // expect(response.body).to.have.property('data');
+ // });
+ // });
+
+ after(() => {
+ cy.deleteUser(userId);
+ });
+});
diff --git a/cypress/e2e/api-user.cy.ts b/cypress/e2e/api-user.cy.ts
new file mode 100644
index 0000000..696cd21
--- /dev/null
+++ b/cypress/e2e/api-user.cy.ts
@@ -0,0 +1,125 @@
+describe('User API tests', () => {
+ Cypress.session.clearAllSavedSessions();
+
+ before(() => {
+ cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
+ });
+
+ let userId;
+
+ it('Creates a user.', () => {
+ cy.fixture('users').then(data => {
+ const userCreate = data.userCreate;
+ cy.request({
+ method: 'POST',
+ url: '/api/users',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: userCreate,
+ }).then(response => {
+ userId = response.body.id;
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('username', 'cypress1');
+ expect(response.body).to.have.property('role', 'user');
+ });
+ });
+ });
+
+ it('Returns all users. Admin access is required.', () => {
+ cy.request({
+ method: 'GET',
+ url: '/api/admin/users',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body.data[0]).to.have.property('id');
+ expect(response.body.data[0]).to.have.property('username');
+ expect(response.body.data[0]).to.have.property('password');
+ expect(response.body.data[0]).to.have.property('role');
+ });
+ });
+
+ it('Updates a user.', () => {
+ cy.fixture('users').then(data => {
+ const userUpdate = data.userUpdate;
+ cy.request({
+ method: 'POST',
+ url: `/api/users/${userId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: userUpdate,
+ }).then(response => {
+ userId = response.body.id;
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('id', userId);
+ expect(response.body).to.have.property('username', 'cypress1');
+ expect(response.body).to.have.property('role', 'view-only');
+ });
+ });
+ });
+
+ it('Gets a user by ID.', () => {
+ cy.request({
+ method: 'GET',
+ url: `/api/users/${userId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('id', userId);
+ expect(response.body).to.have.property('username', 'cypress1');
+ expect(response.body).to.have.property('role', 'view-only');
+ });
+ });
+
+ it('Deletes a user.', () => {
+ cy.request({
+ method: 'DELETE',
+ url: `/api/users/${userId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('ok', true);
+ });
+ });
+
+ it('Gets all websites that belong to a user.', () => {
+ cy.request({
+ method: 'GET',
+ url: `/api/users/${userId}/websites`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('data');
+ });
+ });
+
+ it('Gets all teams that belong to a user.', () => {
+ cy.request({
+ method: 'GET',
+ url: `/api/users/${userId}/teams`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('data');
+ });
+ });
+});
diff --git a/cypress/e2e/api-website.cy.ts b/cypress/e2e/api-website.cy.ts
new file mode 100644
index 0000000..cd336bd
--- /dev/null
+++ b/cypress/e2e/api-website.cy.ts
@@ -0,0 +1,198 @@
+import { uuid } from '../../src/lib/crypto';
+
+describe('Website API tests', () => {
+ Cypress.session.clearAllSavedSessions();
+
+ let websiteId;
+ let teamId;
+
+ before(() => {
+ cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
+ cy.fixture('teams').then(data => {
+ const teamCreate = data.teamCreate;
+ cy.request({
+ method: 'POST',
+ url: '/api/teams',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: teamCreate,
+ }).then(response => {
+ teamId = response.body[0].id;
+ expect(response.status).to.eq(200);
+ expect(response.body[0]).to.have.property('name', 'cypress');
+ expect(response.body[1]).to.have.property('role', 'team-owner');
+ });
+ });
+ });
+
+ it('Creates a website for user.', () => {
+ cy.fixture('websites').then(data => {
+ const websiteCreate = data.websiteCreate;
+ cy.request({
+ method: 'POST',
+ url: '/api/websites',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: websiteCreate,
+ }).then(response => {
+ websiteId = response.body.id;
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('name', 'Cypress Website');
+ expect(response.body).to.have.property('domain', 'cypress.com');
+ });
+ });
+ });
+
+ it('Creates a website for team.', () => {
+ cy.request({
+ method: 'POST',
+ url: '/api/websites',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: {
+ name: 'Team Website',
+ domain: 'teamwebsite.com',
+ teamId: teamId,
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('name', 'Team Website');
+ expect(response.body).to.have.property('domain', 'teamwebsite.com');
+ });
+ });
+
+ it('Creates a website with a fixed ID.', () => {
+ cy.fixture('websites').then(data => {
+ const websiteCreate = data.websiteCreate;
+ const fixedId = uuid();
+ cy.request({
+ method: 'POST',
+ url: '/api/websites',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: { ...websiteCreate, id: fixedId },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('id', fixedId);
+ expect(response.body).to.have.property('name', 'Cypress Website');
+ expect(response.body).to.have.property('domain', 'cypress.com');
+
+ // cleanup
+ cy.request({
+ method: 'DELETE',
+ url: `/api/websites/${fixedId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ });
+ });
+ });
+ });
+
+ it('Returns all tracked websites.', () => {
+ cy.request({
+ method: 'GET',
+ url: '/api/websites',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body.data[0]).to.have.property('id');
+ expect(response.body.data[0]).to.have.property('name');
+ expect(response.body.data[0]).to.have.property('domain');
+ });
+ });
+
+ it('Gets a website by ID.', () => {
+ cy.request({
+ method: 'GET',
+ url: `/api/websites/${websiteId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('name', 'Cypress Website');
+ expect(response.body).to.have.property('domain', 'cypress.com');
+ });
+ });
+
+ it('Updates a website.', () => {
+ cy.fixture('websites').then(data => {
+ const websiteUpdate = data.websiteUpdate;
+ cy.request({
+ method: 'POST',
+ url: `/api/websites/${websiteId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: websiteUpdate,
+ }).then(response => {
+ websiteId = response.body.id;
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('name', 'Cypress Website Updated');
+ expect(response.body).to.have.property('domain', 'cypressupdated.com');
+ });
+ });
+ });
+
+ it('Updates a website with only shareId.', () => {
+ cy.request({
+ method: 'POST',
+ url: `/api/websites/${websiteId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: { shareId: 'ABCDEF' },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('shareId', 'ABCDEF');
+ });
+ });
+
+ it('Resets a website by removing all data related to the website.', () => {
+ cy.request({
+ method: 'POST',
+ url: `/api/websites/${websiteId}/reset`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('ok', true);
+ });
+ });
+
+ it('Deletes a website.', () => {
+ cy.request({
+ method: 'DELETE',
+ url: `/api/websites/${websiteId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ expect(response.body).to.have.property('ok', true);
+ });
+ });
+
+ after(() => {
+ cy.deleteTeam(teamId);
+ });
+});
diff --git a/cypress/e2e/login.cy.ts b/cypress/e2e/login.cy.ts
new file mode 100644
index 0000000..507b1b5
--- /dev/null
+++ b/cypress/e2e/login.cy.ts
@@ -0,0 +1,36 @@
+describe('Login tests', () => {
+ beforeEach(() => {
+ cy.visit('/login');
+ });
+
+ it(
+ 'logs user in with correct credentials and logs user out',
+ {
+ defaultCommandTimeout: 10000,
+ },
+ () => {
+ cy.getDataTest('input-username').find('input').as('inputUsername').click();
+ cy.get('@inputUsername').type(Cypress.env('umami_user'), { delay: 0 });
+ cy.get('@inputUsername').click();
+ cy.getDataTest('input-password')
+ .find('input')
+ .type(Cypress.env('umami_password'), { delay: 0 });
+ cy.getDataTest('button-submit').click();
+ cy.url().should('eq', Cypress.config().baseUrl + '/dashboard');
+ cy.logout();
+ },
+ );
+
+ it('login with blank inputs or incorrect credentials', () => {
+ cy.getDataTest('button-submit').click();
+ cy.contains(/Required/i).should('be.visible');
+
+ cy.getDataTest('input-username').find('input').as('inputUsername');
+ cy.get('@inputUsername').click();
+ cy.get('@inputUsername').type(Cypress.env('umami_user'), { delay: 0 });
+ cy.get('@inputUsername').click();
+ cy.getDataTest('input-password').find('input').type('wrongpassword', { delay: 0 });
+ cy.getDataTest('button-submit').click();
+ cy.contains(/Incorrect username and\/or password./i).should('be.visible');
+ });
+});
diff --git a/cypress/e2e/user.cy.ts b/cypress/e2e/user.cy.ts
new file mode 100644
index 0000000..24df305
--- /dev/null
+++ b/cypress/e2e/user.cy.ts
@@ -0,0 +1,65 @@
+describe('User tests', () => {
+ Cypress.session.clearAllSavedSessions();
+
+ beforeEach(() => {
+ cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
+ cy.visit('/settings/users');
+ });
+
+ it('Add a User', () => {
+ // add user
+ cy.contains(/Create user/i).should('be.visible');
+ cy.getDataTest('button-create-user').click();
+ cy.getDataTest('input-username').find('input').as('inputName').click();
+ cy.get('@inputName').type('Test-user', { delay: 0 });
+ cy.getDataTest('input-password').find('input').as('inputPassword').click();
+ cy.get('@inputPassword').type('testPasswordCypress', { delay: 0 });
+ cy.getDataTest('dropdown-role').click();
+ cy.getDataTest('dropdown-item-user').click();
+ cy.getDataTest('button-submit').click();
+ cy.get('td[label="Username"]').should('contain.text', 'Test-user');
+ cy.get('td[label="Role"]').should('contain.text', 'User');
+ });
+
+ it('Edit a User role and password', () => {
+ // edit user
+ cy.get('table tbody tr')
+ .contains('td', /Test-user/i)
+ .parent()
+ .within(() => {
+ cy.getDataTest('link-button-edit').click(); // Clicks the button inside the row
+ });
+ cy.getDataTest('input-password').find('input').as('inputPassword').click();
+ cy.get('@inputPassword').type('newPassword', { delay: 0 });
+ cy.getDataTest('dropdown-role').click();
+ cy.getDataTest('dropdown-item-viewOnly').click();
+ cy.getDataTest('button-submit').click();
+
+ cy.visit('/settings/users');
+ cy.get('table tbody tr')
+ .contains('td', /Test-user/i)
+ .parent()
+ .should('contain.text', 'View only');
+
+ cy.logout();
+ cy.url().should('eq', Cypress.config().baseUrl + '/login');
+ cy.getDataTest('input-username').find('input').as('inputUsername').click();
+ cy.get('@inputUsername').type('Test-user', { delay: 0 });
+ cy.get('@inputUsername').click();
+ cy.getDataTest('input-password').find('input').type('newPassword', { delay: 0 });
+ cy.getDataTest('button-submit').click();
+ cy.url().should('eq', Cypress.config().baseUrl + '/dashboard');
+ });
+
+ it('Delete a user', () => {
+ // delete user
+ cy.get('table tbody tr')
+ .contains('td', /Test-user/i)
+ .parent()
+ .within(() => {
+ cy.getDataTest('button-delete').click(); // Clicks the button inside the row
+ });
+ cy.contains(/Are you sure you want to delete Test-user?/i).should('be.visible');
+ cy.getDataTest('button-confirm').click();
+ });
+});
diff --git a/cypress/e2e/website.cy.ts b/cypress/e2e/website.cy.ts
new file mode 100644
index 0000000..2dcd602
--- /dev/null
+++ b/cypress/e2e/website.cy.ts
@@ -0,0 +1,89 @@
+describe('Website tests', () => {
+ Cypress.session.clearAllSavedSessions();
+
+ beforeEach(() => {
+ cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
+ });
+
+ 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');
+ });
+
+ 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');
+ });
+
+ 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');
+ });
+});
diff --git a/cypress/fixtures/teams.json b/cypress/fixtures/teams.json
new file mode 100644
index 0000000..4c001e6
--- /dev/null
+++ b/cypress/fixtures/teams.json
@@ -0,0 +1,8 @@
+{
+ "teamCreate": {
+ "name": "cypress"
+ },
+ "teamUpdate": {
+ "name": "cypressUpdate"
+ }
+}
diff --git a/cypress/fixtures/users.json b/cypress/fixtures/users.json
new file mode 100644
index 0000000..235f0d6
--- /dev/null
+++ b/cypress/fixtures/users.json
@@ -0,0 +1,11 @@
+{
+ "userCreate": {
+ "username": "cypress1",
+ "password": "password",
+ "role": "user"
+ },
+ "userUpdate": {
+ "username": "cypress1",
+ "role": "view-only"
+ }
+}
diff --git a/cypress/fixtures/websites.json b/cypress/fixtures/websites.json
new file mode 100644
index 0000000..0b81788
--- /dev/null
+++ b/cypress/fixtures/websites.json
@@ -0,0 +1,10 @@
+{
+ "websiteCreate": {
+ "name": "Cypress Website",
+ "domain": "cypress.com"
+ },
+ "websiteUpdate": {
+ "name": "Cypress Website Updated",
+ "domain": "cypressupdated.com"
+ }
+}
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
new file mode 100644
index 0000000..a95035b
--- /dev/null
+++ b/cypress/support/e2e.ts
@@ -0,0 +1,123 @@
+/// <reference types="cypress" />
+import { uuid } from '../../src/lib/crypto';
+
+Cypress.Commands.add('getDataTest', (value: string) => {
+ return cy.get(`[data-test=${value}]`);
+});
+
+Cypress.Commands.add('logout', () => {
+ cy.getDataTest('button-profile').click();
+ cy.getDataTest('item-logout').click();
+ cy.url().should('eq', Cypress.config().baseUrl + '/login');
+});
+
+Cypress.Commands.add('login', (username: string, password: string) => {
+ cy.session([username, password], () => {
+ cy.request({
+ method: 'POST',
+ url: '/api/auth/login',
+ body: {
+ username,
+ password,
+ },
+ })
+ .then(response => {
+ Cypress.env('authorization', `bearer ${response.body.token}`);
+ window.localStorage.setItem('umami.auth', JSON.stringify(response.body.token));
+ })
+ .its('status')
+ .should('eq', 200);
+ });
+});
+
+Cypress.Commands.add('addWebsite', (name: string, domain: string) => {
+ cy.request({
+ method: 'POST',
+ url: '/api/websites',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: {
+ id: uuid(),
+ createdBy: '41e2b680-648e-4b09-bcd7-3e2b10c06264',
+ name: name,
+ domain: domain,
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ });
+});
+
+Cypress.Commands.add('deleteWebsite', (websiteId: string) => {
+ cy.request({
+ method: 'DELETE',
+ url: `/api/websites/${websiteId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ });
+});
+
+Cypress.Commands.add('addUser', (username: string, password: string, role: string) => {
+ cy.request({
+ method: 'POST',
+ url: '/api/users',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: {
+ username: username,
+ password: password,
+ role: role,
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ });
+});
+
+Cypress.Commands.add('deleteUser', (userId: string) => {
+ cy.request({
+ method: 'DELETE',
+ url: `/api/users/${userId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ });
+});
+
+Cypress.Commands.add('addTeam', (name: string) => {
+ cy.request({
+ method: 'POST',
+ url: '/api/teams',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ body: {
+ name: name,
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ });
+});
+
+Cypress.Commands.add('deleteTeam', (teamId: string) => {
+ cy.request({
+ method: 'DELETE',
+ url: `/api/teams/${teamId}`,
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: Cypress.env('authorization'),
+ },
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ });
+});
diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts
new file mode 100644
index 0000000..b630269
--- /dev/null
+++ b/cypress/support/index.d.ts
@@ -0,0 +1,56 @@
+/// <reference types="cypress" />
+/* global JQuery */
+
+declare namespace Cypress {
+ interface Chainable {
+ /**
+ * Custom command to select DOM element by data-test attribute.
+ * @example cy.getDataTest('greeting')
+ */
+ getDataTest(value: string): Chainable<JQuery<HTMLElement>>;
+ /**
+ * Custom command to logout through UI.
+ * @example cy.logout()
+ */
+ logout(): Chainable<JQuery<HTMLElement>>;
+ /**
+ * Custom command to login user into the app.
+ * @example cy.login('admin', 'password)
+ */
+ login(username: string, password: string): Chainable<JQuery<HTMLElement>>;
+ /**
+ * Custom command to create a website
+ * @example cy.addWebsite('test', 'test.com')
+ */
+ addWebsite(name: string, domain: string): Chainable<JQuery<HTMLElement>>;
+ /**
+ * Custom command to delete a website
+ * @example cy.deleteWebsite('02d89813-7a72-41e1-87f0-8d668f85008b')
+ */
+ deleteWebsite(websiteId: string): Chainable<JQuery<HTMLElement>>;
+ /**
+ * Custom command to create a website
+ * @example cy.deleteWebsite('02d89813-7a72-41e1-87f0-8d668f85008b')
+ */
+ /**
+ * Custom command to create a user
+ * @example cy.addUser('cypress', 'password', 'User')
+ */
+ addUser(username: string, password: string, role: string): Chainable<JQuery<HTMLElement>>;
+ /**
+ * Custom command to delete a user
+ * @example cy.deleteUser('02d89813-7a72-41e1-87f0-8d668f85008b')
+ */
+ deleteUser(userId: string): Chainable<JQuery<HTMLElement>>;
+ /**
+ * Custom command to create a team
+ * @example cy.addTeam('cypressTeam')
+ */
+ addTeam(name: string): Chainable<JQuery<HTMLElement>>;
+ /**
+ * Custom command to create a website
+ * @example cy.deleteTeam('02d89813-7a72-41e1-87f0-8d668f85008b')
+ */
+ deleteTeam(teamId: string): Chainable<JQuery<HTMLElement>>;
+ }
+}
diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json
new file mode 100644
index 0000000..48c3e14
--- /dev/null
+++ b/cypress/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["es5", "dom"],
+ "types": ["cypress", "node"]
+ },
+ "include": ["**/*.ts", "../cypress.config.ts"]
+}