482 lines
17 KiB
JavaScript
482 lines
17 KiB
JavaScript
/**
|
|
* 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.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');
|
|
|
|
await expect(enabledCheckbox).toBeVisible();
|
|
|
|
// Should be enabled by default
|
|
await expect(enabledCheckbox).toBeChecked();
|
|
});
|
|
|
|
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();
|
|
});
|
|
});
|