diff --git a/E2E_TEST_FIX.md b/E2E_TEST_FIX.md new file mode 100644 index 0000000..06a4887 --- /dev/null +++ b/E2E_TEST_FIX.md @@ -0,0 +1,72 @@ +# E2E Test Configuration Fix + +## Problem +Playwright E2E tests were timing out with the error: +``` +Error: Timed out waiting 180000ms from config.webServer. +``` + +## Root Cause +**Port mismatch** between Playwright configuration and Vite dev server: +- `playwright.config.ts` was configured to expect server on port **5173** (Vite's default) +- `vite.config.ts` was configured to run server on port **5000** + +This caused Playwright to wait for a server that would never respond on the expected port. + +## Changes Made + +### 1. Fixed Port Configuration in `playwright.config.ts` +- Changed `baseURL` from `http://localhost:5173` → `http://localhost:5000` +- Changed `webServer.url` from `http://localhost:5173` → `http://localhost:5000` +- Reduced `webServer.timeout` from `180000ms` → `120000ms` (2 minutes) +- Reduced `timeout` from `60000ms` → `45000ms` per test +- Reduced `expect.timeout` from `15000ms` → `10000ms` +- Reduced `actionTimeout` from `15000ms` → `10000ms` +- Reduced `navigationTimeout` from `30000ms` → `20000ms` + +### 2. Optimized Test Files + +#### `e2e/smoke.spec.ts` +- Replaced `page.waitForTimeout()` with `page.waitForLoadState('networkidle')` for more reliable waits +- Added explicit timeout values to all `page.goto()` calls +- Reduced individual test timeouts (20-30s instead of 30-45s) +- More efficient waiting strategies + +#### `e2e/codeforge.spec.ts` +- Same optimizations as smoke tests +- Better handling of async operations +- Explicit timeouts prevent hanging + +## Benefits +1. ✅ Tests now connect to correct port +2. ✅ Faster test execution (no arbitrary waits) +3. ✅ More reliable (networkidle vs fixed timeouts) +4. ✅ Better timeout management per test +5. ✅ Clearer failure messages when tests do fail + +## Test Execution +Run E2E tests with: +```bash +npm run test:e2e +``` + +Or with the fallback: +```bash +npm run test:e2e --if-present || echo "No E2E tests configured" +``` + +## CI/CD Integration +The tests will now: +- Start the dev server on port 5000 +- Wait up to 2 minutes for server to be ready +- Run tests with appropriate per-test timeouts +- Retry failed tests 2x in CI environments +- Generate HTML reports + +## Future Improvements +Consider adding: +- More granular test timeouts based on test complexity +- Test parallelization configuration +- Screenshot comparison tests +- Visual regression testing +- API mocking for faster, more isolated tests diff --git a/e2e/codeforge.spec.ts b/e2e/codeforge.spec.ts index bc3859c..4103c96 100644 --- a/e2e/codeforge.spec.ts +++ b/e2e/codeforge.spec.ts @@ -2,9 +2,9 @@ import { test, expect } from '@playwright/test' test.describe('CodeForge - Core Functionality', () => { test.beforeEach(async ({ page }) => { - test.setTimeout(30000) - await page.goto('/', { waitUntil: 'domcontentloaded' }) - await page.waitForTimeout(2000) + test.setTimeout(20000) + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 10000 }) + await page.waitForLoadState('networkidle', { timeout: 5000 }) }) test('should load the application successfully', async ({ page }) => { @@ -13,7 +13,7 @@ test.describe('CodeForge - Core Functionality', () => { test('should display main navigation', async ({ page }) => { const tabList = page.locator('[role="tablist"]').first() - await expect(tabList).toBeVisible({ timeout: 10000 }) + await expect(tabList).toBeVisible({ timeout: 5000 }) }) test('should switch between tabs', async ({ page }) => { @@ -22,44 +22,43 @@ test.describe('CodeForge - Core Functionality', () => { if (tabCount > 1) { await tabs.nth(1).click() - await page.waitForTimeout(500) - await expect(page.locator('[role="tabpanel"]:visible')).toBeVisible() + await expect(page.locator('[role="tabpanel"]:visible')).toBeVisible({ timeout: 3000 }) } }) }) test.describe('CodeForge - Code Editor', () => { test('should display Monaco editor', async ({ page }) => { - test.setTimeout(45000) - await page.goto('/', { waitUntil: 'domcontentloaded' }) - await page.waitForTimeout(2000) + test.setTimeout(30000) + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 10000 }) + await page.waitForLoadState('networkidle', { timeout: 5000 }) const codeEditorTab = page.locator('button[role="tab"]').filter({ hasText: /Code Editor/i }).first() - if (await codeEditorTab.isVisible({ timeout: 5000 })) { + if (await codeEditorTab.isVisible({ timeout: 3000 })) { await codeEditorTab.click() - await page.waitForTimeout(3000) + await page.waitForLoadState('networkidle', { timeout: 10000 }) const monacoEditor = page.locator('.monaco-editor').first() - await expect(monacoEditor).toBeVisible({ timeout: 20000 }) + await expect(monacoEditor).toBeVisible({ timeout: 15000 }) } }) }) test.describe('CodeForge - Responsive Design', () => { test('should work on mobile viewport', async ({ page }) => { - test.setTimeout(30000) + test.setTimeout(20000) await page.setViewportSize({ width: 375, height: 667 }) - await page.goto('/', { waitUntil: 'domcontentloaded' }) - await page.waitForTimeout(2000) + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 10000 }) + await page.waitForLoadState('networkidle', { timeout: 5000 }) await expect(page.locator('body')).toBeVisible() }) test('should work on tablet viewport', async ({ page }) => { - test.setTimeout(30000) + test.setTimeout(20000) await page.setViewportSize({ width: 768, height: 1024 }) - await page.goto('/', { waitUntil: 'domcontentloaded' }) - await page.waitForTimeout(2000) + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 10000 }) + await page.waitForLoadState('networkidle', { timeout: 5000 }) await expect(page.locator('body')).toBeVisible() }) diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index de0c26e..7dc40c5 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -2,43 +2,41 @@ import { test, expect } from '@playwright/test' test.describe('CodeForge - Smoke Tests', () => { test('app loads successfully', async ({ page }) => { - test.setTimeout(30000) - await page.goto('/', { waitUntil: 'domcontentloaded' }) + test.setTimeout(20000) + await page.goto('/', { waitUntil: 'networkidle', timeout: 15000 }) - await expect(page.locator('body')).toBeVisible({ timeout: 10000 }) - await page.waitForTimeout(2000) + await expect(page.locator('body')).toBeVisible({ timeout: 5000 }) }) test('can navigate to dashboard tab', async ({ page }) => { - test.setTimeout(30000) - await page.goto('/', { waitUntil: 'domcontentloaded' }) - await page.waitForTimeout(2000) + test.setTimeout(20000) + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 10000 }) + await page.waitForLoadState('networkidle', { timeout: 5000 }) const dashboardTab = page.locator('button[role="tab"]').filter({ hasText: /Dashboard/i }).first() - if (await dashboardTab.isVisible({ timeout: 5000 })) { + if (await dashboardTab.isVisible({ timeout: 3000 })) { await dashboardTab.click() - await page.waitForTimeout(500) - await expect(page.locator('[role="tabpanel"]:visible')).toBeVisible({ timeout: 5000 }) + await expect(page.locator('[role="tabpanel"]:visible')).toBeVisible({ timeout: 3000 }) } }) test('Monaco editor loads in code editor', async ({ page }) => { - test.setTimeout(45000) - await page.goto('/', { waitUntil: 'domcontentloaded' }) - await page.waitForTimeout(2000) + test.setTimeout(30000) + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 10000 }) + await page.waitForLoadState('networkidle', { timeout: 5000 }) const codeEditorTab = page.locator('button[role="tab"]').filter({ hasText: /Code Editor/i }).first() - if (await codeEditorTab.isVisible({ timeout: 5000 })) { + if (await codeEditorTab.isVisible({ timeout: 3000 })) { await codeEditorTab.click() - await page.waitForTimeout(3000) + await page.waitForLoadState('networkidle', { timeout: 10000 }) const monaco = page.locator('.monaco-editor').first() - await expect(monaco).toBeVisible({ timeout: 20000 }) + await expect(monaco).toBeVisible({ timeout: 15000 }) } }) test('no critical console errors', async ({ page }) => { - test.setTimeout(30000) + test.setTimeout(20000) const errors: string[] = [] page.on('console', (msg) => { if (msg.type() === 'error') { @@ -46,8 +44,8 @@ test.describe('CodeForge - Smoke Tests', () => { } }) - await page.goto('/', { waitUntil: 'domcontentloaded' }) - await page.waitForTimeout(3000) + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 10000 }) + await page.waitForLoadState('networkidle', { timeout: 5000 }) const criticalErrors = errors.filter(e => !e.includes('Download the React DevTools') && diff --git a/playwright.config.ts b/playwright.config.ts index 689e721..f01e162 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -7,16 +7,16 @@ export default defineConfig({ retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'html', - timeout: 60000, + timeout: 45000, expect: { - timeout: 15000, + timeout: 10000, }, use: { - baseURL: 'http://localhost:5173', + baseURL: 'http://localhost:5000', trace: 'on-first-retry', screenshot: 'only-on-failure', - actionTimeout: 15000, - navigationTimeout: 30000, + actionTimeout: 10000, + navigationTimeout: 20000, }, projects: [ @@ -28,9 +28,9 @@ export default defineConfig({ webServer: { command: 'npm run dev', - url: 'http://localhost:5173', + url: 'http://localhost:5000', reuseExistingServer: !process.env.CI, - timeout: 180000, + timeout: 120000, stdout: 'pipe', stderr: 'pipe', },