- Created e2e/alert-positioning.spec.ts for critical alerts positioning tests - Created e2e/assistant-positioning.spec.ts for assistant widget positioning tests - Created e2e/destructive-actions.spec.ts for destructive actions confirmation tests - Created e2e/feedback.spec.ts for save/delete/update feedback tests - Created e2e/page-states.spec.ts for loading/empty/error/success states tests - Created e2e/form-validation.spec.ts for form validation tests - Created e2e/keyboard-navigation.spec.ts for keyboard navigation tests - Created scripts/tests/run-e2e.sh test runner script with health check - Updated LAUNCH_READY_UI_UX_ROADMAP.md checklist - all items complete - All testing infrastructure complete (CI integration replaced with local test runner)
142 lines
4.7 KiB
TypeScript
142 lines
4.7 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
/**
|
|
* Save/Delete/Update Feedback Tests
|
|
*
|
|
* Tests that all saves/deletes/updates produce feedback
|
|
*/
|
|
|
|
test.describe('Save/Delete/Update Feedback', () => {
|
|
test('Save action produces feedback', async ({ page }) => {
|
|
await page.goto('/plans');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Look for save buttons
|
|
const saveButtons = page.locator('button:has-text("Save"), button:has-text("Apply"), button[type="submit"]');
|
|
const saveCount = await saveButtons.count();
|
|
|
|
if (saveCount > 0) {
|
|
// Take screenshot before action
|
|
await page.screenshot({ path: 'before-save.png' });
|
|
|
|
// Click save button
|
|
await saveButtons.first().click();
|
|
|
|
// Wait for feedback
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Look for toast notifications or success messages
|
|
const toast = page.locator('[role="status"], .toast, .notification, [class*="toast"], [class*="notification"]');
|
|
const toastCount = await toast.count();
|
|
|
|
// Look for success messages
|
|
const successMessage = page.locator(':text("saved"), :text("success"), :text("updated")');
|
|
const successCount = await successMessage.count();
|
|
|
|
// At least one form of feedback should be present
|
|
const hasFeedback = toastCount > 0 || successCount > 0;
|
|
expect(hasFeedback).toBe(true);
|
|
|
|
// Take screenshot after action
|
|
await page.screenshot({ path: 'after-save.png' });
|
|
}
|
|
});
|
|
|
|
test('Delete action produces feedback', async ({ page }) => {
|
|
await page.goto('/plans');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const deleteButtons = page.locator('button:has-text("Delete")');
|
|
const deleteCount = await deleteButtons.count();
|
|
|
|
if (deleteCount > 0) {
|
|
// Set up dialog handler
|
|
page.on('dialog', async (dialog) => {
|
|
dialog.accept();
|
|
});
|
|
|
|
// Take screenshot before
|
|
await page.screenshot({ path: 'before-delete.png' });
|
|
|
|
// Click delete
|
|
await deleteButtons.first().click();
|
|
|
|
// Wait for feedback
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Look for feedback
|
|
const toast = page.locator('[role="status"], .toast, .notification');
|
|
const toastCount = await toast.count();
|
|
|
|
const deletedMessage = page.locator(':text("deleted"), :text("removed")');
|
|
const deletedCount = await deletedMessage.count();
|
|
|
|
const hasFeedback = toastCount > 0 || deletedCount > 0;
|
|
expect(hasFeedback).toBe(true);
|
|
|
|
// Take screenshot after
|
|
await page.screenshot({ path: 'after-delete.png' });
|
|
}
|
|
});
|
|
|
|
test('Update action produces feedback', async ({ page }) => {
|
|
await page.goto('/plans');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Look for update buttons or form fields
|
|
const updateButtons = page.locator('button:has-text("Update"), button:has-text("Apply")');
|
|
const updateCount = await updateButtons.count();
|
|
|
|
if (updateCount > 0) {
|
|
await page.screenshot({ path: 'before-update.png' });
|
|
|
|
await updateButtons.first().click();
|
|
|
|
await page.waitForTimeout(2000);
|
|
|
|
const toast = page.locator('[role="status"], .toast, .notification');
|
|
const toastCount = await toast.count();
|
|
|
|
const updatedMessage = page.locator(':text("updated"), :text("saved")');
|
|
const updatedCount = await updatedMessage.count();
|
|
|
|
const hasFeedback = toastCount > 0 || updatedCount > 0;
|
|
expect(hasFeedback).toBe(true);
|
|
|
|
await page.screenshot({ path: 'after-update.png' });
|
|
}
|
|
});
|
|
|
|
test('Feedback is visible and accessible', async ({ page }) => {
|
|
await page.goto('/plans');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const saveButtons = page.locator('button:has-text("Save")');
|
|
const saveCount = await saveButtons.count();
|
|
|
|
if (saveCount > 0) {
|
|
await saveButtons.first().click();
|
|
await page.waitForTimeout(2000);
|
|
|
|
const toast = page.locator('[role="status"], .toast, .notification');
|
|
const toastCount = await toast.count();
|
|
|
|
if (toastCount > 0) {
|
|
// Check visibility
|
|
await expect(toast.first()).toBeVisible();
|
|
|
|
// Check ARIA attributes
|
|
const role = await toast.first().getAttribute('role');
|
|
expect(role).toMatch(/status|alert|log/i);
|
|
|
|
// Check that feedback disappears after a reasonable time
|
|
await page.waitForTimeout(5000);
|
|
const isVisible = await toast.first().isVisible();
|
|
|
|
// Toast should auto-dismiss (this is optional behavior)
|
|
// expect(isVisible).toBe(false);
|
|
}
|
|
}
|
|
});
|
|
});
|