Files
phaseflow/e2e/plan.spec.ts
Petru Paler 00b84d0b22 Fix E2E test reliability issues and stale data bugs
- 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>
2026-01-13 20:23:32 +00:00

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();
}
});
});
});