import { test, expect } from '@playwright/test'; test.describe('Form Validation and Error Handling', () => { test.describe('Site Form Validation', () => { test('shows validation error for empty site name', async ({ page }) => { await page.goto('/sites/new'); if ((await page.url()).includes('/sites/new')) { // Try to submit empty form const submitButton = page.locator('button[type="submit"]').first(); if (await submitButton.isVisible({ timeout: 2000 }).catch(() => false)) { await submitButton.click(); await page.waitForTimeout(500); // Look for error message const errorMessage = page.locator( '.error, [role="alert"], :has-text("required"), :has-text("can\'t be blank")' ).first(); if (await errorMessage.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(errorMessage).toBeVisible(); } } } }); test('shows field-level validation errors', async ({ page }) => { await page.goto('/sites/new'); if ((await page.url()).includes('/sites/new')) { const nameField = page.locator('input[name*="name"]').first(); if (await nameField.isVisible({ timeout: 2000 }).catch(() => false)) { // Focus and blur to trigger validation await nameField.focus(); await nameField.blur(); await page.waitForTimeout(300); // Look for field error const fieldError = page.locator('.error, [role="alert"]').first(); if (await fieldError.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(fieldError).toBeVisible(); } } } }); }); test.describe('Device Form Validation', () => { test('validates IP address format', async ({ page }) => { await page.goto('/devices/new'); if ((await page.url()).includes('/devices/new')) { const ipField = page.locator('input[name*="ip"], input[name*="host"]').first(); if (await ipField.isVisible({ timeout: 2000 }).catch(() => false)) { // Enter invalid IP await ipField.fill('invalid-ip'); await ipField.blur(); await page.waitForTimeout(300); // Look for validation error const error = page.locator( ':has-text("invalid"), :has-text("format"), .error' ).first(); if (await error.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(error).toBeVisible(); } } } }); test('shows required field indicators', async ({ page }) => { await page.goto('/devices/new'); if ((await page.url()).includes('/devices/new')) { // Look for required field markers const requiredMarker = page.locator( 'label:has-text("*"), .required, [aria-required="true"]' ).first(); if (await requiredMarker.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(requiredMarker).toBeVisible(); } } }); }); test.describe('Error States', () => { test('shows error page for non-existent resources', async ({ page }) => { await page.goto('/devices/00000000-0000-0000-0000-000000000000'); await page.waitForTimeout(500); // Should show 404 or error message const errorIndicator = page.locator( ':has-text("not found"), :has-text("404"), :has-text("doesn\'t exist")' ).first(); if (await errorIndicator.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(errorIndicator).toBeVisible(); } }); test('handles network errors gracefully', async ({ page }) => { await page.goto('/devices'); // Look for any error boundaries or fallbacks await expect(page.locator('body')).toBeVisible(); }); }); test.describe('Form Submission States', () => { test('shows loading state during form submission', async ({ page }) => { await page.goto('/sites/new'); if ((await page.url()).includes('/sites/new')) { const nameField = page.locator('input[name*="name"]').first(); const submitButton = page.locator('button[type="submit"]').first(); if (await nameField.isVisible({ timeout: 2000 }).catch(() => false)) { await nameField.fill(`Test Site ${Date.now()}`); if (await submitButton.isVisible({ timeout: 2000 }).catch(() => false)) { await submitButton.click(); // Look for loading indicator (briefly) const loadingIndicator = page.locator( '[disabled], .loading, :has-text("Saving"), :has-text("Creating")' ).first(); if (await loadingIndicator.isVisible({ timeout: 1000 }).catch(() => false)) { await expect(loadingIndicator).toBeVisible(); } } } } }); test('disables submit button during submission', async ({ page }) => { await page.goto('/sites/new'); if ((await page.url()).includes('/sites/new')) { const submitButton = page.locator('button[type="submit"]').first(); if (await submitButton.isVisible({ timeout: 2000 }).catch(() => false)) { const nameField = page.locator('input[name*="name"]').first(); if (await nameField.isVisible({ timeout: 2000 }).catch(() => false)) { await nameField.fill(`Test Site ${Date.now()}`); await submitButton.click(); // Button should be disabled during submission const isDisabled = await submitButton.isDisabled().catch(() => false); if (isDisabled) { expect(isDisabled).toBe(true); } } } } }); }); test.describe('Success Messages', () => { test('shows success message after creating resource', async ({ page }) => { await page.goto('/sites/new'); if ((await page.url()).includes('/sites/new')) { const nameField = page.locator('input[name*="name"]').first(); const submitButton = page.locator('button[type="submit"]').first(); if (await nameField.isVisible({ timeout: 2000 }).catch(() => false)) { await nameField.fill(`E2E Test Site ${Date.now()}`); if (await submitButton.isVisible({ timeout: 2000 }).catch(() => false)) { await submitButton.click(); await page.waitForTimeout(1000); // Look for success message const successMessage = page.locator( ':has-text("created"), :has-text("success"), .success, [role="status"]' ).first(); if (await successMessage.isVisible({ timeout: 2000 }).catch(() => false)) { await expect(successMessage).toBeVisible(); } } } } }); }); });