diff --git a/tests/README.md b/tests/README.md index 474af11..049ada7 100644 --- a/tests/README.md +++ b/tests/README.md @@ -13,7 +13,7 @@ Playwright tests for verifying the wxWidgets WASM port. ../scripts/build-wasm-test.sh ``` -This builds `wasm-app/minimal_test.{html,js,wasm}`. +This builds `wasm-app/minimal_test.{html,js,wasm}` and standalone test apps. ## Running Tests @@ -22,6 +22,61 @@ npm install npm test ``` +To run specific tests: +```bash +npx playwright test menu.spec.ts # Run menu tests only +npx playwright test --grep "wxTimer" # Run tests matching pattern +``` + +## Test Structure + +``` +tests/ +├── e2e/ # Playwright test specs +│ ├── utils/ # Shared test utilities +│ │ ├── fixtures.ts # Playwright fixtures with auto-logging +│ │ └── test-utils.ts # Logging and helper functions +│ ├── menu.spec.ts # wxMenuBar tests +│ ├── timer.spec.ts # wxTimer tests +│ ├── dialog.spec.ts # wxDialog/wxMessageBox tests +│ ├── tree.spec.ts # wxTreeCtrl tests +│ ├── grid.spec.ts # wxGrid/wxSpinCtrl/wxSearchCtrl tests +│ ├── opengl.spec.ts # OpenGL tab tests +│ ├── wxwidgets.spec.ts # Comprehensive UI interaction tests +│ └── ... +├── logs/ # Test logs (auto-generated) +├── test-results/ # Screenshots (auto-generated) +├── baseline-screenshots/ # Reference screenshots for comparison +├── wasm-app/ # Built WASM test applications +│ ├── minimal_test.html # Main test app +│ └── standalone/ # Individual component test apps +└── playwright.config.ts # Playwright configuration +``` + +## Logging + +Each test automatically captures: +- Console logs with timestamps and log levels +- Page errors with full stack traces + +Log files are written to `logs/` after each test: +- `.log` - All console output +- `.errors.log` - Errors only (created if errors occurred) + +Example log format: +``` +[2025-11-29T19:39:42.165Z] [LOG] [EVENT] Application started +[2025-11-29T19:39:42.733Z] [WARNING] GPU stall due to ReadPixels +[2025-11-29T19:39:42.801Z] [ERROR] Some error message +``` + +## Screenshots + +Tests capture screenshots to `test-results/`. Compare against baselines: +```bash +../scripts/compare-screenshots.sh +``` + ## Viewing the App Directly Start a local server in the wasm-app directory: @@ -41,3 +96,26 @@ python3 -m http.server 8000 ``` Then open http://localhost:8000/minimal_test.html + +## Test Categories + +| Spec File | Tests | Description | +|-----------|-------|-------------| +| `wxwidgets.spec.ts` | Comprehensive | Full UI interaction, stability, OpenGL | +| `menu.spec.ts` | wxMenuBar | Menu bar visibility and interactions | +| `timer.spec.ts` | wxTimer | Timer start/stop/reset functionality | +| `dialog.spec.ts` | wxDialog | Message boxes and custom dialogs | +| `tree.spec.ts` | wxTreeCtrl | Tree control with expand/collapse | +| `grid.spec.ts` | wxGrid | Grid, SpinCtrl, SearchCtrl | +| `opengl.spec.ts` | OpenGL | GL tests (immediate mode, vertex arrays) | +| `aui.spec.ts` | wxAuiManager | Dockable panels | +| `clipboard.spec.ts` | wxClipboard | Copy/paste operations | +| `filedialog.spec.ts` | wxFileDialog | File open/save dialogs | +| `layout.spec.ts` | wxSplitter | Splitter and scrolled windows | +| `toolbar.spec.ts` | wxToolBar | Toolbar buttons and status bar | + +## Known Issues + +- **Timer tests**: May fail due to timing sensitivity +- **Tree tests**: Button click positions may vary +- **wxGrid**: Not fully implemented (expected failures marked with `test.fail`) diff --git a/tests/e2e/aui.spec.ts b/tests/e2e/aui.spec.ts index d179433..2855964 100644 --- a/tests/e2e/aui.spec.ts +++ b/tests/e2e/aui.spec.ts @@ -1,46 +1,21 @@ // wxAuiManager Tests - AUI docking system KiCad uses extensively -import { test, expect, Page } from '@playwright/test'; - -const MAIN_CANVAS = '#canvas'; - -async function tryLoadApp(page: Page, timeout = 15000) { - try { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout }); - await page.waitForTimeout(500); - return true; - } catch { - return false; - } -} +import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; test.describe('wxAuiManager Tests', () => { - test('AUI test app loads successfully', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(`[PAGE_ERROR] ${err.message}`)); - page.on('console', msg => logs.push(`[${msg.type()}] ${msg.text()}`)); - + test('AUI test app loads successfully', async ({ page, testLogger }) => { await page.goto('/standalone/aui/aui_test.html'); const loaded = await tryLoadApp(page); await page.screenshot({ path: 'test-results/aui-01-loaded.png', fullPage: true }); - const hasStartup = logs.some(l => l.includes('AUI test app started')); - - console.log('AUI loaded:', loaded); - console.log('AUI logs:', logs.filter(l => l.includes('AUI'))); - console.log('AUI errors:', errors); + const hasStartup = testLogger.consoleLogs.some(l => l.includes('AUI test app started')); expect(loaded, 'AUI app should load').toBe(true); - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('AUI dockable panels are visible', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('AUI dockable panels are visible', async ({ page, testLogger }) => { await page.goto('/standalone/aui/aui_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -50,16 +25,12 @@ test.describe('wxAuiManager Tests', () => { await page.screenshot({ path: 'test-results/aui-02-panels.png', fullPage: true }); - const hasPanelsLog = logs.some(l => l.includes('dockable panels')); - console.log('Panel logs:', logs.filter(l => l.includes('AUI') || l.includes('panel'))); + const hasPanelsLog = testLogger.consoleLogs.some(l => l.includes('dockable panels')); expect(hasPanelsLog).toBe(true); }); - test('Panel close button can be clicked', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Panel close button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/aui/aui_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -67,28 +38,19 @@ test.describe('wxAuiManager Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click on Properties panel close button (top right of left panel) - // Left panel is at left edge, close button at top right of its title bar await page.mouse.click(box.x + 145, box.y + 35); await page.waitForTimeout(500); await page.screenshot({ path: 'test-results/aui-03-close-clicked.png', fullPage: true }); - const hasCloseEvent = logs.some(l => l.includes('Pane closing')); - console.log('Close events:', logs.filter(l => l.includes('Pane') || l.includes('close'))); - // Smoke test expect(true).toBe(true); }); - test('Panel can be dragged', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Panel can be dragged', async ({ page, testLogger }) => { await page.goto('/standalone/aui/aui_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -96,9 +58,7 @@ test.describe('wxAuiManager Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Drag Properties panel title bar const titleX = box.x + 75; @@ -116,13 +76,7 @@ test.describe('wxAuiManager Tests', () => { expect(true).toBe(true); }); - test('Multiple panels can be interacted with', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(err.message)); - page.on('console', msg => logs.push(msg.text())); - + test('Multiple panels can be interacted with', async ({ page, testLogger }) => { await page.goto('/standalone/aui/aui_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -130,9 +84,7 @@ test.describe('wxAuiManager Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click in Properties panel await page.mouse.click(box.x + 75, box.y + 200); @@ -152,9 +104,6 @@ test.describe('wxAuiManager Tests', () => { await page.screenshot({ path: 'test-results/aui-05-multi-panel.png', fullPage: true }); - console.log('\n=== AUI EVENTS ==='); - logs.filter(l => l.includes('AUI')).forEach(l => console.log(l)); - - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); }); diff --git a/tests/e2e/clipboard.spec.ts b/tests/e2e/clipboard.spec.ts index 5d1dec7..23dba6e 100644 --- a/tests/e2e/clipboard.spec.ts +++ b/tests/e2e/clipboard.spec.ts @@ -1,53 +1,23 @@ // wxClipboard Tests - Clipboard operations for KiCad copy/paste -import { test, expect, Page } from '@playwright/test'; - -const MAIN_CANVAS = '#canvas'; - -async function waitForApp(page: Page, timeout = 30000) { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout }); - await page.waitForTimeout(500); -} - -async function tryLoadApp(page: Page) { - try { - await waitForApp(page, 15000); - return true; - } catch { - return false; - } -} +import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; test.describe('wxClipboard Tests', () => { - test('Clipboard test app loads successfully', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => { - errors.push(`[PAGE_ERROR] ${err.message}`); - }); - page.on('console', msg => { - logs.push(`[${msg.type()}] ${msg.text()}`); - }); - + test('Clipboard test app loads successfully', async ({ page, testLogger }) => { await page.goto('/standalone/clipboard/clipboard_test.html'); const loaded = await tryLoadApp(page); await page.screenshot({ path: 'test-results/clipboard-01-loaded.png', fullPage: true }); - const hasStartupLog = logs.some(l => l.includes('wxClipboard test app started') || l.includes('Clipboard test app started')); - console.log('Clipboard app logs:', logs.filter(l => l.includes('CLIPBOARD'))); - console.log('Clipboard app loaded:', loaded); + const hasStartupLog = testLogger.consoleLogs.some(l => + l.includes('wxClipboard test app started') || l.includes('Clipboard test app started') + ); expect(loaded, 'wxClipboard app should load').toBe(true); - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('Copy button can be clicked', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => logs.push(msg.text())); - + test('Copy button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/clipboard/clipboard_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -55,9 +25,7 @@ test.describe('wxClipboard Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click "Copy to Clipboard" button await page.mouse.click(box.x + 100, box.y + 220); @@ -65,17 +33,10 @@ test.describe('wxClipboard Tests', () => { await page.screenshot({ path: 'test-results/clipboard-02-copy-clicked.png', fullPage: true }); - const hasCopyLog = logs.some(l => l.includes('copy') || l.includes('Copy') || l.includes('Copied')); - console.log('Copy logs:', logs.filter(l => l.includes('CLIPBOARD') || l.includes('copy') || l.includes('Copy'))); - expect(true).toBe(true); // Smoke test }); - test('Paste button can be clicked', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => logs.push(msg.text())); - + test('Paste button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/clipboard/clipboard_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -83,9 +44,7 @@ test.describe('wxClipboard Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // First copy something await page.mouse.click(box.x + 100, box.y + 220); @@ -97,16 +56,10 @@ test.describe('wxClipboard Tests', () => { await page.screenshot({ path: 'test-results/clipboard-03-paste-clicked.png', fullPage: true }); - console.log('Paste logs:', logs.filter(l => l.includes('CLIPBOARD') || l.includes('paste') || l.includes('Paste'))); - expect(true).toBe(true); }); - test('Check clipboard button works', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => logs.push(msg.text())); - + test('Check clipboard button works', async ({ page, testLogger }) => { await page.goto('/standalone/clipboard/clipboard_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -114,9 +67,7 @@ test.describe('wxClipboard Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click "Check Clipboard" button await page.mouse.click(box.x + 400, box.y + 220); @@ -124,16 +75,10 @@ test.describe('wxClipboard Tests', () => { await page.screenshot({ path: 'test-results/clipboard-04-check-clicked.png', fullPage: true }); - console.log('Check logs:', logs.filter(l => l.includes('CLIPBOARD') || l.includes('Check') || l.includes('contains'))); - expect(true).toBe(true); }); - test('Clear clipboard button works', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => logs.push(msg.text())); - + test('Clear clipboard button works', async ({ page, testLogger }) => { await page.goto('/standalone/clipboard/clipboard_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -141,9 +86,7 @@ test.describe('wxClipboard Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // First copy something await page.mouse.click(box.x + 100, box.y + 220); @@ -155,18 +98,10 @@ test.describe('wxClipboard Tests', () => { await page.screenshot({ path: 'test-results/clipboard-05-clear-clicked.png', fullPage: true }); - console.log('Clear logs:', logs.filter(l => l.includes('CLIPBOARD') || l.includes('clear') || l.includes('Clear'))); - expect(true).toBe(true); }); - test('Full clipboard flow: copy, check, paste, clear', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(err.message)); - page.on('console', msg => logs.push(msg.text())); - + test('Full clipboard flow: copy, check, paste, clear', async ({ page, testLogger }) => { await page.goto('/standalone/clipboard/clipboard_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -174,9 +109,7 @@ test.describe('wxClipboard Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // 1. Copy await page.mouse.click(box.x + 100, box.y + 220); @@ -196,9 +129,6 @@ test.describe('wxClipboard Tests', () => { await page.screenshot({ path: 'test-results/clipboard-06-full-flow.png', fullPage: true }); - console.log('\n=== CLIPBOARD EVENTS ==='); - logs.filter(l => l.includes('CLIPBOARD')).forEach(l => console.log(l)); - - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); }); diff --git a/tests/e2e/dialog.spec.ts b/tests/e2e/dialog.spec.ts index ed3b018..d7fa528 100644 --- a/tests/e2e/dialog.spec.ts +++ b/tests/e2e/dialog.spec.ts @@ -1,45 +1,21 @@ -import { test, expect, Page } from '@playwright/test'; - -const MAIN_CANVAS = '#canvas'; - -async function tryLoadApp(page: Page, timeout = 15000) { - try { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout }); - await page.waitForTimeout(500); - return true; - } catch { - return false; - } -} +import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; test.describe('wxDialog/wxMessageBox Tests', () => { - test('Dialog test app loads successfully', async ({ page }) => { - const consoleLogs: string[] = []; - const pageErrors: string[] = []; - - page.on('console', msg => consoleLogs.push(msg.text())); - page.on('pageerror', err => pageErrors.push(err.message)); - + test('Dialog test app loads successfully', async ({ page, testLogger }) => { await page.goto('/standalone/dialog/dialog_test.html'); const loaded = await tryLoadApp(page); await page.screenshot({ path: 'test-results/dialog-01-loaded.png', fullPage: true }); - const hasStartupLog = consoleLogs.some(log => + const hasStartupLog = testLogger.consoleLogs.some(log => log.includes('DIALOG_TEST') && log.includes('started successfully') ); - console.log('Dialog app logs:', consoleLogs.filter(l => l.includes('DIALOG'))); - console.log('Dialog app loaded:', hasStartupLog); - expect(loaded, 'Canvas should be visible').toBe(true); - expect(pageErrors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('Info dialog button can be clicked', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => consoleLogs.push(msg.text())); - + test('Info dialog button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/dialog/dialog_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -47,32 +23,23 @@ test.describe('wxDialog/wxMessageBox Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Info Dialog button is first in the wxMessageBox row - // Layout: description (~80px) + fieldset header + buttons - // Buttons are centered horizontally in 3-button row - // Estimate: first button at ~(centerX - 120), y ~115 const centerX = box.width / 2; await page.mouse.click(box.x + centerX - 110, box.y + 115); await page.waitForTimeout(500); - console.log('Info dialog logs:', consoleLogs.filter(l => l.includes('DIALOG'))); await page.screenshot({ path: 'test-results/dialog-02-info-clicked.png', fullPage: true }); - const hasInfoEvent = consoleLogs.some(log => + const hasInfoEvent = testLogger.consoleLogs.some(log => log.includes('Opening Info dialog') ); expect(hasInfoEvent, 'Info dialog should open').toBe(true); }); - test('Yes/No dialog button can be clicked', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => consoleLogs.push(msg.text())); - + test('Yes/No dialog button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/dialog/dialog_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -80,28 +47,22 @@ test.describe('wxDialog/wxMessageBox Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Yes/No Dialog button is second (center) in the wxMessageBox row const centerX = box.width / 2; await page.mouse.click(box.x + centerX, box.y + 115); await page.waitForTimeout(500); - console.log('Yes/No dialog logs:', consoleLogs.filter(l => l.includes('DIALOG'))); await page.screenshot({ path: 'test-results/dialog-03-yesno-clicked.png', fullPage: true }); - const hasYesNoEvent = consoleLogs.some(log => + const hasYesNoEvent = testLogger.consoleLogs.some(log => log.includes('Opening Yes/No dialog') ); expect(hasYesNoEvent, 'Yes/No dialog should open').toBe(true); }); - test('Error dialog button can be clicked', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => consoleLogs.push(msg.text())); - + test('Error dialog button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/dialog/dialog_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -109,28 +70,22 @@ test.describe('wxDialog/wxMessageBox Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Error Dialog button is third (rightmost) in the wxMessageBox row const centerX = box.width / 2; await page.mouse.click(box.x + centerX + 110, box.y + 115); await page.waitForTimeout(500); - console.log('Error dialog logs:', consoleLogs.filter(l => l.includes('DIALOG'))); await page.screenshot({ path: 'test-results/dialog-04-error-clicked.png', fullPage: true }); - const hasErrorEvent = consoleLogs.some(log => + const hasErrorEvent = testLogger.consoleLogs.some(log => log.includes('Opening Error dialog') ); expect(hasErrorEvent, 'Error dialog should open').toBe(true); }); - test('Custom dialog button can be clicked', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => consoleLogs.push(msg.text())); - + test('Custom dialog button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/dialog/dialog_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -138,17 +93,13 @@ test.describe('wxDialog/wxMessageBox Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Custom Dialog button is first in the wxDialog row (row 2) - // Y position is lower (~175) since it's in the second fieldset const centerX = box.width / 2; await page.mouse.click(box.x + centerX - 60, box.y + 175); await page.waitForTimeout(500); - console.log('Custom dialog logs:', consoleLogs.filter(l => l.includes('DIALOG'))); await page.screenshot({ path: 'test-results/dialog-05-custom-clicked.png', fullPage: true }); expect(true).toBe(true); diff --git a/tests/e2e/dialogs.spec.ts b/tests/e2e/dialogs.spec.ts index 3772e2c..4999216 100644 --- a/tests/e2e/dialogs.spec.ts +++ b/tests/e2e/dialogs.spec.ts @@ -1,39 +1,18 @@ -import { test, expect, Page } from '@playwright/test'; +import { test, expect, MAIN_CANVAS, waitForApp, getCanvasBox } from './utils/fixtures'; -const MAIN_CANVAS = '#canvas'; - -async function waitForApp(page: Page) { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout: 30000 }); - await page.waitForTimeout(500); -} - -async function switchToDialogsTab(page: Page, box: { x: number; y: number }) { +async function switchToDialogsTab(page: any, box: { x: number; y: number }) { // Dialogs tab is the 7th tab (after Grid) - // Tab widths: Controls, Text Input, Drawing, Lists, OpenGL, Grid, Dialogs - // Dialogs tab center is approximately at x = 360-380 await page.mouse.click(box.x + 370, box.y + 35); await page.waitForTimeout(1000); } test.describe('Dialogs Tab Tests', () => { - test('Dialogs tab renders correctly', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => { - errors.push(`[PAGE_ERROR] ${err.message}`); - }); - page.on('console', msg => { - logs.push(`[${msg.type()}] ${msg.text()}`); - }); - + test('Dialogs tab renders correctly', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Screenshot before switching to Dialogs tab await page.screenshot({ path: 'test-results/dialogs-00-initial.png', fullPage: true }); @@ -42,10 +21,6 @@ test.describe('Dialogs Tab Tests', () => { await switchToDialogsTab(page, box); await page.screenshot({ path: 'test-results/dialogs-01-tab-selected.png', fullPage: true }); - // Log any console output - console.log('\n=== CONSOLE LOGS ==='); - logs.filter(l => l.includes('Tab changed')).forEach(log => console.log(log)); - // Verify app is still responsive const isResponsive = await page.evaluate(() => { return document.querySelector('#canvas') !== null; @@ -53,59 +28,39 @@ test.describe('Dialogs Tab Tests', () => { expect(isResponsive).toBe(true); // Verify no critical errors - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); test.describe('wxMessageBox', () => { - test('Info message box opens and closes', async ({ page }) => { - const logs: string[] = []; - const errors: string[] = []; - - page.on('console', msg => logs.push(msg.text())); - page.on('pageerror', err => errors.push(err.message)); - + test('Info message box opens and closes', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToDialogsTab(page, box); // Click "Info Dialog" button (first button in wxMessageBox section) - // Buttons are around y=100-120 in the tab content await page.mouse.click(box.x + 80, box.y + 110); await page.waitForTimeout(500); await page.screenshot({ path: 'test-results/dialogs-msgbox-info-open.png', fullPage: true }); - // Check if message box appeared - const hasInfoLog = logs.some(l => l.includes('Showing Info message box')); - console.log('Info dialog logs:', logs.filter(l => l.includes('Info'))); - - // Click OK to close (message box OK button is usually in center-bottom) - // In WASM, the dialog might be drawn on the canvas + // Click OK to close await page.mouse.click(box.x + 350, box.y + 250); await page.waitForTimeout(300); await page.screenshot({ path: 'test-results/dialogs-msgbox-info-closed.png', fullPage: true }); - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('Yes/No message box returns correct result', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => logs.push(msg.text())); - + test('Yes/No message box returns correct result', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToDialogsTab(page, box); @@ -120,23 +75,13 @@ test.describe('Dialogs Tab Tests', () => { await page.waitForTimeout(300); await page.screenshot({ path: 'test-results/dialogs-msgbox-yesno-closed.png', fullPage: true }); - - // Check logs for user choice - const hasYesNoLog = logs.some(l => l.includes('Yes/No') || l.includes('User clicked')); - console.log('Yes/No dialog logs:', logs.filter(l => l.includes('Yes') || l.includes('No'))); }); - test('Error message box displays correctly', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => logs.push(msg.text())); - + test('Error message box displays correctly', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToDialogsTab(page, box); @@ -156,17 +101,11 @@ test.describe('Dialogs Tab Tests', () => { test.describe('wxDialog', () => { - test('Custom dialog opens and closes', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => logs.push(msg.text())); - + test('Custom dialog opens and closes', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToDialogsTab(page, box); @@ -176,91 +115,52 @@ test.describe('Dialogs Tab Tests', () => { await page.screenshot({ path: 'test-results/dialogs-custom-open.png', fullPage: true }); - // Check if dialog opened - const hasDialogLog = logs.some(l => l.includes('Opening custom dialog')); - console.log('Custom dialog logs:', logs.filter(l => l.includes('dialog'))); - // Click OK to close await page.mouse.click(box.x + 300, box.y + 300); await page.waitForTimeout(300); await page.screenshot({ path: 'test-results/dialogs-custom-closed.png', fullPage: true }); - - // Check if dialog closed with result - const hasClosedLog = logs.some(l => l.includes('Custom dialog closed')); - console.log('Dialog close logs:', logs.filter(l => l.includes('closed'))); }); }); test.describe('wxTimer', () => { - test('Timer starts and increments', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => logs.push(msg.text())); - + test('Timer starts and increments', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToDialogsTab(page, box); await page.screenshot({ path: 'test-results/dialogs-timer-initial.png', fullPage: true }); - // Click "Start Timer" button (in wxTimer section, buttons are CENTERED) - // Looking at screenshot: Start Timer is around x=500, y=230 + // Click "Start Timer" button (centered buttons) await page.mouse.click(box.x + 500, box.y + 230); await page.waitForTimeout(500); await page.screenshot({ path: 'test-results/dialogs-timer-started.png', fullPage: true }); // Wait for a few timer ticks - await page.waitForTimeout(3500); // Wait ~3-4 seconds + await page.waitForTimeout(3500); await page.screenshot({ path: 'test-results/dialogs-timer-running.png', fullPage: true }); - // Check for timer tick logs - const timerLogs = logs.filter(l => l.includes('Timer tick') || l.includes('Timer:')); - console.log('Timer logs:', timerLogs); - - // Click "Stop Timer" (centered, to the right of Start Timer) + // Click "Stop Timer" await page.mouse.click(box.x + 600, box.y + 230); await page.waitForTimeout(500); await page.screenshot({ path: 'test-results/dialogs-timer-stopped.png', fullPage: true }); - // Verify timer was working - const hasTimerTicks = logs.some(l => l.includes('Timer tick')); - const hasTimerStarted = logs.some(l => l.includes('Timer started')); - const hasTimerStopped = logs.some(l => l.includes('Timer stopped')); - - console.log(`Timer started: ${hasTimerStarted}`); - console.log(`Timer ticks: ${timerLogs.length}`); - console.log(`Timer stopped: ${hasTimerStopped}`); - - // Timer behavior - log results but don't fail the test - // wxTimer may have limited support in WASM - if (!hasTimerStarted) { - console.log('NOTE: wxTimer may not be fully implemented in WASM'); - } // Just verify no crashes occurred - this is a smoke test expect(true).toBe(true); }); - test('Timer can be started and stopped multiple times', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => logs.push(msg.text())); - + test('Timer can be started and stopped multiple times', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToDialogsTab(page, box); @@ -282,30 +182,16 @@ test.describe('Dialogs Tab Tests', () => { await page.screenshot({ path: 'test-results/dialogs-timer-multiple.png', fullPage: true }); - // Should have multiple start/stop logs - const startCount = logs.filter(l => l.includes('Timer started')).length; - const stopCount = logs.filter(l => l.includes('Timer stopped')).length; - - console.log(`Start count: ${startCount}, Stop count: ${stopCount}`); - // Basic smoke test - no crashes expect(true).toBe(true); }); }); - test('Full Dialogs tab interaction flow', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(err.message)); - page.on('console', msg => logs.push(msg.text())); - + test('Full Dialogs tab interaction flow', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToDialogsTab(page, box); @@ -329,15 +215,7 @@ test.describe('Dialogs Tab Tests', () => { await page.screenshot({ path: 'test-results/dialogs-full-flow.png', fullPage: true }); - // Print all dialog-related events - console.log('\n=== DIALOG EVENTS ==='); - logs.filter(l => - l.includes('dialog') || - l.includes('Timer') || - l.includes('message box') - ).forEach(log => console.log(log)); - // Verify no crashes - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); }); diff --git a/tests/e2e/filedialog.spec.ts b/tests/e2e/filedialog.spec.ts index e98396f..74d1b0c 100644 --- a/tests/e2e/filedialog.spec.ts +++ b/tests/e2e/filedialog.spec.ts @@ -1,46 +1,21 @@ // wxFileDialog Tests - File dialogs for KiCad open/save operations -import { test, expect, Page } from '@playwright/test'; - -const MAIN_CANVAS = '#canvas'; - -async function tryLoadApp(page: Page, timeout = 15000) { - try { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout }); - await page.waitForTimeout(500); - return true; - } catch { - return false; - } -} +import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; test.describe('wxFileDialog Tests', () => { - test('FileDialog test app loads successfully', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(`[PAGE_ERROR] ${err.message}`)); - page.on('console', msg => logs.push(`[${msg.type()}] ${msg.text()}`)); - + test('FileDialog test app loads successfully', async ({ page, testLogger }) => { await page.goto('/standalone/filedialog/filedialog_test.html'); const loaded = await tryLoadApp(page); await page.screenshot({ path: 'test-results/filedialog-01-loaded.png', fullPage: true }); - const hasStartup = logs.some(l => l.includes('FileDialog test app started')); - - console.log('FileDialog loaded:', loaded); - console.log('FileDialog logs:', logs.filter(l => l.includes('FILEDIALOG'))); - console.log('FileDialog errors:', errors); + const hasStartup = testLogger.consoleLogs.some(l => l.includes('FileDialog test app started')); expect(loaded, 'wxFileDialog app should load').toBe(true); - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('Open file button can be clicked', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Open file button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/filedialog/filedialog_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -48,9 +23,7 @@ test.describe('wxFileDialog Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click "Open File..." button await page.mouse.click(box.x + 100, box.y + 150); @@ -58,16 +31,10 @@ test.describe('wxFileDialog Tests', () => { await page.screenshot({ path: 'test-results/filedialog-02-open-clicked.png', fullPage: true }); - const hasOpenLog = logs.some(l => l.includes('Opening file dialog') || l.includes('Open')); - console.log('Open logs:', logs.filter(l => l.includes('FILEDIALOG') || l.includes('Open'))); - expect(true).toBe(true); // Smoke test }); - test('Save file button can be clicked', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Save file button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/filedialog/filedialog_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -75,9 +42,7 @@ test.describe('wxFileDialog Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click "Save File..." button await page.mouse.click(box.x + 220, box.y + 150); @@ -85,16 +50,10 @@ test.describe('wxFileDialog Tests', () => { await page.screenshot({ path: 'test-results/filedialog-03-save-clicked.png', fullPage: true }); - const hasSaveLog = logs.some(l => l.includes('save dialog') || l.includes('Save')); - console.log('Save logs:', logs.filter(l => l.includes('FILEDIALOG') || l.includes('Save'))); - expect(true).toBe(true); }); - test('Open multiple button can be clicked', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Open multiple button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/filedialog/filedialog_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -102,9 +61,7 @@ test.describe('wxFileDialog Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click "Open Multiple..." button await page.mouse.click(box.x + 350, box.y + 150); @@ -112,18 +69,10 @@ test.describe('wxFileDialog Tests', () => { await page.screenshot({ path: 'test-results/filedialog-04-multiple-clicked.png', fullPage: true }); - console.log('Multiple logs:', logs.filter(l => l.includes('FILEDIALOG') || l.includes('Multiple'))); - expect(true).toBe(true); }); - test('All file dialog buttons accessible', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(err.message)); - page.on('console', msg => logs.push(msg.text())); - + test('All file dialog buttons accessible', async ({ page, testLogger }) => { await page.goto('/standalone/filedialog/filedialog_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -131,9 +80,7 @@ test.describe('wxFileDialog Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Try all three buttons await page.mouse.click(box.x + 100, box.y + 150); @@ -145,9 +92,6 @@ test.describe('wxFileDialog Tests', () => { await page.screenshot({ path: 'test-results/filedialog-05-all-buttons.png', fullPage: true }); - console.log('\n=== FILEDIALOG EVENTS ==='); - logs.filter(l => l.includes('FILEDIALOG')).forEach(l => console.log(l)); - - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); }); diff --git a/tests/e2e/grid.spec.ts b/tests/e2e/grid.spec.ts index e57fc0e..f2a6f9d 100644 --- a/tests/e2e/grid.spec.ts +++ b/tests/e2e/grid.spec.ts @@ -1,16 +1,7 @@ -import { test, expect, Page } from '@playwright/test'; +import { test, expect, MAIN_CANVAS, waitForApp, getCanvasBox } from './utils/fixtures'; -const MAIN_CANVAS = '#canvas'; - -async function waitForApp(page: Page) { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout: 30000 }); - await page.waitForTimeout(500); -} - -async function switchToGridTab(page: Page, box: { x: number; y: number }) { +async function switchToGridTab(page: any, box: { x: number; y: number }) { // Click Grid tab (sixth tab, after OpenGL which is at x=280) - // Tab widths: Controls(~45), Text Input(~55), Drawing(~50), Lists(~35), OpenGL(~50), Grid(~30) - // Grid tab center is approximately at x = 310-320 await page.mouse.click(box.x + 315, box.y + 35); await page.waitForTimeout(1000); } @@ -22,23 +13,7 @@ async function switchToGridTab(page: Page, box: { x: number; y: number }) { test.describe('wxGrid Dedicated Test Page', () => { // This is THE critical test for wxGrid support. - // It loads a separate WASM binary that ONLY tests wxGrid. - // If wxGrid is not implemented in WASM, this page will CRASH at startup. - // When this test starts PASSING, wxGrid is working! - test.fail('wxGrid test page loads successfully', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => { - errors.push(`[PAGE_ERROR] ${err.message}\n${err.stack || ''}`); - }); - page.on('console', msg => { - logs.push(`[${msg.type()}] ${msg.text()}`); - if (msg.type() === 'error') { - errors.push(`[CONSOLE_ERROR] ${msg.text()}`); - } - }); - + test.fail('wxGrid test page loads successfully', async ({ page, testLogger }) => { // Navigate to the dedicated wxGrid test page await page.goto('/standalone/grid/grid_test.html'); @@ -48,40 +23,29 @@ test.describe('wxGrid Dedicated Test Page', () => { await page.waitForTimeout(1000); } catch (e) { // Expected to fail - wxGrid is not implemented - console.log('wxGrid test page failed to load (expected):', e); } await page.screenshot({ path: 'test-results/wxgrid-dedicated-page.png', fullPage: true }); - // Log what happened - console.log('\n=== wxGrid Dedicated Test Page Results ==='); - console.log('Console logs:', logs.length); - console.log('Errors:', errors.length); - errors.forEach(e => console.log(e)); - // Check for the success message from grid_test.cpp - const hasSuccessMessage = logs.some(l => + const hasSuccessMessage = testLogger.consoleLogs.some(l => l.includes('wxGrid test app started successfully') || l.includes('wxGrid initialized successfully') ); // Check for crash errors - const hasCrash = errors.some(e => + const hasCrash = testLogger.errors.some(e => e.includes('function signature mismatch') || e.includes('RuntimeError') || e.includes('Exception thrown') ); - console.log(`Has success message: ${hasSuccessMessage}`); - console.log(`Has crash: ${hasCrash}`); - // This test FAILS if wxGrid is not implemented (crashes or no success message) - // When wxGrid is fixed, this will PASS expect(hasCrash, 'wxGrid should not crash the app').toBe(false); expect(hasSuccessMessage, 'wxGrid app should start successfully').toBe(true); }); - test.fail('wxGrid test page shows grid controls', async ({ page }) => { + test.fail('wxGrid test page shows grid controls', async ({ page, testLogger }) => { await page.goto('/standalone/grid/grid_test.html'); try { @@ -112,7 +76,6 @@ test.describe('wxGrid Dedicated Test Page', () => { variance++; } } - console.log(`[GRID_PAGE_CHECK] Pixel variance: ${variance}`); return variance > 1000; // Grid with labels/lines has high variance }); @@ -128,39 +91,18 @@ test.describe('Grid Tab Tests', () => { test.describe('wxGrid in Main App (DISABLED)', () => { - // This test documents that wxGrid is NOT implemented in WASM - // It should FAIL until wxGrid is fixed. When it starts passing, wxGrid is working! - test.fail('wxGrid renders visible grid cells', async ({ page }) => { - const errors: string[] = []; - - page.on('pageerror', err => { - errors.push(`[PAGE_ERROR] ${err.message}`); - }); - page.on('console', msg => { - if (msg.type() === 'error') { - errors.push(`[CONSOLE_ERROR] ${msg.text()}`); - } - }); - + test.fail('wxGrid renders visible grid cells', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Switch to Grid tab await switchToGridTab(page, box); await page.screenshot({ path: 'test-results/wxgrid-01-tab.png', fullPage: true }); - // The test should find actual grid cells. Since wxGrid is not implemented, - // this will fail - which is exactly what we want to document! - // We check for grid content by looking at the screenshot or canvas pixels - // Evaluate if grid-like content exists (row/column headers, cells) const hasGridContent = await page.evaluate(() => { - // Check if there's any element with grid-like structure - // Since wxGrid would render to canvas, we check canvas content const canvas = document.querySelector('#canvas') as HTMLCanvasElement; if (!canvas) return false; @@ -168,7 +110,6 @@ test.describe('Grid Tab Tests', () => { if (!ctx) return false; // Sample pixels at where grid headers/cells would be (around y=180-200) - // A real grid would have visible lines and text, not just blank space const imageData = ctx.getImageData(100, 180, 300, 50); const data = imageData.data; @@ -178,7 +119,6 @@ test.describe('Grid Tab Tests', () => { for (let i = 0; i < data.length; i += 4) { const r = data[i], g = data[i+1], b = data[i+2]; - // Check for color changes (grid lines, text) if (Math.abs(r - lastPixel.r) > 20 || Math.abs(g - lastPixel.g) > 20 || Math.abs(b - lastPixel.b) > 20) { @@ -187,24 +127,17 @@ test.describe('Grid Tab Tests', () => { lastPixel = { r, g, b }; } - // If there's significant variance, grid content exists - // A blank area would have very low variance - console.log(`[GRID_CHECK] Color variance: ${colorVariance}`); return colorVariance > 500; // Grid has lines and text = high variance }); - // This assertion WILL FAIL because wxGrid is not implemented - // When wxGrid is fixed, this test will pass expect(hasGridContent, 'wxGrid should render visible grid cells with headers and data').toBe(true); }); - test.fail('wxGrid cell selection works', async ({ page }) => { + test.fail('wxGrid cell selection works', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToGridTab(page, box); @@ -214,23 +147,16 @@ test.describe('Grid Tab Tests', () => { await page.screenshot({ path: 'test-results/wxgrid-02-selection.png', fullPage: true }); - // Check console for grid selection events - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - // Since wxGrid is not implemented, we won't get selection events - // This test SHOULD FAIL - const hasGridEvent = logs.some(l => l.includes('Grid cell')); + const hasGridEvent = testLogger.consoleLogs.some(l => l.includes('Grid cell')); expect(hasGridEvent, 'wxGrid should emit cell selection events').toBe(true); }); - test.fail('wxGrid cell editing works', async ({ page }) => { + test.fail('wxGrid cell editing works', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToGridTab(page, box); @@ -244,13 +170,8 @@ test.describe('Grid Tab Tests', () => { await page.screenshot({ path: 'test-results/wxgrid-03-editing.png', fullPage: true }); - // Check console for grid edit events - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - // Since wxGrid is not implemented, we won't get edit events - // This test SHOULD FAIL - const hasEditEvent = logs.some(l => l.includes('Grid cell') && l.includes('changed')); + const hasEditEvent = testLogger.consoleLogs.some(l => l.includes('Grid cell') && l.includes('changed')); expect(hasEditEvent, 'wxGrid should emit cell changed events when editing').toBe(true); }); }); @@ -261,25 +182,16 @@ test.describe('Grid Tab Tests', () => { test.describe('wxSpinCtrl', () => { - test('SpinCtrl renders and is visible', async ({ page }) => { - const errors: string[] = []; - - page.on('pageerror', err => { - errors.push(`[PAGE_ERROR] ${err.message}`); - }); - + test('SpinCtrl renders and is visible', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToGridTab(page, box); await page.screenshot({ path: 'test-results/spinctrl-01-visible.png', fullPage: true }); // SpinCtrl should be visible - check for its box and arrows in the canvas - // Looking at the screenshot, controls are at top of panel around y=120-180 const hasSpinCtrlContent = await page.evaluate(() => { const canvas = document.querySelector('#canvas') as HTMLCanvasElement; if (!canvas) return false; @@ -288,7 +200,6 @@ test.describe('Grid Tab Tests', () => { if (!ctx) return false; // SpinCtrl is at the top left of the Grid tab content, around y=130-170 - // It shows "Value (0-100): 50" with up/down arrows const imageData = ctx.getImageData(10, 130, 180, 50); const data = imageData.data; @@ -296,48 +207,32 @@ test.describe('Grid Tab Tests', () => { let nonBackgroundPixels = 0; for (let i = 0; i < data.length; i += 4) { const r = data[i], g = data[i+1], b = data[i+2]; - // Check for non-light pixels (text is dark, borders are gray) if (r < 230 || g < 230 || b < 230) { nonBackgroundPixels++; } } - console.log(`[SPINCTRL_CHECK] Non-background pixels: ${nonBackgroundPixels}`); return nonBackgroundPixels > 50; // Should have visible text and borders }); expect(hasSpinCtrlContent, 'wxSpinCtrl should be visible with borders and arrows').toBe(true); - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('SpinCtrl up/down arrows work', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => { - logs.push(msg.text()); - }); - + test('SpinCtrl up/down arrows work', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToGridTab(page, box); - // Click up arrow on SpinCtrl (right side of the control, upper half) - // SpinCtrl is around y=350, arrows on right side around x=220 + // Click up arrow on SpinCtrl await page.mouse.click(box.x + 220, box.y + 350); await page.waitForTimeout(200); await page.screenshot({ path: 'test-results/spinctrl-02-after-click.png', fullPage: true }); - // Check for spin events - const hasSpinEvent = logs.some(l => l.includes('SpinCtrl') || l.includes('spin')); - console.log('SpinCtrl logs:', logs.filter(l => l.includes('Spin'))); - - // If SpinCtrl is working, we should see events (or at least no crashes) // This is a smoke test - if it doesn't crash, that's good expect(true).toBe(true); }); @@ -349,25 +244,16 @@ test.describe('Grid Tab Tests', () => { test.describe('wxSearchCtrl', () => { - test('SearchCtrl renders and is visible', async ({ page }) => { - const errors: string[] = []; - - page.on('pageerror', err => { - errors.push(`[PAGE_ERROR] ${err.message}`); - }); - + test('SearchCtrl renders and is visible', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToGridTab(page, box); await page.screenshot({ path: 'test-results/searchctrl-01-visible.png', fullPage: true }); // SearchCtrl should be visible - check for its text field and buttons - // Looking at the screenshot, SearchCtrl is on the right side around y=130-170 const hasSearchCtrlContent = await page.evaluate(() => { const canvas = document.querySelector('#canvas') as HTMLCanvasElement; if (!canvas) return false; @@ -376,7 +262,6 @@ test.describe('Grid Tab Tests', () => { if (!ctx) return false; // SearchCtrl is on the right side of the Grid tab content, around y=130-170 - // It shows "Search..." placeholder with search icon and cancel button const imageData = ctx.getImageData(200, 130, 300, 50); const data = imageData.data; @@ -384,33 +269,23 @@ test.describe('Grid Tab Tests', () => { let nonBackgroundPixels = 0; for (let i = 0; i < data.length; i += 4) { const r = data[i], g = data[i+1], b = data[i+2]; - // Check for non-light pixels (borders, text, icons) if (r < 230 || g < 230 || b < 230) { nonBackgroundPixels++; } } - console.log(`[SEARCHCTRL_CHECK] Non-background pixels: ${nonBackgroundPixels}`); return nonBackgroundPixels > 50; // Should have visible borders and placeholder text }); expect(hasSearchCtrlContent, 'wxSearchCtrl should be visible with text field and buttons').toBe(true); - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('SearchCtrl accepts text input', async ({ page }) => { - const logs: string[] = []; - - page.on('console', msg => { - logs.push(msg.text()); - }); - + test('SearchCtrl accepts text input', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToGridTab(page, box); @@ -430,10 +305,6 @@ test.describe('Grid Tab Tests', () => { await page.screenshot({ path: 'test-results/searchctrl-03-after-enter.png', fullPage: true }); - // Check for search events - const hasSearchEvent = logs.some(l => l.includes('Search') || l.includes('search')); - console.log('SearchCtrl logs:', logs.filter(l => l.toLowerCase().includes('search'))); - // This is a smoke test - verify no crashes expect(true).toBe(true); }); @@ -443,19 +314,11 @@ test.describe('Grid Tab Tests', () => { // Combined Tab Test - Basic smoke test for the Grid tab // ============================================================================ - test('Grid tab loads without crash', async ({ page }) => { - const errors: string[] = []; - - page.on('pageerror', err => { - errors.push(`[PAGE_ERROR] ${err.message}`); - }); - + test('Grid tab loads without crash', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToGridTab(page, box); await page.screenshot({ path: 'test-results/grid-tab-final.png', fullPage: true }); @@ -467,6 +330,6 @@ test.describe('Grid Tab Tests', () => { expect(isResponsive).toBe(true); // Filter out favicon errors which are expected - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); }); diff --git a/tests/e2e/layout.spec.ts b/tests/e2e/layout.spec.ts index b8aab80..ac4f49a 100644 --- a/tests/e2e/layout.spec.ts +++ b/tests/e2e/layout.spec.ts @@ -1,46 +1,21 @@ // wxSplitterWindow and wxScrolledWindow Tests - Layout controls KiCad uses -import { test, expect, Page } from '@playwright/test'; - -const MAIN_CANVAS = '#canvas'; - -async function tryLoadApp(page: Page, timeout = 15000) { - try { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout }); - await page.waitForTimeout(500); - return true; - } catch { - return false; - } -} +import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { - test('Layout test app loads successfully', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(`[PAGE_ERROR] ${err.message}`)); - page.on('console', msg => logs.push(`[${msg.type()}] ${msg.text()}`)); - + test('Layout test app loads successfully', async ({ page, testLogger }) => { await page.goto('/standalone/layout/layout_test.html'); const loaded = await tryLoadApp(page); await page.screenshot({ path: 'test-results/layout-01-loaded.png', fullPage: true }); - const hasStartup = logs.some(l => l.includes('Layout test app started')); - - console.log('Layout loaded:', loaded); - console.log('Layout logs:', logs.filter(l => l.includes('LAYOUT'))); - console.log('Layout errors:', errors); + const hasStartup = testLogger.consoleLogs.some(l => l.includes('Layout test app started')); expect(loaded, 'Layout app should load').toBe(true); - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('Splitter is visible with two panes', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Splitter is visible with two panes', async ({ page, testLogger }) => { await page.goto('/standalone/layout/layout_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -50,16 +25,12 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { await page.screenshot({ path: 'test-results/layout-02-splitter.png', fullPage: true }); - const hasSplitterLog = logs.some(l => l.includes('Splitter position')); - console.log('Splitter logs:', logs.filter(l => l.includes('LAYOUT') || l.includes('Splitter'))); + const hasSplitterLog = testLogger.consoleLogs.some(l => l.includes('Splitter position')); expect(hasSplitterLog).toBe(true); }); - test('Splitter sash can be dragged', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Splitter sash can be dragged', async ({ page, testLogger }) => { await page.goto('/standalone/layout/layout_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -67,9 +38,7 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Splitter sash is at initial position 300 from left const sashX = box.x + 300; @@ -84,17 +53,11 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { await page.screenshot({ path: 'test-results/layout-03-sash-dragged.png', fullPage: true }); - const hasSashEvent = logs.some(l => l.includes('Splitter sash moved')); - console.log('Sash events:', logs.filter(l => l.includes('sash') || l.includes('Splitter'))); - // Smoke test - verify no crash expect(true).toBe(true); }); - test('Scrolled windows show content', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Scrolled windows show content', async ({ page, testLogger }) => { await page.goto('/standalone/layout/layout_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -102,9 +65,7 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Scroll in left pane await page.mouse.move(box.x + 150, box.y + 200); @@ -123,13 +84,7 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { expect(true).toBe(true); }); - test('Layout controls work together', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(err.message)); - page.on('console', msg => logs.push(msg.text())); - + test('Layout controls work together', async ({ page, testLogger }) => { await page.goto('/standalone/layout/layout_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -137,9 +92,7 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Drag sash await page.mouse.move(box.x + 300, box.y + 200); @@ -160,9 +113,6 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { await page.screenshot({ path: 'test-results/layout-06-combined.png', fullPage: true }); - console.log('\n=== LAYOUT EVENTS ==='); - logs.filter(l => l.includes('LAYOUT')).forEach(l => console.log(l)); - - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); }); diff --git a/tests/e2e/menu.spec.ts b/tests/e2e/menu.spec.ts index 513b20c..345893d 100644 --- a/tests/e2e/menu.spec.ts +++ b/tests/e2e/menu.spec.ts @@ -1,55 +1,23 @@ // wxMenuBar Tests - Menu system for KiCad -import { test, expect, Page } from '@playwright/test'; - -const MAIN_CANVAS = '#canvas'; - -async function waitForApp(page: Page, timeout = 30000) { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout }); - await page.waitForTimeout(500); -} - -async function tryLoadApp(page: Page) { - try { - await waitForApp(page, 15000); - return true; - } catch { - return false; - } -} +import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; test.describe('wxMenuBar Tests', () => { - test('Menu test app loads successfully', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => { - errors.push(`[PAGE_ERROR] ${err.message}`); - }); - page.on('console', msg => { - logs.push(`[${msg.type()}] ${msg.text()}`); - }); - + test('Menu test app loads successfully', async ({ page, testLogger }) => { await page.goto('/standalone/menu/menu_test.html'); const loaded = await tryLoadApp(page); await page.screenshot({ path: 'test-results/menu-01-loaded.png', fullPage: true }); - const hasStartupLog = logs.some(l => l.includes('wxMenuBar test app started') || l.includes('Menu test app started')); - - console.log('Menu app logs:', logs.filter(l => l.includes('MENU'))); - console.log('Menu app errors:', errors); - console.log('Menu app loaded:', loaded); - console.log('Has startup log:', hasStartupLog); + const hasStartupLog = testLogger.consoleLogs.some(l => + l.includes('wxMenuBar test app started') || l.includes('Menu test app started') + ); expect(loaded, 'wxMenuBar app should load successfully').toBe(true); - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('Menu bar is visible', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Menu bar is visible', async ({ page, testLogger }) => { await page.goto('/standalone/menu/menu_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -60,16 +28,14 @@ test.describe('wxMenuBar Tests', () => { await page.screenshot({ path: 'test-results/menu-02-menubar.png', fullPage: true }); // Check that app started with menu bar created - const hasMenuBarLog = logs.some(l => l.includes('Menu bar created') || l.includes('Menu test app started')); - console.log('Menu bar logs:', logs.filter(l => l.includes('menu') || l.includes('Menu'))); + const hasMenuBarLog = testLogger.consoleLogs.some(l => + l.includes('Menu bar created') || l.includes('Menu test app started') + ); expect(hasMenuBarLog).toBe(true); }); - test('File menu can be clicked', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('File menu can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/menu/menu_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -77,9 +43,7 @@ test.describe('wxMenuBar Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click on File menu (top left of menu bar, around x=30, y=10-25) await page.mouse.click(box.x + 30, box.y + 15); @@ -91,10 +55,7 @@ test.describe('wxMenuBar Tests', () => { expect(true).toBe(true); }); - test('Edit menu can be clicked', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Edit menu can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/menu/menu_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -102,9 +63,7 @@ test.describe('wxMenuBar Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click on Edit menu (next to File, around x=70, y=15) await page.mouse.click(box.x + 70, box.y + 15); @@ -115,13 +74,7 @@ test.describe('wxMenuBar Tests', () => { expect(true).toBe(true); }); - test('Multiple menus can be accessed', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(err.message)); - page.on('console', msg => logs.push(msg.text())); - + test('Multiple menus can be accessed', async ({ page, testLogger }) => { await page.goto('/standalone/menu/menu_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -129,9 +82,7 @@ test.describe('wxMenuBar Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click through all menus const menuPositions = [30, 70, 110, 150, 190]; // File, Edit, View, Tools, Help @@ -142,9 +93,6 @@ test.describe('wxMenuBar Tests', () => { await page.screenshot({ path: 'test-results/menu-05-all-menus.png', fullPage: true }); - console.log('\n=== MENU EVENTS ==='); - logs.filter(l => l.includes('MENU')).forEach(l => console.log(l)); - - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); }); diff --git a/tests/e2e/opengl.spec.ts b/tests/e2e/opengl.spec.ts index 0eb1cf7..d9cbe36 100644 --- a/tests/e2e/opengl.spec.ts +++ b/tests/e2e/opengl.spec.ts @@ -1,21 +1,14 @@ -import { test, expect, Page } from '@playwright/test'; +import { test, expect, MAIN_CANVAS, waitForApp, getCanvasBox } from './utils/fixtures'; -const MAIN_CANVAS = '#canvas'; - -async function waitForApp(page: Page) { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout: 30000 }); - await page.waitForTimeout(500); -} - -async function switchToOpenGLTab(page: Page, box: { x: number; y: number }) { +async function switchToOpenGLTab(page: any, box: { x: number; y: number }) { // Click OpenGL tab (fifth tab, around x=280) await page.mouse.click(box.x + 280, box.y + 35); await page.waitForTimeout(1000); } // Open the test dropdown and select by index (0-based) -async function selectGLTest(page: Page, box: { x: number; y: number }, index: number) { - // Click the dropdown arrow to open it (dropdown is at around x=170, y=155, arrow at right edge ~x=290) +async function selectGLTest(page: any, box: { x: number; y: number }, index: number) { + // Click the dropdown arrow to open it await page.mouse.click(box.x + 290, box.y + 155); await page.waitForTimeout(300); @@ -26,41 +19,17 @@ async function selectGLTest(page: Page, box: { x: number; y: number }, index: nu } test.describe('OpenGL Tests', () => { - test('Vertex Arrays test - debug freeze', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => { - errors.push(`[PAGE_ERROR] ${err.message}\n${err.stack || 'No stack'}`); - }); - page.on('console', msg => { - const text = msg.text(); - logs.push(`[${msg.type()}] ${text}`); - if (msg.type() === 'error') { - errors.push(`[CONSOLE_ERROR] ${text}`); - } - }); - + test('Vertex Arrays test - debug freeze', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Switch to OpenGL tab await switchToOpenGLTab(page, box); await page.screenshot({ path: 'test-results/gl-01-opengl-tab.png', fullPage: true }); - // The dropdown has these options (from minimal_test.cpp): - // 0: "Immediate Mode (glBegin/glEnd)" - // 1: "Matrix Ops (glPushMatrix)" - // 2: "Vertex Arrays (glVertexPointer)" - // 3: "State Mgmt (glEnable/glBlend)" - // 4: "Texture Coords" - // Select "Vertex Arrays" test (index 2) - console.log('Selecting Vertex Arrays test...'); await selectGLTest(page, box, 2); await page.screenshot({ path: 'test-results/gl-02-vertex-arrays-selected.png', fullPage: true }); @@ -70,12 +39,6 @@ test.describe('OpenGL Tests', () => { await page.screenshot({ path: 'test-results/gl-03-after-wait.png', fullPage: true }); - // Print all logs for debugging - console.log('\n=== CONSOLE LOGS ==='); - logs.forEach(log => console.log(log)); - console.log('\n=== ERRORS ==='); - errors.forEach(err => console.log(err)); - // Check if app is still responsive const isResponsive = await page.evaluate(() => { return document.querySelector('#canvas') !== null; @@ -83,23 +46,11 @@ test.describe('OpenGL Tests', () => { expect(isResponsive).toBe(true); }); - test('All GL tests individually', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => { - errors.push(`[PAGE_ERROR] ${err.message}\n${err.stack || 'No stack'}`); - }); - page.on('console', msg => { - logs.push(`[${msg.type()}] ${msg.text()}`); - }); - + test('All GL tests individually', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToOpenGLTab(page, box); @@ -112,17 +63,13 @@ test.describe('OpenGL Tests', () => { ]; for (let i = 0; i < testNames.length; i++) { - console.log(`\n--- Testing: ${testNames[i]} (index ${i}) ---`); - try { await selectGLTest(page, box, i); await page.screenshot({ path: `test-results/gl-test-${i}-${testNames[i].replace(/\s+/g, '-').toLowerCase()}.png`, fullPage: true }); - console.log(`${testNames[i]}: OK`); } catch (e) { - console.log(`${testNames[i]}: FAILED - ${e}`); await page.screenshot({ path: `test-results/gl-test-${i}-${testNames[i].replace(/\s+/g, '-').toLowerCase()}-error.png`, fullPage: true @@ -131,24 +78,13 @@ test.describe('OpenGL Tests', () => { await page.waitForTimeout(500); } - - console.log('\n=== ERRORS ==='); - errors.forEach(err => console.log(err)); }); - test('Run All Tests button', async ({ page }) => { - const errors: string[] = []; - - page.on('pageerror', err => { - errors.push(`${err.message}\n${err.stack || 'No stack'}`); - }); - + test('Run All Tests button', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); await switchToOpenGLTab(page, box); diff --git a/tests/e2e/timer.spec.ts b/tests/e2e/timer.spec.ts index 499ccf0..5533103 100644 --- a/tests/e2e/timer.spec.ts +++ b/tests/e2e/timer.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { test, expect, waitForApp } from './utils/fixtures'; test.describe('wxTimer Tests', () => { test.beforeEach(async ({ page }) => { @@ -10,38 +10,19 @@ test.describe('wxTimer Tests', () => { await page.waitForTimeout(1000); }); - test('Timer test app loads successfully', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => { - if (msg.text().includes('[TIMER_')) { - consoleLogs.push(msg.text()); - } - }); - + test('Timer test app loads successfully', async ({ page, testLogger }) => { await page.waitForTimeout(500); - const hasStartupLog = consoleLogs.some(log => + const hasStartupLog = testLogger.consoleLogs.some(log => log.includes('TIMER_TEST') && log.includes('started successfully') ); - console.log('Timer app logs:', consoleLogs); - console.log('Timer app loaded:', hasStartupLog); - await page.screenshot({ path: 'test-results/timer-01-loaded.png' }); - const pageErrors: string[] = []; - page.on('pageerror', err => pageErrors.push(err.message)); - expect(pageErrors.length).toBe(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('Slow timer can be started and stopped', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => { - if (msg.text().includes('[TIMER_')) { - consoleLogs.push(msg.text()); - } - }); - + test('Slow timer can be started and stopped', async ({ page, testLogger }) => { await page.waitForTimeout(500); const canvas = page.locator('canvas'); @@ -50,10 +31,9 @@ test.describe('wxTimer Tests', () => { await canvas.click({ position: { x: 240, y: 125 } }); await page.waitForTimeout(500); - console.log('After start:', consoleLogs); await page.screenshot({ path: 'test-results/timer-02-started.png' }); - const hasStartEvent = consoleLogs.some(log => + const hasStartEvent = testLogger.consoleLogs.some(log => log.includes('Slow timer started') ); expect(hasStartEvent).toBe(true); @@ -62,32 +42,23 @@ test.describe('wxTimer Tests', () => { await page.waitForTimeout(1500); await page.screenshot({ path: 'test-results/timer-03-ticked.png' }); - const hasTickEvent = consoleLogs.some(log => + const hasTickEvent = testLogger.consoleLogs.some(log => log.includes('TIMER_TICK') ); - console.log('Has tick event:', hasTickEvent); // Click Stop button await canvas.click({ position: { x: 320, y: 125 } }); await page.waitForTimeout(500); - console.log('After stop:', consoleLogs); await page.screenshot({ path: 'test-results/timer-04-stopped.png' }); - const hasStopEvent = consoleLogs.some(log => + const hasStopEvent = testLogger.consoleLogs.some(log => log.includes('Slow timer stopped') ); expect(hasStopEvent).toBe(true); }); - test('Fast timer can be started and updates gauge', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => { - if (msg.text().includes('[TIMER_')) { - consoleLogs.push(msg.text()); - } - }); - + test('Fast timer can be started and updates gauge', async ({ page, testLogger }) => { await page.waitForTimeout(500); const canvas = page.locator('canvas'); @@ -96,10 +67,9 @@ test.describe('wxTimer Tests', () => { await canvas.click({ position: { x: 220, y: 260 } }); await page.waitForTimeout(500); - console.log('Fast timer started:', consoleLogs); await page.screenshot({ path: 'test-results/timer-05-fast-started.png' }); - const hasFastStartEvent = consoleLogs.some(log => + const hasFastStartEvent = testLogger.consoleLogs.some(log => log.includes('Fast timer started') ); expect(hasFastStartEvent).toBe(true); @@ -112,23 +82,15 @@ test.describe('wxTimer Tests', () => { await canvas.click({ position: { x: 340, y: 260 } }); await page.waitForTimeout(500); - console.log('Fast timer stopped:', consoleLogs); await page.screenshot({ path: 'test-results/timer-07-fast-stopped.png' }); - const hasFastStopEvent = consoleLogs.some(log => + const hasFastStopEvent = testLogger.consoleLogs.some(log => log.includes('Fast timer stopped') ); expect(hasFastStopEvent).toBe(true); }); - test('Reset counters button works', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => { - if (msg.text().includes('[TIMER_EVENT]')) { - consoleLogs.push(msg.text()); - } - }); - + test('Reset counters button works', async ({ page, testLogger }) => { await page.waitForTimeout(500); const canvas = page.locator('canvas'); @@ -141,10 +103,9 @@ test.describe('wxTimer Tests', () => { await canvas.click({ position: { x: 300, y: 320 } }); await page.waitForTimeout(500); - console.log('After reset:', consoleLogs); await page.screenshot({ path: 'test-results/timer-08-reset.png' }); - const hasResetEvent = consoleLogs.some(log => + const hasResetEvent = testLogger.consoleLogs.some(log => log.includes('Counters reset') ); expect(hasResetEvent).toBe(true); diff --git a/tests/e2e/toolbar.spec.ts b/tests/e2e/toolbar.spec.ts index 52d39d8..b748301 100644 --- a/tests/e2e/toolbar.spec.ts +++ b/tests/e2e/toolbar.spec.ts @@ -1,46 +1,21 @@ // wxToolBar and wxStatusBar Tests - Toolbar and status bar KiCad uses -import { test, expect, Page } from '@playwright/test'; - -const MAIN_CANVAS = '#canvas'; - -async function tryLoadApp(page: Page, timeout = 15000) { - try { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout }); - await page.waitForTimeout(500); - return true; - } catch { - return false; - } -} +import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; test.describe('wxToolBar & wxStatusBar Tests', () => { - test('Toolbar test app loads successfully', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(`[PAGE_ERROR] ${err.message}`)); - page.on('console', msg => logs.push(`[${msg.type()}] ${msg.text()}`)); - + test('Toolbar test app loads successfully', async ({ page, testLogger }) => { await page.goto('/standalone/toolbar/toolbar_test.html'); const loaded = await tryLoadApp(page); await page.screenshot({ path: 'test-results/toolbar-01-loaded.png', fullPage: true }); - const hasStartup = logs.some(l => l.includes('Toolbar test app started')); - - console.log('Toolbar loaded:', loaded); - console.log('Toolbar logs:', logs.filter(l => l.includes('TOOLBAR'))); - console.log('Toolbar errors:', errors); + const hasStartup = testLogger.consoleLogs.some(l => l.includes('Toolbar test app started')); expect(loaded, 'Toolbar app should load').toBe(true); - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('Toolbar buttons are visible', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Toolbar buttons are visible', async ({ page, testLogger }) => { await page.goto('/standalone/toolbar/toolbar_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -50,16 +25,12 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { await page.screenshot({ path: 'test-results/toolbar-02-buttons.png', fullPage: true }); - const hasToolbarLog = logs.some(l => l.includes('Toolbar created')); - console.log('Toolbar logs:', logs.filter(l => l.includes('TOOLBAR') || l.includes('Toolbar'))); + const hasToolbarLog = testLogger.consoleLogs.some(l => l.includes('Toolbar created')); expect(hasToolbarLog).toBe(true); }); - test('New tool button can be clicked', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('New tool button can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/toolbar/toolbar_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -67,9 +38,7 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click New button (first tool, around x=30) await page.mouse.click(box.x + 30, box.y + 45); @@ -77,17 +46,11 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { await page.screenshot({ path: 'test-results/toolbar-03-new-clicked.png', fullPage: true }); - const hasNewEvent = logs.some(l => l.includes('New clicked')); - console.log('New logs:', logs.filter(l => l.includes('TOOLBAR') || l.includes('New'))); - // Smoke test expect(true).toBe(true); }); - test('Zoom tools can be clicked', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Zoom tools can be clicked', async ({ page, testLogger }) => { await page.goto('/standalone/toolbar/toolbar_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -95,9 +58,7 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click Zoom In await page.mouse.click(box.x + 230, box.y + 45); @@ -109,16 +70,10 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { await page.screenshot({ path: 'test-results/toolbar-04-zoom.png', fullPage: true }); - const hasZoomEvent = logs.some(l => l.includes('Zoom')); - console.log('Zoom logs:', logs.filter(l => l.includes('TOOLBAR') || l.includes('Zoom'))); - expect(true).toBe(true); }); - test('Toggle tool changes state', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Toggle tool changes state', async ({ page, testLogger }) => { await page.goto('/standalone/toolbar/toolbar_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -126,9 +81,7 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click Toggle button (after separator, around x=350) await page.mouse.click(box.x + 350, box.y + 45); @@ -142,16 +95,10 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { await page.screenshot({ path: 'test-results/toolbar-06-toggle-off.png', fullPage: true }); - const hasToggleEvents = logs.some(l => l.includes('Toggle')); - console.log('Toggle logs:', logs.filter(l => l.includes('TOOLBAR') || l.includes('Toggle'))); - expect(true).toBe(true); }); - test('Status bar shows messages', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => logs.push(msg.text())); - + test('Status bar shows messages', async ({ page, testLogger }) => { await page.goto('/standalone/toolbar/toolbar_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -161,19 +108,12 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { await page.screenshot({ path: 'test-results/toolbar-07-statusbar.png', fullPage: true }); - const hasStatusBarLog = logs.some(l => l.includes('Status bar created')); - console.log('Status bar logs:', logs.filter(l => l.includes('TOOLBAR') || l.includes('Status'))); + const hasStatusBarLog = testLogger.consoleLogs.some(l => l.includes('Status bar created')); expect(hasStatusBarLog).toBe(true); }); - test('All toolbar buttons accessible', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(err.message)); - page.on('console', msg => logs.push(msg.text())); - + test('All toolbar buttons accessible', async ({ page, testLogger }) => { await page.goto('/standalone/toolbar/toolbar_test.html'); const loaded = await tryLoadApp(page); if (!loaded) { @@ -181,9 +121,7 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { return; } - const canvas = page.locator(MAIN_CANVAS); - const box = await canvas.boundingBox(); - if (!box) throw new Error('Canvas not found'); + const box = await getCanvasBox(page); // Click all toolbar buttons const buttonPositions = [30, 85, 140, 230, 290, 350]; // New, Open, Save, ZoomIn, ZoomOut, Toggle @@ -194,9 +132,6 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { await page.screenshot({ path: 'test-results/toolbar-08-all-buttons.png', fullPage: true }); - console.log('\n=== TOOLBAR EVENTS ==='); - logs.filter(l => l.includes('TOOLBAR')).forEach(l => console.log(l)); - - expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); }); diff --git a/tests/e2e/tree.spec.ts b/tests/e2e/tree.spec.ts index ac77eee..16acb11 100644 --- a/tests/e2e/tree.spec.ts +++ b/tests/e2e/tree.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from './utils/fixtures'; test.describe('wxTreeCtrl Tests', () => { test.beforeEach(async ({ page }) => { @@ -10,60 +10,31 @@ test.describe('wxTreeCtrl Tests', () => { await page.waitForTimeout(1000); }); - test('Tree test app loads successfully', async ({ page }) => { - const consoleLogs: string[] = []; - const pageErrors: string[] = []; - - page.on('console', msg => { - if (msg.text().includes('[TREE_')) { - consoleLogs.push(msg.text()); - } - }); - page.on('pageerror', err => pageErrors.push(err.message)); - + test('Tree test app loads successfully', async ({ page, testLogger }) => { await page.waitForTimeout(500); - const hasStartupLog = consoleLogs.some(log => + const hasStartupLog = testLogger.consoleLogs.some(log => log.includes('TREE_TEST') && log.includes('started successfully') ); - console.log('Tree app loaded:', hasStartupLog); - console.log('Tree app logs:', consoleLogs); - console.log('Tree app errors:', pageErrors); - await page.screenshot({ path: 'test-results/tree-01-loaded.png' }); - expect(pageErrors.length).toBe(0); + expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0); }); - test('Tree is populated with KiCad-like hierarchy', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => { - if (msg.text().includes('[TREE_EVENT]')) { - consoleLogs.push(msg.text()); - } - }); - + test('Tree is populated with KiCad-like hierarchy', async ({ page, testLogger }) => { await page.waitForTimeout(500); - const hasPopulatedLog = consoleLogs.some(log => + const hasPopulatedLog = testLogger.consoleLogs.some(log => log.includes('Tree populated with KiCad-like hierarchy') ); - console.log('Tree hierarchy logs:', consoleLogs); await page.screenshot({ path: 'test-results/tree-02-hierarchy.png' }); expect(hasPopulatedLog).toBe(true); }); - test('Tree item can be selected', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => { - if (msg.text().includes('[TREE_EVENT]')) { - consoleLogs.push(msg.text()); - } - }); - + test('Tree item can be selected', async ({ page, testLogger }) => { await page.waitForTimeout(500); const canvas = page.locator('canvas'); @@ -72,23 +43,14 @@ test.describe('wxTreeCtrl Tests', () => { await canvas.click({ position: { x: 100, y: 180 } }); await page.waitForTimeout(300); - console.log('Selection logs:', consoleLogs); await page.screenshot({ path: 'test-results/tree-03-selected.png' }); - const hasSelectionEvent = consoleLogs.some(log => + const hasSelectionEvent = testLogger.consoleLogs.some(log => log.includes('Selection changed') ); - console.log('Has selection event:', hasSelectionEvent); }); - test('Expand All button works', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => { - if (msg.text().includes('[TREE_EVENT]')) { - consoleLogs.push(msg.text()); - } - }); - + test('Expand All button works', async ({ page, testLogger }) => { await page.waitForTimeout(500); const canvas = page.locator('canvas'); @@ -97,23 +59,15 @@ test.describe('wxTreeCtrl Tests', () => { await canvas.click({ position: { x: 80, y: 95 } }); await page.waitForTimeout(500); - console.log('Expand all logs:', consoleLogs); await page.screenshot({ path: 'test-results/tree-04-expanded.png' }); - const hasExpandEvent = consoleLogs.some(log => + const hasExpandEvent = testLogger.consoleLogs.some(log => log.includes('All items expanded') ); expect(hasExpandEvent).toBe(true); }); - test('Collapse All button works', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => { - if (msg.text().includes('[TREE_EVENT]')) { - consoleLogs.push(msg.text()); - } - }); - + test('Collapse All button works', async ({ page, testLogger }) => { await page.waitForTimeout(500); const canvas = page.locator('canvas'); @@ -122,23 +76,15 @@ test.describe('wxTreeCtrl Tests', () => { await canvas.click({ position: { x: 200, y: 95 } }); await page.waitForTimeout(500); - console.log('Collapse all logs:', consoleLogs); await page.screenshot({ path: 'test-results/tree-05-collapsed.png' }); - const hasCollapseEvent = consoleLogs.some(log => + const hasCollapseEvent = testLogger.consoleLogs.some(log => log.includes('All items collapsed') ); expect(hasCollapseEvent).toBe(true); }); - test('Add Item button works with selection', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => { - if (msg.text().includes('[TREE_EVENT]')) { - consoleLogs.push(msg.text()); - } - }); - + test('Add Item button works with selection', async ({ page, testLogger }) => { await page.waitForTimeout(500); const canvas = page.locator('canvas'); @@ -151,23 +97,15 @@ test.describe('wxTreeCtrl Tests', () => { await canvas.click({ position: { x: 310, y: 95 } }); await page.waitForTimeout(500); - console.log('Add item logs:', consoleLogs); await page.screenshot({ path: 'test-results/tree-06-added.png' }); - const hasAddEvent = consoleLogs.some(log => + const hasAddEvent = testLogger.consoleLogs.some(log => log.includes('Added new item') || log.includes('No item selected') ); expect(hasAddEvent).toBe(true); }); - test('Delete Item button works with selection', async ({ page }) => { - const consoleLogs: string[] = []; - page.on('console', msg => { - if (msg.text().includes('[TREE_EVENT]')) { - consoleLogs.push(msg.text()); - } - }); - + test('Delete Item button works with selection', async ({ page, testLogger }) => { await page.waitForTimeout(500); const canvas = page.locator('canvas'); @@ -180,10 +118,9 @@ test.describe('wxTreeCtrl Tests', () => { await canvas.click({ position: { x: 440, y: 95 } }); await page.waitForTimeout(500); - console.log('Delete item logs:', consoleLogs); await page.screenshot({ path: 'test-results/tree-07-deleted.png' }); - const hasDeleteEvent = consoleLogs.some(log => + const hasDeleteEvent = testLogger.consoleLogs.some(log => log.includes('Deleted item') || log.includes('Cannot delete') ); expect(hasDeleteEvent).toBe(true); diff --git a/tests/e2e/utils/fixtures.ts b/tests/e2e/utils/fixtures.ts new file mode 100644 index 0000000..f296b3b --- /dev/null +++ b/tests/e2e/utils/fixtures.ts @@ -0,0 +1,23 @@ +import { test as base } from '@playwright/test'; +import { setupTestLogger, writeTestLogs, TestLogger, MAIN_CANVAS, waitForApp, tryLoadApp, getCanvasBox } from './test-utils'; + +// Extend base test with automatic logging +export const test = base.extend<{ + testLogger: TestLogger; +}>({ + testLogger: async ({ page }, use, testInfo) => { + // Build test name from describe block + test title + const testName = testInfo.titlePath.join(' - '); + + const logger = setupTestLogger(page); + + await use(logger); + + // Write logs after test completes + writeTestLogs(testName, logger); + logger.cleanup(); + }, +}); + +export { expect } from '@playwright/test'; +export { MAIN_CANVAS, waitForApp, tryLoadApp, getCanvasBox }; diff --git a/tests/e2e/utils/test-utils.ts b/tests/e2e/utils/test-utils.ts new file mode 100644 index 0000000..b32e6ee --- /dev/null +++ b/tests/e2e/utils/test-utils.ts @@ -0,0 +1,94 @@ +import { Page } from '@playwright/test'; +import * as fs from 'fs'; +import * as path from 'path'; + +export const MAIN_CANVAS = '#canvas'; +export const LOGS_DIR = path.join(__dirname, '..', '..', 'logs'); + +export interface TestLogger { + consoleLogs: string[]; + errors: string[]; + cleanup: () => void; +} + +// Ensure logs directory exists +export function ensureLogsDir() { + if (!fs.existsSync(LOGS_DIR)) { + fs.mkdirSync(LOGS_DIR, { recursive: true }); + } +} + +// Setup logging for a test - captures console and errors +export function setupTestLogger(page: Page): TestLogger { + const consoleLogs: string[] = []; + const errors: string[] = []; + + const consoleHandler = (msg: any) => { + const type = msg.type(); + const text = msg.text(); + const timestamp = new Date().toISOString(); + consoleLogs.push(`[${timestamp}] [${type.toUpperCase()}] ${text}`); + }; + + const errorHandler = (err: Error) => { + const timestamp = new Date().toISOString(); + const stack = err.stack || 'No stack trace available'; + errors.push(`[${timestamp}] [ERROR] ${err.message}\n${stack}`); + }; + + page.on('console', consoleHandler); + page.on('pageerror', errorHandler); + + const cleanup = () => { + page.off('console', consoleHandler); + page.off('pageerror', errorHandler); + }; + + return { consoleLogs, errors, cleanup }; +} + +// Write logs to files after test completion +export function writeTestLogs(testName: string, logger: TestLogger) { + ensureLogsDir(); + + // Sanitize test name for filesystem + const safeTestName = testName + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); + + // Always write console log file + const logFile = path.join(LOGS_DIR, `${safeTestName}.log`); + fs.writeFileSync(logFile, logger.consoleLogs.join('\n')); + + // Only write error file if there are errors (excluding favicon) + const realErrors = logger.errors.filter(e => !e.includes('favicon')); + if (realErrors.length > 0) { + const errorFile = path.join(LOGS_DIR, `${safeTestName}.errors.log`); + fs.writeFileSync(errorFile, realErrors.join('\n\n')); + } +} + +// Helper to wait for app initialization +export async function waitForApp(page: Page, timeout = 30000) { + await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout }); + await page.waitForTimeout(500); +} + +// Helper to try loading app with fallback +export async function tryLoadApp(page: Page, timeout = 15000): Promise { + try { + await waitForApp(page, timeout); + return true; + } catch { + return false; + } +} + +// Get canvas bounding box helper +export async function getCanvasBox(page: Page) { + const canvas = page.locator(MAIN_CANVAS); + const box = await canvas.boundingBox(); + if (!box) throw new Error('Canvas not found'); + return box; +} diff --git a/tests/e2e/wxwidgets.spec.ts b/tests/e2e/wxwidgets.spec.ts index a90a04e..082ac91 100644 --- a/tests/e2e/wxwidgets.spec.ts +++ b/tests/e2e/wxwidgets.spec.ts @@ -1,13 +1,5 @@ -import { test, expect, Page } from '@playwright/test'; - -// Use #canvas for the main canvas (wxWidgets creates window-specific canvases too) -const MAIN_CANVAS = '#canvas'; - -// Helper to wait for app to be fully loaded -async function waitForApp(page: Page) { - await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout: 30000 }); - await page.waitForTimeout(500); // Let UI settle -} +import { test, expect, MAIN_CANVAS, waitForApp } from './utils/fixtures'; +import { Page } from '@playwright/test'; // Capture console events with [EVENT] prefix function captureEvents(page: Page): string[] { @@ -56,19 +48,7 @@ async function dragCanvas(page: Page, startX: number, startY: number, endX: numb } test.describe('wxWidgets WASM - Diagnostics', () => { - test('comprehensive UI interaction test', async ({ page }) => { - const allLogs: string[] = []; - const errors: string[] = []; - - // Capture ALL console messages - page.on('console', msg => { - allLogs.push(`[${msg.type()}] ${msg.text()}`); - }); - page.on('pageerror', err => { - // Include stack trace for better debugging - const stack = err.stack || 'No stack trace available'; - errors.push(`[PAGE_ERROR] ${err.message}\n${stack}`); - }); + test('comprehensive UI interaction test', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); @@ -80,8 +60,8 @@ test.describe('wxWidgets WASM - Diagnostics', () => { await page.waitForSelector('#canvas', { state: 'visible', timeout: 30000 }); } catch (e) { await page.screenshot({ path: 'test-results/02-timeout.png', fullPage: true }); - console.log('Logs so far:', allLogs); - console.log('Errors:', errors); + console.log('Logs so far:', testLogger.consoleLogs); + console.log('Errors:', testLogger.errors); throw e; } @@ -302,18 +282,13 @@ test.describe('wxWidgets WASM - Diagnostics', () => { // Print all logs console.log('\n=== ALL CONSOLE LOGS ==='); - allLogs.forEach(log => console.log(log)); + testLogger.consoleLogs.forEach(log => console.log(log)); console.log('\n=== ALL ERRORS ==='); - errors.forEach(err => console.log(err)); + testLogger.errors.forEach(err => console.log(err)); console.log('========================\n'); - // Save logs to file - const fs = require('fs'); - fs.writeFileSync('test-results/console-logs.txt', allLogs.join('\n')); - fs.writeFileSync('test-results/errors.txt', errors.join('\n')); - // Fail test if there are critical errors - const criticalErrors = errors.filter(e => + const criticalErrors = testLogger.errors.filter(e => !e.includes('SharedArrayBuffer') && !e.includes('cross-origin') ); @@ -326,20 +301,12 @@ test.describe('wxWidgets WASM - Diagnostics', () => { }); test.describe('wxWidgets WASM - Loading', () => { - test('app loads without JavaScript errors', async ({ page }) => { - const errors: string[] = []; - page.on('pageerror', err => errors.push(`${err.message}\n${err.stack || 'No stack'}`)); - page.on('console', msg => { - if (msg.type() === 'error') { - errors.push(msg.text()); - } - }); - + test('app loads without JavaScript errors', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); // Filter out known non-critical errors - const criticalErrors = errors.filter(e => + const criticalErrors = testLogger.errors.filter(e => !e.includes('SharedArrayBuffer') && !e.includes('cross-origin') && !isKnownWarning(e) @@ -348,7 +315,7 @@ test.describe('wxWidgets WASM - Loading', () => { expect(criticalErrors).toHaveLength(0); }); - test('canvas element is rendered with dimensions', async ({ page }) => { + test('canvas element is rendered with dimensions', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); const canvas = page.locator(MAIN_CANVAS); @@ -360,7 +327,7 @@ test.describe('wxWidgets WASM - Loading', () => { expect(box?.height).toBeGreaterThan(0); }); - test('loading progress completes', async ({ page }) => { + test('loading progress completes', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await page.waitForFunction(() => { @@ -373,7 +340,7 @@ test.describe('wxWidgets WASM - Loading', () => { }, { timeout: 30000 }); }); - test('WASM module initializes successfully', async ({ page }) => { + test('WASM module initializes successfully', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -384,20 +351,19 @@ test.describe('wxWidgets WASM - Loading', () => { expect(moduleExists).toBe(true); }); - test('application started event is logged', async ({ page }) => { - const events = captureEvents(page); + test('application started event is logged', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); // Wait for the startup event to be logged await page.waitForTimeout(500); - expect(events.some(e => e.includes('Application started'))).toBe(true); + expect(testLogger.consoleLogs.some(e => e.includes('Application started'))).toBe(true); }); }); test.describe('wxWidgets WASM - Canvas Interaction', () => { - test('canvas receives click events', async ({ page }) => { + test('canvas receives click events', async ({ page, testLogger }) => { const events = captureEvents(page); await page.goto('/minimal_test.html'); await waitForApp(page); @@ -411,7 +377,7 @@ test.describe('wxWidgets WASM - Canvas Interaction', () => { expect(events.length).toBeGreaterThanOrEqual(1); }); - test('canvas receives keyboard events', async ({ page }) => { + test('canvas receives keyboard events', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -428,7 +394,7 @@ test.describe('wxWidgets WASM - Canvas Interaction', () => { }); test.describe('wxWidgets WASM - Mouse Drawing', () => { - test('mouse drag creates drawing stroke', async ({ page }) => { + test('mouse drag creates drawing stroke', async ({ page, testLogger }) => { const events = captureEvents(page); await page.goto('/minimal_test.html'); await waitForApp(page); @@ -458,7 +424,7 @@ test.describe('wxWidgets WASM - Mouse Drawing', () => { }); test.describe('wxWidgets WASM - Event Logging', () => { - test('events are logged to console with [EVENT] prefix', async ({ page }) => { + test('events are logged to console with [EVENT] prefix', async ({ page, testLogger }) => { const events = captureEvents(page); await page.goto('/minimal_test.html'); await waitForApp(page); @@ -471,7 +437,7 @@ test.describe('wxWidgets WASM - Event Logging', () => { expect(events[0]).toContain('Application started'); }); - test('multiple interactions produce multiple log entries', async ({ page }) => { + test('multiple interactions produce multiple log entries', async ({ page, testLogger }) => { const events = captureEvents(page); await page.goto('/minimal_test.html'); await waitForApp(page); @@ -492,7 +458,7 @@ test.describe('wxWidgets WASM - Event Logging', () => { }); test.describe('wxWidgets WASM - Visual Rendering', () => { - test('frame renders with visible content', async ({ page }) => { + test('frame renders with visible content', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -508,7 +474,7 @@ test.describe('wxWidgets WASM - Visual Rendering', () => { expect(box?.height).toBeGreaterThan(100); }); - test('window has reasonable dimensions', async ({ page }) => { + test('window has reasonable dimensions', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -523,10 +489,7 @@ test.describe('wxWidgets WASM - Visual Rendering', () => { }); test.describe('wxWidgets WASM - Stability', () => { - test('app remains stable after multiple interactions', async ({ page }) => { - const errors: string[] = []; - page.on('pageerror', err => errors.push(`${err.message}\n${err.stack || 'No stack'}`)); - + test('app remains stable after multiple interactions', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -541,7 +504,7 @@ test.describe('wxWidgets WASM - Stability', () => { await expect(canvas).toBeVisible(); // No JavaScript errors should have occurred - const criticalErrors = errors.filter(e => + const criticalErrors = testLogger.errors.filter(e => !e.includes('SharedArrayBuffer') && !e.includes('cross-origin') && !isKnownWarning(e) @@ -549,7 +512,7 @@ test.describe('wxWidgets WASM - Stability', () => { expect(criticalErrors).toHaveLength(0); }); - test('app handles rapid mouse movements', async ({ page }) => { + test('app handles rapid mouse movements', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -570,12 +533,7 @@ test.describe('wxWidgets WASM - Stability', () => { }); test.describe('wxWidgets WASM - OpenGL', () => { - test('OpenGL tab switches successfully', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => { - logs.push(msg.text()); - }); - + test('OpenGL tab switches successfully', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -588,25 +546,14 @@ test.describe('wxWidgets WASM - OpenGL', () => { await page.waitForTimeout(1500); // Give GL time to initialize // Check that we switched to the OpenGL tab - const tabChanged = logs.some(log => log.includes('Tab changed to: OpenGL')); + const tabChanged = testLogger.consoleLogs.some(log => log.includes('Tab changed to: OpenGL')); expect(tabChanged).toBe(true); // Save screenshot for visual verification await page.screenshot({ path: 'test-results/opengl-tab-initial.png', fullPage: true }); }); - test('OpenGL tab interaction without crashes', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(`${err.message}\n${err.stack || 'No stack'}`)); - page.on('console', msg => { - if (msg.type() === 'error' && !isKnownWarning(msg.text())) { - errors.push(msg.text()); - } - logs.push(msg.text()); - }); - + test('OpenGL tab interaction without crashes', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -630,21 +577,11 @@ test.describe('wxWidgets WASM - OpenGL', () => { // Note: wxPrintf logs go to stdout which may not appear in browser console // The main verification is that the app doesn't crash - expect(errors.length).toBe(0); + const criticalErrors = testLogger.errors.filter(e => !isKnownWarning(e)); + expect(criticalErrors.length).toBe(0); }); - test('OpenGL tab renders without errors', async ({ page }) => { - const errors: string[] = []; - const logs: string[] = []; - - page.on('pageerror', err => errors.push(`${err.message}\n${err.stack || 'No stack'}`)); - page.on('console', msg => { - logs.push(`[${msg.type()}] ${msg.text()}`); - if (msg.type() === 'error' && !isKnownWarning(msg.text())) { - errors.push(msg.text()); - } - }); - + test('OpenGL tab renders without errors', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -692,26 +629,17 @@ test.describe('wxWidgets WASM - OpenGL', () => { const screenshot = await page.screenshot(); expect(screenshot.length).toBeGreaterThan(0); - // Save for visual inspection including errors - const fs = require('fs'); - fs.writeFileSync('test-results/opengl-render.png', screenshot); - fs.writeFileSync('test-results/opengl-debug.json', JSON.stringify({ glCanvasInfo, logs, errors }, null, 2)); - // App should still be responsive after GL rendering await expect(page.locator(MAIN_CANVAS)).toBeVisible(); // No critical JavaScript errors - expect(errors.length).toBe(0); + const criticalErrors = testLogger.errors.filter(e => !isKnownWarning(e)); + expect(criticalErrors.length).toBe(0); }); }); test.describe('wxWidgets WASM - Canvas Z-Ordering and Visibility', () => { - test('GL canvas should hide when switching away from OpenGL tab', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => { - logs.push(msg.text()); - }); - + test('GL canvas should hide when switching away from OpenGL tab', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -724,7 +652,7 @@ test.describe('wxWidgets WASM - Canvas Z-Ordering and Visibility', () => { await page.waitForTimeout(1000); // Verify we're on OpenGL tab - const onOpenGLTab = logs.some(log => log.includes('Tab changed to: OpenGL')); + const onOpenGLTab = testLogger.consoleLogs.some(log => log.includes('Tab changed to: OpenGL')); expect(onOpenGLTab).toBe(true); await page.screenshot({ path: 'test-results/glcanvas-01-on-opengl-tab.png', fullPage: true }); @@ -783,12 +711,7 @@ test.describe('wxWidgets WASM - Canvas Z-Ordering and Visibility', () => { } }); - test('dropdown should appear above GL canvas on OpenGL tab', async ({ page }) => { - const logs: string[] = []; - page.on('console', msg => { - logs.push(msg.text()); - }); - + test('dropdown should appear above GL canvas on OpenGL tab', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page); @@ -859,7 +782,7 @@ test.describe('wxWidgets WASM - Canvas Z-Ordering and Visibility', () => { console.log('Windows AFTER click:', JSON.stringify(windowsAfter)); // Check for any logged events - const clickEvents = logs.filter(log => + const clickEvents = testLogger.consoleLogs.filter(log => log.includes('GL Test selected') || log.includes('clicked') || log.includes('Choice') @@ -915,7 +838,7 @@ test.describe('wxWidgets WASM - Canvas Z-Ordering and Visibility', () => { // This will be verified by visual inspection of screenshots }); - test('switching tabs multiple times maintains correct visibility', async ({ page }) => { + test('switching tabs multiple times maintains correct visibility', async ({ page, testLogger }) => { await page.goto('/minimal_test.html'); await waitForApp(page);