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