mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-12 17:22:38 +02:00
test(web): add Playwright config and dashboard smoke test
This commit is contained in:
parent
c3614f7a3e
commit
876f1da020
3 changed files with 143 additions and 0 deletions
65
surfsense_web/playwright.config.ts
Normal file
65
surfsense_web/playwright.config.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || "3000";
|
||||||
|
const BACKEND_PORT = process.env.BACKEND_PORT || "8000";
|
||||||
|
const baseURL = process.env.PLAYWRIGHT_BASE_URL || `http://localhost:${PORT}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Playwright configuration for SurfSense web E2E tests.
|
||||||
|
*
|
||||||
|
* Tests live under `tests/` and are NEVER bundled into the production Next.js
|
||||||
|
* build (`.next/standalone/`) or the Electron desktop build, because:
|
||||||
|
* - This file and `tests/` are listed in `.dockerignore`.
|
||||||
|
* - `electron-builder.yml` only ships `.next/standalone/`, not source files.
|
||||||
|
* - `@playwright/test` is a `devDependency`, so production `pnpm install`
|
||||||
|
* with `--prod` skips it entirely.
|
||||||
|
*
|
||||||
|
* @see https://playwright.dev/docs/test-configuration
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./tests",
|
||||||
|
timeout: 30_000,
|
||||||
|
expect: { timeout: 5_000 },
|
||||||
|
fullyParallel: true,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
reporter: process.env.CI
|
||||||
|
? [["html", { open: "never" }], ["github"], ["list"]]
|
||||||
|
: [["html", { open: "on-failure" }], ["list"]],
|
||||||
|
use: {
|
||||||
|
baseURL,
|
||||||
|
trace: "on-first-retry",
|
||||||
|
screenshot: "only-on-failure",
|
||||||
|
video: "retain-on-failure",
|
||||||
|
extraHTTPHeaders: {
|
||||||
|
"x-playwright-test": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "setup",
|
||||||
|
testMatch: /.*\.setup\.ts/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
dependencies: ["setup"],
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Chrome"],
|
||||||
|
storageState: "playwright/.auth/user.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
webServer: process.env.PLAYWRIGHT_NO_WEB_SERVER
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
command: "pnpm dev",
|
||||||
|
url: `http://localhost:${PORT}`,
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
timeout: 120_000,
|
||||||
|
env: {
|
||||||
|
NEXT_PUBLIC_FASTAPI_BACKEND_URL: `http://localhost:${BACKEND_PORT}`,
|
||||||
|
NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE: "LOCAL",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
57
surfsense_web/tests/auth.setup.ts
Normal file
57
surfsense_web/tests/auth.setup.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import path from "node:path";
|
||||||
|
import { expect, test as setup } from "@playwright/test";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-time authentication setup. Logs in via the FastAPI backend directly
|
||||||
|
* (skipping the UI) and persists the resulting localStorage token so every
|
||||||
|
* test in the chromium project starts already authenticated.
|
||||||
|
*
|
||||||
|
* Mirrors the real auth flow in `lib/apis/auth-api.service.ts`:
|
||||||
|
* POST /auth/jwt/login -> { access_token }
|
||||||
|
* localStorage.setItem("surfsense_bearer_token", access_token)
|
||||||
|
*
|
||||||
|
* Requires a seeded test user in the dev/test DB. Configure via env:
|
||||||
|
* PLAYWRIGHT_TEST_EMAIL, PLAYWRIGHT_TEST_PASSWORD
|
||||||
|
* NEXT_PUBLIC_FASTAPI_BACKEND_URL (defaults to http://localhost:8000)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const authFile = path.join(__dirname, "..", "playwright", ".auth", "user.json");
|
||||||
|
|
||||||
|
const TEST_USER_EMAIL = process.env.PLAYWRIGHT_TEST_EMAIL || "test@surfsense.test";
|
||||||
|
const TEST_USER_PASSWORD = process.env.PLAYWRIGHT_TEST_PASSWORD || "TestPassword123!";
|
||||||
|
const BACKEND_URL = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";
|
||||||
|
const STORAGE_KEY = "surfsense_bearer_token";
|
||||||
|
|
||||||
|
setup("authenticate", async ({ page, request }) => {
|
||||||
|
const response = await request.post(`${BACKEND_URL}/auth/jwt/login`, {
|
||||||
|
form: {
|
||||||
|
username: TEST_USER_EMAIL,
|
||||||
|
password: TEST_USER_PASSWORD,
|
||||||
|
grant_type: "password",
|
||||||
|
},
|
||||||
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
response.ok(),
|
||||||
|
`Login to ${BACKEND_URL}/auth/jwt/login failed (${response.status()}). ` +
|
||||||
|
`Check that the backend is running and that PLAYWRIGHT_TEST_EMAIL ` +
|
||||||
|
`(${TEST_USER_EMAIL}) is seeded with PLAYWRIGHT_TEST_PASSWORD. ` +
|
||||||
|
`Body: ${await response.text()}`
|
||||||
|
).toBeTruthy();
|
||||||
|
|
||||||
|
const { access_token } = (await response.json()) as { access_token: string };
|
||||||
|
expect(access_token, "Backend response missing access_token").toBeTruthy();
|
||||||
|
|
||||||
|
await page.addInitScript(
|
||||||
|
({ key, token }) => {
|
||||||
|
localStorage.setItem(key, token);
|
||||||
|
},
|
||||||
|
{ key: STORAGE_KEY, token: access_token }
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.goto("/dashboard");
|
||||||
|
await expect(page).toHaveURL(/\/dashboard/);
|
||||||
|
|
||||||
|
await page.context().storageState({ path: authFile });
|
||||||
|
});
|
||||||
21
surfsense_web/tests/dashboard.spec.ts
Normal file
21
surfsense_web/tests/dashboard.spec.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracer-bullet test: proves the entire E2E pipeline works end-to-end.
|
||||||
|
*
|
||||||
|
* Verifies:
|
||||||
|
* - Web server is reachable
|
||||||
|
* - Auth setup ran successfully (storageState contains valid token)
|
||||||
|
* - Dashboard route renders for an authenticated user
|
||||||
|
*
|
||||||
|
* Keep this test minimal. Product-specific behaviour belongs in dedicated
|
||||||
|
* spec files (new-chat, search-spaces, editor-panel, etc.).
|
||||||
|
*/
|
||||||
|
test.describe("Dashboard", () => {
|
||||||
|
test("loads dashboard with sidebar navigation for authenticated user", async ({ page }) => {
|
||||||
|
await page.goto("/dashboard");
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/dashboard/);
|
||||||
|
await expect(page.getByRole("navigation").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue