diff --git a/.gitignore b/.gitignore
index 050397c9..8f39d0f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@ yarn-error.log*
# local env files
.env
.env.*
+*.env.*
*.dev.yml
diff --git a/cypress.config.ts b/cypress.config.ts
new file mode 100644
index 00000000..5bed49b8
--- /dev/null
+++ b/cypress.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'cypress';
+
+export default defineConfig({
+ e2e: {
+ baseUrl: 'http://localhost:3000',
+ },
+});
diff --git a/cypress/docker-compose.yml b/cypress/docker-compose.yml
new file mode 100644
index 00000000..3cd8f546
--- /dev/null
+++ b/cypress/docker-compose.yml
@@ -0,0 +1,51 @@
+---
+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
+volumes:
+ umami-db-data:
diff --git a/cypress/e2e/login.cy.ts b/cypress/e2e/login.cy.ts
new file mode 100644
index 00000000..4bd6a6b7
--- /dev/null
+++ b/cypress/e2e/login.cy.ts
@@ -0,0 +1,18 @@
+describe('Login test', () => {
+ it(
+ 'logs user in with correct credentials and logs user out',
+ {
+ defaultCommandTimeout: 10000,
+ },
+ () => {
+ cy.visit('/login');
+ cy.dataCy('input-username').type(Cypress.env('umami_user'));
+ cy.dataCy('input-password').type(Cypress.env('umami_password'));
+ cy.dataCy('button-submit').click();
+ cy.url().should('eq', Cypress.config().baseUrl + '/dashboard');
+ cy.dataCy('button-profile').click();
+ cy.dataCy('item-logout').click();
+ cy.url().should('eq', Cypress.config().baseUrl + '/login');
+ },
+ );
+});
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
new file mode 100644
index 00000000..2ee2ed8e
--- /dev/null
+++ b/cypress/support/e2e.ts
@@ -0,0 +1,5 @@
+///
+
+Cypress.Commands.add('dataCy', value => {
+ return cy.get(`[data-cy=${value}]`);
+});
diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts
new file mode 100644
index 00000000..da94c844
--- /dev/null
+++ b/cypress/support/index.d.ts
@@ -0,0 +1,11 @@
+///
+
+declare namespace Cypress {
+ interface Chainable {
+ /**
+ * Custom command to select DOM element by data-cy attribute.
+ * @example cy.dataCy('greeting')
+ */
+ dataCy(value: string): Chainable>;
+ }
+}
diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json
new file mode 100644
index 00000000..48c3e14e
--- /dev/null
+++ b/cypress/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["es5", "dom"],
+ "types": ["cypress", "node"]
+ },
+ "include": ["**/*.ts", "../cypress.config.ts"]
+}
diff --git a/package.json b/package.json
index 0124f360..f4a00e45 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "2.10.1",
+ "version": "2.11.0",
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
"author": "Umami Software, Inc. ",
"license": "MIT",
@@ -42,7 +42,9 @@
"change-password": "node scripts/change-password.js",
"lint": "next lint --quiet",
"prepare": "node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky install",
- "postbuild": "node scripts/postbuild.js"
+ "postbuild": "node scripts/postbuild.js",
+ "cypress-open": "cypress open cypress run",
+ "cypress-run": "cypress run cypress run"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
@@ -133,6 +135,7 @@
"@typescript-eslint/eslint-plugin": "^6.7.3",
"@typescript-eslint/parser": "^6.7.3",
"cross-env": "^7.0.3",
+ "cypress": "^13.6.6",
"esbuild": "^0.17.17",
"eslint": "^8.33.0",
"eslint-config-next": "^14.0.4",
diff --git a/src/app/login/LoginForm.tsx b/src/app/login/LoginForm.tsx
index 28d79458..03192413 100644
--- a/src/app/login/LoginForm.tsx
+++ b/src/app/login/LoginForm.tsx
@@ -42,17 +42,30 @@ export function LoginForm() {
umami