Add 36 new E2E tests across 5 test files
All checks were successful
Deploy / deploy (push) Successful in 1m41s
All checks were successful
Deploy / deploy (push) Successful in 1m41s
New E2E test files: - e2e/health.spec.ts: 3 tests for health/observability endpoints - e2e/history.spec.ts: 7 tests for history page - e2e/plan.spec.ts: 7 tests for exercise plan page - e2e/decision-engine.spec.ts: 8 tests for decision display and overrides - e2e/cycle.spec.ts: 11 tests for cycle tracking, settings, and period logging Total E2E tests: 100 (up from 64) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
154
e2e/history.spec.ts
Normal file
154
e2e/history.spec.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
// ABOUTME: E2E tests for the history page showing past training decisions.
|
||||
// ABOUTME: Tests table display, pagination, date filtering, and empty states.
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.describe("history page", () => {
|
||||
test.describe("unauthenticated", () => {
|
||||
test("redirects to login when not authenticated", async ({ page }) => {
|
||||
await page.goto("/history");
|
||||
|
||||
// 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 history
|
||||
await page.waitForURL("/", { timeout: 10000 });
|
||||
await page.goto("/history");
|
||||
});
|
||||
|
||||
test("displays history page with title", async ({ page }) => {
|
||||
// Check for history page title
|
||||
const heading = page.getByRole("heading", { name: "History" });
|
||||
await expect(heading).toBeVisible();
|
||||
});
|
||||
|
||||
test("shows date filter controls", async ({ page }) => {
|
||||
// Check for date filter inputs
|
||||
const startDateInput = page.getByLabel(/start date/i);
|
||||
const endDateInput = page.getByLabel(/end date/i);
|
||||
|
||||
await expect(startDateInput).toBeVisible();
|
||||
await expect(endDateInput).toBeVisible();
|
||||
|
||||
// Check for Apply and Clear buttons
|
||||
const applyButton = page.getByRole("button", { name: /apply/i });
|
||||
const clearButton = page.getByRole("button", { name: /clear/i });
|
||||
|
||||
await expect(applyButton).toBeVisible();
|
||||
await expect(clearButton).toBeVisible();
|
||||
});
|
||||
|
||||
test("shows table with correct columns when data exists", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Wait for data to load
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// Check if there's data or empty state
|
||||
const table = page.locator("table");
|
||||
const emptyState = page.getByText(/no history found/i);
|
||||
|
||||
const hasTable = await table.isVisible().catch(() => false);
|
||||
const hasEmptyState = await emptyState.isVisible().catch(() => false);
|
||||
|
||||
if (hasTable) {
|
||||
// Verify table headers exist
|
||||
const headers = page.locator("thead th");
|
||||
await expect(headers).toHaveCount(6);
|
||||
|
||||
// Check for specific column headers
|
||||
await expect(
|
||||
page.getByRole("columnheader", { name: /date/i }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("columnheader", { name: /day.*phase/i }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("columnheader", { name: /decision/i }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("columnheader", { name: /body battery/i }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("columnheader", { name: /hrv/i }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("columnheader", { name: /intensity/i }),
|
||||
).toBeVisible();
|
||||
} else if (hasEmptyState) {
|
||||
// Empty state is valid when no history data
|
||||
await expect(emptyState).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test("shows empty state when no data", async ({ page }) => {
|
||||
// This test verifies empty state UI is present when applicable
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
const emptyState = page.getByText(/no history found/i);
|
||||
const table = page.locator("table tbody tr");
|
||||
|
||||
const hasRows = await table
|
||||
.first()
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
const hasEmptyState = await emptyState.isVisible().catch(() => false);
|
||||
|
||||
// Either has data rows OR shows empty state (both valid)
|
||||
expect(hasRows || hasEmptyState).toBe(true);
|
||||
});
|
||||
|
||||
test("has link back to dashboard", async ({ page }) => {
|
||||
const dashboardLink = page.getByRole("link", {
|
||||
name: /back to dashboard/i,
|
||||
});
|
||||
await expect(dashboardLink).toBeVisible();
|
||||
|
||||
// Click and verify navigation
|
||||
await dashboardLink.click();
|
||||
await expect(page).toHaveURL("/");
|
||||
});
|
||||
|
||||
test("shows entry count", async ({ page }) => {
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// Look for entries count text (e.g., "5 entries")
|
||||
const entriesText = page.getByText(/\d+ entries/);
|
||||
const hasEntriesText = await entriesText.isVisible().catch(() => false);
|
||||
|
||||
// May not be visible if no data, check for either count or empty state
|
||||
const emptyState = page.getByText(/no history found/i);
|
||||
const hasEmptyState = await emptyState.isVisible().catch(() => false);
|
||||
|
||||
expect(hasEntriesText || hasEmptyState).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user