diff --git a/apps/frontend/.gitignore b/apps/frontend/.gitignore new file mode 100644 index 0000000000..58786aac75 --- /dev/null +++ b/apps/frontend/.gitignore @@ -0,0 +1,7 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/apps/frontend/package.json b/apps/frontend/package.json index a4c0fe7ff7..f301825d7c 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -11,11 +11,13 @@ "lint": "eslint . && prettier --check .", "fix": "eslint . --fix && prettier --write .", "intl:extract": "formatjs extract \"{,src/components,src/composables,src/layouts,src/middleware,src/modules,src/pages,src/plugins,src/utils}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" \"src/error.vue\" --ignore '**/*.d.ts' --ignore 'node_modules' --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace", - "test": "nuxi build" + "test": "playwright test", + "test:gen": "playwright codegen http://localhost:3000" }, "devDependencies": { "@formatjs/cli": "^6.2.12", "@nuxt/devtools": "^1.3.3", + "@playwright/test": "^1.54.2", "@types/dompurify": "^3.0.5", "@types/node": "^20.1.0", "@vintl/compact-number": "^2.0.5", diff --git a/apps/frontend/playwright.config.ts b/apps/frontend/playwright.config.ts new file mode 100644 index 0000000000..fb46afa5a8 --- /dev/null +++ b/apps/frontend/playwright.config.ts @@ -0,0 +1,61 @@ +import { defineConfig, devices } from '@playwright/test' + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: 'http://localhost:3000', + 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' }, + // }, + ], + + webServer: { + command: 'pnpm run dev', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + }, +}) diff --git a/apps/frontend/tests/switch-theme.spec.ts b/apps/frontend/tests/switch-theme.spec.ts new file mode 100644 index 0000000000..83e500e7db --- /dev/null +++ b/apps/frontend/tests/switch-theme.spec.ts @@ -0,0 +1,32 @@ +import { expect, test, type Page } from '@playwright/test' + +async function hasThemeOnElement(page: Page, element: string) { + const htmlElement = page.locator('html') + return await htmlElement.evaluate((el, theme) => el.classList.contains(theme), element) +} + +test('test', async ({ page }, testInfo) => { + await page.goto('http://localhost:3000/') + await page.getByRole('link', { name: 'Settings' }).click() + await page.locator('html').click() + + await page.getByRole('button', { name: 'Light' }).click() + const lightMode = await page.screenshot() + expect(await hasThemeOnElement(page, 'light-mode')).toBe(true) + + await page.waitForTimeout(1000) + + await page.getByRole('button', { name: 'Dark' }).click() + const darkMode = await page.screenshot() + expect(await hasThemeOnElement(page, 'dark-mode')).toBe(true) + + await page.waitForTimeout(1000) + + await page.getByRole('button', { name: 'OLED' }).click() + const oledMode = await page.screenshot() + expect(await hasThemeOnElement(page, 'oled-mode')).toBe(true) + + testInfo.attach('test-results/light-mode', { body: lightMode, contentType: 'image/png' }) + testInfo.attach('test-results/dark-mode', { body: darkMode, contentType: 'image/png' }) + testInfo.attach('test-results/oled-mode', { body: oledMode, contentType: 'image/png' }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f49ee444ab..86f644cd0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -324,6 +324,9 @@ importers: '@nuxt/devtools': specifier: ^1.3.3 version: 1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.43.1))(vue@3.5.13(typescript@5.5.4)) + '@playwright/test': + specifier: ^1.54.2 + version: 1.54.2 '@types/dompurify': specifier: ^3.0.5 version: 3.0.5 @@ -2167,6 +2170,11 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@playwright/test@1.54.2': + resolution: {integrity: sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA==} + engines: {node: '>=18'} + hasBin: true + '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} @@ -4612,6 +4620,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -6142,6 +6155,16 @@ packages: pkg-types@2.2.0: resolution: {integrity: sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==} + playwright-core@1.54.2: + resolution: {integrity: sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.54.2: + resolution: {integrity: sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==} + engines: {node: '>=18'} + hasBin: true + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -9933,6 +9956,10 @@ snapshots: '@pkgr/core@0.2.9': {} + '@playwright/test@1.54.2': + dependencies: + playwright: 1.54.2 + '@polka/url@1.0.0-next.25': {} '@prettier/plugin-xml@3.4.2(prettier@3.6.2)': @@ -12937,6 +12964,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -15060,6 +15090,14 @@ snapshots: pathe: 2.0.3 optional: true + playwright-core@1.54.2: {} + + playwright@1.54.2: + dependencies: + playwright-core: 1.54.2 + optionalDependencies: + fsevents: 2.3.2 + pluralize@8.0.0: {} postcss-calc@10.0.2(postcss@8.4.49):