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>
160 lines
4.4 KiB
TypeScript
160 lines
4.4 KiB
TypeScript
import * as path from "node:path";
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
const authFile = path.join(import.meta.dirname, ".auth", "user.json");
|
|
|
|
/**
|
|
* Settings Page E2E Tests
|
|
*
|
|
* These tests verify the settings functionality including
|
|
* notification settings, language selection, and stock thresholds.
|
|
*/
|
|
test.describe("Settings Page", () => {
|
|
test.use({ storageState: authFile });
|
|
|
|
test("should display settings page", async ({ page }) => {
|
|
await page.goto("/settings");
|
|
|
|
// Wait for app to load
|
|
await expect(page.locator("body")).not.toContainText(/Loading\.\.\.|Initializing\.\.\./, {
|
|
timeout: 10000,
|
|
});
|
|
|
|
// Should display navigation
|
|
await expect(page.getByRole("navigation")).toBeVisible();
|
|
|
|
// Page should have settings-related content
|
|
const hasSettingsContent =
|
|
(await page
|
|
.getByText(/settings|configuration|notifications/i)
|
|
.isVisible()
|
|
.catch(() => false)) ||
|
|
(await page
|
|
.getByText(/language|email|stock/i)
|
|
.isVisible()
|
|
.catch(() => false));
|
|
|
|
expect(hasSettingsContent).toBeTruthy();
|
|
});
|
|
|
|
test("should display language settings", async ({ page }) => {
|
|
await page.goto("/settings");
|
|
|
|
await expect(page.getByRole("navigation")).toBeVisible({ timeout: 10000 });
|
|
|
|
// Look for language setting section
|
|
const hasLanguageSetting =
|
|
(await page
|
|
.getByText(/language/i)
|
|
.isVisible()
|
|
.catch(() => false)) ||
|
|
(await page
|
|
.getByRole("combobox", { name: /language/i })
|
|
.isVisible()
|
|
.catch(() => false));
|
|
|
|
expect(hasLanguageSetting).toBeTruthy();
|
|
});
|
|
|
|
test("should display notification settings", async ({ page }) => {
|
|
await page.goto("/settings");
|
|
|
|
await expect(page.getByRole("navigation")).toBeVisible({ timeout: 10000 });
|
|
|
|
// Look for notification settings
|
|
const hasNotificationSettings =
|
|
(await page
|
|
.getByText(/notification|email|push/i)
|
|
.isVisible()
|
|
.catch(() => false)) ||
|
|
(await page
|
|
.getByRole("checkbox")
|
|
.first()
|
|
.isVisible()
|
|
.catch(() => false));
|
|
|
|
expect(hasNotificationSettings).toBeTruthy();
|
|
});
|
|
|
|
test("should display stock threshold settings", async ({ page }) => {
|
|
await page.goto("/settings");
|
|
|
|
await expect(page.getByRole("navigation")).toBeVisible({ timeout: 10000 });
|
|
|
|
// Look for stock threshold settings
|
|
const hasStockSettings =
|
|
(await page
|
|
.getByText(/stock|threshold|days|reminder/i)
|
|
.isVisible()
|
|
.catch(() => false)) ||
|
|
(await page
|
|
.getByRole("spinbutton")
|
|
.first()
|
|
.isVisible()
|
|
.catch(() => false));
|
|
|
|
expect(hasStockSettings).toBeTruthy();
|
|
});
|
|
|
|
test("should have a save button", async ({ page }) => {
|
|
await page.goto("/settings");
|
|
|
|
await expect(page.getByRole("navigation")).toBeVisible({ timeout: 10000 });
|
|
|
|
// Look for save button
|
|
const saveButton = page.getByRole("button", { name: /save/i });
|
|
const hasSaveButton = await saveButton.isVisible().catch(() => false);
|
|
|
|
expect(hasSaveButton).toBeTruthy();
|
|
});
|
|
|
|
test("should allow toggling notification checkboxes", async ({ page }) => {
|
|
await page.goto("/settings");
|
|
|
|
await expect(page.getByRole("navigation")).toBeVisible({ timeout: 10000 });
|
|
|
|
// Find first checkbox and test toggle
|
|
const checkbox = page.getByRole("checkbox").first();
|
|
const hasCheckbox = await checkbox.isVisible().catch(() => false);
|
|
|
|
if (hasCheckbox) {
|
|
const initialState = await checkbox.isChecked();
|
|
|
|
// Toggle the checkbox
|
|
await checkbox.click();
|
|
|
|
// Wait for checkbox state to change (auto-waiting via assertion)
|
|
if (initialState) {
|
|
await expect(checkbox).not.toBeChecked();
|
|
} else {
|
|
await expect(checkbox).toBeChecked();
|
|
}
|
|
|
|
// Toggle back
|
|
await checkbox.click();
|
|
await expect(checkbox).toHaveJSProperty("checked", initialState);
|
|
}
|
|
});
|
|
|
|
test("should persist settings page on navigation", async ({ page }) => {
|
|
await page.goto("/settings");
|
|
|
|
await expect(page.getByRole("navigation")).toBeVisible({ timeout: 10000 });
|
|
|
|
// Navigate away and back
|
|
const dashboardLink = page.getByRole("link", { name: /dashboard/i });
|
|
if (await dashboardLink.isVisible()) {
|
|
await dashboardLink.click();
|
|
await expect(page).toHaveURL(/dashboard/);
|
|
|
|
// Navigate back to settings
|
|
const settingsLink = page.getByRole("link", { name: /settings/i });
|
|
await settingsLink.click();
|
|
await expect(page).toHaveURL(/settings/);
|
|
|
|
// Settings content should still be there
|
|
await expect(page.getByRole("navigation")).toBeVisible();
|
|
}
|
|
});
|
|
});
|