Migrate tests to use rendered element registry for toolbar, menu, layout, and AUI tests

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 <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2025-12-29 13:06:54 +01:00
commit 04eebab024
6 changed files with 336 additions and 77 deletions

View file

@ -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 }) => {

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
}

View file

@ -35,6 +35,32 @@ export interface RegistryStats {
byType: Record<string, number>;
}
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<string, WxElement>;
version: number;
@ -48,6 +74,15 @@ export interface WxElementRegistry {
getElement(id: string): WxElement | null;
dump(): void;
getStats(): RegistryStats;
// Rendered elements support
renderedElements?: Map<string, WxRenderedElement>;
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<WxRenderedElement | null> {
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<WxRenderedElement[]> {
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<WxRenderedElement[]> {
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<boolean> {
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<boolean> {
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<boolean> {
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<WxRenderedElement | null> {
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<boolean> {
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<boolean> {
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<void> {
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<RegistryStats | null> {
return page.evaluate(() => {
const registry = window.wxElementRegistry;
if (!registry || !registry.getRenderedStats) return null;
return registry.getRenderedStats();
});
}

@ -1 +1 @@
Subproject commit f2f601b5aa8b60fcea1da0e1157e70ad96600fe0
Subproject commit ae9d0a81edeee5d09b27f6e49f43130c148d9aac