From 04eebab024b3970e9d141b3e2a648f30a41c95ff Mon Sep 17 00:00:00 2001 From: Viktor Vaczi Date: Mon, 29 Dec 2025 13:06:54 +0100 Subject: [PATCH] Migrate tests to use rendered element registry for toolbar, menu, layout, and AUI tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update tests to use semantic identifiers instead of hardcoded pixel positions: - toolbar.spec.ts: Use clickToolbarTool('New') instead of click(box.x + 30, box.y + 45) - menu.spec.ts: Use clickMenuBarItem('File') instead of click(box.x + 30, box.y + 15) - layout.spec.ts: Use getSplitterSash() to get actual sash position for drag operations - aui.spec.ts: Use clickAuiButton('close', 'Properties') for panel button clicks Also extends element-tracker.ts with: - WxRenderedElement interface for toolbar tools, menu items, sashes, AUI parts - findRenderedByLabel(), findRenderedByType() query functions - clickToolbarTool(), clickMenuBarItem(), getSplitterSash(), clickAuiButton() helpers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- tests/e2e/aui.spec.ts | 25 ++- tests/e2e/layout.spec.ts | 42 +++-- tests/e2e/menu.spec.ts | 33 ++-- tests/e2e/toolbar.spec.ts | 50 +++--- tests/e2e/utils/element-tracker.ts | 261 +++++++++++++++++++++++++++++ wxwidgets | 2 +- 6 files changed, 336 insertions(+), 77 deletions(-) diff --git a/tests/e2e/aui.spec.ts b/tests/e2e/aui.spec.ts index 2855964..fe186dd 100644 --- a/tests/e2e/aui.spec.ts +++ b/tests/e2e/aui.spec.ts @@ -1,5 +1,6 @@ // wxAuiManager Tests - AUI docking system KiCad uses extensively import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; +import { clickAuiButton, findRenderedByLabel, findRenderedByType } from './utils/element-tracker'; test.describe('wxAuiManager Tests', () => { @@ -38,16 +39,16 @@ test.describe('wxAuiManager Tests', () => { return; } - const box = await getCanvasBox(page); + // Find all AUI parts to verify they're registered + const auiParts = await findRenderedByType(page, 'auipart'); + expect(auiParts.length, 'Should have AUI parts registered').toBeGreaterThan(0); - // Click on Properties panel close button (top right of left panel) - await page.mouse.click(box.x + 145, box.y + 35); + // Click on Properties panel close button using element registry + const clicked = await clickAuiButton(page, 'close', 'Properties'); + expect(clicked, 'Properties close button should be found and clicked').toBe(true); await page.waitForTimeout(500); await page.screenshot({ path: 'test-results/aui-03-close-clicked.png', fullPage: true }); - - // Smoke test - expect(true).toBe(true); }); test('Panel can be dragged', async ({ page, testLogger }) => { @@ -60,20 +61,18 @@ test.describe('wxAuiManager Tests', () => { const box = await getCanvasBox(page); - // Drag Properties panel title bar - const titleX = box.x + 75; - const titleY = box.y + 35; + // Get Properties panel caption using element registry + const caption = await findRenderedByLabel(page, 'Properties', { elementType: 'auipart', subType: 'caption' }); + expect(caption, 'Properties caption should be found in registry').not.toBeNull(); - await page.mouse.move(titleX, titleY); + // Drag panel title bar using registry coordinates + await page.mouse.move(caption!.centerX, caption!.centerY); await page.mouse.down(); await page.mouse.move(box.x + 300, box.y + 200, { steps: 10 }); await page.mouse.up(); await page.waitForTimeout(500); await page.screenshot({ path: 'test-results/aui-04-dragged.png', fullPage: true }); - - // Smoke test - expect(true).toBe(true); }); test('Multiple panels can be interacted with', async ({ page, testLogger }) => { diff --git a/tests/e2e/layout.spec.ts b/tests/e2e/layout.spec.ts index ac4f49a..39dcd53 100644 --- a/tests/e2e/layout.spec.ts +++ b/tests/e2e/layout.spec.ts @@ -1,5 +1,6 @@ // wxSplitterWindow and wxScrolledWindow Tests - Layout controls KiCad uses import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; +import { getSplitterSash, findRenderedByType } from './utils/element-tracker'; test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { @@ -38,23 +39,22 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { return; } - const box = await getCanvasBox(page); + // Get splitter sash from element registry + const sash = await getSplitterSash(page); + expect(sash, 'Splitter sash should be found in registry').not.toBeNull(); - // Splitter sash is at initial position 300 from left - const sashX = box.x + 300; - const sashY = box.y + 200; - - // Drag sash to the right - await page.mouse.move(sashX, sashY); + // Drag sash to the right using its center position + await page.mouse.move(sash!.centerX, sash!.centerY); await page.mouse.down(); - await page.mouse.move(sashX + 100, sashY, { steps: 10 }); + await page.mouse.move(sash!.centerX + 100, sash!.centerY, { steps: 10 }); await page.mouse.up(); await page.waitForTimeout(500); await page.screenshot({ path: 'test-results/layout-03-sash-dragged.png', fullPage: true }); - // Smoke test - verify no crash - expect(true).toBe(true); + // Verify sash position updated + const sashAfter = await getSplitterSash(page); + expect(sashAfter, 'Splitter sash should still be found after drag').not.toBeNull(); }); test('Scrolled windows show content', async ({ page, testLogger }) => { @@ -92,22 +92,28 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => { return; } - const box = await getCanvasBox(page); + // Get splitter sash from element registry + const sash = await getSplitterSash(page); + expect(sash, 'Splitter sash should be found in registry').not.toBeNull(); - // Drag sash - await page.mouse.move(box.x + 300, box.y + 200); + // Drag sash using registry coordinates + await page.mouse.move(sash!.centerX, sash!.centerY); await page.mouse.down(); - await page.mouse.move(box.x + 400, box.y + 200, { steps: 5 }); + await page.mouse.move(sash!.centerX + 100, sash!.centerY, { steps: 5 }); await page.mouse.up(); await page.waitForTimeout(300); - // Scroll left pane - await page.mouse.move(box.x + 100, box.y + 200); + // Get updated sash position after drag + const sashAfter = await getSplitterSash(page); + expect(sashAfter, 'Splitter sash should still be found after drag').not.toBeNull(); + + // Scroll left pane (use position left of sash) + await page.mouse.move(sashAfter!.centerX - 100, sashAfter!.centerY); await page.mouse.wheel(0, 50); await page.waitForTimeout(200); - // Scroll right pane - await page.mouse.move(box.x + 600, box.y + 200); + // Scroll right pane (use position right of sash) + await page.mouse.move(sashAfter!.centerX + 100, sashAfter!.centerY); await page.mouse.wheel(0, 50); await page.waitForTimeout(200); diff --git a/tests/e2e/menu.spec.ts b/tests/e2e/menu.spec.ts index 345893d..39ec9de 100644 --- a/tests/e2e/menu.spec.ts +++ b/tests/e2e/menu.spec.ts @@ -1,5 +1,6 @@ // wxMenuBar Tests - Menu system for KiCad import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; +import { clickMenuBarItem, findRenderedByType } from './utils/element-tracker'; test.describe('wxMenuBar Tests', () => { @@ -43,16 +44,12 @@ test.describe('wxMenuBar Tests', () => { return; } - 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); + // Click on File menu using element registry + const clicked = await clickMenuBarItem(page, 'File'); + expect(clicked, 'File menu should be found and clicked').toBe(true); await page.waitForTimeout(500); await page.screenshot({ path: 'test-results/menu-03-file-clicked.png', fullPage: true }); - - // Smoke test - no crash - expect(true).toBe(true); }); test('Edit menu can be clicked', async ({ page, testLogger }) => { @@ -63,15 +60,12 @@ test.describe('wxMenuBar Tests', () => { return; } - 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); + // Click on Edit menu using element registry + const clicked = await clickMenuBarItem(page, 'Edit'); + expect(clicked, 'Edit menu should be found and clicked').toBe(true); await page.waitForTimeout(500); await page.screenshot({ path: 'test-results/menu-04-edit-clicked.png', fullPage: true }); - - expect(true).toBe(true); }); test('Multiple menus can be accessed', async ({ page, testLogger }) => { @@ -82,12 +76,15 @@ test.describe('wxMenuBar Tests', () => { return; } - const box = await getCanvasBox(page); + // Verify all menu bar items are registered + const menuItems = await findRenderedByType(page, 'menuitem', { subType: 'menubar' }); + expect(menuItems.length, 'Should have 5 menu bar items').toBeGreaterThanOrEqual(5); - // Click through all menus - const menuPositions = [30, 70, 110, 150, 190]; // File, Edit, View, Tools, Help - for (let i = 0; i < menuPositions.length; i++) { - await page.mouse.click(box.x + menuPositions[i], box.y + 15); + // Click through all menus using element registry + const menuLabels = ['File', 'Edit', 'View', 'Tools', 'Help']; + for (const label of menuLabels) { + const clicked = await clickMenuBarItem(page, label); + expect(clicked, `Menu "${label}" should be found and clicked`).toBe(true); await page.waitForTimeout(300); } diff --git a/tests/e2e/toolbar.spec.ts b/tests/e2e/toolbar.spec.ts index b748301..70317b6 100644 --- a/tests/e2e/toolbar.spec.ts +++ b/tests/e2e/toolbar.spec.ts @@ -1,5 +1,6 @@ // wxToolBar and wxStatusBar Tests - Toolbar and status bar KiCad uses import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures'; +import { clickToolbarTool, findRenderedByType } from './utils/element-tracker'; test.describe('wxToolBar & wxStatusBar Tests', () => { @@ -38,16 +39,12 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { return; } - const box = await getCanvasBox(page); - - // Click New button (first tool, around x=30) - await page.mouse.click(box.x + 30, box.y + 45); + // Click New button using element registry + const clicked = await clickToolbarTool(page, 'New'); + expect(clicked, 'New tool should be found and clicked').toBe(true); await page.waitForTimeout(500); await page.screenshot({ path: 'test-results/toolbar-03-new-clicked.png', fullPage: true }); - - // Smoke test - expect(true).toBe(true); }); test('Zoom tools can be clicked', async ({ page, testLogger }) => { @@ -58,19 +55,17 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { return; } - const box = await getCanvasBox(page); - - // Click Zoom In - await page.mouse.click(box.x + 230, box.y + 45); + // Click Zoom In using element registry + const zoomInClicked = await clickToolbarTool(page, 'Zoom In'); + expect(zoomInClicked, 'Zoom In tool should be found and clicked').toBe(true); await page.waitForTimeout(300); - // Click Zoom Out - await page.mouse.click(box.x + 290, box.y + 45); + // Click Zoom Out using element registry + const zoomOutClicked = await clickToolbarTool(page, 'Zoom Out'); + expect(zoomOutClicked, 'Zoom Out tool should be found and clicked').toBe(true); await page.waitForTimeout(300); await page.screenshot({ path: 'test-results/toolbar-04-zoom.png', fullPage: true }); - - expect(true).toBe(true); }); test('Toggle tool changes state', async ({ page, testLogger }) => { @@ -81,21 +76,19 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { return; } - const box = await getCanvasBox(page); - - // Click Toggle button (after separator, around x=350) - await page.mouse.click(box.x + 350, box.y + 45); + // Click Toggle button using element registry + const toggleClicked1 = await clickToolbarTool(page, 'Toggle'); + expect(toggleClicked1, 'Toggle tool should be found and clicked').toBe(true); await page.waitForTimeout(300); await page.screenshot({ path: 'test-results/toolbar-05-toggle-on.png', fullPage: true }); // Click again to toggle off - await page.mouse.click(box.x + 350, box.y + 45); + const toggleClicked2 = await clickToolbarTool(page, 'Toggle'); + expect(toggleClicked2, 'Toggle tool should be found and clicked again').toBe(true); await page.waitForTimeout(300); await page.screenshot({ path: 'test-results/toolbar-06-toggle-off.png', fullPage: true }); - - expect(true).toBe(true); }); test('Status bar shows messages', async ({ page, testLogger }) => { @@ -121,12 +114,15 @@ test.describe('wxToolBar & wxStatusBar Tests', () => { return; } - const box = await getCanvasBox(page); + // Verify all tools are registered + const tools = await findRenderedByType(page, 'tool'); + expect(tools.length, 'Should have 6 tools registered').toBeGreaterThanOrEqual(6); - // Click all toolbar buttons - const buttonPositions = [30, 85, 140, 230, 290, 350]; // New, Open, Save, ZoomIn, ZoomOut, Toggle - for (const x of buttonPositions) { - await page.mouse.click(box.x + x, box.y + 45); + // Click all toolbar buttons by label + const toolLabels = ['New', 'Open', 'Save', 'Zoom In', 'Zoom Out', 'Toggle']; + for (const label of toolLabels) { + const clicked = await clickToolbarTool(page, label); + expect(clicked, `Tool "${label}" should be found and clicked`).toBe(true); await page.waitForTimeout(200); } diff --git a/tests/e2e/utils/element-tracker.ts b/tests/e2e/utils/element-tracker.ts index 2d1fb59..e35b6f0 100644 --- a/tests/e2e/utils/element-tracker.ts +++ b/tests/e2e/utils/element-tracker.ts @@ -35,6 +35,32 @@ export interface RegistryStats { byType: Record; } +export interface WxRenderedElement { + id: string; + parentId: string; + elementType: 'tool' | 'menuitem' | 'sash' | 'auipart'; + subType: string; + label: string; + tooltip: string; + screenX: number; + screenY: number; + width: number; + height: number; + centerX: number; + centerY: number; + enabled: boolean; + index: number; + lastUpdated: number; +} + +export interface RenderedFindOptions { + enabled?: boolean; + elementType?: string; + subType?: string; + parentId?: string; + exact?: boolean; +} + export interface WxElementRegistry { elements: Map; version: number; @@ -48,6 +74,15 @@ export interface WxElementRegistry { getElement(id: string): WxElement | null; dump(): void; getStats(): RegistryStats; + // Rendered elements support + renderedElements?: Map; + renderedVersion?: number; + findRenderedByLabel?(label: string, options?: RenderedFindOptions): WxRenderedElement[]; + findRenderedByType?(elementType: string, options?: RenderedFindOptions): WxRenderedElement[]; + findRenderedByParent?(parentId: string, options?: RenderedFindOptions): WxRenderedElement[]; + findAllRendered?(filter?: RenderedFindOptions & { label?: string }): WxRenderedElement[]; + dumpRendered?(): void; + getRenderedStats?(): RegistryStats; } declare global { @@ -268,3 +303,229 @@ export async function waitForElement( return null; } } + +// ============================================================================ +// Rendered Elements (toolbar tools, menu items, splitter sashes, AUI parts) +// ============================================================================ + +/** + * Find rendered element by label (e.g., toolbar tool "New", menu item "File") + */ +export async function findRenderedByLabel( + page: Page, + label: string, + options: RenderedFindOptions = {} +): Promise { + const elements = await page.evaluate( + ([label, opts]: [string, RenderedFindOptions]) => { + const registry = window.wxElementRegistry; + if (!registry || !registry.findRenderedByLabel) return []; + return registry.findRenderedByLabel(label, opts); + }, + [label, options] as [string, RenderedFindOptions] + ); + return elements.length > 0 ? elements[0] : null; +} + +/** + * Find all rendered elements by label + */ +export async function findAllRenderedByLabel( + page: Page, + label: string, + options: RenderedFindOptions = {} +): Promise { + return page.evaluate( + ([label, opts]: [string, RenderedFindOptions]) => { + const registry = window.wxElementRegistry; + if (!registry || !registry.findRenderedByLabel) return []; + return registry.findRenderedByLabel(label, opts); + }, + [label, options] as [string, RenderedFindOptions] + ); +} + +/** + * Find all rendered elements of a type (tool, menuitem, sash, auipart) + */ +export async function findRenderedByType( + page: Page, + elementType: string, + options: RenderedFindOptions = {} +): Promise { + return page.evaluate( + ([type, opts]: [string, RenderedFindOptions]) => { + const registry = window.wxElementRegistry; + if (!registry || !registry.findRenderedByType) return []; + return registry.findRenderedByType(type, opts); + }, + [elementType, options] as [string, RenderedFindOptions] + ); +} + +/** + * Click on a toolbar tool by label or tooltip + */ +export async function clickToolbarTool( + page: Page, + label: string +): Promise { + const tool = await findRenderedByLabel(page, label, { elementType: 'tool' }); + if (!tool) { + console.warn(`Toolbar tool "${label}" not found`); + return false; + } + if (!tool.enabled) { + console.warn(`Toolbar tool "${label}" is disabled`); + return false; + } + await page.mouse.click(tool.centerX, tool.centerY); + return true; +} + +/** + * Click on a menu bar item by label + */ +export async function clickMenuBarItem( + page: Page, + label: string +): Promise { + const menuItem = await findRenderedByLabel(page, label, { + elementType: 'menuitem', + subType: 'menubar' + }); + if (!menuItem) { + console.warn(`Menu bar item "${label}" not found`); + return false; + } + if (!menuItem.enabled) { + console.warn(`Menu bar item "${label}" is disabled`); + return false; + } + await page.mouse.click(menuItem.centerX, menuItem.centerY); + return true; +} + +/** + * Click on a popup menu item by label + */ +export async function clickMenuItem( + page: Page, + label: string +): Promise { + const menuItem = await findRenderedByLabel(page, label, { + elementType: 'menuitem' + }); + if (!menuItem) { + console.warn(`Menu item "${label}" not found`); + return false; + } + if (!menuItem.enabled) { + console.warn(`Menu item "${label}" is disabled`); + return false; + } + await page.mouse.click(menuItem.centerX, menuItem.centerY); + return true; +} + +/** + * Get splitter sash element + */ +export async function getSplitterSash( + page: Page, + parentId?: string +): Promise { + const options: RenderedFindOptions = {}; + if (parentId) options.parentId = parentId; + + const sashes = await findRenderedByType(page, 'sash', options); + return sashes.length > 0 ? sashes[0] : null; +} + +/** + * Click on an AUI pane button (close, pin, maximize) + */ +export async function clickAuiButton( + page: Page, + buttonType: 'close' | 'pin' | 'maximize', + paneCaption?: string +): Promise { + const options: RenderedFindOptions = { + elementType: 'auipart', + subType: buttonType + }; + + let button: WxRenderedElement | null = null; + + if (paneCaption) { + // Find the specific pane first, then find the button by parent + const caption = await findRenderedByLabel(page, paneCaption, { + elementType: 'auipart', + subType: 'caption' + }); + if (caption) { + // Button index is based on pane index + const paneIndex = caption.index; + const buttons = await findRenderedByType(page, 'auipart', { subType: buttonType }); + button = buttons.find(b => Math.floor(b.index / 10) === paneIndex) || null; + } + } else { + // Just find the first button of this type + const buttons = await findRenderedByType(page, 'auipart', options); + button = buttons.length > 0 ? buttons[0] : null; + } + + if (!button) { + console.warn(`AUI ${buttonType} button not found`); + return false; + } + + await page.mouse.click(button.centerX, button.centerY); + return true; +} + +/** + * Click on a rendered element by label (searches all types) + */ +export async function clickRenderedByLabel( + page: Page, + label: string, + options: RenderedFindOptions = {} +): Promise { + const element = await findRenderedByLabel(page, label, options); + if (!element) { + console.warn(`Rendered element "${label}" not found`); + return false; + } + if (!element.enabled) { + console.warn(`Rendered element "${label}" is disabled`); + return false; + } + await page.mouse.click(element.centerX, element.centerY); + return true; +} + +/** + * Dump all rendered elements to console (for debugging) + */ +export async function dumpRenderedElements(page: Page): Promise { + await page.evaluate(() => { + const registry = window.wxElementRegistry; + if (registry && registry.dumpRendered) { + registry.dumpRendered(); + } else { + console.log('[wxElementRegistry] Rendered elements not available'); + } + }); +} + +/** + * Get rendered elements statistics + */ +export async function getRenderedStats(page: Page): Promise { + return page.evaluate(() => { + const registry = window.wxElementRegistry; + if (!registry || !registry.getRenderedStats) return null; + return registry.getRenderedStats(); + }); +} diff --git a/wxwidgets b/wxwidgets index f2f601b..ae9d0a8 160000 --- a/wxwidgets +++ b/wxwidgets @@ -1 +1 @@ -Subproject commit f2f601b5aa8b60fcea1da0e1157e70ad96600fe0 +Subproject commit ae9d0a81edeee5d09b27f6e49f43130c148d9aac