- Fix race conditions: Set workers: 1 since all tests share test user state - Fix stale data: GET /api/user and /api/cycle/current now fetch fresh data from database instead of returning stale PocketBase auth store cache - Fix timing: Replace waitForTimeout with retry-based Playwright assertions - Fix mobile test: Use exact heading match to avoid strict mode violation - Add test user setup: Include notificationTime and update rule for users All 1014 unit tests and 190 E2E tests pass. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
167 lines
5.6 KiB
TypeScript
167 lines
5.6 KiB
TypeScript
// ABOUTME: E2E tests for the exercise plan reference page.
|
|
// ABOUTME: Tests phase display, training guidelines, and current status.
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
test.describe("plan page", () => {
|
|
test.describe("unauthenticated", () => {
|
|
test("redirects to login when not authenticated", async ({ page }) => {
|
|
await page.goto("/plan");
|
|
|
|
// Should redirect to login
|
|
await expect(page).toHaveURL(/\/login/);
|
|
});
|
|
});
|
|
|
|
test.describe("authenticated", () => {
|
|
// These tests require TEST_USER_EMAIL and TEST_USER_PASSWORD env vars
|
|
test.beforeEach(async ({ page }) => {
|
|
const email = process.env.TEST_USER_EMAIL;
|
|
const password = process.env.TEST_USER_PASSWORD;
|
|
|
|
if (!email || !password) {
|
|
test.skip();
|
|
return;
|
|
}
|
|
|
|
// Login via the login page
|
|
await page.goto("/login");
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
const emailInput = page.getByLabel(/email/i);
|
|
const hasEmailForm = await emailInput.isVisible().catch(() => false);
|
|
|
|
if (!hasEmailForm) {
|
|
test.skip();
|
|
return;
|
|
}
|
|
|
|
await emailInput.fill(email);
|
|
await page.getByLabel(/password/i).fill(password);
|
|
await page.getByRole("button", { name: /sign in/i }).click();
|
|
|
|
// Wait for redirect to dashboard then navigate to plan
|
|
await page.waitForURL("/", { timeout: 10000 });
|
|
await page.goto("/plan");
|
|
});
|
|
|
|
test("displays exercise plan page with title", async ({ page }) => {
|
|
// Check for plan page title
|
|
const heading = page.getByRole("heading", { name: "Exercise Plan" });
|
|
await expect(heading).toBeVisible();
|
|
});
|
|
|
|
test("shows current cycle status section", async ({ page }) => {
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Wait for page to finish loading - look for Current Status or error state
|
|
const statusSection = page.getByRole("heading", {
|
|
name: "Current Status",
|
|
});
|
|
// Use text content to find error alert (avoid Next.js route announcer)
|
|
const errorAlert = page.getByText(/error:/i);
|
|
|
|
try {
|
|
// Wait for Current Status section to be visible (data loaded successfully)
|
|
await expect(statusSection).toBeVisible({ timeout: 10000 });
|
|
|
|
// Should show day number
|
|
await expect(page.getByText(/day \d+/i)).toBeVisible({ timeout: 5000 });
|
|
|
|
// Should show training type
|
|
await expect(page.getByText(/training type:/i)).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
|
|
// Should show weekly limit
|
|
await expect(page.getByText(/weekly limit:/i)).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
} catch {
|
|
// If status section not visible, check for error alert
|
|
await expect(errorAlert).toBeVisible({ timeout: 5000 });
|
|
}
|
|
});
|
|
|
|
test("shows all 5 phase cards", async ({ page }) => {
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Check for Phase Overview section
|
|
const phaseOverview = page.getByRole("heading", {
|
|
name: "Phase Overview",
|
|
});
|
|
const hasPhaseOverview = await phaseOverview
|
|
.isVisible()
|
|
.catch(() => false);
|
|
|
|
if (hasPhaseOverview) {
|
|
// Should show all 5 phase cards using data-testid
|
|
await expect(page.getByTestId("phase-MENSTRUAL")).toBeVisible();
|
|
await expect(page.getByTestId("phase-FOLLICULAR")).toBeVisible();
|
|
await expect(page.getByTestId("phase-OVULATION")).toBeVisible();
|
|
await expect(page.getByTestId("phase-EARLY_LUTEAL")).toBeVisible();
|
|
await expect(page.getByTestId("phase-LATE_LUTEAL")).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test("shows strength training reference table", async ({ page }) => {
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Check for Strength Training section
|
|
const strengthSection = page.getByRole("heading", {
|
|
name: /strength training/i,
|
|
});
|
|
const hasStrength = await strengthSection.isVisible().catch(() => false);
|
|
|
|
if (hasStrength) {
|
|
// Should have exercise table
|
|
const table = page.locator("table");
|
|
await expect(table).toBeVisible();
|
|
|
|
// Check for some exercises
|
|
await expect(page.getByText("Squats")).toBeVisible();
|
|
await expect(page.getByText("Push-ups")).toBeVisible();
|
|
await expect(page.getByText("Plank")).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test("shows rebounding techniques", async ({ page }) => {
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Check for Rebounding Techniques section
|
|
const reboundingSection = page.getByRole("heading", {
|
|
name: /rebounding techniques/i,
|
|
});
|
|
const hasRebounding = await reboundingSection
|
|
.isVisible()
|
|
.catch(() => false);
|
|
|
|
if (hasRebounding) {
|
|
// Should show techniques section - use first() for specific match
|
|
await expect(
|
|
page.getByText("Health bounce, lymphatic drainage"),
|
|
).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test("shows weekly guidelines", async ({ page }) => {
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Check for Weekly Guidelines section
|
|
const weeklySection = page.getByRole("heading", {
|
|
name: "Weekly Guidelines",
|
|
});
|
|
const hasWeekly = await weeklySection.isVisible().catch(() => false);
|
|
|
|
if (hasWeekly) {
|
|
// Should show guidelines for each phase - use exact matches
|
|
await expect(
|
|
page.getByRole("heading", { name: "Menstrual Phase (Days 1-3)" }),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByRole("heading", { name: "Follicular Phase (Days 4-14)" }),
|
|
).toBeVisible();
|
|
}
|
|
});
|
|
});
|
|
});
|