diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml new file mode 100644 index 00000000..91a4dda8 --- /dev/null +++ b/.github/workflows/test-frontend.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +jobs: + test: + 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: yarn + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + - name: Run Playwright tests + run: yarn playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/frontend/.gitignore b/frontend/.gitignore index 14cd5a9c..5b0c88b2 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -37,3 +37,6 @@ next-env.d.ts .vscode/ .idea/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/frontend/mockdata/mockConsultants.ts b/frontend/mockdata/mockConsultants.ts new file mode 100644 index 00000000..0158c604 --- /dev/null +++ b/frontend/mockdata/mockConsultants.ts @@ -0,0 +1,10 @@ +import { Variant } from "@/types"; + +export const MockConsultants: Variant[] = [{ + id: 'id', + name: 'Test Consultant', + email: 'test@company.io', + competences: ['Frontend'], + department: 'My Department', + bookings: [{year: 2023, weekNumber: 10, bookedHours: 10}] +}] \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 2fb5f58a..58be589b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "next dev", + "start-test": "NEXT_PUBLIC_NO_AUTH=true next dev", "build": "next build", "start": "next start", "lint": "next lint" @@ -31,6 +32,7 @@ "typescript": "5.2.2" }, "devDependencies": { + "@playwright/test": "^1.38.1", "eslint-config-prettier": "^9.0.0", "prettier": "3.0.3" } diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 00000000..e49b24d4 --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* 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://127.0.0.1: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: 'yarn dev', + url: 'http://127.0.0.1:3000', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/frontend/src/utils/ApiUtils.ts b/frontend/src/auth/fetchWithToken.ts similarity index 73% rename from frontend/src/utils/ApiUtils.ts rename to frontend/src/auth/fetchWithToken.ts index afcab0d9..e78e0804 100644 --- a/frontend/src/utils/ApiUtils.ts +++ b/frontend/src/auth/fetchWithToken.ts @@ -1,7 +1,12 @@ -import { msalInstance } from "@/utils/msalInstance"; +import { msalInstance } from "@/auth/msalInstance"; import { loginRequest } from "@/authConfig"; +import { MockConsultants } from "../../mockdata/mockConsultants"; export async function fetchWithToken(path: string) { + if(process.env.NEXT_PUBLIC_NO_AUTH){ + return mockedCall(path); + } + const account = msalInstance.getActiveAccount(); if (!account) { throw Error( @@ -34,9 +39,15 @@ export async function fetchWithToken(path: string) { try { const response = await fetch(path, options); - const res = await response.json(); - return res; + return await response.json(); } catch (error) { console.error(error); } } + +function mockedCall(path: string){ + if(path.includes('/variants')){ + return MockConsultants; + } +} + diff --git a/frontend/src/utils/msalInstance.ts b/frontend/src/auth/msalInstance.ts similarity index 100% rename from frontend/src/utils/msalInstance.ts rename to frontend/src/auth/msalInstance.ts diff --git a/frontend/src/components/AppProviders.tsx b/frontend/src/components/AppProviders.tsx index cfee9b12..a0837014 100644 --- a/frontend/src/components/AppProviders.tsx +++ b/frontend/src/components/AppProviders.tsx @@ -3,7 +3,7 @@ import { EventType } from "@azure/msal-browser"; import { MsalProvider } from "@azure/msal-react"; import { CssBaseline } from "@mui/material"; import { QueryClient, QueryClientProvider } from "react-query"; -import { msalInstance } from "../utils/msalInstance"; +import { msalInstance } from "@/auth/msalInstance"; import PageLayout from "./PageLayout"; import ThemeRegistry from "./ThemeRegistry/ThemeRegistry"; diff --git a/frontend/src/components/PageLayout.tsx b/frontend/src/components/PageLayout.tsx index caef3e99..8065696b 100644 --- a/frontend/src/components/PageLayout.tsx +++ b/frontend/src/components/PageLayout.tsx @@ -3,9 +3,10 @@ import { AuthenticatedTemplate, UnauthenticatedTemplate, } from "@azure/msal-react"; -import { Box, Container, Grid } from "@mui/material"; +import { Box } from "@mui/material"; import VibesAppBar from "./VibesNavBar"; import SignInSignOutButton from "./vibes-buttons/SignInSignOutButton"; +import React from "react"; export default function PageLayout({ children, @@ -15,9 +16,9 @@ export default function PageLayout({ return (
- {children} + {children} - + - +
); } + +function Authenticated({ children }: { children: React.ReactNode }) { + return process.env.NEXT_PUBLIC_NO_AUTH ? <>{children} : {children}; +} + +function Unauthenticated({ children }: { children: React.ReactNode }) { + return process.env.NEXT_PUBLIC_NO_AUTH ? + <> : {children}; +} \ No newline at end of file diff --git a/frontend/src/hooks/useVibesApi.ts b/frontend/src/hooks/useVibesApi.ts index e14ac01e..6c6ffbfd 100644 --- a/frontend/src/hooks/useVibesApi.ts +++ b/frontend/src/hooks/useVibesApi.ts @@ -1,12 +1,12 @@ "use client" import { Variant } from '@/types'; -import { fetchWithToken } from '@/utils/ApiUtils'; +import { fetchWithToken } from '@/auth/fetchWithToken'; import { useIsAuthenticated } from '@azure/msal-react'; import { useQuery, useQueryClient } from "react-query"; import { useEffect } from "react"; function useVibesApi(includeOccupied: boolean) { - const isAuthenticated = useIsAuthenticated(); + const isAuthenticated = useIsAuthenticated() || process.env.NEXT_PUBLIC_NO_AUTH; const client = useQueryClient(); //TODO: We need a better way of handling state/cache. This works for now though, but it's a bit hacky ngl diff --git a/frontend/tests/consultant-list.spec.ts b/frontend/tests/consultant-list.spec.ts new file mode 100644 index 00000000..99368727 --- /dev/null +++ b/frontend/tests/consultant-list.spec.ts @@ -0,0 +1,10 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('/'); + const consultantName = await page.getByText('Test Consultant'); + + // Expect a title "to contain" a substring. + await expect(consultantName).toBeVisible() + +}); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e517aaa4..3645ad2c 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -475,6 +475,13 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@playwright/test@^1.38.1": + version "1.38.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.38.1.tgz#8ef4263e355cd1d8ad7905d471d268e8acb82ed6" + integrity sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ== + dependencies: + playwright "1.38.1" + "@popperjs/core@^2.11.8": version "2.11.8" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" @@ -1541,6 +1548,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -2389,6 +2401,20 @@ pirates@^4.0.1: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== +playwright-core@1.38.1: + version "1.38.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.38.1.tgz#75a3c470aa9576b7d7c4e274de3d79977448ba08" + integrity sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg== + +playwright@1.38.1: + version "1.38.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.38.1.tgz#82ecd9bc4f4f64dbeee8a11c31793748e2528130" + integrity sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow== + dependencies: + playwright-core "1.38.1" + optionalDependencies: + fsevents "2.3.2" + postcss-import@^15.1.0: version "15.1.0" resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70"