diff --git a/.gitignore b/.gitignore index 2b8fe9cc..f229b066 100644 --- a/.gitignore +++ b/.gitignore @@ -30,8 +30,14 @@ yarn-error.log* bin/ build/ +# Vue distributable files /frontend/dist/* !/frontend/dist/.gitkeep +# Playwright files +/frontend/test-results/ +/frontend/playwright-report/ +/frontend/playwright/.cache/ + default.nix -Dockerfile.dev \ No newline at end of file +Dockerfile.dev diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bab0e714..04e5b01a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -33,6 +33,7 @@ }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^1.2.0", + "@playwright/test": "^1.38.1", "@types/lodash-es": "^4.17.9", "@types/node": "^20.6.0", "@typescript-eslint/eslint-plugin": "^6.7.0", @@ -2428,6 +2429,21 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@playwright/test": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz", + "integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==", + "dev": true, + "dependencies": { + "playwright": "1.38.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@rollup/pluginutils": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", @@ -5823,6 +5839,50 @@ "pathe": "^1.1.0" } }, + "node_modules/playwright": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz", + "integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==", + "dev": true, + "dependencies": { + "playwright-core": "1.38.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", + "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==", + "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/postcss": { "version": "8.4.29", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", diff --git a/frontend/package.json b/frontend/package.json index d133e16e..936ffb30 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,7 +10,8 @@ "typecheck": "vue-tsc -p ./tsconfig.json --noEmit", "lint": "npm run typecheck && eslint --ext .vue,.ts src/", "lint:fix": "eslint --ext .vue,.ts --fix src/", - "format": "prettier --write ." + "format": "prettier --write .", + "test": "playwright test" }, "dependencies": { "@vue/eslint-config-typescript": "^11.0.3", @@ -38,6 +39,7 @@ }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^1.2.0", + "@playwright/test": "^1.38.1", "@types/lodash-es": "^4.17.9", "@types/node": "^20.6.0", "@typescript-eslint/eslint-plugin": "^6.7.0", diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 00000000..f43d7d82 --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,80 @@ +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:5173", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + + /* Set default locale to English (US) */ + locale: "en-US", + }, + + /* 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://127.0.0.1:5173", + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/frontend/tests/auth.spec.ts b/frontend/tests/auth.spec.ts new file mode 100644 index 00000000..877ac746 --- /dev/null +++ b/frontend/tests/auth.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from "@playwright/test"; + +test("redirect to login", async ({ page }) => { + await page.goto("/"); + await expect(page).toHaveURL(/\/login/); + + await page.goto("/files/"); + await expect(page).toHaveURL(/\/login\?redirect=\/files\//); +}); + +test("login and logout", async ({ page, context }) => { + await page.goto("/login"); + await expect(page).toHaveTitle("Login - File Browser"); + + await page.getByRole("button", { name: "Login" }).click(); + await expect( + page.getByText("Wrong credentials", { exact: true }) + ).toBeVisible(); + + await page.getByPlaceholder("Username").fill("admin"); + await page.getByPlaceholder("Password").fill("admin"); + await page.getByRole("button", { name: "Login" }).click(); + await page.waitForURL("**/files/", { timeout: 5000 }); + await expect(page).toHaveTitle(/.*Files - File Browser/); + + let cookies = await context.cookies(); + await expect(cookies.find((c) => c.name == "auth")?.value).toBeDefined(); + + await page.getByRole("button", { name: "Logout" }).click(); + await page.waitForURL("**/login", { timeout: 5000 }); + await expect(page).toHaveTitle("Login - File Browser"); + + cookies = await context.cookies(); + await expect(cookies.find((c) => c.name == "auth")?.value).toBeUndefined(); +});