diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 0cd82a07..920542b7 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -10,7 +10,6 @@ env:
org.opencontainers.image.vendor=${{github.repository_owner}},
org.opencontainers.image.licenses="MIT",
org.opencontainers.image.version=${{github.ref_name}},
- org.opencontainers.image.created=${{ env.NOW }},
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}},
org.opencontainers.image.revision=${{github.sha}},
org.opencontainers.image.url="https://umami.is/",
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/e2e/login.cy.ts b/cypress/e2e/login.cy.ts
new file mode 100644
index 00000000..f0490560
--- /dev/null
+++ b/cypress/e2e/login.cy.ts
@@ -0,0 +1,12 @@
+describe('Login test', () => {
+ it('logs user in with correct credentials and logs user out', () => {
+ 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..18edb199
--- /dev/null
+++ b/cypress/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["es5", "dom"],
+ "types": ["cypress", "node"]
+ },
+ "include": ["**/*.ts"]
+}
diff --git a/package.json b/package.json
index 4a38c4e8..24946e3f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "2.10.0",
+ "version": "2.11.0",
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
"author": "Mike Cao ",
"license": "MIT",
@@ -42,7 +42,8 @@
"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"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
@@ -133,6 +134,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