Add 4 new E2E tests for mobile viewport behavior
All checks were successful
Deploy / deploy (push) Successful in 2m28s
All checks were successful
Deploy / deploy (push) Successful in 2m28s
Tests mobile responsiveness including: - Login page renders correctly on mobile viewport - Dashboard displays correctly on mobile viewport - Dashboard uses single-column layout on mobile (< 768px) - Navigation elements are interactive on mobile Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ This file is maintained by Ralph. Run `./ralph-sandbox.sh plan 3` to generate ta
|
|||||||
|
|
||||||
## Current Status: Feature Complete
|
## Current Status: Feature Complete
|
||||||
|
|
||||||
**Test Coverage:** 1014 unit tests (51 files) + 186 E2E tests (12 files) = 1200 total tests
|
**Test Coverage:** 1014 unit tests (51 files) + 190 E2E tests (13 files) = 1204 total tests
|
||||||
|
|
||||||
All P0-P5 items are complete. The project is feature complete.
|
All P0-P5 items are complete. The project is feature complete.
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ All P0-P5 items are complete. The project is feature complete.
|
|||||||
| PeriodDateModal | 22 | Period input modal |
|
| PeriodDateModal | 22 | Period input modal |
|
||||||
| Skeletons | 29 | Loading states with shimmer |
|
| Skeletons | 29 | Loading states with shimmer |
|
||||||
|
|
||||||
### E2E Tests (12 files, 186 tests)
|
### E2E Tests (13 files, 190 tests)
|
||||||
| File | Tests | Coverage |
|
| File | Tests | Coverage |
|
||||||
|------|-------|----------|
|
|------|-------|----------|
|
||||||
| smoke.spec.ts | 3 | Basic app functionality |
|
| smoke.spec.ts | 3 | Basic app functionality |
|
||||||
@@ -112,6 +112,7 @@ All P0-P5 items are complete. The project is feature complete.
|
|||||||
| history.spec.ts | 7 | History page |
|
| history.spec.ts | 7 | History page |
|
||||||
| plan.spec.ts | 7 | Plan page |
|
| plan.spec.ts | 7 | Plan page |
|
||||||
| health.spec.ts | 3 | Health/observability |
|
| health.spec.ts | 3 | Health/observability |
|
||||||
|
| mobile.spec.ts | 4 | Mobile viewport behavior, responsive layout |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -124,7 +125,6 @@ These are optional enhancements to improve E2E coverage. Not required for featur
|
|||||||
|------|-------|-------------|
|
|------|-------|-------------|
|
||||||
| notifications.spec.ts | 3 | Notification preferences |
|
| notifications.spec.ts | 3 | Notification preferences |
|
||||||
| dark-mode.spec.ts | 2 | System preference detection |
|
| dark-mode.spec.ts | 2 | System preference detection |
|
||||||
| mobile.spec.ts | 4 | Mobile viewport behavior |
|
|
||||||
|
|
||||||
### Existing File Extensions
|
### Existing File Extensions
|
||||||
| File | Additional Tests | Focus Area |
|
| File | Additional Tests | Focus Area |
|
||||||
@@ -148,6 +148,7 @@ These are optional enhancements to improve E2E coverage. Not required for featur
|
|||||||
|
|
||||||
## Revision History
|
## Revision History
|
||||||
|
|
||||||
|
- 2026-01-13: Added mobile.spec.ts with 4 E2E tests (mobile viewport behavior, responsive layout)
|
||||||
- 2026-01-13: Added 6 auth E2E tests (OIDC button display, loading states, session persistence across pages/refresh)
|
- 2026-01-13: Added 6 auth E2E tests (OIDC button display, loading states, session persistence across pages/refresh)
|
||||||
- 2026-01-13: Added 5 settings persistence E2E tests (notification time, timezone, multi-field persistence)
|
- 2026-01-13: Added 5 settings persistence E2E tests (notification time, timezone, multi-field persistence)
|
||||||
- 2026-01-13: Added 5 period-logging E2E tests (modal flow, future date restriction, edit/delete flows)
|
- 2026-01-13: Added 5 period-logging E2E tests (modal flow, future date restriction, edit/delete flows)
|
||||||
|
|||||||
132
e2e/mobile.spec.ts
Normal file
132
e2e/mobile.spec.ts
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// ABOUTME: E2E tests for mobile viewport behavior and responsive design.
|
||||||
|
// ABOUTME: Tests that the dashboard displays correctly on mobile-sized screens.
|
||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
// Mobile viewport: iPhone SE/8 (375x667)
|
||||||
|
const MOBILE_VIEWPORT = { width: 375, height: 667 };
|
||||||
|
|
||||||
|
test.describe("mobile viewport", () => {
|
||||||
|
test.describe("unauthenticated", () => {
|
||||||
|
test("login page renders correctly on mobile viewport", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.setViewportSize(MOBILE_VIEWPORT);
|
||||||
|
await page.goto("/login");
|
||||||
|
|
||||||
|
// Login form should be visible - heading is "PhaseFlow"
|
||||||
|
const heading = page.getByRole("heading", { name: /phaseflow/i });
|
||||||
|
await expect(heading).toBeVisible();
|
||||||
|
|
||||||
|
// Email input should be visible
|
||||||
|
const emailInput = page.getByLabel(/email/i);
|
||||||
|
await expect(emailInput).toBeVisible();
|
||||||
|
|
||||||
|
// Viewport width should be mobile
|
||||||
|
const viewportSize = page.viewportSize();
|
||||||
|
expect(viewportSize?.width).toBe(375);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("authenticated", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
const email = process.env.TEST_USER_EMAIL;
|
||||||
|
const password = process.env.TEST_USER_PASSWORD;
|
||||||
|
|
||||||
|
if (!email || !password) {
|
||||||
|
test.skip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set mobile viewport before navigating
|
||||||
|
await page.setViewportSize(MOBILE_VIEWPORT);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
await page.waitForURL("/", { timeout: 10000 });
|
||||||
|
await page.waitForLoadState("networkidle");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("dashboard displays correctly on mobile viewport", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Verify viewport is mobile size
|
||||||
|
const viewportSize = page.viewportSize();
|
||||||
|
expect(viewportSize?.width).toBe(375);
|
||||||
|
|
||||||
|
// Header should be visible
|
||||||
|
const header = page.getByRole("heading", { name: /phaseflow/i });
|
||||||
|
await expect(header).toBeVisible();
|
||||||
|
|
||||||
|
// Settings link should be visible
|
||||||
|
const settingsLink = page.getByRole("link", { name: /settings/i });
|
||||||
|
await expect(settingsLink).toBeVisible();
|
||||||
|
|
||||||
|
// Decision card should be visible
|
||||||
|
const decisionCard = page
|
||||||
|
.locator('[data-testid="decision-card"]')
|
||||||
|
.or(page.getByText(/rest|gentle|light|reduced|train/i).first());
|
||||||
|
await expect(decisionCard).toBeVisible();
|
||||||
|
|
||||||
|
// Data panel should be visible
|
||||||
|
const dataPanel = page.getByText(/body battery|hrv/i).first();
|
||||||
|
await expect(dataPanel).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("dashboard uses single-column layout on mobile", async ({ page }) => {
|
||||||
|
// On mobile (<768px), the Data Panel and Nutrition Panel should stack vertically
|
||||||
|
// This is controlled by the md:grid-cols-2 class
|
||||||
|
|
||||||
|
// Find the grid container that holds Data Panel and Nutrition Panel
|
||||||
|
// It should NOT have two-column grid on mobile (should be single column)
|
||||||
|
const gridContainer = page.locator(".grid.gap-4").first();
|
||||||
|
const containerExists = await gridContainer
|
||||||
|
.isVisible()
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
|
if (containerExists) {
|
||||||
|
// Get the computed grid template columns
|
||||||
|
const gridTemplateColumns = await gridContainer.evaluate((el) => {
|
||||||
|
return window.getComputedStyle(el).gridTemplateColumns;
|
||||||
|
});
|
||||||
|
|
||||||
|
// On mobile (375px < 768px), should NOT be two columns
|
||||||
|
// Single column would be "none" or a single value like "1fr"
|
||||||
|
// Two columns would be something like "1fr 1fr" or "repeat(2, 1fr)"
|
||||||
|
const isTwoColumn = gridTemplateColumns.includes(" ");
|
||||||
|
expect(isTwoColumn).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("navigation elements are interactive on mobile", async ({ page }) => {
|
||||||
|
// Settings link should be clickable
|
||||||
|
const settingsLink = page.getByRole("link", { name: /settings/i });
|
||||||
|
await expect(settingsLink).toBeVisible();
|
||||||
|
|
||||||
|
// Click settings and verify navigation
|
||||||
|
await settingsLink.click();
|
||||||
|
await expect(page).toHaveURL(/\/settings/);
|
||||||
|
|
||||||
|
// Back button should work to return to dashboard
|
||||||
|
const backLink = page.getByRole("link", { name: /back|dashboard|home/i });
|
||||||
|
await expect(backLink).toBeVisible();
|
||||||
|
await backLink.click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL("/");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user