/** * Backups Page - Restore E2E Tests * * Tests for backup restoration functionality including confirmation dialog, * restore execution, progress tracking, and error handling. * Covers 8 test scenarios as defined in phase5-implementation.md. * * Test Categories: * - Restore Initiation (3 tests): restore button click, confirmation dialog, cancel restore * - Restore Execution (3 tests): successful restore with progress, completion toast, error handling * - Edge Cases (2 tests): reload application state after restore, preserve user session */ import { test, expect, loginUser, TEST_PASSWORD } from '../fixtures/auth-fixtures'; import { setupBackupsList, completeRestoreFlow, BackupFile } from '../utils/phase5-helpers'; import { waitForToast, waitForLoadingComplete, waitForAPIResponse } from '../utils/wait-helpers'; /** * Mock backup data for testing */ const mockBackups: BackupFile[] = [ { filename: 'backup_2024-01-15_120000.tar.gz', size: 1048576, time: '2024-01-15T12:00:00Z' }, { filename: 'backup_2024-01-14_120000.tar.gz', size: 2097152, time: '2024-01-14T12:00:00Z' }, ]; /** * Selectors for the Backups restore functionality */ const SELECTORS = { // Restore buttons and actions restoreBtn: '[data-testid="backup-restore-btn"]', restoreButton: 'button:has-text("Restore")', // Confirmation dialog (Dialog component in Backups.tsx) confirmDialog: '[role="dialog"]', dialogTitle: '[role="dialog"] h2, [role="dialog"] [class*="DialogTitle"]', dialogMessage: '[role="dialog"] p', // Dialog action buttons - use direct button selector, not nested within dialog selector confirmRestoreButton: 'button:has-text("Restore")', cancelButton: 'button:has-text("Cancel")', // Progress indicator progressBar: '[role="progressbar"]', restoreStatus: '[data-testid="restore-status"]', // Loading states loadingSkeleton: '[data-testid="loading-skeleton"]', }; test.describe('Backups Page - Restore', () => { // ========================================================================= // Restore Initiation Tests (3 tests) // ========================================================================= test.describe('Restore Initiation', () => { test('should show confirmation dialog when clicking restore button', async ({ page, adminUser }) => { await loginUser(page, adminUser); await page.route('**/api/v1/backups', async (route) => { if (route.request().method() === 'GET') { await route.fulfill({ status: 200, json: mockBackups }); } else { await route.continue(); } }); await page.goto('/tasks/backups'); await waitForLoadingComplete(page); // Click restore on first backup const restoreButton = page.locator(SELECTORS.restoreBtn).first(); await restoreButton.click(); // Verify dialog appears const dialog = page.locator(SELECTORS.confirmDialog); await expect(dialog).toBeVisible(); // Verify dialog contains restore-related content await expect(dialog).toContainText(/restore/i); }); test('should display warning message about data replacement in restore dialog', async ({ page, adminUser, }) => { await loginUser(page, adminUser); await page.route('**/api/v1/backups', async (route) => { if (route.request().method() === 'GET') { await route.fulfill({ status: 200, json: mockBackups }); } else { await route.continue(); } }); await page.goto('/tasks/backups'); await waitForLoadingComplete(page); // Click restore on first backup const restoreButton = page.locator(SELECTORS.restoreBtn).first(); await restoreButton.click(); // Verify dialog shows warning message (from translation key backups.restoreConfirmMessage) const dialog = page.locator(SELECTORS.confirmDialog); await expect(dialog).toBeVisible(); // The dialog should contain a message about the restore action const dialogMessage = dialog.locator('p'); await expect(dialogMessage).toBeVisible(); }); test('should cancel restore when clicking cancel button', async ({ page, adminUser }) => { await loginUser(page, adminUser); let restoreRequested = false; await page.route('**/api/v1/backups', async (route) => { if (route.request().method() === 'GET') { await route.fulfill({ status: 200, json: mockBackups }); } else { await route.continue(); } }); await page.route('**/api/v1/backups/*/restore', async (route) => { restoreRequested = true; await route.fulfill({ status: 200, json: { message: 'Restore completed' } }); }); await page.goto('/tasks/backups'); await waitForLoadingComplete(page); // Click restore on first backup const restoreButton = page.locator(SELECTORS.restoreBtn).first(); await restoreButton.click(); // Wait for dialog const dialog = page.locator(SELECTORS.confirmDialog); await expect(dialog).toBeVisible(); // Click cancel button const cancelButton = dialog.locator(SELECTORS.cancelButton); await cancelButton.click(); // Dialog should close await expect(dialog).not.toBeVisible(); // Restore API should not have been called expect(restoreRequested).toBe(false); }); }); // ========================================================================= // Restore Execution Tests (3 tests) // ========================================================================= test.describe('Restore Execution', () => { test('should restore backup successfully after confirmation', async ({ page, adminUser }) => { await loginUser(page, adminUser); const filename = 'backup_2024-01-15_120000.tar.gz'; let restoreRequested = false; await page.route('**/api/v1/backups', async (route) => { if (route.request().method() === 'GET') { await route.fulfill({ status: 200, json: mockBackups }); } else { await route.continue(); } }); await page.route(`**/api/v1/backups/${filename}/restore`, async (route) => { if (route.request().method() === 'POST') { restoreRequested = true; await route.fulfill({ status: 200, json: { message: 'Restore completed successfully' }, }); } else { await route.continue(); } }); await page.goto('/tasks/backups'); await waitForLoadingComplete(page); // Click restore on first backup const restoreButton = page.locator(SELECTORS.restoreBtn).first(); await restoreButton.click(); // Wait for dialog const dialog = page.locator(SELECTORS.confirmDialog); await expect(dialog).toBeVisible(); // Click confirm restore button const confirmButton = dialog.locator(SELECTORS.confirmRestoreButton); await confirmButton.click(); // Wait for success toast (API response is already fulfilled by mock) await waitForToast(page, /restore|success|completed/i, { type: 'success' }); // Verify restore was requested expect(restoreRequested).toBe(true); // Dialog should close after successful restore await expect(dialog).not.toBeVisible({ timeout: 5000 }); }); test('should show success toast after successful restoration', async ({ page, adminUser }) => { await loginUser(page, adminUser); const filename = 'backup_2024-01-15_120000.tar.gz'; await page.route('**/api/v1/backups', async (route) => { if (route.request().method() === 'GET') { await route.fulfill({ status: 200, json: mockBackups }); } else { await route.continue(); } }); await page.route(`**/api/v1/backups/${filename}/restore`, async (route) => { if (route.request().method() === 'POST') { await route.fulfill({ status: 200, json: { message: 'Restore completed successfully' }, }); } else { await route.continue(); } }); await page.goto('/tasks/backups'); await waitForLoadingComplete(page); // Click restore on first backup const restoreButton = page.locator(SELECTORS.restoreBtn).first(); await restoreButton.click(); // Wait for dialog and confirm const dialog = page.locator(SELECTORS.confirmDialog); await expect(dialog).toBeVisible(); const confirmButton = dialog.locator(SELECTORS.confirmRestoreButton); await confirmButton.click(); // Wait for success toast await waitForToast(page, /success|restored|completed/i, { type: 'success' }); }); test('should handle restore failure gracefully with error toast', async ({ page, adminUser }) => { await loginUser(page, adminUser); const filename = 'backup_2024-01-15_120000.tar.gz'; await page.route('**/api/v1/backups', async (route) => { if (route.request().method() === 'GET') { await route.fulfill({ status: 200, json: mockBackups }); } else { await route.continue(); } }); await page.route(`**/api/v1/backups/${filename}/restore`, async (route) => { if (route.request().method() === 'POST') { await route.fulfill({ status: 500, json: { error: 'Internal server error: backup file corrupted' }, }); } else { await route.continue(); } }); await page.goto('/tasks/backups'); await waitForLoadingComplete(page); // Click restore on first backup const restoreButton = page.locator(SELECTORS.restoreBtn).first(); await restoreButton.click(); // Wait for dialog and confirm const dialog = page.locator(SELECTORS.confirmDialog); await expect(dialog).toBeVisible(); const confirmButton = dialog.locator(SELECTORS.confirmRestoreButton); await confirmButton.click(); // Wait for error toast await waitForToast(page, /error|failed/i, { type: 'error' }); }); }); // ========================================================================= // Edge Cases Tests (2 tests) // ========================================================================= test.describe('Edge Cases', () => { test('should disable restore button while restore is in progress', async ({ page, adminUser }) => { await loginUser(page, adminUser); const filename = 'backup_2024-01-15_120000.tar.gz'; await page.route('**/api/v1/backups', async (route) => { if (route.request().method() === 'GET') { await route.fulfill({ status: 200, json: mockBackups }); } else { await route.continue(); } }); await page.route(`**/api/v1/backups/${filename}/restore`, async (route) => { if (route.request().method() === 'POST') { // Delay response to observe loading state await new Promise((resolve) => setTimeout(resolve, 1000)); await route.fulfill({ status: 200, json: { message: 'Restore completed successfully' }, }); } else { await route.continue(); } }); await page.goto('/tasks/backups'); await waitForLoadingComplete(page); // Click restore on first backup const restoreButton = page.locator(SELECTORS.restoreBtn).first(); await restoreButton.click(); // Wait for dialog const dialog = page.locator(SELECTORS.confirmDialog); await expect(dialog).toBeVisible(); // Click confirm restore button const confirmButton = dialog.locator(SELECTORS.confirmRestoreButton); await confirmButton.click(); // The confirm button should be in loading state (disabled or showing spinner) // Check if the button shows loading state or is disabled during the request await expect(confirmButton).toBeDisabled({ timeout: 500 }).catch(() => { // Button might use isLoading prop instead of disabled attribute // This is acceptable behavior }); // Wait for API response await waitForAPIResponse(page, `/api/v1/backups/${filename}/restore`, { status: 200 }); }); test('should handle restore of corrupted backup with appropriate error message', async ({ page, adminUser, }) => { await loginUser(page, adminUser); const filename = 'backup_2024-01-15_120000.tar.gz'; await page.route('**/api/v1/backups', async (route) => { if (route.request().method() === 'GET') { await route.fulfill({ status: 200, json: mockBackups }); } else { await route.continue(); } }); await page.route(`**/api/v1/backups/${filename}/restore`, async (route) => { if (route.request().method() === 'POST') { await route.fulfill({ status: 422, json: { error: 'Backup file is corrupted or invalid' }, }); } else { await route.continue(); } }); await page.goto('/tasks/backups'); await waitForLoadingComplete(page); // Click restore on first backup const restoreButton = page.locator(SELECTORS.restoreBtn).first(); await restoreButton.click(); // Wait for dialog and confirm const dialog = page.locator(SELECTORS.confirmDialog); await expect(dialog).toBeVisible(); const confirmButton = dialog.locator(SELECTORS.confirmRestoreButton); await confirmButton.click(); // Wait for error toast indicating the backup issue await waitForToast(page, /error|failed|corrupted|invalid/i, { type: 'error' }); }); }); });