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
- + - + - + {formatMessage(labels.login)} diff --git a/src/components/input/ProfileButton.tsx b/src/components/input/ProfileButton.tsx index 11cf1613..4b3c3f7b 100644 --- a/src/components/input/ProfileButton.tsx +++ b/src/components/input/ProfileButton.tsx @@ -25,7 +25,7 @@ export function ProfileButton() { return ( -