Add 8 new E2E tests for accessibility and error recovery
All checks were successful
Deploy / deploy (push) Successful in 1m40s

- calendar.spec.ts: +4 accessibility tests (ARIA role, aria-labels, keyboard navigation, accessible nav buttons)
- settings.spec.ts: +1 error recovery test (retry after failed save)
- mobile.spec.ts: +3 calendar mobile tests (rendering, touch targets, navigation)

Total E2E tests: 190 → 198

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-13 18:48:58 +00:00
parent f3d7f8bd35
commit b6f139883f
4 changed files with 336 additions and 8 deletions

View File

@@ -633,4 +633,131 @@ test.describe("calendar", () => {
}
});
});
test.describe("accessibility", () => {
test.beforeEach(async ({ page }) => {
const email = process.env.TEST_USER_EMAIL;
const password = process.env.TEST_USER_PASSWORD;
if (!email || !password) {
test.skip();
return;
}
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();
await page.waitForURL("/", { timeout: 10000 });
await page.goto("/calendar");
await page.waitForLoadState("networkidle");
});
test("calendar grid has proper ARIA role and label", async ({ page }) => {
// Calendar should have role="grid" per WAI-ARIA calendar pattern
const calendarGrid = page.getByRole("grid", { name: /calendar/i });
await expect(calendarGrid).toBeVisible();
});
test("day cells have descriptive aria-labels", async ({ page }) => {
// Day buttons should have descriptive aria-labels including date and phase info
const dayButtons = page.locator("button[data-day]");
const hasDayButtons = await dayButtons
.first()
.isVisible()
.catch(() => false);
if (!hasDayButtons) {
test.skip();
return;
}
// Get the first visible day button's aria-label
const firstDayButton = dayButtons.first();
const ariaLabel = await firstDayButton.getAttribute("aria-label");
// Aria-label should contain date information (month and year)
expect(ariaLabel).toMatch(
/january|february|march|april|may|june|july|august|september|october|november|december/i,
);
expect(ariaLabel).toMatch(/\d{4}/); // Should contain year
});
test("keyboard navigation with arrow keys works", async ({ page }) => {
// Focus on a day button in the calendar grid
const dayButtons = page.locator("button[data-day]");
const hasDayButtons = await dayButtons
.first()
.isVisible()
.catch(() => false);
if (!hasDayButtons) {
test.skip();
return;
}
// Click a day button to focus it
const calendarGrid = page.getByRole("grid", { name: /calendar/i });
const hasGrid = await calendarGrid.isVisible().catch(() => false);
if (!hasGrid) {
test.skip();
return;
}
// Focus the grid and press Tab to focus first day
await calendarGrid.focus();
await page.keyboard.press("Tab");
// Get currently focused element
const focusedBefore = await page.evaluate(() => {
const el = document.activeElement;
return el ? el.getAttribute("data-day") : null;
});
// Press ArrowRight to move to next day
await page.keyboard.press("ArrowRight");
// Get new focused element
const focusedAfter = await page.evaluate(() => {
const el = document.activeElement;
return el ? el.getAttribute("data-day") : null;
});
// If both values exist, verify navigation occurred
if (focusedBefore && focusedAfter) {
expect(focusedAfter).not.toBe(focusedBefore);
}
});
test("navigation buttons have accessible labels", async ({ page }) => {
// Previous and next month buttons should have aria-labels
const prevButton = page.getByRole("button", { name: /previous month/i });
const nextButton = page.getByRole("button", { name: /next month/i });
const hasPrev = await prevButton.isVisible().catch(() => false);
const hasNext = await nextButton.isVisible().catch(() => false);
// At least one navigation button should be accessible
expect(hasPrev || hasNext).toBe(true);
if (hasPrev) {
await expect(prevButton).toBeVisible();
}
if (hasNext) {
await expect(nextButton).toBeVisible();
}
});
});
});