869b5774fb
* Initial plan * Add Playwright E2E testing infrastructure - Add @playwright/test dependency - Create playwright.config.ts with best practices configuration - Create e2e test structure with fixtures and auth setup - Add E2E tests for auth, dashboard, medications, and settings pages - Add npm scripts for running E2E tests - Update .gitignore for Playwright artifacts - Add E2E test job to CI workflow - Update vite.config.ts to support BACKEND_URL env variable - Update biome.json to include e2e files in linting Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Remove waitForTimeout anti-pattern from E2E tests Replace hard-coded timeouts with proper Playwright waiting strategies: - Use waitForLoadState('networkidle') for page load - Use element.waitFor() for dynamic elements - Use expect assertions for state verification Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Remove E2E tests from CI workflow E2E tests will only be run locally as requested. Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> Co-authored-by: Daniel Volz <mail@danielvolz.org>
77 lines
2.5 KiB
TypeScript
77 lines
2.5 KiB
TypeScript
import * as fs from "node:fs";
|
|
import * as path from "node:path";
|
|
import { expect, test as setup } from "@playwright/test";
|
|
import { TEST_USER } from "./fixtures";
|
|
|
|
const authFile = path.join(import.meta.dirname, ".auth", "user.json");
|
|
|
|
/**
|
|
* Global setup for authentication
|
|
* This runs before all tests to ensure a test user exists and stores the authenticated state
|
|
*/
|
|
setup("authenticate", async ({ page }) => {
|
|
// Create .auth directory if it doesn't exist
|
|
const authDir = path.dirname(authFile);
|
|
if (!fs.existsSync(authDir)) {
|
|
fs.mkdirSync(authDir, { recursive: true });
|
|
}
|
|
|
|
await page.goto("/");
|
|
|
|
// Wait for the app to fully load (network idle + content visible)
|
|
await page.waitForLoadState("networkidle");
|
|
await expect(page.locator("body")).not.toHaveText(/^$/, { timeout: 15000 });
|
|
|
|
// Check if auth is disabled (we can access dashboard directly)
|
|
const dashboardVisible = await page
|
|
.getByText(/dashboard|medications|schedule/i)
|
|
.isVisible()
|
|
.catch(() => false);
|
|
if (dashboardVisible) {
|
|
// Auth is disabled - save empty state and return
|
|
await page.context().storageState({ path: authFile });
|
|
return;
|
|
}
|
|
|
|
// Check if we need to register (first user setup)
|
|
const needsSetup = await page
|
|
.getByText(/create.*first.*user|create.*account|register|first user setup/i)
|
|
.isVisible()
|
|
.catch(() => false);
|
|
|
|
if (needsSetup) {
|
|
// Register the test user
|
|
const usernameField = page.getByLabel(/username/i);
|
|
const passwordField = page.getByLabel(/password/i).first();
|
|
|
|
await usernameField.fill(TEST_USER.username);
|
|
await passwordField.fill(TEST_USER.password);
|
|
|
|
// Look for register/create button
|
|
const registerButton = page.getByRole("button", { name: /register|create|sign up/i });
|
|
await registerButton.click();
|
|
|
|
// Wait for successful registration and redirect
|
|
await expect(page.getByRole("navigation")).toBeVisible({ timeout: 15000 });
|
|
} else {
|
|
// Need to login
|
|
const usernameField = page.getByLabel(/username/i);
|
|
const passwordField = page.getByLabel(/password/i);
|
|
|
|
// Check if we're on login page
|
|
if (await usernameField.isVisible().catch(() => false)) {
|
|
await usernameField.fill(TEST_USER.username);
|
|
await passwordField.fill(TEST_USER.password);
|
|
|
|
const loginButton = page.getByRole("button", { name: /sign in|log in|login/i });
|
|
await loginButton.click();
|
|
|
|
// Wait for successful login
|
|
await expect(page.getByRole("navigation")).toBeVisible({ timeout: 15000 });
|
|
}
|
|
}
|
|
|
|
// Save the authenticated state
|
|
await page.context().storageState({ path: authFile });
|
|
});
|