diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
new file mode 100644
index 0000000..cc12434
--- /dev/null
+++ b/.github/workflows/playwright.yml
@@ -0,0 +1,48 @@
+name: Playwright Tests
+on:
+ push:
+ branches: [main, master]
+ pull_request:
+ branches: [main, master]
+jobs:
+ test:
+ services:
+ postgres:
+ image: postgres:13
+ env:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: postgres
+ POSTGRES_DB: postgres
+ ports:
+ - 5432:5432
+ timeout-minutes: 60
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ - name: Install dependencies
+ run: npm ci
+ - name: Install Playwright Browsers
+ run: npm run build && npx playwright install --with-deps
+ env:
+ DATABASE_URL: ${{ secrets.DATABASE_URL }}
+ POSTGRES_URL_NON_POOLING: ${{ secrets.POSTGRES_URL_NON_POOLING }}
+ VERCEL_EMAIL_API_KEY: ${{ secrets.VERCEL_EMAIL_API_KEY }}
+ VERCEL_EMAIL_SENDER: ${{ secrets.VERCEL_EMAIL_SENDER }}
+ - name: Run Playwright tests
+ run: npx playwright test --workers=1 --reporter=list
+ env:
+ # The hostname used to communicate with the PostgreSQL service container
+ POSTGRES_HOST: postgres
+ # The default PostgreSQL port
+ POSTGRES_PORT: 5432
+ DATABASE_URL: ${{ secrets.DATABASE_URL }}
+
+ - uses: actions/upload-artifact@v3
+ if: always()
+ with:
+ name: playwright-report
+ path: playwright-report/
+ retention-days: 30
diff --git a/.gitignore b/.gitignore
index ec3dbb3..4a1c16b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,4 +34,8 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
-storybook*
\ No newline at end of file
+storybook*
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/README.md b/README.md
index ff2e89a..0391c81 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,10 @@
-This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+
## Getting Started
diff --git a/package-lock.json b/package-lock.json
index 5893fc7..3871f7b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"bcrypt": "^5.1.1",
"crypto": "^1.0.1",
"date-fns": "^3.3.0",
+ "dotenv": "^16.4.1",
"jsonwebtoken": "^9.0.2",
"next": "14.0.4",
"next-auth": "^4.24.5",
@@ -29,6 +30,7 @@
"zod": "^3.22.4"
},
"devDependencies": {
+ "@playwright/test": "^1.41.2",
"@storybook/addon-essentials": "^7.6.10",
"@storybook/addon-interactions": "^7.6.10",
"@storybook/addon-links": "^7.6.10",
@@ -3466,6 +3468,21 @@
"node": ">=14"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz",
+ "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.41.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.5.11",
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz",
@@ -9600,10 +9617,9 @@
}
},
"node_modules/dotenv": {
- "version": "16.3.2",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz",
- "integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==",
- "dev": true,
+ "version": "16.4.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz",
+ "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==",
"engines": {
"node": ">=12"
},
@@ -14976,6 +14992,50 @@
"node": ">=10"
}
},
+ "node_modules/playwright": {
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz",
+ "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==",
+ "dev": true,
+ "dependencies": {
+ "playwright-core": "1.41.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz",
+ "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/pnp-webpack-plugin": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz",
diff --git a/package.json b/package.json
index 96d0c5d..20df8ba 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev",
- "build": "npm run prisma:generate && npm run build-storybook && NODE_ENV=production next build",
+ "build": "npm run prisma:generate && prisma migrate dev && npm run build-storybook && NODE_ENV=production next build",
"start": "next start",
"format": "npx prettier --write .",
"lint": "next lint",
@@ -23,6 +23,7 @@
"bcrypt": "^5.1.1",
"crypto": "^1.0.1",
"date-fns": "^3.3.0",
+ "dotenv": "^16.4.1",
"jsonwebtoken": "^9.0.2",
"next": "14.0.4",
"next-auth": "^4.24.5",
@@ -38,6 +39,7 @@
"zod": "^3.22.4"
},
"devDependencies": {
+ "@playwright/test": "^1.41.2",
"@storybook/addon-essentials": "^7.6.10",
"@storybook/addon-interactions": "^7.6.10",
"@storybook/addon-links": "^7.6.10",
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 0000000..6da7e53
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,80 @@
+import { defineConfig, devices } from '@playwright/test';
+import dotenv from 'dotenv';
+import path from 'path';
+
+/**
+ * Read environment variables from file.
+ * https://github.com/motdotla/dotenv
+ */
+// require('dotenv').config();
+dotenv.config({ path: path.resolve(__dirname, '.', '.env.local') });
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: './tests',
+ /* Run tests in files in parallel */
+ fullyParallel: false,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* Retry on CI only */
+ retries: process.env.CI ? 2 : 0,
+ /* Opt out of parallel tests on CI. */
+ workers: process.env.CI ? 1 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: 'html',
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: 'http://localhost:3000',
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: 'on-first-retry',
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+
+ // {
+ // name: 'firefox',
+ // use: { ...devices['Desktop Firefox'] },
+ // },
+
+ // {
+ // name: 'webkit',
+ // use: { ...devices['Desktop Safari'] },
+ // },
+
+ /* Test against mobile viewports. */
+ // {
+ // name: 'Mobile Chrome',
+ // use: { ...devices['Pixel 5'] },
+ // },
+ // {
+ // name: 'Mobile Safari',
+ // use: { ...devices['iPhone 12'] },
+ // },
+
+ /* Test against branded browsers. */
+ // {
+ // name: 'Microsoft Edge',
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
+ // },
+ // {
+ // name: 'Google Chrome',
+ // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
+ // },
+ ],
+
+ /* Run your local dev server before starting the tests */
+ webServer: {
+ command: 'npm run dev',
+ url: 'http://localhost:3000',
+ reuseExistingServer: !process.env.CI,
+ },
+});
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 880e651..afbdd6e 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -8,6 +8,7 @@ generator client {
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
+ directUrl = env("POSTGRES_URL_NON_POOLING") // uses a direct connection
}
enum Provider {
diff --git a/src/api/email.ts b/src/api/email.ts
index 06da9dc..d976d70 100644
--- a/src/api/email.ts
+++ b/src/api/email.ts
@@ -11,6 +11,7 @@ export const SendInviteEmail = async (
subject: string,
): Promise => {
try {
+ console.log('Sending email to:', email);
// TODO: change from address to actual platform email - need to purchasedomain. currently not developed
const sendResponse = await resend.emails.send({
from: SenderString,
diff --git a/src/consts.ts b/src/consts.ts
index dfe6194..5eb6985 100644
--- a/src/consts.ts
+++ b/src/consts.ts
@@ -1,5 +1,9 @@
+import { Role } from '@prisma/client';
+
export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+export const baseURL = 'http://localhost:3000';
+
export enum Routes {
AUTH = '/auth',
DASHBOARD = '/dashboard',
@@ -11,3 +15,10 @@ export enum Endpoints {
AUTH = '/auth',
TASKS = '/tasks',
}
+export const User = {
+ email: 'test@gmail.com',
+ password: '123123',
+ name: 'test',
+ teamName: 'test',
+ Role: Role.ADMIN,
+};
diff --git a/tests/Auth/auth.spec.ts b/tests/Auth/auth.spec.ts
new file mode 100644
index 0000000..e78819f
--- /dev/null
+++ b/tests/Auth/auth.spec.ts
@@ -0,0 +1,65 @@
+import { Routes, baseURL, User } from '@/consts';
+import { test, expect } from '@playwright/test';
+import prisma from '../../lib/prismadb';
+import bcrypt from 'bcrypt';
+import { Provider } from '@prisma/client';
+
+test.beforeEach(async ({ page }) => {
+ await page.goto(baseURL);
+});
+
+test('load login page', async ({ page }) => {
+ await page.goto(baseURL + Routes.AUTH);
+ expect(page.url()).toBe(baseURL + Routes.AUTH);
+ expect(await page.title()).toBe('Management System');
+});
+
+test('check db', async () => {
+ const users = await prisma.user.findMany();
+ expect(users).toBeTruthy();
+});
+
+test('create static user in db', async () => {
+ const passwordHash = bcrypt.hashSync(User.password, 10);
+ const user = await prisma.user.create({
+ data: {
+ email: User.email,
+ fullName: User.name,
+ passwordHash: passwordHash,
+ lastLogin: new Date(),
+ role: User.Role,
+ provider: Provider.EMAIL,
+ },
+ select: {
+ id: true,
+ email: true,
+ fullName: true,
+ passwordHash: true,
+ lastLogin: true,
+ teamId: true,
+ role: true,
+ },
+ });
+ const team = await prisma.team.create({
+ data: {
+ name: User.teamName,
+ users: {
+ connect: {
+ id: user.id,
+ },
+ },
+ },
+ });
+});
+
+test('login test without signup', async ({ page }) => {
+ // Expect a title "to contain" a substring.
+ await page.getByPlaceholder('Email').fill(User.email);
+ await page.getByPlaceholder('Password').fill(User.password);
+ await page.getByRole('button', { name: 'Log in' }).click();
+ await page.waitForURL(Routes.DASHBOARD);
+ expect(page.url()).toBe(baseURL + Routes.DASHBOARD);
+ expect(await page.getByRole('button', { name: 'Logout' }).textContent()).toBe(
+ 'Logout',
+ );
+});