/** * Setup Page E2E Tests * * End-to-end tests for the initial configuration/setup page * Tests form validation, password strength, submission, and all configuration sections */ import { expect, test } from '@playwright/test'; test.describe('Setup Page - Initial Load', () => { test('should load setup page when not configured', async ({ page }) => { // Note: This test assumes auth is not configured // In actual testing, you may need to reset the auth state await page.goto('/setup'); await expect(page).toHaveTitle(/Setup/i); await expect(page.locator('h1')).toContainText('Welcome to AniWorld Manager'); }); test('should display all configuration sections', async ({ page }) => { await page.goto('/setup'); // Check for all major sections await expect(page.locator('text=General Settings')).toBeVisible(); await expect(page.locator('text=Security Settings')).toBeVisible(); await expect(page.locator('text=Scheduler Settings')).toBeVisible(); await expect(page.locator('text=Logging Settings')).toBeVisible(); await expect(page.locator('text=Backup Settings')).toBeVisible(); await expect(page.locator('text=NFO Settings')).toBeVisible(); }); test('should display setup form with all required fields', async ({ page }) => { await page.goto('/setup'); // Check required fields exist await expect(page.locator('#name')).toBeVisible(); await expect(page.locator('#data_dir')).toBeVisible(); await expect(page.locator('#directory')).toBeVisible(); await expect(page.locator('#password')).toBeVisible(); await expect(page.locator('#confirm-password')).toBeVisible(); }); test('should have submit button', async ({ page }) => { await page.goto('/setup'); const submitBtn = page.locator('button[type="submit"]'); await expect(submitBtn).toBeVisible(); await expect(submitBtn).toContainText(/Complete Setup/i); }); }); test.describe('Setup Page - Form Validation', () => { test.beforeEach(async ({ page }) => { await page.goto('/setup'); }); test('should require master password', async ({ page }) => { // Try to submit without password const submitBtn = page.locator('button[type="submit"]'); // Fill other required fields await page.fill('#name', 'Test App'); await page.fill('#directory', '/test/anime'); // Leave password empty and try to submit await submitBtn.click(); // Form should not submit (HTML5 validation) const validationMessage = await page.locator('#password').evaluate( (el: HTMLInputElement) => el.validationMessage ); expect(validationMessage).toBeTruthy(); }); test('should enforce minimum password length', async ({ page }) => { const passwordInput = page.locator('#password'); // Try short password await passwordInput.fill('short'); const validationMessage = await passwordInput.evaluate( (el: HTMLInputElement) => el.validationMessage ); expect(validationMessage).toContain('at least 8'); }); test('should show error when passwords do not match', async ({ page }) => { await page.fill('#password', 'ValidPassword123!'); await page.fill('#confirm-password', 'DifferentPassword123!'); // The form should validate on submit or blur await page.locator('#confirm-password').blur(); // Look for error message (may vary based on implementation) // This is a flexible check await page.waitForTimeout(500); }); test('should require anime directory field', async ({ page }) => { const directoryInput = page.locator('#directory'); await directoryInput.clear(); await page.locator('button[type="submit"]').click(); const validationMessage = await directoryInput.evaluate( (el: HTMLInputElement) => el.validationMessage ); expect(validationMessage).toBeTruthy(); }); test('should validate scheduler interval is positive', async ({ page }) => { const intervalInput = page.locator('#scheduler_interval_minutes'); await intervalInput.fill('0'); const validationMessage = await intervalInput.evaluate( (el: HTMLInputElement) => el.validationMessage ); expect(validationMessage).toBeTruthy(); }); }); test.describe('Setup Page - Password Strength Indicator', () => { test.beforeEach(async ({ page }) => { await page.goto('/setup'); }); test('should show password strength indicator', async ({ page }) => { const strengthText = page.locator('#strength-text'); await expect(strengthText).toBeVisible(); }); test('should update strength indicator as password is typed', async ({ page }) => { const passwordInput = page.locator('#password'); const strengthText = page.locator('#strength-text'); // Type weak password await passwordInput.fill('abc'); await page.waitForTimeout(200); const initialText = await strengthText.textContent(); expect(initialText).toBeTruthy(); // Type stronger password await passwordInput.fill('StrongPassword123!'); await page.waitForTimeout(200); const updatedText = await strengthText.textContent(); expect(updatedText).not.toBe(initialText); }); test('should show weak strength for simple passwords', async ({ page }) => { await page.fill('#password', 'password'); await page.waitForTimeout(200); const strengthText = await page.locator('#strength-text').textContent(); expect(strengthText?.toLowerCase()).toContain('weak'); }); test('should show strong strength for complex passwords', async ({ page }) => { await page.fill('#password', 'MyVeryStrong@Password123!'); await page.waitForTimeout(200); const strengthText = await page.locator('#strength-text').textContent(); // Check for 'strong' or 'very strong' expect(strengthText?.toLowerCase()).toMatch(/(strong|excellent)/); }); test('should display strength bars', async ({ page }) => { // Check for strength bar elements await expect(page.locator('#strength-1')).toBeVisible(); await expect(page.locator('#strength-2')).toBeVisible(); await expect(page.locator('#strength-3')).toBeVisible(); await expect(page.locator('#strength-4')).toBeVisible(); }); }); test.describe('Setup Page - Password Toggle', () => { test.beforeEach(async ({ page }) => { await page.goto('/setup'); }); test('should toggle password visibility', async ({ page }) => { const passwordInput = page.locator('#password'); const toggleBtn = page.locator('#password-toggle'); // Password should be hidden by default await expect(passwordInput).toHaveAttribute('type', 'password'); // Click toggle await toggleBtn.click(); // Should now be visible await expect(passwordInput).toHaveAttribute('type', 'text'); // Click again to hide await toggleBtn.click(); await expect(passwordInput).toHaveAttribute('type', 'password'); }); test('should toggle confirm password visibility', async ({ page }) => { const confirmInput = page.locator('#confirm-password'); const toggleBtn = page.locator('#confirm-password-toggle'); await expect(confirmInput).toHaveAttribute('type', 'password'); await toggleBtn.click(); await expect(confirmInput).toHaveAttribute('type', 'text'); }); test('should update toggle icon when clicked', async ({ page }) => { const toggleBtn = page.locator('#password-toggle'); const icon = toggleBtn.locator('i'); // Initially should show 'eye' icon await expect(icon).toHaveClass(/fa-eye/); // Click to show password await toggleBtn.click(); // Should show 'eye-slash' icon await expect(icon).toHaveClass(/fa-eye-slash/); }); }); test.describe('Setup Page - Configuration Sections', () => { test.beforeEach(async ({ page }) => { await page.goto('/setup'); }); test('should have general settings fields with default values', async ({ page }) => { const nameInput = page.locator('#name'); const dataDirInput = page.locator('#data_dir'); // Check default values await expect(nameInput).toHaveValue('Aniworld'); await expect(dataDirInput).toHaveValue('data'); }); test('should have scheduler settings with checkbox', async ({ page }) => { const enabledCheckbox = page.locator('#scheduler_enabled'); const intervalInput = page.locator('#scheduler_interval_minutes'); await expect(enabledCheckbox).toBeVisible(); await expect(intervalInput).toBeVisible(); // Should be enabled by default await expect(enabledCheckbox).toBeChecked(); await expect(intervalInput).toHaveValue('60'); }); test('should allow toggling scheduler enabled', async ({ page }) => { const checkbox = page.locator('#scheduler_enabled'); await expect(checkbox).toBeChecked(); await checkbox.uncheck(); await expect(checkbox).not.toBeChecked(); await checkbox.check(); await expect(checkbox).toBeChecked(); }); test('should have logging settings fields', async ({ page }) => { // Look for logging level field (exact field name may vary) const loggingSection = page.locator('text=Logging Settings').locator('..'); await expect(loggingSection).toBeVisible(); }); test('should have backup settings fields', async ({ page }) => { const backupSection = page.locator('text=Backup Settings').locator('..'); await expect(backupSection).toBeVisible(); }); test('should have NFO settings fields', async ({ page }) => { const nfoSection = page.locator('text=NFO Settings').locator('..'); await expect(nfoSection).toBeVisible(); }); }); test.describe('Setup Page - Form Submission', () => { test.beforeEach(async ({ page }) => { await page.goto('/setup'); }); test('should submit form with valid data', async ({ page }) => { // Fill required fields await page.fill('#name', 'Test Aniworld'); await page.fill('#data_dir', 'test_data'); await page.fill('#directory', '/test/anime/directory'); await page.fill('#password', 'TestPassword123!'); await page.fill('#confirm-password', 'TestPassword123!'); // Setup request interception to check the submission const responsePromise = page.waitForResponse( response => response.url().includes('/api/setup') && response.status() === 201 ); // Submit form await page.click('button[type="submit"]'); // Wait for response (or timeout if endpoint doesn't exist yet) try { const response = await responsePromise; expect(response.status()).toBe(201); } catch (error) { // Endpoint might not exist in test environment console.log('Setup endpoint not available in test'); } }); test('should show loading state during submission', async ({ page }) => { // Fill form await page.fill('#password', 'TestPassword123!'); await page.fill('#confirm-password', 'TestPassword123!'); await page.fill('#directory', '/test/anime'); // Click submit await page.click('button[type="submit"]'); // Button should show loading state const submitBtn = page.locator('button[type="submit"]'); // Check for loading indicator (spinner, disabled state, or text change) await page.waitForTimeout(100); const isDisabled = await submitBtn.isDisabled(); expect(isDisabled).toBe(true); }); test('should disable submit button while processing', async ({ page }) => { await page.fill('#password', 'TestPassword123!'); await page.fill('#confirm-password', 'TestPassword123!'); await page.fill('#directory', '/test/anime'); const submitBtn = page.locator('button[type="submit"]'); await submitBtn.click(); await page.waitForTimeout(50); await expect(submitBtn).toBeDisabled(); }); test('should handle form submission errors gracefully', async ({ page }) => { // Fill with potentially invalid data await page.fill('#password', 'weak'); await page.fill('#confirm-password', 'weak'); await page.fill('#directory', ''); await page.click('button[type="submit"]'); // Form validation should prevent submission await page.waitForTimeout(500); // Should still be on setup page await expect(page).toHaveURL(/\/setup/); }); }); test.describe('Setup Page - Theme Integration', () => { test('should have theme toggle button', async ({ page }) => { await page.goto('/setup'); const themeToggle = page.locator('#theme-toggle'); await expect(themeToggle).toBeVisible(); }); test('should toggle theme on setup page', async ({ page }) => { await page.goto('/setup'); const initialTheme = await page.evaluate(() => document.documentElement.getAttribute('data-theme') ); await page.click('#theme-toggle'); const newTheme = await page.evaluate(() => document.documentElement.getAttribute('data-theme') ); expect(newTheme).not.toBe(initialTheme); }); test('should persist theme choice during setup', async ({ page }) => { await page.goto('/setup'); // Switch to dark theme await page.click('#theme-toggle'); const theme = await page.evaluate(() => document.documentElement.getAttribute('data-theme') ); expect(theme).toBe('dark'); // Fill and submit form (theme should remain) await page.fill('#password', 'TestPassword123!'); const themeAfterInput = await page.evaluate(() => document.documentElement.getAttribute('data-theme') ); expect(themeAfterInput).toBe('dark'); }); }); test.describe('Setup Page - Accessibility', () => { test.beforeEach(async ({ page }) => { await page.goto('/setup'); }); test('should have accessible form labels', async ({ page }) => { const nameLabel = page.locator('label[for="name"]'); const passwordLabel = page.locator('label[for="password"]'); await expect(nameLabel).toBeVisible(); await expect(passwordLabel).toBeVisible(); }); test('should support keyboard navigation through form', async ({ page }) => { // Start at first input await page.locator('#name').focus(); // Tab through fields await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); // Should be able to reach submit button await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); }); test('should have proper ARIA attributes', async ({ page }) => { const form = page.locator('#setup-form'); await expect(form).toBeVisible(); // Check for required field indicators const requiredInputs = page.locator('input[required]'); const count = await requiredInputs.count(); expect(count).toBeGreaterThan(0); }); }); test.describe('Setup Page - Edge Cases', () => { test.beforeEach(async ({ page }) => { await page.goto('/setup'); }); test('should handle very long input values', async ({ page }) => { const longValue = 'a'.repeat(1000); await page.fill('#name', longValue); const value = await page.locator('#name').inputValue(); expect(value.length).toBeGreaterThan(0); }); test('should handle special characters in inputs', async ({ page }) => { await page.fill('#name', 'Test<>"/&Name'); await page.fill('#directory', '/path/with spaces/and-dashes'); // Should accept the values const nameValue = await page.locator('#name').inputValue(); expect(nameValue).toContain('Test'); }); test('should handle rapid form interactions', async ({ page }) => { // Rapidly fill and clear fields for (let i = 0; i < 5; i++) { await page.fill('#password', `password${i}`); await page.fill('#password', ''); } // Form should remain stable await expect(page.locator('#password')).toBeVisible(); }); test('should handle clicking submit multiple times', async ({ page }) => { await page.fill('#password', 'TestPassword123!'); await page.fill('#confirm-password', 'TestPassword123!'); await page.fill('#directory', '/test/anime'); const submitBtn = page.locator('button[type="submit"]'); // Click multiple times rapidly await submitBtn.click(); await submitBtn.click(); await submitBtn.click(); // Button should be disabled after first click await expect(submitBtn).toBeDisabled(); }); });