From 245b77a270170a83df03765395b34c11fdc27b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Go=CC=81mez=20Bachiller?= Date: Tue, 21 Nov 2023 19:16:22 +0100 Subject: [PATCH] feat: add e2e tests with playwright --- .env | 1 + .gitignore | 8 ++++ e2e/auth.setup.ts | 28 ++++++++++++ e2e/login/do-login.spec.ts | 44 +++++++++++++++++++ e2e/main/dark-mode.spec.ts | 14 ++++++ e2e/main/main.spec.ts | 11 +++++ e2e/settings/open-settings.spec.ts | 19 ++++++++ e2e/settings/update-settings.spec.ts | 20 +++++++++ jest.config.mjs | 8 +++- package.json | 8 +++- playwright.config.ts | 27 ++++++++++++ src/app/(auth)/verify/page.tsx | 4 +- .../ThemeSwitcher/ThemeSwitcher.tsx | 1 + yarn.lock | 26 +++++++++++ 14 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 e2e/auth.setup.ts create mode 100644 e2e/login/do-login.spec.ts create mode 100644 e2e/main/dark-mode.spec.ts create mode 100644 e2e/main/main.spec.ts create mode 100644 e2e/settings/open-settings.spec.ts create mode 100644 e2e/settings/update-settings.spec.ts create mode 100644 playwright.config.ts diff --git a/.env b/.env index 5f52c08..5b43f01 100644 --- a/.env +++ b/.env @@ -7,3 +7,4 @@ MAILER_DSN=smtp://localhost:1025 MAIL_FROM=aulasoftwarelibre@uco.es MAILER_SECRET=CHANGE_ME MAILER_URL=http://localhost:3000/api/mailer/ +WEBMAIL_URL=http://localhost:8025/ diff --git a/.gitignore b/.gitignore index dfe98e9..d69b98b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,11 @@ next-env.d.ts # prisma migrations !prisma/migrations/**/*.sql + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.auth/ +/playwright/.cache/ + diff --git a/e2e/auth.setup.ts b/e2e/auth.setup.ts new file mode 100644 index 0000000..023df26 --- /dev/null +++ b/e2e/auth.setup.ts @@ -0,0 +1,28 @@ +import { test as setup } from 'next/experimental/testmode/playwright' + +export const USER_AUTH_FILE = 'playwright/.auth/user.json' + +setup('authenticate as user', async ({ page, request }) => { + // Insert email + await page.goto('/signin') + await page.getByPlaceholder('Introduce tu email').fill('noreply@uco.es') + await page.getByRole('button', { name: 'Continuar con el email' }).click() + + // Open WebMail + await page.getByRole('button', { name: 'Abrir UCOWebMail' }).click() + const webMailPage = await page.waitForEvent('popup') + await webMailPage.getByText('aulasoftwarelibre@uco.es').first().click() + + // Open login link + await webMailPage + .frameLocator('#preview-html') + .getByRole('link', { name: 'Sign in' }) + .click() + + // Store credentials + const loggedPage = await webMailPage.waitForEvent('popup') + await loggedPage.context().storageState({ path: USER_AUTH_FILE }) + + // Purge WebMail + await request.delete('http://localhost:8025/api/v1/messages') +}) diff --git a/e2e/login/do-login.spec.ts b/e2e/login/do-login.spec.ts new file mode 100644 index 0000000..7b1c15b --- /dev/null +++ b/e2e/login/do-login.spec.ts @@ -0,0 +1,44 @@ +import { expect, test } from 'next/experimental/testmode/playwright' + +test.describe('Do login with email', () => { + test.afterEach(async ({ request }) => { + await request.delete('http://localhost:8025/api/v1/messages') + }) + + test('open login page', async ({ page }) => { + // Arrange + await page.goto('/') + + // Act + await page.getByRole('button', { name: 'avatar' }).click() + await page.getByText('Iniciar sesión').click() + + // Assert + await expect(page).toHaveURL('/signin') + await expect(page.locator('body')).toHaveText(/Iniciar sesión en/) + }) + + test('receive email login link', async ({ page, request }) => { + // Arrange + await page.goto('/signin') + const email = `noreply+${+new Date()}@uco.es` + + // Act + await page.getByPlaceholder('Introduce tu email').fill(email) + await page.getByRole('button', { name: 'Continuar con el email' }).click() + + // Assert + await expect(page).toHaveURL('/verify') + await expect(page.locator('body')).toHaveText(/Verifica tu correo/) + + const response = await request.get('http://localhost:8025/api/v1/messages') + expect(response.ok()).toBeTruthy() + expect(await response.json()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + Raw: expect.objectContaining({ To: [email] }), + }), + ]), + ) + }) +}) diff --git a/e2e/main/dark-mode.spec.ts b/e2e/main/dark-mode.spec.ts new file mode 100644 index 0000000..fe9be54 --- /dev/null +++ b/e2e/main/dark-mode.spec.ts @@ -0,0 +1,14 @@ +// import { expect, test } from '@playwright/test' +import { expect, test } from 'next/experimental/testmode/playwright' + +test('switch from dark mode to light mode', async ({ page }) => { + // Arrange + await page.goto('/') + const htmlElement = page.locator('html') + + // Act + await page.getByTestId('theme-switcher').click() + + // Assert + await expect(htmlElement).toHaveClass('light') +}) diff --git a/e2e/main/main.spec.ts b/e2e/main/main.spec.ts new file mode 100644 index 0000000..97e4fa0 --- /dev/null +++ b/e2e/main/main.spec.ts @@ -0,0 +1,11 @@ +// import { expect, test } from '@playwright/test' +import { expect, test } from 'next/experimental/testmode/playwright' + +test('/', async ({ page }) => { + // Arrange + // Act + await page.goto('/') + + // Assert + await expect(page.locator('body')).toHaveText(/Codex/) +}) diff --git a/e2e/settings/open-settings.spec.ts b/e2e/settings/open-settings.spec.ts new file mode 100644 index 0000000..795d78f --- /dev/null +++ b/e2e/settings/open-settings.spec.ts @@ -0,0 +1,19 @@ +import { expect, test } from 'next/experimental/testmode/playwright' + +import { USER_AUTH_FILE } from '../auth.setup' + +test.describe('open settings page', () => { + test.use({ storageState: USER_AUTH_FILE }) + + test('open from main menu', async ({ page }) => { + // Arrange + await page.goto('/') + // Act + await page.getByRole('button', { name: 'avatar' }).click() + await page.getByText('noreply@uco.es').click() + + // Assert + await expect(page).toHaveURL('/settings/profile') + await expect(page.locator('body')).toHaveText(/Ajustes de usuario/) + }) +}) diff --git a/e2e/settings/update-settings.spec.ts b/e2e/settings/update-settings.spec.ts new file mode 100644 index 0000000..cff206e --- /dev/null +++ b/e2e/settings/update-settings.spec.ts @@ -0,0 +1,20 @@ +import { expect, test } from 'next/experimental/testmode/playwright' + +import { USER_AUTH_FILE } from '../auth.setup' + +test.describe('update user settings', () => { + test.use({ storageState: USER_AUTH_FILE }) + + test('update name', async ({ page }) => { + // Arrange + await page.goto('/settings/profile') + // Act + await page.getByPlaceholder('Nombre').fill('John Doe') + await page.getByRole('button', { name: 'Enviar' }).click() + await page.reload() + + // Assert + await expect(page).toHaveURL('/settings/profile') + await expect(page.getByPlaceholder('Nombre')).toHaveValue('John Doe') + }) +}) diff --git a/jest.config.mjs b/jest.config.mjs index 5f98c30..164d06a 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -10,8 +10,14 @@ const createJestConfig = nextJest({ const config = { // Add more setup options before each test is run // setupFilesAfterEnv: ['/jest.setup.js'], - + collectCoverage: true, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}' + ], + coverageDirectory: "/coverage", + coverageReporters: ["html", "text"], testEnvironment: 'jest-environment-jsdom', + testMatch: ['/src/**/*.(spec|test).ts'] } // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async diff --git a/package.json b/package.json index fd4aff4..f87d0dc 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,12 @@ "start": "next start", "lint": "next lint", "prepare": "husky install", - "test": "jest", + "test": "jest --watchAll", + "test:ci": "jest --coverage=no", + "test:e2e": "playwright test", + "pretest:e2e": "playwright install", + "test:e2e:ui": "playwright test --ui", + "pretest:e2e:ui": "playwright install", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "prisma:migrate": "prisma migrate dev", @@ -38,6 +43,7 @@ "devDependencies": { "@commitlint/cli": "^18.4.2", "@commitlint/config-conventional": "^18.4.2", + "@playwright/test": "^1.40.0", "@storybook/addon-essentials": "^7.5.3", "@storybook/addon-interactions": "^7.5.3", "@storybook/addon-links": "^7.5.3", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..c96231c --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,27 @@ +import { defineConfig, devices } from 'next/experimental/testmode/playwright' +import path from 'path' + +const PORT = process.env.PORT || 3000 +const baseURL = `http://localhost:${PORT}` + +export default defineConfig({ + projects: [ + { + name: 'Desktop Chrome', + use: { + ...devices['Desktop Chrome'], + }, + }, + ], + reporter: 'html', + testDir: path.join(__dirname, 'e2e'), + use: { + baseURL, + trace: 'retry-with-trace', + }, + webServer: { + command: 'yarn dev --experimental-test-proxy', + reuseExistingServer: !process.env.CI, + url: 'http://localhost:3000', + }, +}) diff --git a/src/app/(auth)/verify/page.tsx b/src/app/(auth)/verify/page.tsx index ad14c5f..4ae9e6e 100644 --- a/src/app/(auth)/verify/page.tsx +++ b/src/app/(auth)/verify/page.tsx @@ -13,6 +13,8 @@ export const metadata: Metadata = { title: 'Codex | Verifica tu correo', } +const WEBMAIL_URL = process.env.WEBMAIL_URL as string + export default async function Page() { const session = await auth() @@ -28,7 +30,7 @@ export default async function Page() { size="lg" radius="none" as={Link} - href="https://webmail.uco.es" + href={WEBMAIL_URL} target="_blank" > Abrir UCOWebMail diff --git a/src/components/ThemeSwitcher/ThemeSwitcher.tsx b/src/components/ThemeSwitcher/ThemeSwitcher.tsx index ab5e8d0..5b8b750 100644 --- a/src/components/ThemeSwitcher/ThemeSwitcher.tsx +++ b/src/components/ThemeSwitcher/ThemeSwitcher.tsx @@ -18,6 +18,7 @@ export default function ThemeSwitcher() { onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} startContent={} endContent={} + data-testid="theme-switcher" /> ) } diff --git a/yarn.lock b/yarn.lock index 98fdbfa..b35cd74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2852,6 +2852,13 @@ picocolors "^1.0.0" tslib "^2.6.0" +"@playwright/test@^1.40.0": + version "1.40.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.40.0.tgz#d06c506977dd7863aa16e07f2136351ecc1be6ed" + integrity sha512-PdW+kn4eV99iP5gxWNSDQCbhMaDVej+RXL5xr6t04nbKLCBwYtA046t7ofoczHOm8u6c+45hpDKQVZqtqwkeQg== + dependencies: + playwright "1.40.0" + "@pmmmwh/react-refresh-webpack-plugin@^0.5.5": version "0.5.11" resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz#7c2268cedaa0644d677e8c4f377bc8fb304f714a" @@ -8533,6 +8540,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, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -11359,6 +11371,20 @@ pkg-dir@^7.0.0: dependencies: find-up "^6.3.0" +playwright-core@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.40.0.tgz#82f61e5504cb3097803b6f8bbd98190dd34bdf14" + integrity sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q== + +playwright@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.40.0.tgz#2a1824b9fe5c4fe52ed53db9ea68003543a99df0" + integrity sha512-gyHAgQjiDf1m34Xpwzaqb76KgfzYrhK7iih+2IzcOCoZWr/8ZqmdBw+t0RU85ZmfJMgtgAiNtBQ/KS2325INXw== + dependencies: + playwright-core "1.40.0" + optionalDependencies: + fsevents "2.3.2" + pnp-webpack-plugin@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz#65741384f6d8056f36e2255a8d67ffc20866f5c9"