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). +
+vercel +GitHub issues +GitHub pull requests +contributors +
+ ## 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', + ); +});