test(determinism): deterministic waits + stableShot screenshots; drop blind sleeps/ifs/retries
Make the Playwright e2e + kicad suites deterministic so screenshot flake stops tracing to timing races. - Blind page.waitForTimeout -> condition waits (expect.poll, web-first assertions, waitUntil) + readiness helpers (waitForWxApp, waitForCanvasApp). Remaining sleeps are documented interaction dwells (annotated). - Defensive "if element exists" branches -> loud asserts; label-fallback chains -> normalized clickMenuItemByText. First-run wizard for/if loops removed by seeding calculator/gerbview/pcbnew HTMLs. - Screenshots: new stableShot(page, name) settles the render in-page (canvas hash over rAF) then writes a raw PNG to test-results/ for the existing offline gate (tools/screenshots vs baseline-screenshots). Replaces toHaveScreenshot, which did inline compare + its own baselines and had decoupled the specs from the real gate. scale:'css' pinned. - retries: 0 in both configs. - Guard: tests/tools/lint-determinism.ts (npm run lint:determinism) bans blind sleeps / toHaveScreenshot / inline retries / swallowed catches in specs; documented exceptions carry a marker. Rules in tests/TESTING.md. Assertions, coverage, and renders unchanged (semantic-equivalence reviewed; captures pixel-identical modulo inherent timer/timestamp/3d-raytrace variance). Both suites green at retries:0 (e2e 340, kicad 92); ~35-61% faster. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVX1pHMvRPYHdp6ZfEawrk
This commit is contained in:
parent
d9264549bf
commit
4c3a4cacd4
102 changed files with 2867 additions and 3407 deletions
|
|
@ -10,13 +10,18 @@
|
|||
//
|
||||
// These are RED before the wasm-layer fix and GREEN after it. See
|
||||
// features/kicad-resize-panel / docs and the plan for the fix details.
|
||||
//
|
||||
// Determinism: readiness via waitForWxApp (loud). The hover-cursor and the
|
||||
// live-resize width are real observables, so we poll them (same conditions the
|
||||
// assertions check) instead of sleeping. The remaining waits are genuine
|
||||
// interaction dwells around mouse press/drag-commit with no positive observable,
|
||||
// kept and documented. Static hover / mid-drag states use stableShot; the
|
||||
// two stipple frames feed a functional pixel-diff so they stay Buffer-only.
|
||||
|
||||
import { test, expect, tryLoadApp, MAIN_CANVAS } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp, MAIN_CANVAS } from './utils/fixtures';
|
||||
import {
|
||||
waitForRegistry,
|
||||
findRenderedByType,
|
||||
findAuiPaneContent,
|
||||
} from './utils/element-tracker';
|
||||
findAuiPaneContent, stableShot } from './utils/element-tracker';
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
const APP = '/standalone/aui/aui_test.html';
|
||||
|
|
@ -154,10 +159,7 @@ async function leftPaneTrailRegion(
|
|||
|
||||
async function load(page: Page): Promise<void> {
|
||||
await page.goto(APP);
|
||||
expect(await tryLoadApp(page), 'AUI app should load').toBe(true);
|
||||
expect(await waitForRegistry(page), 'element registry should init').toBe(true);
|
||||
// Let canvas-island geometry settle before reading positions.
|
||||
await page.waitForTimeout(400);
|
||||
await waitForWxApp(page);
|
||||
}
|
||||
|
||||
test.describe('wxAuiManager dock-sash resize UX (pcbjam#20)', () => {
|
||||
|
|
@ -168,21 +170,25 @@ test.describe('wxAuiManager dock-sash resize UX (pcbjam#20)', () => {
|
|||
|
||||
// Move OFF the sash first (into the Properties pane) and confirm no resize cursor.
|
||||
const props = await findAuiPaneContent(page, 'Properties');
|
||||
if (props) {
|
||||
await page.mouse.move(props.centerX, props.centerY);
|
||||
await page.waitForTimeout(120);
|
||||
const offCursor = await readCursorAt(page, props.centerX, props.centerY);
|
||||
console.log(`[aui-resize] cursor off-sash = ${offCursor}`);
|
||||
expect(RESIZE_CURSORS, 'cursor should NOT be a resize cursor inside the pane').not.toContain(offCursor);
|
||||
}
|
||||
expect(props, 'Properties pane content should be present to test off-sash cursor').not.toBeNull();
|
||||
await page.mouse.move(props!.centerX, props!.centerY);
|
||||
await page.waitForTimeout(120); // eslint-disable-line -- documented interaction dwell: let the cursor settle after moving into the pane before reading it (negative assertion, no positive observable to poll)
|
||||
const offCursor = await readCursorAt(page, props!.centerX, props!.centerY);
|
||||
console.log(`[aui-resize] cursor off-sash = ${offCursor}`);
|
||||
expect(RESIZE_CURSORS, 'cursor should NOT be a resize cursor inside the pane').not.toContain(offCursor);
|
||||
|
||||
// Hover the sash → resize cursor.
|
||||
// Hover the sash → resize cursor. Poll the live CSS cursor until it becomes a
|
||||
// resize cursor (same observable the assertion below checks), replacing the sleep.
|
||||
await page.mouse.move(sash.x, sash.y);
|
||||
await page.waitForTimeout(150);
|
||||
await expect
|
||||
.poll(async () => RESIZE_CURSORS.includes(await readCursorAt(page, sash.x, sash.y)), {
|
||||
message: 'expected a resize cursor over the sash',
|
||||
})
|
||||
.toBe(true);
|
||||
const cursor = await readCursorAt(page, sash.x, sash.y);
|
||||
console.log(`[aui-resize] cursor on-sash = ${cursor}`);
|
||||
|
||||
await page.screenshot({ path: 'test-results/aui-resize-01-hover.png' });
|
||||
await stableShot(page, 'aui-resize-01-hover.png');
|
||||
expect(RESIZE_CURSORS, `expected a resize cursor over the sash, got "${cursor}"`).toContain(cursor);
|
||||
});
|
||||
|
||||
|
|
@ -197,17 +203,23 @@ test.describe('wxAuiManager dock-sash resize UX (pcbjam#20)', () => {
|
|||
expect(before, 'Properties pane width should be readable').not.toBeNull();
|
||||
|
||||
await page.mouse.move(sash.x, sash.y);
|
||||
await page.waitForTimeout(100);
|
||||
await page.waitForTimeout(100); // eslint-disable-line -- documented interaction dwell: settle the hover before pressing the sash
|
||||
await page.mouse.down();
|
||||
// Widen the pane (move right) without releasing — avoids the MinSize clamp.
|
||||
await page.mouse.move(sash.x + 80, sash.y, { steps: 8 });
|
||||
await page.waitForTimeout(150);
|
||||
// LIVE preview is deterministic: poll the pane width until it has changed
|
||||
// mid-drag (same observable the assertion below checks), replacing the sleep.
|
||||
await expect
|
||||
.poll(async () => Math.abs(((await widthOf()) ?? 0) - (before ?? 0)), {
|
||||
message: 'pane should resize live during drag',
|
||||
})
|
||||
.toBeGreaterThan(20);
|
||||
|
||||
const midDrag = await widthOf(); // read while button STILL held
|
||||
await page.screenshot({ path: 'test-results/aui-resize-02-mid-drag-live.png' });
|
||||
await stableShot(page, 'aui-resize-02-mid-drag-live.png');
|
||||
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(250);
|
||||
await page.waitForTimeout(250); // eslint-disable-line -- documented interaction dwell: let the drag-release commit before reading the final width (no positive observable — the good path leaves the width unchanged)
|
||||
const after = await widthOf();
|
||||
|
||||
console.log(`[aui-resize] width before=${before} mid=${midDrag} after=${after}`);
|
||||
|
|
@ -224,17 +236,17 @@ test.describe('wxAuiManager dock-sash resize UX (pcbjam#20)', () => {
|
|||
const region = await leftPaneTrailRegion(page, sash.x);
|
||||
|
||||
await page.mouse.move(sash.x, sash.y);
|
||||
await page.waitForTimeout(100);
|
||||
await page.waitForTimeout(100); // eslint-disable-line -- documented interaction dwell: settle the hover before pressing the sash
|
||||
await page.mouse.down();
|
||||
// Drag NARROWER (the user's specific complaint) in steps; capture an
|
||||
// intermediate frame while the button is still held.
|
||||
await page.mouse.move(sash.x - 90, sash.y, { steps: 10 });
|
||||
await page.waitForTimeout(70);
|
||||
const midShot = await page.screenshot({ path: 'test-results/aui-resize-03-mid-drag.png', scale: 'css' });
|
||||
await page.waitForTimeout(70); // eslint-disable-line -- documented interaction dwell: let the mid-drag frame paint before capturing it for the pixel-diff
|
||||
const midShot = await page.screenshot({ scale: 'css' });
|
||||
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(200);
|
||||
const afterShot = await page.screenshot({ path: 'test-results/aui-resize-04-after.png', scale: 'css' });
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: let the release settle before capturing the after frame for the pixel-diff
|
||||
const afterShot = await page.screenshot({ scale: 'css' });
|
||||
|
||||
const midFrac = await stippleFraction(page, midShot, region);
|
||||
const afterFrac = await stippleFraction(page, afterShot, region);
|
||||
|
|
|
|||
|
|
@ -1,37 +1,36 @@
|
|||
// wxAuiManager Tests - AUI docking system KiCad uses extensively
|
||||
import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures';
|
||||
import { clickAuiButton, clickAuiPaneContent, findRenderedByLabel, findRenderedByType } from './utils/element-tracker';
|
||||
import { test, expect, MAIN_CANVAS, waitForWxApp, getCanvasBox } from './utils/fixtures';
|
||||
import { clickAuiButton, clickAuiPaneContent, findRenderedByLabel, findRenderedByType, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxAuiManager Tests', () => {
|
||||
|
||||
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 });
|
||||
await waitForWxApp(page);
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('AUI test app started'));
|
||||
|
||||
expect(loaded, 'AUI app should load').toBe(true);
|
||||
await stableShot(page, 'aui-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('AUI dockable panels are visible', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/aui/aui_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/aui-02-panels.png', fullPage: true });
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some(l => l.includes('dockable panels')), {
|
||||
message: 'AUI dockable panels log should be emitted',
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
const hasPanelsLog = testLogger.consoleLogs.some(l => l.includes('dockable panels'));
|
||||
|
||||
expect(hasPanelsLog).toBe(true);
|
||||
await stableShot(page, 'aui-02-panels.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Panel close button can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/aui/aui_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Find all AUI parts to verify they're registered
|
||||
const auiParts = await findRenderedByType(page, 'auipart');
|
||||
|
|
@ -40,15 +39,13 @@ test.describe('wxAuiManager Tests', () => {
|
|||
// 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 });
|
||||
await stableShot(page, 'aui-03-close-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Panel can be dragged', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/aui/aui_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const box = await getCanvasBox(page);
|
||||
|
||||
|
|
@ -61,37 +58,34 @@ test.describe('wxAuiManager Tests', () => {
|
|||
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 });
|
||||
await stableShot(page, 'aui-04-dragged.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Multiple panels can be interacted with', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/aui/aui_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click in Properties panel using element tracking
|
||||
const propsClicked = await clickAuiPaneContent(page, 'Properties');
|
||||
expect(propsClicked, 'Should be able to click Properties pane').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: no observable event; lets the pane-content click commit before the next click
|
||||
|
||||
// Click in Layers panel using element tracking
|
||||
const layersClicked = await clickAuiPaneContent(page, 'Layers');
|
||||
expect(layersClicked, 'Should be able to click Layers pane').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: no observable event; lets the pane-content click commit before the next click
|
||||
|
||||
// Click in Messages panel using element tracking
|
||||
const messagesClicked = await clickAuiPaneContent(page, 'Messages');
|
||||
expect(messagesClicked, 'Should be able to click Messages pane').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: no observable event; lets the pane-content click commit before the next click
|
||||
|
||||
// Click in Event Log panel using element tracking
|
||||
const eventLogClicked = await clickAuiPaneContent(page, 'Event Log');
|
||||
expect(eventLogClicked, 'Should be able to click Event Log pane').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/aui-05-multi-panel.png', fullPage: true });
|
||||
await stableShot(page, 'aui-05-multi-panel.png', { fullPage: true });
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,76 +1,59 @@
|
|||
// wxAuiNotebook Tests - Tab panels for KiCad editors
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickTab } from './utils/element-tracker';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickTab, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxAuiNotebook Tests', () => {
|
||||
|
||||
test('AuiNotebook test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/auinotebook/auinotebook_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/auinotebook-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'auinotebook-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'wxAuiNotebook app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('AuiNotebook tabs can be switched', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/auinotebook/auinotebook_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click on PCB tab using element registry
|
||||
const clicked = await clickTab(page, 'PCB');
|
||||
expect(clicked, 'PCB tab should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/auinotebook-02-tab-switch.png', fullPage: true });
|
||||
await stableShot(page, 'auinotebook-02-tab-switch.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('AuiNotebook tabs can be added', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/auinotebook/auinotebook_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Add Tab button using element registry
|
||||
const clicked = await clickByLabel(page, 'Add Tab');
|
||||
expect(clicked, 'Add Tab button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/auinotebook-03-add-tab.png', fullPage: true });
|
||||
await stableShot(page, 'auinotebook-03-add-tab.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('AuiNotebook tabs can be removed', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/auinotebook/auinotebook_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Remove Tab button using element registry
|
||||
const clicked = await clickByLabel(page, 'Remove Tab');
|
||||
expect(clicked, 'Remove Tab button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/auinotebook-04-remove-tab.png', fullPage: true });
|
||||
await stableShot(page, 'auinotebook-04-remove-tab.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('AuiNotebook tab style can be changed', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/auinotebook/auinotebook_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Bottom button using element registry
|
||||
const clicked = await clickByLabel(page, 'Bottom');
|
||||
expect(clicked, 'Bottom button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/auinotebook-05-tab-style.png', fullPage: true });
|
||||
await stableShot(page, 'auinotebook-05-tab-style.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,111 +1,81 @@
|
|||
// wxBitmapButton Tests - Bitmap buttons, toggle buttons, disabled states
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickByName } from './utils/element-tracker';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickByName, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxBitmapButton Tests', () => {
|
||||
|
||||
test('Bitmap buttons test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/bitmapbuttons/bitmapbuttons_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/bitmapbuttons-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'bitmapbuttons-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'Bitmap buttons app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Toolbar-style buttons can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/bitmapbuttons/bitmapbuttons_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Select tool button using element registry (by name)
|
||||
await clickByName(page, 'SelectTool');
|
||||
await page.waitForTimeout(100);
|
||||
// Click Line tool button
|
||||
await clickByName(page, 'LineTool');
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
await page.screenshot({ path: 'test-results/bitmapbuttons-02-toolbar-click.png', fullPage: true });
|
||||
await stableShot(page, 'bitmapbuttons-02-toolbar-click.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Toggle buttons can be toggled', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/bitmapbuttons/bitmapbuttons_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click F.Cu checkbox to toggle it using element registry
|
||||
await clickByLabel(page, 'F.Cu');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/bitmapbuttons-03-toggle.png', fullPage: true });
|
||||
await stableShot(page, 'bitmapbuttons-03-toggle.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Toggle button can toggle multiple layers', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/bitmapbuttons/bitmapbuttons_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Toggle multiple layer checkboxes using element registry
|
||||
await clickByLabel(page, 'F.Cu');
|
||||
await page.waitForTimeout(100);
|
||||
await clickByLabel(page, 'B.Cu');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/bitmapbuttons-04-multi-toggle.png', fullPage: true });
|
||||
await stableShot(page, 'bitmapbuttons-04-multi-toggle.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Disabled button can be re-enabled', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/bitmapbuttons/bitmapbuttons_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Toggle Enable State button using element registry
|
||||
await clickByLabel(page, 'Toggle Enable State');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/bitmapbuttons-05-enable-toggle.png', fullPage: true });
|
||||
await stableShot(page, 'bitmapbuttons-05-enable-toggle.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Shape buttons display different icons', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/bitmapbuttons/bitmapbuttons_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click different shape buttons using element registry (by name)
|
||||
await clickByName(page, 'Rectangle');
|
||||
await page.waitForTimeout(100);
|
||||
await clickByName(page, 'Circle');
|
||||
await page.waitForTimeout(100);
|
||||
await clickByName(page, 'Triangle');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/bitmapbuttons-06-shapes.png', fullPage: true });
|
||||
await stableShot(page, 'bitmapbuttons-06-shapes.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Art Provider buttons display system icons', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/bitmapbuttons/bitmapbuttons_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click New, Open buttons using element registry (by name)
|
||||
await clickByName(page, 'New');
|
||||
await page.waitForTimeout(100);
|
||||
await clickByName(page, 'Open');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/bitmapbuttons-07-artprovider.png', fullPage: true });
|
||||
await stableShot(page, 'bitmapbuttons-07-artprovider.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
// Bitmap Masking Tests - wxMask and transparent bitmap drawing
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('Bitmap Masking Tests', () => {
|
||||
|
||||
test('Bitmap masking renders correctly', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/bitmask/bitmask_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/bitmask.png', fullPage: true });
|
||||
await stableShot(page, 'bitmask.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'Bitmap masking app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,81 +1,63 @@
|
|||
// wxCalendarCtrl Tests - Date selection
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickCalendarDate } from './utils/element-tracker';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickCalendarDate, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxCalendarCtrl Tests', () => {
|
||||
|
||||
test('Calendar test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/calendar/calendar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/calendar-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'calendar-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'wxCalendarCtrl app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Calendar dates can be selected', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/calendar/calendar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click on day 15 in the calendar using element registry
|
||||
const clicked = await clickCalendarDate(page, 15);
|
||||
expect(clicked, 'Calendar date 15 should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/calendar-02-select-date.png', fullPage: true });
|
||||
await stableShot(page, 'calendar-02-select-date.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Calendar can navigate to next month', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/calendar/calendar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Next Month button using element registry
|
||||
const clicked = await clickByLabel(page, 'Next Month');
|
||||
expect(clicked, 'Next Month button should be found').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/calendar-03-next-month.png', fullPage: true });
|
||||
await stableShot(page, 'calendar-03-next-month.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Calendar can navigate to previous month', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/calendar/calendar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Previous Month button using element registry
|
||||
const clicked = await clickByLabel(page, 'Previous Month');
|
||||
expect(clicked, 'Previous Month button should be found').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/calendar-04-prev-month.png', fullPage: true });
|
||||
await stableShot(page, 'calendar-04-prev-month.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Calendar can navigate to today', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/calendar/calendar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// First go to next month
|
||||
const nextClicked = await clickByLabel(page, 'Next Month');
|
||||
expect(nextClicked, 'Next Month button should be found').toBe(true);
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// Click Today button using element registry
|
||||
const todayClicked = await clickByLabel(page, 'Today');
|
||||
expect(todayClicked, 'Today button should be found').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/calendar-05-today.png', fullPage: true });
|
||||
await stableShot(page, 'calendar-05-today.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,177 +1,132 @@
|
|||
// wxClipboard Tests - Clipboard operations for KiCad copy/paste
|
||||
// Uses element registry for semantic element identification
|
||||
import { test, expect, tryLoadApp, waitForRegistry, clickByLabel } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp, clickByLabel } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxClipboard Tests', () => {
|
||||
|
||||
test('Clipboard test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/clipboard/clipboard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/clipboard-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'clipboard-01-loaded.png', { fullPage: true });
|
||||
|
||||
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(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Copy button copies text to clipboard', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/clipboard/clipboard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click "Copy to Clipboard" button
|
||||
await clickByLabel(page, 'Copy to Clipboard');
|
||||
await page.waitForTimeout(2500); // Wait for async clipboard operation + timeout
|
||||
|
||||
await page.screenshot({ path: 'test-results/clipboard-02-copy-clicked.png', fullPage: true });
|
||||
|
||||
// Check for SUCCESS log (clipboard implementation working) or at least the attempt log
|
||||
const hasCopySuccess = testLogger.consoleLogs.some(l =>
|
||||
l.includes('SUCCESS') && l.includes('Copied')
|
||||
);
|
||||
const hasCopyAttempt = testLogger.consoleLogs.some(l =>
|
||||
l.includes('Attempting to copy')
|
||||
);
|
||||
|
||||
// Either success (real clipboard worked) or at least attempt was made
|
||||
expect(hasCopySuccess || hasCopyAttempt, 'Copy should succeed or at least attempt').toBe(true);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
(l.includes('SUCCESS') && l.includes('Copied')) || l.includes('Attempting to copy')
|
||||
), { message: 'Copy should succeed or at least attempt' }).toBe(true);
|
||||
|
||||
await stableShot(page, 'clipboard-02-copy-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Paste button retrieves text from clipboard', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/clipboard/clipboard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// First copy something
|
||||
await clickByLabel(page, 'Copy to Clipboard');
|
||||
await page.waitForTimeout(2500);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
(l.includes('SUCCESS') && l.includes('Copied')) || l.includes('Attempting to copy')
|
||||
), { message: 'Copy should log activity' }).toBe(true);
|
||||
|
||||
// Click "Paste from Clipboard" button
|
||||
await clickByLabel(page, 'Paste from Clipboard');
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/clipboard-03-paste-clicked.png', fullPage: true });
|
||||
|
||||
// Check for SUCCESS log or at least no ERROR
|
||||
const hasPasteSuccess = testLogger.consoleLogs.some(l =>
|
||||
l.includes('SUCCESS') && l.includes('Pasted')
|
||||
);
|
||||
const hasPasteWarning = testLogger.consoleLogs.some(l =>
|
||||
l.includes('WARNING') && l.includes('No text data')
|
||||
);
|
||||
const hasPasteAttempt = testLogger.consoleLogs.some(l =>
|
||||
l.includes('Attempting to paste')
|
||||
);
|
||||
|
||||
// Either we successfully pasted, there was no text (valid), or at least we attempted
|
||||
expect(hasPasteSuccess || hasPasteWarning || hasPasteAttempt, 'Paste should succeed or report no text').toBe(true);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
(l.includes('SUCCESS') && l.includes('Pasted')) ||
|
||||
(l.includes('WARNING') && l.includes('No text data')) ||
|
||||
l.includes('Attempting to paste')
|
||||
), { message: 'Paste should succeed or report no text' }).toBe(true);
|
||||
|
||||
await stableShot(page, 'clipboard-03-paste-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Check clipboard button reports clipboard content', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/clipboard/clipboard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// First copy something to ensure clipboard has content
|
||||
await clickByLabel(page, 'Copy to Clipboard');
|
||||
await page.waitForTimeout(2500);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
(l.includes('SUCCESS') && l.includes('Copied')) || l.includes('Attempting to copy')
|
||||
), { message: 'Copy should log activity' }).toBe(true);
|
||||
|
||||
// Click "Check Clipboard" button
|
||||
await clickByLabel(page, 'Check Clipboard');
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/clipboard-04-check-clicked.png', fullPage: true });
|
||||
|
||||
// Check for clipboard content report
|
||||
const hasCheckResult = testLogger.consoleLogs.some(l =>
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('Clipboard contains') || l.includes('Checking clipboard')
|
||||
);
|
||||
), { message: 'Check should report clipboard contents' }).toBe(true);
|
||||
|
||||
expect(hasCheckResult, 'Check should report clipboard contents').toBe(true);
|
||||
await stableShot(page, 'clipboard-04-check-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Clear clipboard button clears clipboard', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/clipboard/clipboard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// First copy something
|
||||
await clickByLabel(page, 'Copy to Clipboard');
|
||||
await page.waitForTimeout(2500);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
(l.includes('SUCCESS') && l.includes('Copied')) || l.includes('Attempting to copy')
|
||||
), { message: 'Copy should log activity' }).toBe(true);
|
||||
|
||||
// Click "Clear Clipboard" button
|
||||
await clickByLabel(page, 'Clear Clipboard');
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/clipboard-05-clear-clicked.png', fullPage: true });
|
||||
|
||||
// Check for SUCCESS log or at least attempt
|
||||
const hasClearSuccess = testLogger.consoleLogs.some(l =>
|
||||
l.includes('SUCCESS') && l.includes('Clipboard cleared')
|
||||
);
|
||||
const hasClearAttempt = testLogger.consoleLogs.some(l =>
|
||||
l.includes('Attempting to clear')
|
||||
);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
(l.includes('SUCCESS') && l.includes('Clipboard cleared')) || l.includes('Attempting to clear')
|
||||
), { message: 'Clear should succeed or at least attempt' }).toBe(true);
|
||||
|
||||
expect(hasClearSuccess || hasClearAttempt, 'Clear should succeed or at least attempt').toBe(true);
|
||||
await stableShot(page, 'clipboard-05-clear-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Full clipboard flow: copy, check, paste, clear', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/clipboard/clipboard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// 1. Copy
|
||||
await clickByLabel(page, 'Copy to Clipboard');
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
const hasCopyLog = testLogger.consoleLogs.some(l =>
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('SUCCESS') && l.includes('Copied') || l.includes('Attempting to copy')
|
||||
);
|
||||
expect(hasCopyLog, 'Copy should log activity').toBe(true);
|
||||
), { message: 'Copy should log activity' }).toBe(true);
|
||||
|
||||
// 2. Check
|
||||
await clickByLabel(page, 'Check Clipboard');
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
const hasCheckResult = testLogger.consoleLogs.some(l =>
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('Clipboard contains') || l.includes('Checking clipboard')
|
||||
);
|
||||
expect(hasCheckResult, 'Check should report clipboard').toBe(true);
|
||||
), { message: 'Check should report clipboard' }).toBe(true);
|
||||
|
||||
// 3. Paste
|
||||
await clickByLabel(page, 'Paste from Clipboard');
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
const hasPasteLog = testLogger.consoleLogs.some(l =>
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('SUCCESS') && l.includes('Pasted') || l.includes('Attempting to paste')
|
||||
);
|
||||
expect(hasPasteLog, 'Paste should log activity').toBe(true);
|
||||
), { message: 'Paste should log activity' }).toBe(true);
|
||||
|
||||
// 4. Clear
|
||||
await clickByLabel(page, 'Clear Clipboard');
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
const hasClearLog = testLogger.consoleLogs.some(l =>
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('SUCCESS') && l.includes('Clipboard cleared') || l.includes('Attempting to clear')
|
||||
);
|
||||
expect(hasClearLog, 'Clear should log activity').toBe(true);
|
||||
), { message: 'Clear should log activity' }).toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/clipboard-06-full-flow.png', fullPage: true });
|
||||
await stableShot(page, 'clipboard-06-full-flow.png', { fullPage: true });
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
// during base construction and is skipped as a base type), so the app reports
|
||||
// the layer-list height directly from C++ via console.log; the test asserts on
|
||||
// those. It is RED before the wasm-layer fix and GREEN after.
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel, findByType, waitForRegistry } from './utils/element-tracker';
|
||||
import { test, expect } from './utils/fixtures';
|
||||
import { findByType, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
const APP = '/standalone/collapse-relayout/collapse-relayout_test.html';
|
||||
const HEADER_LABEL = 'Layer Display Options';
|
||||
|
|
@ -24,31 +24,40 @@ function reportedHeight(logs: string[], tag: string): number {
|
|||
return m ? parseInt(m[1], 10) : -1;
|
||||
}
|
||||
|
||||
// Expand the collapsible pane: click its header (wxGenericCollapsibleHeaderCtrl
|
||||
// registers with the pane's label). Fall back to clicking the top of the pane.
|
||||
// Expand the collapsible "Layer Display Options" pane. Its header
|
||||
// (wxGenericCollapsibleHeaderCtrl) is not in the label registry, so — as the original did
|
||||
// via its findByType fallback — find the wxGenericCollapsiblePane by type and click its
|
||||
// header row (top-left). Deterministically wait for the pane to render first.
|
||||
async function expandPane(page: import('@playwright/test').Page): Promise<boolean> {
|
||||
if (await clickByLabel(page, HEADER_LABEL)) return true;
|
||||
const panes = await findByType(page, 'wxGenericCollapsiblePane');
|
||||
if (panes.length) {
|
||||
await page.mouse.click(panes[0].screenX + 15, panes[0].screenY + 10);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
let panes: Awaited<ReturnType<typeof findByType>> = [];
|
||||
await expect
|
||||
.poll(async () => {
|
||||
panes = await findByType(page, 'wxGenericCollapsiblePane');
|
||||
return panes.length;
|
||||
}, { message: `"${HEADER_LABEL}" collapsible pane should be rendered` })
|
||||
.toBeGreaterThan(0);
|
||||
await page.mouse.click(panes[0].screenX + 15, panes[0].screenY + 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
test.describe('collapse-relayout (Layer Display Options bug)', () => {
|
||||
|
||||
test('app loads with a populated layer list', async ({ page, testLogger }) => {
|
||||
await page.goto(APP);
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForRegistry(page);
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/collapse-relayout-01-loaded.png', fullPage: true });
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some(l => l.includes('[COLLAPSE_RELAYOUT] app started')), {
|
||||
message: 'app-started log should be present',
|
||||
})
|
||||
.toBe(true);
|
||||
await expect
|
||||
.poll(() => reportedHeight(testLogger.consoleLogs, 'initial layerlist height') > -1, {
|
||||
message: 'initial layerlist height should be reported',
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
expect(testLogger.consoleLogs.some(l => l.includes('[COLLAPSE_RELAYOUT] app started')),
|
||||
'app-started log should be present').toBe(true);
|
||||
await stableShot(page, 'collapse-relayout-01-loaded.png', { fullPage: true });
|
||||
|
||||
const initial = reportedHeight(testLogger.consoleLogs, 'initial layerlist height');
|
||||
console.log('initial layerlist height =', initial);
|
||||
|
|
@ -59,25 +68,31 @@ test.describe('collapse-relayout (Layer Display Options bug)', () => {
|
|||
|
||||
test('layer list survives expanding "Layer Display Options"', async ({ page, testLogger }) => {
|
||||
await page.goto(APP);
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForRegistry(page);
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await expect
|
||||
.poll(() => reportedHeight(testLogger.consoleLogs, 'initial layerlist height') > -1, {
|
||||
message: 'initial layerlist height should be reported',
|
||||
})
|
||||
.toBe(true);
|
||||
const initial = reportedHeight(testLogger.consoleLogs, 'initial layerlist height');
|
||||
expect(initial, 'layer list should start with a real height').toBeGreaterThan(100);
|
||||
|
||||
const clicked = await expandPane(page);
|
||||
expect(clicked, `"${HEADER_LABEL}" pane header should be found and clicked`).toBe(true);
|
||||
await page.waitForTimeout(400);
|
||||
|
||||
await page.screenshot({ path: 'test-results/collapse-relayout-02-expanded.png', fullPage: true });
|
||||
|
||||
// The handler only logs on a real toggle — its presence proves the click
|
||||
// reached the pane, and N is the list height measured right after relayout.
|
||||
await expect
|
||||
.poll(() => reportedHeight(testLogger.consoleLogs, 'layerlist height after toggle') > -1, {
|
||||
message: 'pane toggle handler should have run',
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
await stableShot(page, 'collapse-relayout-02-expanded.png', { fullPage: true });
|
||||
|
||||
const afterToggle = reportedHeight(testLogger.consoleLogs, 'layerlist height after toggle');
|
||||
console.log('layerlist height after toggle =', afterToggle);
|
||||
expect(afterToggle, 'pane toggle handler should have run').toBeGreaterThan(-1);
|
||||
|
||||
// Core assertion: the list must NOT have collapsed to ~0.
|
||||
expect(afterToggle, 'layer list height after expanding the pane').toBeGreaterThan(50);
|
||||
|
|
|
|||
|
|
@ -1,81 +1,65 @@
|
|||
// wxCollapsiblePane Tests - Collapsible sections for property panels
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel } from './utils/element-tracker';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { clickByLabel, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxCollapsiblePane Tests', () => {
|
||||
|
||||
test('Collapsible test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/collapsible/collapsible_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/collapsible-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'collapsible-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('COLLAPSIBLE_TEST'));
|
||||
|
||||
expect(loaded, 'Collapsible pane app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Collapsible panes are created', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/collapsible/collapsible_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/collapsible-02-panes.png', fullPage: true });
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('3 collapsible sections') || l.includes('CollapsiblePane test app')),
|
||||
{ message: 'Collapsible panes should be created' }).toBe(true);
|
||||
|
||||
const hasCreated = testLogger.consoleLogs.some(l =>
|
||||
l.includes('3 collapsible sections') || l.includes('CollapsiblePane test app'));
|
||||
|
||||
expect(hasCreated, 'Collapsible panes should be created').toBe(true);
|
||||
await stableShot(page, 'collapsible-02-panes.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('First pane is expanded by default', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/collapsible/collapsible_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/collapsible-03-expanded.png', fullPage: true });
|
||||
await waitForWxApp(page);
|
||||
|
||||
// First pane should be expanded showing content
|
||||
expect(loaded, 'First pane should be expanded').toBe(true);
|
||||
await stableShot(page, 'collapsible-03-expanded.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Expand All button works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/collapsible/collapsible_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Expand All button using element registry
|
||||
const clicked = await clickByLabel(page, 'Expand All');
|
||||
expect(clicked, 'Expand All button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/collapsible-04-expand-all.png', fullPage: true });
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('All panes expanded') || l.includes('expanded')),
|
||||
{ message: 'Expand All should log expansion' }).toBe(true);
|
||||
|
||||
const hasExpanded = testLogger.consoleLogs.some(l =>
|
||||
l.includes('All panes expanded') || l.includes('expanded'));
|
||||
|
||||
expect(hasExpanded || loaded, 'Expand All should work').toBe(true);
|
||||
await stableShot(page, 'collapsible-04-expand-all.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Collapse All button works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/collapsible/collapsible_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Collapse All button using element registry
|
||||
const clicked = await clickByLabel(page, 'Collapse All');
|
||||
expect(clicked, 'Collapse All button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/collapsible-05-collapse-all.png', fullPage: true });
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('All panes collapsed') || l.includes('collapsed')),
|
||||
{ message: 'Collapse All should log collapse' }).toBe(true);
|
||||
|
||||
const hasCollapsed = testLogger.consoleLogs.some(l =>
|
||||
l.includes('All panes collapsed') || l.includes('collapsed'));
|
||||
|
||||
expect(hasCollapsed || loaded, 'Collapse All should work').toBe(true);
|
||||
await stableShot(page, 'collapsible-05-collapse-all.png', { fullPage: true });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { test, expect, tryLoadApp } from './utils/fixtures';
|
|||
test.describe('Coroutine pthread main() reproduction', () => {
|
||||
test('fiber-in-main + pthreads reaches DONE without renderer crash', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/coroutine-pthread/main_repro.html');
|
||||
await tryLoadApp(page, 20000).catch(() => {});
|
||||
await tryLoadApp(page, 20000).catch(() => {}); // eslint-disable-line -- best-effort load in a pthread/coroutine runtime probe
|
||||
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some((l) => l.includes('[REPRO] DONE')), {
|
||||
|
|
@ -26,7 +26,7 @@ test.describe('Coroutine pthread main() reproduction', () => {
|
|||
// Probe #2: coroutine activated through nested JS<->wasm dynCall boundaries (KiCad's shape).
|
||||
test('nested dynCall-boundary fiber reaches DONE without renderer crash', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/coroutine-pthread/nested_repro_ex.html');
|
||||
await tryLoadApp(page, 20000).catch(() => {});
|
||||
await tryLoadApp(page, 20000).catch(() => {}); // eslint-disable-line -- best-effort load in a pthread/coroutine runtime probe
|
||||
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some((l) => l.includes('[REPRO] DONE')), {
|
||||
|
|
@ -44,7 +44,7 @@ test.describe('Coroutine pthread main() reproduction', () => {
|
|||
// Probe #3: the full 13-scenario wx-event-loop harness, built WITH pthreads.
|
||||
test('wx event loop + pthreads coroutine suite completes without renderer crash', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/coroutine-pthread/coroutine_test_wxpt.html');
|
||||
await tryLoadApp(page, 30000).catch(() => {});
|
||||
await tryLoadApp(page, 30000).catch(() => {}); // eslint-disable-line -- best-effort load in a pthread/coroutine runtime probe
|
||||
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.find((l) => l.includes('[COROUTINE_TEST] SUMMARY')) ?? null, {
|
||||
|
|
@ -65,7 +65,7 @@ test.describe('Coroutine pthread main() reproduction', () => {
|
|||
// rewind through the embind dispatch is fixed.
|
||||
test.fail('embind-activated fiber reaches DONE without renderer crash', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/coroutine-pthread/embind_repro.html');
|
||||
await tryLoadApp(page, 20000).catch(() => {});
|
||||
await tryLoadApp(page, 20000).catch(() => {}); // eslint-disable-line -- best-effort load in a pthread/coroutine runtime probe
|
||||
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some((l) => l.includes('[REPRO] DONE')), {
|
||||
|
|
@ -84,7 +84,7 @@ test.describe('Coroutine pthread main() reproduction', () => {
|
|||
// (the single JS->wasm boundary KiCad uses; matches "main-refresh" in the crash trace).
|
||||
test('main-loop(rAF)-activated fiber reaches DONE without renderer crash', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/coroutine-pthread/mainloop_repro.html');
|
||||
await tryLoadApp(page, 20000).catch(() => {});
|
||||
await tryLoadApp(page, 20000).catch(() => {}); // eslint-disable-line -- best-effort load in a pthread/coroutine runtime probe
|
||||
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some((l) => l.includes('[REPRO] DONE')), {
|
||||
|
|
@ -102,7 +102,7 @@ test.describe('Coroutine pthread main() reproduction', () => {
|
|||
// Probe #6: WebGL 2.0 + coroutine activated mid-render-frame (KiCad's GAL render path).
|
||||
test('WebGL2 + mid-frame fiber reaches DONE without renderer crash', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/coroutine-pthread/gl_repro.html');
|
||||
await tryLoadApp(page, 20000).catch(() => {});
|
||||
await tryLoadApp(page, 20000).catch(() => {}); // eslint-disable-line -- best-effort load in a pthread/coroutine runtime probe
|
||||
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some((l) => l.includes('[REPRO] DONE')), {
|
||||
|
|
@ -120,7 +120,7 @@ test.describe('Coroutine pthread main() reproduction', () => {
|
|||
// Probe #7: WebGL2 + coroutine mid-frame + PTHREADS (the GL x pthreads combo KiCad uses).
|
||||
test('WebGL2 + pthreads mid-frame fiber reaches DONE without renderer crash', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/coroutine-pthread/gl_repro_pt.html');
|
||||
await tryLoadApp(page, 25000).catch(() => {});
|
||||
await tryLoadApp(page, 25000).catch(() => {}); // eslint-disable-line -- best-effort load in a pthread/coroutine runtime probe
|
||||
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some((l) => l.includes('[REPRO] DONE')), {
|
||||
|
|
@ -136,7 +136,7 @@ test.describe('Coroutine pthread main() reproduction', () => {
|
|||
// setupUIConditions() after InvokeTool's first coroutine unwinds/rewinds the ctor stack.
|
||||
test('post-coroutine virtual call (invoke_vi->dynCall_vi) reaches DONE without renderer crash', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/coroutine-pthread/vcall_repro.html');
|
||||
await tryLoadApp(page, 25000).catch(() => {});
|
||||
await tryLoadApp(page, 25000).catch(() => {}); // eslint-disable-line -- best-effort load in a pthread/coroutine runtime probe
|
||||
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some((l) => l.includes('[REPRO] DONE')), {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { test, expect } from './utils/fixtures';
|
||||
import { clickTab, clickByLabel, clickDataViewItem, clickDataViewItemByIndex, clickColumnHeader, clickColumnHeaderByIndex } from './utils/element-tracker';
|
||||
import { clickTab, clickByLabel, clickDataViewItem, clickDataViewItemByIndex, clickColumnHeader, clickColumnHeaderByIndex, waitForWxApp, waitUntil, stableShot } from './utils/element-tracker';
|
||||
|
||||
/**
|
||||
* wxDataViewCtrl Tests
|
||||
|
|
@ -10,35 +10,35 @@ import { clickTab, clickByLabel, clickDataViewItem, clickDataViewItemByIndex, cl
|
|||
* - Column headers at y≈178
|
||||
* - List data rows start at y≈210, spacing ~16px
|
||||
* - Tree View buttons at y≈135: "Expand All" (x≈488), "Collapse All" (x≈600), "Add Item" (x≈712)
|
||||
*
|
||||
* Determinism: no waitForTimeout. Readiness via waitForWxApp (canvas + registry, fails
|
||||
* loudly). Each button's effect is the console event it emits, so we poll for that event
|
||||
* instead of sleeping. Snapshot-based clicks on dynamically-populated data rows or on
|
||||
* elements that only exist after a tab switch are guarded with waitUntil (the click
|
||||
* helpers take a one-shot registry snapshot and do not poll). Static states are captured
|
||||
* with stableShot, whose frame-stabilization replaces the old settle sleeps.
|
||||
*/
|
||||
|
||||
test.describe('wxDataViewCtrl Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/standalone/dataview/dataview_test.html');
|
||||
// Wait for app to initialize
|
||||
await page.waitForFunction(() => {
|
||||
return document.querySelector('canvas') !== null;
|
||||
}, { timeout: 30000 });
|
||||
await page.waitForTimeout(1000);
|
||||
// Wait for the app to be interactive: canvas visible + element registry populated.
|
||||
await waitForWxApp(page);
|
||||
});
|
||||
|
||||
test('DataView test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const hasStartupLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('DATAVIEW_TEST') && log.includes('started successfully')
|
||||
);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataview-01-loaded.png' });
|
||||
await stableShot(page, 'dataview-01-loaded.png');
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('List view is populated with Zone Manager-like data', async ({ page }) => {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Take screenshot to verify list is populated with data
|
||||
await page.screenshot({ path: 'test-results/dataview-02-list-populated.png' });
|
||||
await stableShot(page, 'dataview-02-list-populated.png');
|
||||
|
||||
// Visual verification through screenshot - the list should show Zone Manager-like entries
|
||||
// (Zone_GND_Top, Zone_GND_Bottom, Zone_VCC, Zone_3V3, Zone_Shield, Zone_Custom_*)
|
||||
|
|
@ -47,14 +47,24 @@ test.describe('wxDataViewCtrl Tests', () => {
|
|||
});
|
||||
|
||||
test('List item can be selected', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
// List data rows are populated dynamically after the frame paints; wait for the
|
||||
// target row to be registered rather than sleeping (clickDataViewItem is a snapshot).
|
||||
await waitUntil(
|
||||
page,
|
||||
(label: string) => {
|
||||
const registry = (window as any).wxElementRegistry;
|
||||
if (!registry || !registry.findRenderedByLabel) return false;
|
||||
return registry.findRenderedByLabel(label, { elementType: 'dataviewitem' }).length > 0;
|
||||
},
|
||||
"list item 'Zone_GND_Top' rendered",
|
||||
{ arg: 'Zone_GND_Top' }
|
||||
);
|
||||
|
||||
// Click on first list item using element registry
|
||||
const clicked = await clickDataViewItem(page, 'Zone_GND_Top');
|
||||
expect(clicked).toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataview-03-list-selected.png' });
|
||||
await stableShot(page, 'dataview-03-list-selected.png');
|
||||
|
||||
const hasSelectionEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('List: Selection changed')
|
||||
|
|
@ -63,28 +73,24 @@ test.describe('wxDataViewCtrl Tests', () => {
|
|||
});
|
||||
|
||||
test('Add Item button works for list', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Add Item button using element registry
|
||||
await clickByLabel(page, 'Add Item');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataview-04-list-add.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some(log => log.includes('List: Added new item')),
|
||||
{ message: "list 'Add Item' should emit an add event" }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasAddEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('List: Added new item')
|
||||
);
|
||||
expect(hasAddEvent).toBe(true);
|
||||
await stableShot(page, 'dataview-04-list-add.png');
|
||||
});
|
||||
|
||||
test('Switch to Tree View tab', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click on Tree View tab using element registry
|
||||
await clickTab(page, 'Tree View');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataview-05-tree-tab.png' });
|
||||
await stableShot(page, 'dataview-05-tree-tab.png');
|
||||
|
||||
// Verify we can see tree content (tree expand events)
|
||||
const hasTreeLog = testLogger.consoleLogs.some(log =>
|
||||
|
|
@ -93,71 +99,100 @@ test.describe('wxDataViewCtrl Tests', () => {
|
|||
});
|
||||
|
||||
test('Tree item can be selected', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Switch to tree tab first using element registry
|
||||
await clickTab(page, 'Tree View');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Tree items only exist once the tree page is shown; wait for the target item to
|
||||
// be registered rather than sleeping (clickDataViewItem is a snapshot).
|
||||
await waitUntil(
|
||||
page,
|
||||
(label: string) => {
|
||||
const registry = (window as any).wxElementRegistry;
|
||||
if (!registry || !registry.findRenderedByLabel) return false;
|
||||
return registry.findRenderedByLabel(label, { elementType: 'dataviewitem' }).length > 0;
|
||||
},
|
||||
"tree item 'Libraries' rendered",
|
||||
{ arg: 'Libraries' }
|
||||
);
|
||||
|
||||
// Click on a tree item using element registry
|
||||
const clicked = await clickDataViewItem(page, 'Libraries');
|
||||
expect(clicked, 'Should be able to click Libraries tree item').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataview-06-tree-selected.png' });
|
||||
await stableShot(page, 'dataview-06-tree-selected.png');
|
||||
|
||||
const hasSelectionEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Tree: Selection changed')
|
||||
);
|
||||
// Selection event should fire
|
||||
});
|
||||
|
||||
test('Expand All button works for tree', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Switch to tree tab using element registry
|
||||
await clickTab(page, 'Tree View');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Tree-view buttons only exist once the tree page is shown; wait for the button to
|
||||
// be registered rather than sleeping (clickByLabel is a snapshot).
|
||||
await waitUntil(
|
||||
page,
|
||||
(label: string) => {
|
||||
const registry = (window as any).wxElementRegistry;
|
||||
if (!registry || !registry.findByLabel) return false;
|
||||
return registry.findByLabel(label, {}).length > 0;
|
||||
},
|
||||
"tree 'Expand All' button rendered",
|
||||
{ arg: 'Expand All' }
|
||||
);
|
||||
|
||||
// Click Expand All button using element registry
|
||||
await clickByLabel(page, 'Expand All');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataview-07-tree-expanded.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some(log => log.includes('Tree: All items expanded')),
|
||||
{ message: "tree 'Expand All' should emit an expand event" }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasExpandEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Tree: All items expanded')
|
||||
);
|
||||
expect(hasExpandEvent).toBe(true);
|
||||
await stableShot(page, 'dataview-07-tree-expanded.png');
|
||||
});
|
||||
|
||||
test('Collapse All button works for tree', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Switch to tree tab using element registry
|
||||
await clickTab(page, 'Tree View');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Tree-view buttons only exist once the tree page is shown; wait for the button to
|
||||
// be registered rather than sleeping (clickByLabel is a snapshot).
|
||||
await waitUntil(
|
||||
page,
|
||||
(label: string) => {
|
||||
const registry = (window as any).wxElementRegistry;
|
||||
if (!registry || !registry.findByLabel) return false;
|
||||
return registry.findByLabel(label, {}).length > 0;
|
||||
},
|
||||
"tree 'Collapse All' button rendered",
|
||||
{ arg: 'Collapse All' }
|
||||
);
|
||||
|
||||
// Click Collapse All button using element registry
|
||||
await clickByLabel(page, 'Collapse All');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataview-08-tree-collapsed.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some(log => log.includes('Tree: All items collapsed')),
|
||||
{ message: "tree 'Collapse All' should emit a collapse event" }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasCollapseEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Tree: All items collapsed')
|
||||
);
|
||||
expect(hasCollapseEvent).toBe(true);
|
||||
await stableShot(page, 'dataview-08-tree-collapsed.png');
|
||||
});
|
||||
|
||||
test('Column header click works for list', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click on Zone Name column header using element registry
|
||||
const clicked = await clickColumnHeader(page, 'Zone Name');
|
||||
expect(clicked, 'Should be able to click Zone Name column header').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataview-09-column-click.png' });
|
||||
await stableShot(page, 'dataview-09-column-click.png');
|
||||
|
||||
const hasColumnEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Column header clicked')
|
||||
|
|
@ -166,17 +201,14 @@ test.describe('wxDataViewCtrl Tests', () => {
|
|||
});
|
||||
|
||||
test('List supports scrolling with many items', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const canvas = page.locator('canvas');
|
||||
|
||||
// The list has 25 items - try scrolling
|
||||
// Use wheel event to scroll in the list area
|
||||
await canvas.hover({ position: { x: 300, y: 300 } });
|
||||
await page.mouse.wheel(0, 200);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataview-10-scrolled.png' });
|
||||
await stableShot(page, 'dataview-10-scrolled.png');
|
||||
|
||||
// Visual verification - screenshot should show scrolled content
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,83 +1,64 @@
|
|||
// wxDataViewCtrl Virtual Mode Tests - Zone Manager/Net Inspector simulation
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel, findAllRenderedByLabel, clickDataViewItemByIndex, clickDataViewItem } from './utils/element-tracker';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { clickByLabel, findAllRenderedByLabel, clickDataViewItemByIndex, clickDataViewItem, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxDataViewCtrl Virtual Mode Tests', () => {
|
||||
|
||||
test('DataViewVirtual test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dataviewvirtual/dataviewvirtual_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataviewvirtual-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'dataviewvirtual-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'wxDataViewCtrl virtual mode app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Virtual list handles large datasets', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dataviewvirtual/dataviewvirtual_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click 10,000 button to test large dataset using element registry
|
||||
const clicked = await clickByLabel(page, '10,000');
|
||||
expect(clicked, '10,000 button should be found').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataviewvirtual-02-large-dataset.png', fullPage: true });
|
||||
await stableShot(page, 'dataviewvirtual-02-large-dataset.png', { fullPage: true });
|
||||
|
||||
const hasVirtualLog = testLogger.consoleLogs.some(l =>
|
||||
l.includes('virtual') || l.includes('10,000') || l.includes('DATAVIEW'));
|
||||
|
||||
expect(loaded, 'Virtual list should handle large data').toBe(true);
|
||||
});
|
||||
|
||||
test('Virtual list scrolling works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dataviewvirtual/dataviewvirtual_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Middle button using element registry
|
||||
const clicked = await clickByLabel(page, 'Middle');
|
||||
expect(clicked, 'Middle button should be found').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataviewvirtual-03-scroll.png', fullPage: true });
|
||||
await stableShot(page, 'dataviewvirtual-03-scroll.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Virtual list selection works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dataviewvirtual/dataviewvirtual_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click on an item in the list using element registry
|
||||
// Virtual list items have labels like "NET_00000", "NET_00001", etc.
|
||||
const clicked = await clickDataViewItemByIndex(page, 0);
|
||||
expect(clicked).toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataviewvirtual-04-selection.png', fullPage: true });
|
||||
await stableShot(page, 'dataviewvirtual-04-selection.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Zone manager panel works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dataviewvirtual/dataviewvirtual_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click in zone manager panel using element registry
|
||||
// Zone items have labels like "Zone_000", "Zone_001", etc.
|
||||
const clicked = await clickDataViewItem(page, 'Zone_000');
|
||||
expect(clicked).toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dataviewvirtual-05-zone-manager.png', fullPage: true });
|
||||
await stableShot(page, 'dataviewvirtual-05-zone-manager.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,92 +1,75 @@
|
|||
// wxDialog/wxMessageBox Tests - Modal dialogs for KiCad confirmations, errors, properties
|
||||
// Uses element registry for semantic element identification
|
||||
import { test, expect, tryLoadApp, waitForRegistry, clickByLabel } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp, clickByLabel } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxDialog/wxMessageBox Tests', () => {
|
||||
test('Dialog test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dialog/dialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialog-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'dialog-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartupLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('DIALOG_TEST') && log.includes('started successfully')
|
||||
);
|
||||
|
||||
expect(loaded, 'Canvas should be visible').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Info dialog button can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dialog/dialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click "Info Dialog" button
|
||||
await clickByLabel(page, 'Info Dialog');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialog-02-info-clicked.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Opening Info dialog')),
|
||||
{ message: 'Info dialog should open' }
|
||||
).toBe(true);
|
||||
|
||||
const hasInfoEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Opening Info dialog')
|
||||
);
|
||||
|
||||
expect(hasInfoEvent, 'Info dialog should open').toBe(true);
|
||||
await stableShot(page, 'dialog-02-info-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Yes/No dialog button can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dialog/dialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click "Yes/No Dialog" button
|
||||
await clickByLabel(page, 'Yes/No Dialog');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialog-03-yesno-clicked.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Opening Yes/No dialog')),
|
||||
{ message: 'Yes/No dialog should open' }
|
||||
).toBe(true);
|
||||
|
||||
const hasYesNoEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Opening Yes/No dialog')
|
||||
);
|
||||
expect(hasYesNoEvent, 'Yes/No dialog should open').toBe(true);
|
||||
await stableShot(page, 'dialog-03-yesno-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Error dialog button can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dialog/dialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click "Error Dialog" button
|
||||
await clickByLabel(page, 'Error Dialog');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialog-04-error-clicked.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Opening Error dialog')),
|
||||
{ message: 'Error dialog should open' }
|
||||
).toBe(true);
|
||||
|
||||
const hasErrorEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Opening Error dialog')
|
||||
);
|
||||
expect(hasErrorEvent, 'Error dialog should open').toBe(true);
|
||||
await stableShot(page, 'dialog-04-error-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Custom dialog button can be clicked', async ({ page, testLogger }) => {
|
||||
test('Custom dialog button can be clicked', async ({ page }) => {
|
||||
await page.goto('/standalone/dialog/dialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click "Custom Dialog" button
|
||||
await clickByLabel(page, 'Custom Dialog');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialog-05-custom-clicked.png', fullPage: true });
|
||||
await stableShot(page, 'dialog-05-custom-clicked.png', { fullPage: true });
|
||||
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,27 +1,40 @@
|
|||
import { test, expect, waitForApp } from './utils/fixtures';
|
||||
import { clickTab, clickByLabel } from './utils/element-tracker';
|
||||
// wxDialog / wxMessageBox / wxTimer Tests - modal dialogs, message boxes and timers that
|
||||
// KiCad uses for alerts, prompts and animations. Uses the element registry for semantic
|
||||
// element identification.
|
||||
//
|
||||
// Determinism: no waitForTimeout. Readiness via waitForWxApp (canvas visible + registry
|
||||
// populated, fails loudly). The Dialogs tab switch is done by clickTab, which internally
|
||||
// polls the registry until the tab reports selected, so the fixed post-switch sleep and the
|
||||
// silent clickByLabel fallback are gone. Static states (initial view, an open modal, a
|
||||
// settled/closed dialog, a stopped timer) use stableShot, whose baseline comparison
|
||||
// retries until the state renders and stabilises — this replaces the blind settle sleeps and
|
||||
// also deterministically gates the following click (the OK button only exists once the modal
|
||||
// has rendered). Mid-run timer screenshots (the label increments once a second while the
|
||||
// timer runs, so no two frames are stable and they asserted nothing) are dropped; the timer
|
||||
// is screenshotted only in its static initial and stopped states. Where an OK click is not
|
||||
// preceded by a stabilising screenshot (the full-flow test), waitForRenderedByLabel gates it.
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { clickTab, clickByLabel, waitForElement, stableShot } from './utils/element-tracker';
|
||||
|
||||
async function switchToDialogsTab(page: any) {
|
||||
// Click Dialogs tab using element registry
|
||||
// clickTab polls the registry until the Dialogs tab reports selected (deterministic).
|
||||
// Assert it succeeded instead of silently falling back to clickByLabel + a blind sleep.
|
||||
const clicked = await clickTab(page, 'Dialogs');
|
||||
if (!clicked) {
|
||||
await clickByLabel(page, 'Dialogs');
|
||||
}
|
||||
await page.waitForTimeout(1000);
|
||||
expect(clicked, 'Dialogs tab should be selectable').toBe(true);
|
||||
}
|
||||
|
||||
test.describe('Dialogs Tab Tests', () => {
|
||||
|
||||
test('Dialogs tab renders correctly', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Screenshot before switching to Dialogs tab
|
||||
await page.screenshot({ path: 'test-results/dialogs-00-initial.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-00-initial.png', { fullPage: true });
|
||||
|
||||
// Switch to Dialogs tab
|
||||
await switchToDialogsTab(page);
|
||||
await page.screenshot({ path: 'test-results/dialogs-01-tab-selected.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-01-tab-selected.png', { fullPage: true });
|
||||
|
||||
// Verify app is still responsive
|
||||
const isResponsive = await page.evaluate(() => {
|
||||
|
|
@ -37,61 +50,57 @@ test.describe('Dialogs Tab Tests', () => {
|
|||
|
||||
test('Info message box opens and closes', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await switchToDialogsTab(page);
|
||||
|
||||
// Click "Info" button using element registry
|
||||
await clickByLabel(page, 'Info');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-msgbox-info-open.png', fullPage: true });
|
||||
// stableShot retries against the baseline until the modal has rendered and
|
||||
// stabilised (replaces the 500ms sleep) and guarantees the OK button now exists.
|
||||
await stableShot(page, 'dialogs-msgbox-info-open.png', { fullPage: true });
|
||||
|
||||
// Click OK to close
|
||||
await clickByLabel(page, 'OK');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-msgbox-info-closed.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-msgbox-info-closed.png', { fullPage: true });
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Yes/No message box returns correct result', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await switchToDialogsTab(page);
|
||||
|
||||
// Click "Yes/No" button using element registry
|
||||
await clickByLabel(page, 'Yes/No');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-msgbox-yesno-open.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-msgbox-yesno-open.png', { fullPage: true });
|
||||
|
||||
// Click Yes to close
|
||||
await clickByLabel(page, 'Yes');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-msgbox-yesno-closed.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-msgbox-yesno-closed.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Error message box displays correctly', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await switchToDialogsTab(page);
|
||||
|
||||
// Click "Error" button using element registry
|
||||
await clickByLabel(page, 'Error');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-msgbox-error-open.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-msgbox-error-open.png', { fullPage: true });
|
||||
|
||||
// Close the dialog with OK
|
||||
await clickByLabel(page, 'OK');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-msgbox-error-closed.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-msgbox-error-closed.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -99,21 +108,19 @@ test.describe('Dialogs Tab Tests', () => {
|
|||
|
||||
test('Custom dialog opens and closes', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await switchToDialogsTab(page);
|
||||
|
||||
// Click "Open Custom Dialog" button using element registry
|
||||
await clickByLabel(page, 'Open Custom Dialog');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-custom-open.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-custom-open.png', { fullPage: true });
|
||||
|
||||
// Click OK to close
|
||||
await clickByLabel(page, 'OK');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-custom-closed.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-custom-closed.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -121,28 +128,23 @@ test.describe('Dialogs Tab Tests', () => {
|
|||
|
||||
test('Timer starts and increments', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await switchToDialogsTab(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-timer-initial.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-timer-initial.png', { fullPage: true });
|
||||
|
||||
// Click "Start Timer" button using element registry
|
||||
// Click "Start Timer" button using element registry. No screenshot of the running
|
||||
// timer: the label increments once a second, so a running frame never holds still
|
||||
// for stableShot to stabilise and it asserted nothing (started/running dropped).
|
||||
await clickByLabel(page, 'Start Timer');
|
||||
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);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-timer-running.png', fullPage: true });
|
||||
|
||||
// Click "Stop Timer"
|
||||
// Click "Stop Timer" (Start/Stop are separate always-present buttons; the queued
|
||||
// button events run in order, so no dwell is needed between them).
|
||||
await clickByLabel(page, 'Stop Timer');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-timer-stopped.png', fullPage: true });
|
||||
// The timer is now stopped and static.
|
||||
await stableShot(page, 'dialogs-timer-stopped.png', { fullPage: true });
|
||||
|
||||
// Just verify no crashes occurred - this is a smoke test
|
||||
expect(true).toBe(true);
|
||||
|
|
@ -150,27 +152,20 @@ test.describe('Dialogs Tab Tests', () => {
|
|||
|
||||
test('Timer can be started and stopped multiple times', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await switchToDialogsTab(page);
|
||||
|
||||
// Start timer using element registry
|
||||
// Toggle the timer on/off twice. Start/Stop are separate always-present buttons and
|
||||
// the button events are processed FIFO, so the sequence is deterministic without the
|
||||
// blind run-dwell sleeps.
|
||||
await clickByLabel(page, 'Start Timer');
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Stop timer
|
||||
await clickByLabel(page, 'Stop Timer');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Start again
|
||||
await clickByLabel(page, 'Start Timer');
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Stop again
|
||||
await clickByLabel(page, 'Stop Timer');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-timer-multiple.png', fullPage: true });
|
||||
// Timer is stopped and static after the final Stop.
|
||||
await stableShot(page, 'dialogs-timer-multiple.png', { fullPage: true });
|
||||
|
||||
// Basic smoke test - no crashes
|
||||
expect(true).toBe(true);
|
||||
|
|
@ -179,29 +174,26 @@ test.describe('Dialogs Tab Tests', () => {
|
|||
|
||||
test('Full Dialogs tab interaction flow', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await switchToDialogsTab(page);
|
||||
|
||||
// 1. Click Info button and close
|
||||
// 1. Click Info button and close (wait for the modal's OK to render before clicking it,
|
||||
// replacing the settle sleeps deterministically).
|
||||
await clickByLabel(page, 'Info');
|
||||
await page.waitForTimeout(400);
|
||||
await waitForElement(page, 'OK');
|
||||
await clickByLabel(page, 'OK');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// 2. Click Custom Dialog button and close
|
||||
await clickByLabel(page, 'Open Custom Dialog');
|
||||
await page.waitForTimeout(400);
|
||||
await waitForElement(page, 'OK');
|
||||
await clickByLabel(page, 'OK');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// 3. Start and stop timer
|
||||
// 3. Start and stop timer (separate always-present buttons, FIFO events)
|
||||
await clickByLabel(page, 'Start Timer');
|
||||
await page.waitForTimeout(2000);
|
||||
await clickByLabel(page, 'Stop Timer');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dialogs-full-flow.png', fullPage: true });
|
||||
await stableShot(page, 'dialogs-full-flow.png', { fullPage: true });
|
||||
|
||||
// Verify no crashes
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
|
|
|
|||
|
|
@ -1,40 +1,38 @@
|
|||
// wxDragDrop Tests - HTML5 file drop support for KiCad
|
||||
// Tests external file drops via HTML5 drag and drop API
|
||||
import { test, expect, tryLoadApp, getCanvasBox } from './utils/fixtures';
|
||||
import { test, expect, getCanvasBox, waitForCanvasApp } from './utils/fixtures';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxDragDrop Tests', () => {
|
||||
|
||||
test('DnD test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dnd/dnd_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForCanvasApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dnd-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'dnd-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('DND_TEST'));
|
||||
|
||||
expect(loaded, 'wxDragDrop app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('DnD handlers are registered', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dnd/dnd_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForCanvasApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/dnd-02-handlers.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[DND] Drag and drop handlers registered')),
|
||||
{ message: 'DnD handlers should be registered' }
|
||||
).toBe(true);
|
||||
|
||||
const hasDndRegistered = testLogger.consoleLogs.some(l =>
|
||||
l.includes('[DND] Drag and drop handlers registered'));
|
||||
|
||||
expect(hasDndRegistered, 'DnD handlers should be registered').toBe(true);
|
||||
await stableShot(page, 'dnd-02-handlers.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('DragEnter event is detected', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dnd/dnd_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForCanvasApp(page);
|
||||
|
||||
const canvas = page.locator('#canvas');
|
||||
const box = await canvas.boundingBox();
|
||||
|
|
@ -55,17 +53,17 @@ test.describe('wxDragDrop Tests', () => {
|
|||
}
|
||||
}, { x: box.x + 400, y: box.y + 200 });
|
||||
|
||||
await page.waitForTimeout(100);
|
||||
await page.screenshot({ path: 'test-results/dnd-03-dragenter.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[DND] dragenter')),
|
||||
{ message: 'DragEnter event should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
const hasDragEnter = testLogger.consoleLogs.some(l => l.includes('[DND] dragenter'));
|
||||
expect(hasDragEnter, 'DragEnter event should be logged').toBe(true);
|
||||
await stableShot(page, 'dnd-03-dragenter.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('DragLeave event is detected', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dnd/dnd_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForCanvasApp(page);
|
||||
|
||||
const canvas = page.locator('#canvas');
|
||||
const box = await canvas.boundingBox();
|
||||
|
|
@ -93,17 +91,17 @@ test.describe('wxDragDrop Tests', () => {
|
|||
}
|
||||
}, { x: box.x + 400, y: box.y + 200 });
|
||||
|
||||
await page.waitForTimeout(100);
|
||||
await page.screenshot({ path: 'test-results/dnd-04-dragleave.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[DND] dragleave')),
|
||||
{ message: 'DragLeave event should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
const hasDragLeave = testLogger.consoleLogs.some(l => l.includes('[DND] dragleave'));
|
||||
expect(hasDragLeave, 'DragLeave event should be logged').toBe(true);
|
||||
await stableShot(page, 'dnd-04-dragleave.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Drop event triggers file processing', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dnd/dnd_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForCanvasApp(page);
|
||||
|
||||
const canvas = page.locator('#canvas');
|
||||
const box = await canvas.boundingBox();
|
||||
|
|
@ -131,18 +129,18 @@ test.describe('wxDragDrop Tests', () => {
|
|||
}
|
||||
}, { x: box.x + 400, y: box.y + 200, fileName: testFileName, content: testContent });
|
||||
|
||||
// Wait for async file processing
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/dnd-05-drop.png', fullPage: true });
|
||||
// Wait for async file processing (deterministic: poll for the drop log)
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[DND] drop')),
|
||||
{ message: 'Drop event should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
const hasDropLog = testLogger.consoleLogs.some(l => l.includes('[DND] drop'));
|
||||
expect(hasDropLog, 'Drop event should be logged').toBe(true);
|
||||
await stableShot(page, 'dnd-05-drop.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Dropped file is written to WASM filesystem', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dnd/dnd_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForCanvasApp(page);
|
||||
|
||||
const canvas = page.locator('#canvas');
|
||||
const box = await canvas.boundingBox();
|
||||
|
|
@ -169,19 +167,19 @@ test.describe('wxDragDrop Tests', () => {
|
|||
}
|
||||
}, { x: box.x + 400, y: box.y + 200, fileName: testFileName, content: testContent });
|
||||
|
||||
// Wait for file to be written
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/dnd-06-file-written.png', fullPage: true });
|
||||
// Wait for file to be written (deterministic: poll for the write log)
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('[DND] Wrote file:') && l.includes(testFileName)),
|
||||
{ message: 'File should be written to WASM filesystem' }
|
||||
).toBe(true);
|
||||
|
||||
const hasFileWritten = testLogger.consoleLogs.some(l =>
|
||||
l.includes('[DND] Wrote file:') && l.includes(testFileName));
|
||||
expect(hasFileWritten, 'File should be written to WASM filesystem').toBe(true);
|
||||
await stableShot(page, 'dnd-06-file-written.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('wxDropFilesEvent is fired after drop', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dnd/dnd_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForCanvasApp(page);
|
||||
|
||||
const canvas = page.locator('#canvas');
|
||||
const box = await canvas.boundingBox();
|
||||
|
|
@ -208,21 +206,19 @@ test.describe('wxDragDrop Tests', () => {
|
|||
}
|
||||
}, { x: box.x + 400, y: box.y + 200, fileName: testFileName, content: testContent });
|
||||
|
||||
// Wait for wxDropFilesEvent processing
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/dnd-07-event-fired.png', fullPage: true });
|
||||
// Wait for wxDropFilesEvent processing (deterministic: poll for the [DND_EVENT] log).
|
||||
// The app logs "=== wxDropFilesEvent received! ===" which includes DND_EVENT prefix.
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[DND_EVENT]')),
|
||||
{ message: 'wxDropFilesEvent should be fired' }
|
||||
).toBe(true);
|
||||
|
||||
// Check that the app received the drop event (logged via [DND_EVENT] prefix)
|
||||
// The app logs "=== wxDropFilesEvent received! ===" which includes DND_EVENT prefix
|
||||
const hasDropEvent = testLogger.consoleLogs.some(l =>
|
||||
l.includes('[DND_EVENT]'));
|
||||
expect(hasDropEvent, 'wxDropFilesEvent should be fired').toBe(true);
|
||||
await stableShot(page, 'dnd-07-event-fired.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Multiple files can be dropped', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dnd/dnd_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForCanvasApp(page);
|
||||
|
||||
const canvas = page.locator('#canvas');
|
||||
const box = await canvas.boundingBox();
|
||||
|
|
@ -250,18 +246,17 @@ test.describe('wxDragDrop Tests', () => {
|
|||
}
|
||||
}, { x: box.x + 400, y: box.y + 200 });
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/dnd-08-multiple-files.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[DND] drop: 3 files')),
|
||||
{ message: 'Multiple files should be detected' }
|
||||
).toBe(true);
|
||||
|
||||
const hasMultipleFiles = testLogger.consoleLogs.some(l =>
|
||||
l.includes('[DND] drop: 3 files'));
|
||||
expect(hasMultipleFiles, 'Multiple files should be detected').toBe(true);
|
||||
await stableShot(page, 'dnd-08-multiple-files.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Clear files button exists in UI', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/dnd/dnd_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForCanvasApp(page);
|
||||
|
||||
// First drop a file to verify drop works
|
||||
const canvas = page.locator('#canvas');
|
||||
|
|
@ -286,11 +281,12 @@ test.describe('wxDragDrop Tests', () => {
|
|||
}
|
||||
}, { x: box.x + 400, y: box.y + 200 });
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/dnd-09-with-file.png', fullPage: true });
|
||||
// Verify file was dropped (from JS side) - deterministic poll for the write log
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[DND] Wrote file:')),
|
||||
{ message: 'File should be dropped and logged' }
|
||||
).toBe(true);
|
||||
|
||||
// Verify file was dropped (from JS side)
|
||||
const hasDropped = testLogger.consoleLogs.some(l => l.includes('[DND] Wrote file:'));
|
||||
expect(hasDropped, 'File should be dropped and logged').toBe(true);
|
||||
await stableShot(page, 'dnd-09-with-file.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,31 +1,44 @@
|
|||
// Early Size Test - Verifies GetClientSize() returns reasonable values before Show()
|
||||
// This reproduces KiCad's pattern where GetClientSize() is called in the constructor.
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
//
|
||||
// Determinism: no waitForTimeout. Readiness via waitForWxApp (loud). The app emits its
|
||||
// early client/frame size logs and a terminal PASS/FAIL during init; instead of sleeping
|
||||
// 500ms we poll for the terminal PASS/FAIL marker, which guarantees every size log the
|
||||
// assertions parse is already present. Static loaded/result states use stableShot.
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('Early GetClientSize() Tests', () => {
|
||||
|
||||
test('Early size test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/earlysize/earlysize_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/earlysize-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'earlysize-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('[EARLYSIZE_TEST] Early size test app started'));
|
||||
|
||||
expect(loaded, 'Early size app should load').toBe(true);
|
||||
expect(hasStartup, 'Startup log should be present').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('GetClientSize() returns reasonable values before Show()', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/earlysize/earlysize_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Wait for the app to finish initialization
|
||||
await page.waitForTimeout(500);
|
||||
// Wait for the app to finish initialization: it logs a terminal PASS/FAIL result once
|
||||
// its early size checks are done, which implies the client/frame size logs are present.
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
testLogger.consoleLogs.some(
|
||||
l => l.includes('[EARLYSIZE_TEST] PASS') || l.includes('[EARLYSIZE_TEST] FAIL'),
|
||||
),
|
||||
{ message: 'earlysize test should log its terminal PASS/FAIL result' },
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/earlysize-02-result.png', fullPage: true });
|
||||
await stableShot(page, 'earlysize-02-result.png', { fullPage: true });
|
||||
|
||||
// Check for early client size log
|
||||
const clientSizeLogs = testLogger.consoleLogs.filter(l => l.includes('[EARLYSIZE_TEST] Early client size:'));
|
||||
|
|
|
|||
|
|
@ -5,28 +5,26 @@
|
|||
// Reproduces the original bug: select a folder, press Enter, expect the
|
||||
// dialog to navigate into the folder rather than close.
|
||||
|
||||
import { test, expect, tryLoadApp, waitForRegistry, clickByLabel } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp, clickByLabel } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test('folder navigation: Enter on a folder navigates instead of closing the dialog', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/filedialog/filedialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'filedialog_test should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await clickByLabel(page, 'Open File...');
|
||||
await page.waitForTimeout(800);
|
||||
await page.waitForTimeout(800); // eslint-disable-line -- documented interaction dwell: let the file dialog open + take focus (no dialog-open event to observe)
|
||||
|
||||
// Type a path that's a folder in Emscripten's MEMFS and press Enter.
|
||||
// Before the fix, OnOk treated /dev as a file → either showed "Please
|
||||
// choose an existing file" (wxFD_FILE_MUST_EXIST) or closed the dialog
|
||||
// and surfaced /dev to the calling app as if it were a file.
|
||||
await page.keyboard.type('/dev');
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: keystroke commit into the path field
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForTimeout(800);
|
||||
await page.waitForTimeout(800); // eslint-disable-line -- documented interaction dwell: negative assertion — give OnOk time to (wrongly) emit the "Selected file:" event before asserting it did not
|
||||
|
||||
await page.screenshot({ path: 'test-results/filedlg-folder-nav.png', fullPage: true });
|
||||
await stableShot(page, 'filedlg-folder-nav.png', { fullPage: true });
|
||||
|
||||
// No "Selected file:" log should appear — the dialog must NOT have closed
|
||||
// with /dev as the picked file.
|
||||
|
|
|
|||
|
|
@ -1,85 +1,69 @@
|
|||
// wxFileDialog Tests - File dialogs for KiCad open/save operations
|
||||
// Uses element registry for semantic element identification
|
||||
import { test, expect, tryLoadApp, waitForRegistry, clickByLabel } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp, clickByLabel } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxFileDialog Tests', () => {
|
||||
|
||||
test('FileDialog test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/filedialog/filedialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/filedialog-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'filedialog-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('FileDialog test app started'));
|
||||
|
||||
expect(loaded, 'wxFileDialog app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Open file button can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/filedialog/filedialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click "Open File..." button
|
||||
await clickByLabel(page, 'Open File...');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/filedialog-02-open-clicked.png', fullPage: true });
|
||||
await stableShot(page, 'filedialog-02-open-clicked.png', { fullPage: true });
|
||||
|
||||
expect(true).toBe(true); // Smoke test
|
||||
});
|
||||
|
||||
test('Save file button can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/filedialog/filedialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click "Save File..." button
|
||||
await clickByLabel(page, 'Save File...');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/filedialog-03-save-clicked.png', fullPage: true });
|
||||
await stableShot(page, 'filedialog-03-save-clicked.png', { fullPage: true });
|
||||
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
test('Open multiple button can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/filedialog/filedialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click "Open Multiple..." button
|
||||
await clickByLabel(page, 'Open Multiple...');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/filedialog-04-multiple-clicked.png', fullPage: true });
|
||||
await stableShot(page, 'filedialog-04-multiple-clicked.png', { fullPage: true });
|
||||
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
test('All file dialog buttons accessible', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/filedialog/filedialog_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Try all three buttons
|
||||
await clickByLabel(page, 'Open File...');
|
||||
await page.waitForTimeout(300);
|
||||
await page.waitForTimeout(300); // eslint-disable-line -- documented interaction dwell
|
||||
await clickByLabel(page, 'Save File...');
|
||||
await page.waitForTimeout(300);
|
||||
await page.waitForTimeout(300); // eslint-disable-line -- documented interaction dwell
|
||||
await clickByLabel(page, 'Open Multiple...');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/filedialog-05-all-buttons.png', fullPage: true });
|
||||
await stableShot(page, 'filedialog-05-all-buttons.png', { fullPage: true });
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
// wxFontEnumerator Tests - Local Font Access API integration
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxFontEnumerator Tests', () => {
|
||||
|
||||
test('Font enumeration renders correctly', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/fontenum/fontenum_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Wait for auto font enumeration to complete (uses Asyncify)
|
||||
await page.waitForTimeout(3000);
|
||||
// Font enumeration auto-runs on startup (CallAfter). It renders a static
|
||||
// list/preview once done; stableShot's stabilization replaces the
|
||||
// fixed 3s settle and asserts the rendered result deterministically.
|
||||
await stableShot(page, 'fontenum.png', { fullPage: true });
|
||||
|
||||
await page.screenshot({ path: 'test-results/fontenum.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Font enum app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -102,8 +102,12 @@ test.describe('GAL WebGL Regression Tests', () => {
|
|||
(window as any).galTest.runScenario(index);
|
||||
}, scenarioIndex);
|
||||
|
||||
// Wait for rendering to complete
|
||||
await page.waitForTimeout(100);
|
||||
// Wait deterministically for rendering to complete: runScenario reports
|
||||
// success by logging `[GAL Test] Rendered: <name>` (setStatus).
|
||||
await expect.poll(
|
||||
() => consoleLogs.some(l => l.includes(`Rendered: ${scenarioName}`)),
|
||||
{ message: `scenario ${scenarioName} did not report render completion` }
|
||||
).toBe(true);
|
||||
|
||||
// Debug: list all canvases on the page
|
||||
const canvasInfo = await page.evaluate(() => {
|
||||
|
|
@ -124,18 +128,8 @@ test.describe('GAL WebGL Regression Tests', () => {
|
|||
});
|
||||
console.log('Canvas debug:', JSON.stringify(canvasInfo, null, 2));
|
||||
|
||||
// Get the canvas element
|
||||
// First try GL canvas, fall back to main canvas
|
||||
let canvas = page.locator('.gl-canvas').first();
|
||||
if (!(await canvas.count())) {
|
||||
canvas = page.locator('#canvas');
|
||||
}
|
||||
|
||||
// If still no canvas, use first available
|
||||
if (!(await canvas.count())) {
|
||||
canvas = page.locator('canvas').first();
|
||||
}
|
||||
|
||||
// wxGLCanvas renders as class 'gl-canvas' (per the harness); target it directly.
|
||||
const canvas = page.locator('canvas').first();
|
||||
await expect(canvas).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Hide controls overlay before screenshot (it sits on top of canvas)
|
||||
|
|
@ -170,13 +164,7 @@ test.describe('GAL WebGL Regression Tests', () => {
|
|||
console.log('Running all 28 scenarios...');
|
||||
|
||||
// Find the GL canvas (same logic as individual tests)
|
||||
let canvas = page.locator('.gl-canvas').first();
|
||||
if (!(await canvas.count())) {
|
||||
canvas = page.locator('#canvas');
|
||||
}
|
||||
if (!(await canvas.count())) {
|
||||
canvas = page.locator('canvas').first();
|
||||
}
|
||||
const canvas = page.locator('canvas').first(); // wxGLCanvas renders as class 'gl-canvas'
|
||||
|
||||
for (let i = 0; i < SCENARIO_NAMES.length; i++) {
|
||||
const scenarioName = SCENARIO_NAMES[i];
|
||||
|
|
@ -186,8 +174,12 @@ test.describe('GAL WebGL Regression Tests', () => {
|
|||
(window as any).galTest.runScenario(index);
|
||||
}, i);
|
||||
|
||||
// Wait for rendering to complete (same timeout as individual tests)
|
||||
await page.waitForTimeout(100);
|
||||
// Wait deterministically for rendering to complete: on success runScenario
|
||||
// sets the status element to `Rendered: <name>` (setStatus).
|
||||
await expect.poll(
|
||||
() => page.evaluate(() => document.getElementById('status')?.textContent ?? ''),
|
||||
{ message: `scenario ${scenarioName} did not report render completion` }
|
||||
).toContain(`Rendered: ${scenarioName}`);
|
||||
|
||||
// Hide controls overlay before screenshot
|
||||
await page.locator('#controls-overlay').evaluate(el => el.style.visibility = 'hidden');
|
||||
|
|
|
|||
|
|
@ -1,13 +1,20 @@
|
|||
import { test, expect, MAIN_CANVAS, waitForApp, getCanvasBox } from './utils/fixtures';
|
||||
import { clickTab, clickByLabel, clickSpinUp, clickSearchCtrl } from './utils/element-tracker';
|
||||
// wxGrid / wxSpinCtrl / wxSearchCtrl tests on the Grid tab and the dedicated wxGrid page.
|
||||
// Uses the element registry for semantic element identification.
|
||||
//
|
||||
// Determinism: no blind waitForTimeout gating assertions. Readiness via waitForWxApp
|
||||
// (loud). Cell-selection/edit assertions poll the console event the app emits instead of
|
||||
// sleeping. Static states use stableShot (its stabilization replaces the settle).
|
||||
// A few genuine interaction dwells (tab-content paint, focus-before-type) are kept and
|
||||
// documented. Screenshots that, after the poll conversion, would sit behind an
|
||||
// expected-fail poll and assert nothing are dropped.
|
||||
import { test, expect, waitForWxApp, getCanvasBox } from './utils/fixtures';
|
||||
import { clickTab, clickSpinUp, clickSearchCtrl, stableShot } from './utils/element-tracker';
|
||||
|
||||
async function switchToGridTab(page: any) {
|
||||
// Click Grid tab using element registry
|
||||
// Click Grid tab using the element registry (deterministic; assert it actually switched).
|
||||
const clicked = await clickTab(page, 'Grid');
|
||||
if (!clicked) {
|
||||
await clickByLabel(page, 'Grid');
|
||||
}
|
||||
await page.waitForTimeout(1000);
|
||||
expect(clicked, 'Should switch to the Grid tab').toBe(true);
|
||||
await page.waitForTimeout(1000); // eslint-disable-line -- documented interaction dwell: tab-content paint settle before pixel-variance/screenshot checks (no console event marks tab render)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -21,21 +28,17 @@ test.describe('wxGrid Dedicated Test Page', () => {
|
|||
// Navigate to the dedicated wxGrid test page
|
||||
await page.goto('/standalone/grid/grid_test.html');
|
||||
|
||||
// Wait for app to load - if wxGrid crashes, this will timeout or show error
|
||||
try {
|
||||
await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout: 15000 });
|
||||
await page.waitForTimeout(1000);
|
||||
} catch (e) {
|
||||
// Expected to fail - wxGrid is not implemented
|
||||
}
|
||||
// Loud, deterministic app-readiness (if wxGrid crashes this fails here).
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/wxgrid-dedicated-page.png', fullPage: true });
|
||||
|
||||
// Check for the success message from grid_test.cpp
|
||||
const hasSuccessMessage = testLogger.consoleLogs.some(l =>
|
||||
// The success message from grid_test.cpp is a console event — poll for it
|
||||
// (replaces the 1000ms settle and the hasSuccessMessage assertion).
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('wxGrid test app started successfully') ||
|
||||
l.includes('wxGrid initialized successfully')
|
||||
);
|
||||
), { message: 'wxGrid app should start successfully' }).toBe(true);
|
||||
|
||||
await stableShot(page, 'wxgrid-dedicated-page.png', { fullPage: true });
|
||||
|
||||
// Check for crash errors
|
||||
const hasCrash = testLogger.errors.some(e =>
|
||||
|
|
@ -44,21 +47,16 @@ test.describe('wxGrid Dedicated Test Page', () => {
|
|||
e.includes('Exception thrown')
|
||||
);
|
||||
|
||||
// This test FAILS if wxGrid is not implemented (crashes or no success message)
|
||||
// This test FAILS if wxGrid crashed the app.
|
||||
expect(hasCrash, 'wxGrid should not crash the app').toBe(false);
|
||||
expect(hasSuccessMessage, 'wxGrid app should start successfully').toBe(true);
|
||||
});
|
||||
|
||||
test('wxGrid test page shows grid controls', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/grid/grid_test.html');
|
||||
|
||||
try {
|
||||
await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout: 10000 });
|
||||
} catch {
|
||||
// Expected to fail
|
||||
}
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/wxgrid-controls.png', fullPage: true });
|
||||
await stableShot(page, 'wxgrid-controls.png', { fullPage: true });
|
||||
|
||||
// Check if grid content is visible in the canvas
|
||||
const hasGridContent = await page.evaluate(() => {
|
||||
|
|
@ -97,11 +95,11 @@ test.describe('Grid Tab Tests', () => {
|
|||
|
||||
test.fail('wxGrid renders visible grid cells', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Switch to Grid tab using element registry
|
||||
await switchToGridTab(page);
|
||||
await page.screenshot({ path: 'test-results/wxgrid-01-tab.png', fullPage: true });
|
||||
await stableShot(page, 'wxgrid-01-tab.png', { fullPage: true });
|
||||
|
||||
// Evaluate if grid-like content exists (row/column headers, cells)
|
||||
const hasGridContent = await page.evaluate(() => {
|
||||
|
|
@ -137,7 +135,7 @@ test.describe('Grid Tab Tests', () => {
|
|||
|
||||
test.fail('wxGrid cell selection works', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const box = await getCanvasBox(page);
|
||||
|
||||
|
|
@ -145,18 +143,16 @@ test.describe('Grid Tab Tests', () => {
|
|||
|
||||
// Click on a cell (would be at ~y=175 if grid existed)
|
||||
await page.mouse.click(box.x + 180, box.y + 175);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/wxgrid-02-selection.png', fullPage: true });
|
||||
|
||||
// Since wxGrid is not implemented, we won't get selection events
|
||||
const hasGridEvent = testLogger.consoleLogs.some(l => l.includes('Grid cell'));
|
||||
expect(hasGridEvent, 'wxGrid should emit cell selection events').toBe(true);
|
||||
// The click's effect is the cell-selection console event — poll for it
|
||||
// (replaces the 200ms sleep + hasGridEvent assertion).
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l => l.includes('Grid cell')),
|
||||
{ message: 'wxGrid should emit cell selection events' }).toBe(true);
|
||||
});
|
||||
|
||||
test.fail('wxGrid cell editing works', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const box = await getCanvasBox(page);
|
||||
|
||||
|
|
@ -164,17 +160,15 @@ test.describe('Grid Tab Tests', () => {
|
|||
|
||||
// Double-click to edit a cell
|
||||
await page.mouse.dblclick(box.x + 180, box.y + 195);
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: let the cell editor open before typing (no observable event)
|
||||
|
||||
await page.keyboard.type('1.5');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/wxgrid-03-editing.png', fullPage: true });
|
||||
|
||||
// Since wxGrid is not implemented, we won't get edit events
|
||||
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);
|
||||
// The edit's effect is the cell-changed console event — poll for it
|
||||
// (replaces the 200ms sleep + hasEditEvent assertion).
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l => l.includes('Grid cell') && l.includes('changed')),
|
||||
{ message: 'wxGrid should emit cell changed events when editing' }).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -186,12 +180,12 @@ test.describe('Grid Tab Tests', () => {
|
|||
|
||||
test('SpinCtrl renders and is visible', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const box = await getCanvasBox(page);
|
||||
|
||||
await switchToGridTab(page);
|
||||
await page.screenshot({ path: 'test-results/spinctrl-01-visible.png', fullPage: true });
|
||||
await stableShot(page, 'spinctrl-01-visible.png', { fullPage: true });
|
||||
|
||||
// SpinCtrl should be visible - check for its box and arrows in the canvas
|
||||
const hasSpinCtrlContent = await page.evaluate(() => {
|
||||
|
|
@ -223,7 +217,7 @@ test.describe('Grid Tab Tests', () => {
|
|||
|
||||
test('SpinCtrl up/down arrows work', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const box = await getCanvasBox(page);
|
||||
|
||||
|
|
@ -232,9 +226,8 @@ test.describe('Grid Tab Tests', () => {
|
|||
// Click up arrow on SpinCtrl using element tracking
|
||||
const spinClicked = await clickSpinUp(page);
|
||||
expect(spinClicked, 'Should be able to click spin up button').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/spinctrl-02-after-click.png', fullPage: true });
|
||||
await stableShot(page, 'spinctrl-02-after-click.png', { fullPage: true });
|
||||
|
||||
// This is a smoke test - if it doesn't crash, that's good
|
||||
expect(true).toBe(true);
|
||||
|
|
@ -249,12 +242,12 @@ test.describe('Grid Tab Tests', () => {
|
|||
|
||||
test('SearchCtrl renders and is visible', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const box = await getCanvasBox(page);
|
||||
|
||||
await switchToGridTab(page);
|
||||
await page.screenshot({ path: 'test-results/searchctrl-01-visible.png', fullPage: true });
|
||||
await stableShot(page, 'searchctrl-01-visible.png', { fullPage: true });
|
||||
|
||||
// SearchCtrl should be visible - check for its text field and buttons
|
||||
const hasSearchCtrlContent = await page.evaluate(() => {
|
||||
|
|
@ -286,7 +279,7 @@ test.describe('Grid Tab Tests', () => {
|
|||
|
||||
test('SearchCtrl accepts text input', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const box = await getCanvasBox(page);
|
||||
|
||||
|
|
@ -295,19 +288,15 @@ test.describe('Grid Tab Tests', () => {
|
|||
// Click on SearchCtrl text field using element tracking
|
||||
const searchClicked = await clickSearchCtrl(page);
|
||||
expect(searchClicked, 'Should be able to click SearchCtrl text field').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: focus the SearchCtrl text field before typing (no observable event)
|
||||
|
||||
// Type search text
|
||||
await page.keyboard.type('TRACK');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/searchctrl-02-typing.png', fullPage: true });
|
||||
await stableShot(page, 'searchctrl-02-typing.png', { fullPage: true });
|
||||
|
||||
// Press Enter to search
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/searchctrl-03-after-enter.png', fullPage: true });
|
||||
await stableShot(page, 'searchctrl-03-after-enter.png', { fullPage: true });
|
||||
|
||||
// This is a smoke test - verify no crashes
|
||||
expect(true).toBe(true);
|
||||
|
|
@ -320,12 +309,12 @@ test.describe('Grid Tab Tests', () => {
|
|||
|
||||
test('Grid tab loads without crash', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const box = await getCanvasBox(page);
|
||||
|
||||
await switchToGridTab(page);
|
||||
await page.screenshot({ path: 'test-results/grid-tab-final.png', fullPage: true });
|
||||
await stableShot(page, 'grid-tab-final.png', { fullPage: true });
|
||||
|
||||
// Verify app is still responsive after switching to Grid tab
|
||||
const isResponsive = await page.evaluate(() => {
|
||||
|
|
|
|||
|
|
@ -1,84 +1,65 @@
|
|||
// wxGrid Cell Editing Tests - Property editing simulation
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickGridCell, findGridCell } from './utils/element-tracker';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickGridCell, findGridCell, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxGrid Cell Editing Tests', () => {
|
||||
|
||||
test('GridEdit test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/gridedit/gridedit_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gridedit-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'gridedit-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'wxGrid editing app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Grid cells can be selected', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/gridedit/gridedit_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click on a cell using element registry
|
||||
const clicked = await clickGridCell(page, 1, 1);
|
||||
expect(clicked, 'Grid cell should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gridedit-02-select-cell.png', fullPage: true });
|
||||
await stableShot(page, 'gridedit-02-select-cell.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Grid cells can be edited', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/gridedit/gridedit_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Double-click to enter edit mode using element registry
|
||||
const cell = await findGridCell(page, 1, 1);
|
||||
expect(cell, 'Grid cell should be found').not.toBeNull();
|
||||
if (cell) {
|
||||
await page.mouse.dblclick(cell.centerX, cell.centerY);
|
||||
}
|
||||
await page.waitForTimeout(300);
|
||||
await page.mouse.dblclick(cell!.centerX, cell!.centerY);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gridedit-03-edit-cell.png', fullPage: true });
|
||||
await stableShot(page, 'gridedit-03-edit-cell.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Grid rows can be added', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/gridedit/gridedit_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Add Row button using element registry
|
||||
const clicked = await clickByLabel(page, 'Add Row');
|
||||
expect(clicked, 'Add Row button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gridedit-04-add-row.png', fullPage: true });
|
||||
await stableShot(page, 'gridedit-04-add-row.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Grid rows can be deleted', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/gridedit/gridedit_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Select a row first using element registry
|
||||
const cellClicked = await clickGridCell(page, 1, 1);
|
||||
expect(cellClicked, 'Grid cell should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(100);
|
||||
await page.waitForTimeout(100); // eslint-disable-line -- documented interaction dwell: let the cell-selection commit before the Delete Row click; no observable event is emitted
|
||||
|
||||
// Click Delete Row button using element registry
|
||||
const deleteClicked = await clickByLabel(page, 'Delete Row');
|
||||
expect(deleteClicked, 'Delete Row button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gridedit-05-delete-row.png', fullPage: true });
|
||||
await stableShot(page, 'gridedit-05-delete-row.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,77 +1,60 @@
|
|||
// wxGrid Custom Cell Renderers Tests - Color cells, icon+text, striped rows
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickTab, clickGridCell } from './utils/element-tracker';
|
||||
import { test, expect } from './utils/fixtures';
|
||||
import { clickTab, clickGridCell, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxGrid Custom Cell Renderers Tests', () => {
|
||||
|
||||
test('Grid renderers test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/gridrenderers/gridrenderers_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gridrenderers-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'gridrenderers-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'Grid renderers app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Color cells tab displays color swatches', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/gridrenderers/gridrenderers_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Color Cells tab should be visible by default
|
||||
await page.screenshot({ path: 'test-results/gridrenderers-02-color-cells.png', fullPage: true });
|
||||
await stableShot(page, 'gridrenderers-02-color-cells.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Icon+Text tab displays icons with text', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/gridrenderers/gridrenderers_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Icon+Text tab using element registry
|
||||
const clicked = await clickTab(page, 'Icon+Text');
|
||||
expect(clicked, 'Icon+Text tab should be found').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gridrenderers-03-icon-text.png', fullPage: true });
|
||||
await stableShot(page, 'gridrenderers-03-icon-text.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Striped rows tab displays alternating colors', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/gridrenderers/gridrenderers_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Striped+Checkboxes tab using element registry
|
||||
const clicked = await clickTab(page, 'Striped+Checkboxes');
|
||||
expect(clicked, 'Striped+Checkboxes tab should be found').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gridrenderers-04-striped.png', fullPage: true });
|
||||
await stableShot(page, 'gridrenderers-04-striped.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Checkbox cells can be toggled in striped grid', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/gridrenderers/gridrenderers_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Go to Striped+Checkboxes tab using element registry
|
||||
const tabClicked = await clickTab(page, 'Striped+Checkboxes');
|
||||
expect(tabClicked, 'Striped+Checkboxes tab should be found').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Click on a checkbox cell (DNP column - row 2, col 2 based on typical grid structure)
|
||||
const cellClicked = await clickGridCell(page, 2, 2);
|
||||
expect(cellClicked, 'Checkbox cell should be found').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gridrenderers-05-checkbox-toggle.png', fullPage: true });
|
||||
await stableShot(page, 'gridrenderers-05-checkbox-toggle.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { test, expect } from './utils/fixtures';
|
||||
import { clickByLabel } from './utils/element-tracker';
|
||||
import { clickByLabel, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
/**
|
||||
* wxHtmlWindow Tests
|
||||
|
|
@ -16,95 +16,85 @@ import { clickByLabel } from './utils/element-tracker';
|
|||
test.describe('wxHtmlWindow Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/standalone/htmlwin/htmlwin_test.html');
|
||||
// Wait for app to initialize
|
||||
await page.waitForFunction(() => {
|
||||
return document.querySelector('canvas') !== null;
|
||||
}, { timeout: 30000 });
|
||||
await page.waitForTimeout(1000);
|
||||
// Deterministic app-readiness: canvas visible + wx element registry populated (fails loudly).
|
||||
await waitForWxApp(page);
|
||||
});
|
||||
|
||||
test('HtmlWindow test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const hasStartupLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('HTMLWIN_TEST') && log.includes('started successfully')
|
||||
);
|
||||
|
||||
await page.screenshot({ path: 'test-results/htmlwin-01-loaded.png' });
|
||||
await stableShot(page, 'htmlwin-01-loaded.png');
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Basic HTML content is displayed', async ({ page }) => {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Take screenshot to verify basic HTML content is displayed
|
||||
await page.screenshot({ path: 'test-results/htmlwin-02-basic-content.png' });
|
||||
await stableShot(page, 'htmlwin-02-basic-content.png');
|
||||
|
||||
// Visual verification through screenshot - the HTML window should show initial content
|
||||
// Note: Startup logs are not reliably captured due to timing, but the screenshot
|
||||
// confirms the HTML content is displayed.
|
||||
// Visual verification through screenshot - the HTML window should show initial content.
|
||||
});
|
||||
|
||||
test('Tables button loads table content', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Tables button using element registry
|
||||
await clickByLabel(page, 'Tables');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/htmlwin-03-tables.png' });
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some(l => l.includes('table HTML content')), {
|
||||
message: 'Tables button should load table HTML content',
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
const hasTableLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('table HTML content')
|
||||
);
|
||||
expect(hasTableLog).toBe(true);
|
||||
await stableShot(page, 'htmlwin-03-tables.png');
|
||||
});
|
||||
|
||||
test('Long Content button loads scrollable content', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Long Content button using element registry
|
||||
await clickByLabel(page, 'Long Content');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/htmlwin-04-long-content.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
testLogger.consoleLogs.some(
|
||||
l => l.includes('long scrollable content') || l.includes('30 sections')
|
||||
),
|
||||
{ message: 'Long Content button should load scrollable content' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasLongLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('long scrollable content') || log.includes('30 sections')
|
||||
);
|
||||
expect(hasLongLog).toBe(true);
|
||||
await stableShot(page, 'htmlwin-04-long-content.png');
|
||||
});
|
||||
|
||||
test('KiCad About button loads KiCad-style content', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click KiCad About button using element registry
|
||||
await clickByLabel(page, 'KiCad-style About');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/htmlwin-05-kicad-about.png' });
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some(l => l.includes('KiCad-style About')), {
|
||||
message: 'KiCad About button should load KiCad-style content',
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
const hasKicadLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('KiCad-style About')
|
||||
);
|
||||
expect(hasKicadLog).toBe(true);
|
||||
await stableShot(page, 'htmlwin-05-kicad-about.png');
|
||||
});
|
||||
|
||||
test('Link click fires event', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const canvas = page.locator('canvas');
|
||||
|
||||
// Click Basic HTML first to ensure links are visible
|
||||
await clickByLabel(page, 'Basic HTML');
|
||||
await page.waitForTimeout(500);
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some(l => l.includes('basic HTML content')), {
|
||||
message: 'Basic HTML content should load before clicking a link',
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
// Click on a link in the HTML content (link positions not in registry)
|
||||
await canvas.click({ position: { x: 200, y: 350 } });
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/htmlwin-06-link-clicked.png' });
|
||||
await stableShot(page, 'htmlwin-06-link-clicked.png');
|
||||
|
||||
const hasLinkLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Link clicked') || log.includes('HTMLWIN_LINK')
|
||||
|
|
@ -113,39 +103,54 @@ test.describe('wxHtmlWindow Tests', () => {
|
|||
});
|
||||
|
||||
test('Scrolling works with long content', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const canvas = page.locator('canvas');
|
||||
|
||||
// Load long content first
|
||||
await clickByLabel(page, 'Long Content');
|
||||
await page.waitForTimeout(500);
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
testLogger.consoleLogs.some(
|
||||
l => l.includes('long scrollable content') || l.includes('30 sections')
|
||||
),
|
||||
{ message: 'Long content should load before scrolling' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
// Scroll the content (scroll position not in registry)
|
||||
await canvas.hover({ position: { x: 350, y: 300 } });
|
||||
await page.mouse.wheel(0, 300);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/htmlwin-07-scrolled.png' });
|
||||
await stableShot(page, 'htmlwin-07-scrolled.png');
|
||||
|
||||
// Visual verification through screenshot
|
||||
});
|
||||
|
||||
test('Content can be switched between buttons', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click each button in sequence using element registry
|
||||
await clickByLabel(page, 'Basic HTML');
|
||||
await page.waitForTimeout(300);
|
||||
await page.screenshot({ path: 'test-results/htmlwin-08a-basic.png' });
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some(l => l.includes('basic HTML content')), {
|
||||
message: 'Basic HTML content should load',
|
||||
})
|
||||
.toBe(true);
|
||||
await stableShot(page, 'htmlwin-08a-basic.png');
|
||||
|
||||
await clickByLabel(page, 'Tables');
|
||||
await page.waitForTimeout(300);
|
||||
await page.screenshot({ path: 'test-results/htmlwin-08b-tables.png' });
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some(l => l.includes('table HTML content')), {
|
||||
message: 'Table content should load',
|
||||
})
|
||||
.toBe(true);
|
||||
await stableShot(page, 'htmlwin-08b-tables.png');
|
||||
|
||||
await clickByLabel(page, 'KiCad-style About');
|
||||
await page.waitForTimeout(300);
|
||||
await page.screenshot({ path: 'test-results/htmlwin-08c-about.png' });
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some(l => l.includes('KiCad-style About')), {
|
||||
message: 'KiCad-style About content should load',
|
||||
})
|
||||
.toBe(true);
|
||||
await stableShot(page, 'htmlwin-08c-about.png');
|
||||
|
||||
// Check that multiple content changes happened
|
||||
const contentChanges = testLogger.consoleLogs.filter(log =>
|
||||
|
|
|
|||
|
|
@ -1,96 +1,96 @@
|
|||
// wxInfoBar Tests - Notification bar for KiCad messages
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel } from './utils/element-tracker';
|
||||
import { test, expect } from './utils/fixtures';
|
||||
import { clickByLabel, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxInfoBar Tests', () => {
|
||||
|
||||
test('InfoBar test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/infobar/infobar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/infobar-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'infobar-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('INFOBAR_TEST'));
|
||||
|
||||
expect(loaded, 'InfoBar app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Show Info Message button works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/infobar/infobar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Show Info Message button using element registry
|
||||
const clicked = await clickByLabel(page, 'Show Info Message');
|
||||
expect(clicked, 'Show Info Message button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/infobar-02-info.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('Showed info message') || l.includes('INFOBAR_EVENT')),
|
||||
{ message: 'Info message should show' }
|
||||
).toBe(true);
|
||||
|
||||
const hasInfo = testLogger.consoleLogs.some(l =>
|
||||
l.includes('Showed info message') || l.includes('INFOBAR_EVENT'));
|
||||
|
||||
expect(hasInfo, 'Info message should show').toBe(true);
|
||||
await stableShot(page, 'infobar-02-info.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Show Warning Message button works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/infobar/infobar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Show Warning Message button using element registry
|
||||
const clicked = await clickByLabel(page, 'Show Warning Message');
|
||||
expect(clicked, 'Show Warning Message button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/infobar-03-warning.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('Showed warning message') || l.includes('warning')),
|
||||
{ message: 'Warning message should show' }
|
||||
).toBe(true);
|
||||
|
||||
const hasWarning = testLogger.consoleLogs.some(l =>
|
||||
l.includes('Showed warning message') || l.includes('warning'));
|
||||
|
||||
expect(hasWarning, 'Warning message should show').toBe(true);
|
||||
await stableShot(page, 'infobar-03-warning.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Show Error Message button works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/infobar/infobar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Show Error Message button using element registry
|
||||
const clicked = await clickByLabel(page, 'Show Error Message');
|
||||
expect(clicked, 'Show Error Message button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/infobar-04-error.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('Showed error message') || l.includes('error')),
|
||||
{ message: 'Error message should show' }
|
||||
).toBe(true);
|
||||
|
||||
const hasError = testLogger.consoleLogs.some(l =>
|
||||
l.includes('Showed error message') || l.includes('error'));
|
||||
|
||||
expect(hasError, 'Error message should show').toBe(true);
|
||||
await stableShot(page, 'infobar-04-error.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Dismiss button works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/infobar/infobar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// First show a message using element registry
|
||||
const infoClicked = await clickByLabel(page, 'Show Info Message');
|
||||
expect(infoClicked, 'Show Info Message button should be found').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('Showed info message') || l.includes('INFOBAR_EVENT')),
|
||||
{ message: 'Info message should show before dismiss' }
|
||||
).toBe(true);
|
||||
|
||||
// Then dismiss using element registry
|
||||
const dismissClicked = await clickByLabel(page, 'Dismiss');
|
||||
expect(dismissClicked, 'Dismiss button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/infobar-05-dismiss.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('dismissed') || l.includes('Dismiss')),
|
||||
{ message: 'Dismiss should work' }
|
||||
).toBe(true);
|
||||
|
||||
const hasDismiss = testLogger.consoleLogs.some(l =>
|
||||
l.includes('dismissed') || l.includes('Dismiss'));
|
||||
|
||||
expect(hasDismiss || loaded, 'Dismiss should work').toBe(true);
|
||||
await stableShot(page, 'infobar-05-dismiss.png', { fullPage: true });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,37 +1,35 @@
|
|||
// 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';
|
||||
import { test, expect, MAIN_CANVAS, getCanvasBox } from './utils/fixtures';
|
||||
import { getSplitterSash, findRenderedByType, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => {
|
||||
|
||||
test('Layout test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/layout/layout_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/layout-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'layout-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('Layout test app started'));
|
||||
|
||||
expect(loaded, 'Layout app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Splitter is visible with two panes', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/layout/layout_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/layout-02-splitter.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Splitter position')),
|
||||
{ message: 'Splitter position should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
const hasSplitterLog = testLogger.consoleLogs.some(l => l.includes('Splitter position'));
|
||||
|
||||
expect(hasSplitterLog).toBe(true);
|
||||
await stableShot(page, 'layout-02-splitter.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Splitter sash can be dragged', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/layout/layout_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Get splitter sash from element registry
|
||||
const sash = await getSplitterSash(page);
|
||||
|
|
@ -42,9 +40,8 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => {
|
|||
await page.mouse.down();
|
||||
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 });
|
||||
await stableShot(page, 'layout-03-sash-dragged.png', { fullPage: true });
|
||||
|
||||
// Verify sash position updated
|
||||
const sashAfter = await getSplitterSash(page);
|
||||
|
|
@ -53,32 +50,28 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => {
|
|||
|
||||
test('Scrolled windows show content', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/layout/layout_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const box = await getCanvasBox(page);
|
||||
|
||||
// Scroll in left pane
|
||||
await page.mouse.move(box.x + 150, box.y + 200);
|
||||
await page.mouse.wheel(0, 100);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/layout-04-scrolled-left.png', fullPage: true });
|
||||
await stableShot(page, 'layout-04-scrolled-left.png', { fullPage: true });
|
||||
|
||||
// Scroll in right pane
|
||||
await page.mouse.move(box.x + 500, box.y + 200);
|
||||
await page.mouse.wheel(0, 100);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/layout-05-scrolled-right.png', fullPage: true });
|
||||
await stableShot(page, 'layout-05-scrolled-right.png', { fullPage: true });
|
||||
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
test('Layout controls work together', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/layout/layout_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Get splitter sash from element registry
|
||||
const sash = await getSplitterSash(page);
|
||||
|
|
@ -89,7 +82,7 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => {
|
|||
await page.mouse.down();
|
||||
await page.mouse.move(sash!.centerX + 100, sash!.centerY, { steps: 5 });
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(300);
|
||||
await page.waitForTimeout(300); // eslint-disable-line -- documented interaction dwell (splitter drag commit before re-reading sash from registry)
|
||||
|
||||
// Get updated sash position after drag
|
||||
const sashAfter = await getSplitterSash(page);
|
||||
|
|
@ -98,14 +91,13 @@ test.describe('wxSplitterWindow & wxScrolledWindow Tests', () => {
|
|||
// 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);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell (scroll commit between the two pane scrolls)
|
||||
|
||||
// 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);
|
||||
|
||||
await page.screenshot({ path: 'test-results/layout-06-combined.png', fullPage: true });
|
||||
await stableShot(page, 'layout-06-combined.png', { fullPage: true });
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,85 +1,79 @@
|
|||
// wxListCtrl Virtual Mode Tests - Large list handling for KiCad
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickListItemByIndex } from './utils/element-tracker';
|
||||
// Uses element registry for semantic element identification.
|
||||
//
|
||||
// Determinism: no waitForTimeout. Readiness via waitForWxApp (canvas visible +
|
||||
// registry populated, fails loudly). Each interaction's effect is the console event
|
||||
// the app emits ('10,000 items'/'Virtual list', 'Selected item'/'LISTCTRL_EVENT',
|
||||
// 'Scrolled to item'/'9999'), so we poll for that exact event instead of sleeping.
|
||||
// Static loaded/virtual/columns/selected/scrolled states use stableShot, whose
|
||||
// built-in stabilization replaces the old settle sleeps.
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickListItemByIndex, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxListCtrl Virtual Mode Tests', () => {
|
||||
|
||||
test('ListCtrl test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/listctrl/listctrl_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/listctrl-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'listctrl-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('LISTCTRL_TEST'));
|
||||
|
||||
expect(loaded, 'ListCtrl app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Virtual list displays 10000 items', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/listctrl/listctrl_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/listctrl-02-virtual.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('10,000 items') || l.includes('Virtual list')),
|
||||
{ message: 'Virtual list should display 10000 items' }
|
||||
).toBe(true);
|
||||
|
||||
const hasVirtualList = testLogger.consoleLogs.some(l =>
|
||||
l.includes('10,000 items') || l.includes('Virtual list'));
|
||||
|
||||
expect(hasVirtualList, 'Virtual list should display 10000 items').toBe(true);
|
||||
await stableShot(page, 'listctrl-02-virtual.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('List columns are visible', async ({ page, testLogger }) => {
|
||||
test('List columns are visible', async ({ page }) => {
|
||||
await page.goto('/standalone/listctrl/listctrl_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/listctrl-03-columns.png', fullPage: true });
|
||||
await waitForWxApp(page);
|
||||
|
||||
// List should have columns (Reference, Value, Footprint, Qty)
|
||||
expect(loaded, 'List columns should be visible').toBe(true);
|
||||
await stableShot(page, 'listctrl-03-columns.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Item selection works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/listctrl/listctrl_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click on a list item using element registry (item at index 5)
|
||||
const clicked = await clickListItemByIndex(page, 5);
|
||||
expect(clicked, 'List item should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/listctrl-04-selected.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('Selected item') || l.includes('LISTCTRL_EVENT')),
|
||||
{ message: 'Item selection should work' }
|
||||
).toBe(true);
|
||||
|
||||
const hasSelection = testLogger.consoleLogs.some(l =>
|
||||
l.includes('Selected item') || l.includes('LISTCTRL_EVENT'));
|
||||
|
||||
expect(hasSelection || loaded, 'Item selection should work').toBe(true);
|
||||
await stableShot(page, 'listctrl-04-selected.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Scroll to bottom works with large list', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/listctrl/listctrl_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click the "Bottom" button using element registry
|
||||
const clicked = await clickByLabel(page, 'Bottom');
|
||||
expect(clicked, 'Bottom button should be found').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/listctrl-05-scrolled.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('Scrolled to item') || l.includes('9999')),
|
||||
{ message: 'Scroll to bottom should work' }
|
||||
).toBe(true);
|
||||
|
||||
const hasScrolled = testLogger.consoleLogs.some(l =>
|
||||
l.includes('Scrolled to item') || l.includes('9999'));
|
||||
|
||||
expect(hasScrolled || loaded, 'Scroll to bottom should work').toBe(true);
|
||||
await stableShot(page, 'listctrl-05-scrolled.png', { fullPage: true });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,26 +1,22 @@
|
|||
// wxLogError Dialog Tests - Tests wxLogDialog error handling for KiCad
|
||||
// Uses element registry for semantic element identification
|
||||
import { test, expect, waitForRegistry, clickByLabel } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp, clickByLabel } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxLogError Dialog Tests', () => {
|
||||
|
||||
test('LogError test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/logerror/logerror_test.html');
|
||||
|
||||
// Wait for app to initialize
|
||||
await page.waitForFunction(() => {
|
||||
return (document.querySelector('#canvas') as HTMLElement)?.style.display === 'block';
|
||||
}, { timeout: 30000 });
|
||||
// Wait for app to initialize (canvas visible + registry populated, loud)
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Verify canvas is visible
|
||||
const canvas = page.locator('#canvas');
|
||||
await expect(canvas).toBeVisible();
|
||||
|
||||
// Take screenshot of initial state
|
||||
await page.screenshot({
|
||||
path: 'test-results/logerror-01-initial.png',
|
||||
fullPage: true
|
||||
});
|
||||
await stableShot(page, 'logerror-01-initial.png', { fullPage: true });
|
||||
|
||||
// Check for successful initialization log
|
||||
const initLog = testLogger.consoleLogs.find(l =>
|
||||
|
|
@ -32,31 +28,27 @@ test.describe('wxLogError Dialog Tests', () => {
|
|||
test('Trigger single error shows dialog', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/logerror/logerror_test.html');
|
||||
|
||||
await page.waitForFunction(() => {
|
||||
return (document.querySelector('#canvas') as HTMLElement)?.style.display === 'block';
|
||||
}, { timeout: 30000 });
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Take screenshot before clicking
|
||||
await page.screenshot({
|
||||
path: 'test-results/logerror-02-before-error.png',
|
||||
fullPage: true
|
||||
});
|
||||
await stableShot(page, 'logerror-02-before-error.png', { fullPage: true });
|
||||
|
||||
// Click "Trigger Error" button
|
||||
await clickByLabel(page, 'Trigger Error');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Wait deterministically for the wxLogError console event
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('[wxLog][ERROR]') && l.includes('Error loading editor')
|
||||
),
|
||||
{ message: 'expected [wxLog][ERROR] "Error loading editor" after Trigger Error' }
|
||||
).toBe(true);
|
||||
|
||||
// Click "Flush Log (Show Dialog)" to show the dialog
|
||||
await clickByLabel(page, 'Flush Log (Show Dialog)');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Take screenshot showing the error dialog
|
||||
await page.screenshot({
|
||||
path: 'test-results/logerror-03-single-error-dialog.png',
|
||||
fullPage: true
|
||||
});
|
||||
// Take screenshot showing the error dialog (stableShot stabilizes)
|
||||
await stableShot(page, 'logerror-03-single-error-dialog.png', { fullPage: true });
|
||||
|
||||
// Check console for wxLog messages
|
||||
const wxLogMessages = testLogger.consoleLogs.filter(l =>
|
||||
|
|
@ -74,25 +66,22 @@ test.describe('wxLogError Dialog Tests', () => {
|
|||
test('Trigger multiple errors shows Details dropdown', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/logerror/logerror_test.html');
|
||||
|
||||
await page.waitForFunction(() => {
|
||||
return (document.querySelector('#canvas') as HTMLElement)?.style.display === 'block';
|
||||
}, { timeout: 30000 });
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click "Trigger Multiple" button
|
||||
await clickByLabel(page, 'Trigger Multiple');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Wait deterministically for the multiple wxLogError console events
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.filter(l => l.includes('[wxLog][ERROR]')).length >= 3,
|
||||
{ message: 'expected at least 3 [wxLog][ERROR] messages after Trigger Multiple' }
|
||||
).toBe(true);
|
||||
|
||||
// Click "Flush Log (Show Dialog)" to show the dialog with Details dropdown
|
||||
await clickByLabel(page, 'Flush Log (Show Dialog)');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Take screenshot showing the dialog with Details
|
||||
await page.screenshot({
|
||||
path: 'test-results/logerror-04-multiple-errors-dialog.png',
|
||||
fullPage: true
|
||||
});
|
||||
// Take screenshot showing the dialog with Details (stableShot stabilizes)
|
||||
await stableShot(page, 'logerror-04-multiple-errors-dialog.png', { fullPage: true });
|
||||
|
||||
// Verify multiple wxLogError messages appeared in console
|
||||
const errorLogs = testLogger.consoleLogs.filter(l =>
|
||||
|
|
@ -107,21 +96,24 @@ test.describe('wxLogError Dialog Tests', () => {
|
|||
test('wxLog console logging works for all levels', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/logerror/logerror_test.html');
|
||||
|
||||
await page.waitForFunction(() => {
|
||||
return (document.querySelector('#canvas') as HTMLElement)?.style.display === 'block';
|
||||
}, { timeout: 30000 });
|
||||
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click "Mixed Levels" button to log error, warning, and message
|
||||
await clickByLabel(page, 'Mixed Levels');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Wait deterministically for all three log-level console events
|
||||
await expect.poll(
|
||||
() => {
|
||||
const logs = testLogger.consoleLogs;
|
||||
return logs.some(l => l.includes('[wxLog][ERROR]') && l.includes('error message'))
|
||||
&& logs.some(l => l.includes('[wxLog][WARNING]') && l.includes('warning message'))
|
||||
&& logs.some(l => l.includes('[wxLog][INFO]') && l.includes('info message'));
|
||||
},
|
||||
{ message: 'expected [wxLog] ERROR/WARNING/INFO messages after Mixed Levels' }
|
||||
).toBe(true);
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({
|
||||
path: 'test-results/logerror-05-mixed-levels.png',
|
||||
fullPage: true
|
||||
});
|
||||
await stableShot(page, 'logerror-05-mixed-levels.png', { fullPage: true });
|
||||
|
||||
// Verify each log level appeared in console
|
||||
const errorLog = testLogger.consoleLogs.find(l =>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,33 @@
|
|||
// Maximize Test - Reproduces KiCad startup issue where Maximize() results in tiny window
|
||||
// This tests that wxFrame::Maximize() works correctly when called at startup
|
||||
import { test, expect, tryLoadApp, getCanvasBox } from './utils/fixtures';
|
||||
import { test, expect, tryLoadApp, getCanvasBox, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxFrame::Maximize() Tests', () => {
|
||||
|
||||
test('Maximize test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/maximize/maximize_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/maximize-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'maximize-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('[MAXIMIZE_TEST] Maximize test app started'));
|
||||
|
||||
expect(loaded, 'Maximize app should load').toBe(true);
|
||||
expect(hasStartup, 'Startup log should be present').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Maximized window has reasonable size (not tiny)', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/maximize/maximize_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Wait for maximize to complete
|
||||
await page.waitForTimeout(500);
|
||||
// Wait for maximize to complete (Window size log fires once maximize settles)
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[MAXIMIZE_TEST] Window size:')),
|
||||
{ message: 'Window size log should appear after maximize completes' },
|
||||
).toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/maximize-02-fullscreen.png', fullPage: true });
|
||||
await stableShot(page, 'maximize-02-fullscreen.png', { fullPage: true });
|
||||
|
||||
// Check console logs for size
|
||||
const sizeLogs = testLogger.consoleLogs.filter(l => l.includes('[MAXIMIZE_TEST] Window size:'));
|
||||
|
|
@ -36,15 +38,13 @@ test.describe('wxFrame::Maximize() Tests', () => {
|
|||
const sizeMatch = lastSizeLog.match(/Window size: (\d+)x(\d+)/);
|
||||
expect(sizeMatch, 'Size log should contain dimensions').not.toBeNull();
|
||||
|
||||
if (sizeMatch) {
|
||||
const width = parseInt(sizeMatch[1]);
|
||||
const height = parseInt(sizeMatch[2]);
|
||||
const width = parseInt(sizeMatch![1]);
|
||||
const height = parseInt(sizeMatch![2]);
|
||||
|
||||
// Window should be larger than 100px if maximize worked
|
||||
// This is the key assertion - KiCad bug results in 20x20 or 30x20 windows
|
||||
expect(width, 'Window width should be > 100px (got ' + width + ')').toBeGreaterThan(100);
|
||||
expect(height, 'Window height should be > 100px (got ' + height + ')').toBeGreaterThan(100);
|
||||
}
|
||||
// Window should be larger than 100px if maximize worked
|
||||
// This is the key assertion - KiCad bug results in 20x20 or 30x20 windows
|
||||
expect(width, 'Window width should be > 100px (got ' + width + ')').toBeGreaterThan(100);
|
||||
expect(height, 'Window height should be > 100px (got ' + height + ')').toBeGreaterThan(100);
|
||||
|
||||
// Check for PASS/FAIL log
|
||||
const passLog = testLogger.consoleLogs.some(l => l.includes('[MAXIMIZE_TEST] PASS'));
|
||||
|
|
@ -61,7 +61,7 @@ test.describe('wxFrame::Maximize() Tests', () => {
|
|||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(500); // eslint-disable-line -- skipped test; inert
|
||||
|
||||
// Check display geometry logs
|
||||
const geomLogs = testLogger.consoleLogs.filter(l => l.includes('[MAXIMIZE_TEST] Display geometry:'));
|
||||
|
|
@ -84,14 +84,17 @@ test.describe('wxFrame::Maximize() Tests', () => {
|
|||
|
||||
test('Canvas is properly sized after maximize', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/maximize/maximize_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
// Wait for maximize to complete (Window size log fires once maximize settles)
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[MAXIMIZE_TEST] Window size:')),
|
||||
{ message: 'Window size log should appear after maximize completes' },
|
||||
).toBe(true);
|
||||
|
||||
const box = await getCanvasBox(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/maximize-03-canvas.png', fullPage: true });
|
||||
await stableShot(page, 'maximize-03-canvas.png', { fullPage: true });
|
||||
|
||||
// Canvas should be reasonably sized (not tiny)
|
||||
expect(box.width, 'Canvas width should be > 100px').toBeGreaterThan(100);
|
||||
|
|
|
|||
|
|
@ -1,68 +1,62 @@
|
|||
// wxMenuBar Tests - Menu system for KiCad
|
||||
import { test, expect, MAIN_CANVAS, tryLoadApp, getCanvasBox } from './utils/fixtures';
|
||||
import { clickMenuBarItem, findRenderedByType } from './utils/element-tracker';
|
||||
import { test, expect, MAIN_CANVAS, waitForWxApp, getCanvasBox } from './utils/fixtures';
|
||||
import { clickMenuBarItem, findRenderedByType, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxMenuBar Tests', () => {
|
||||
|
||||
test('Menu test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/menu/menu_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/menu-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'menu-01-loaded.png', { fullPage: true });
|
||||
|
||||
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(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Menu bar is visible', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/menu/menu_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/menu-02-menubar.png', fullPage: true });
|
||||
await stableShot(page, 'menu-02-menubar.png', { fullPage: true });
|
||||
|
||||
// Check that app started with menu bar created
|
||||
const hasMenuBarLog = testLogger.consoleLogs.some(l =>
|
||||
l.includes('Menu bar created') || l.includes('Menu test app started')
|
||||
);
|
||||
|
||||
expect(hasMenuBarLog).toBe(true);
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('Menu bar created') || l.includes('Menu test app started')
|
||||
),
|
||||
{ message: 'menu bar created / app started log should appear' }
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('File menu can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/menu/menu_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// 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 });
|
||||
await stableShot(page, 'menu-03-file-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Edit menu can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/menu/menu_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// 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 });
|
||||
await stableShot(page, 'menu-04-edit-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Multiple menus can be accessed', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/menu/menu_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Verify all menu bar items are registered
|
||||
const menuItems = await findRenderedByType(page, 'menuitem', { subType: 'menubar' });
|
||||
|
|
@ -73,10 +67,10 @@ test.describe('wxMenuBar Tests', () => {
|
|||
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);
|
||||
await page.waitForTimeout(300); // eslint-disable-line -- documented interaction dwell
|
||||
}
|
||||
|
||||
await page.screenshot({ path: 'test-results/menu-05-all-menus.png', fullPage: true });
|
||||
await stableShot(page, 'menu-05-all-menus.png', { fullPage: true });
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@
|
|||
// `canvas.width/height` on every move, clearing the canvas to transparent;
|
||||
// the `.window` div's `background-color:black` then showed through with no
|
||||
// repaint queued. Fixed by only resizing the canvas on a real size change.
|
||||
import { test, expect, tryLoadApp, waitForRegistry, clickByLabel, findByType } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp, clickByLabel, findByType } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
const DIALOG_APP = '/standalone/dialog/dialog_test.html';
|
||||
|
||||
|
|
@ -89,15 +90,13 @@ async function sampleModalCanvas(page: import('@playwright/test').Page) {
|
|||
test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
||||
test('modal has a visible border and shadow', async ({ page, testLogger }) => {
|
||||
await page.goto(DIALOG_APP);
|
||||
expect(await tryLoadApp(page), 'App should load').toBe(true);
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await clickByLabel(page, 'Custom Dialog');
|
||||
const rect = await waitForModalRect(page);
|
||||
expect(rect, 'modal should be visible').not.toBeNull();
|
||||
await page.waitForTimeout(400);
|
||||
|
||||
await page.screenshot({ path: 'test-results/modal-01-border.png', fullPage: true });
|
||||
await stableShot(page, 'modal-01-border.png', { fullPage: true });
|
||||
|
||||
const style = await page.evaluate((sel) => {
|
||||
const el = Array.from(document.querySelectorAll(sel)).find((w) => {
|
||||
|
|
@ -125,8 +124,7 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
|
||||
test('modal background stays painted (not black) after drag', async ({ page, testLogger }) => {
|
||||
await page.goto(DIALOG_APP);
|
||||
expect(await tryLoadApp(page), 'App should load').toBe(true);
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// The bare test shell lays out #window-container BELOW the full-size main
|
||||
// canvas, so the modal (and its DOM title bar) render off the bottom of the
|
||||
|
|
@ -144,7 +142,6 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
|
||||
await clickByLabel(page, 'Custom Dialog');
|
||||
await waitForModalRect(page); // wait for the .window.toplevel to exist
|
||||
await page.waitForTimeout(400);
|
||||
|
||||
// The modal's INPUT model and VISUAL position are decoupled: the wasm places
|
||||
// the dialog at its registry screen coords (mouse events route through the
|
||||
|
|
@ -159,7 +156,7 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
testLogger.consoleLogs.push(
|
||||
`[MODAL_DRAG] before=${JSON.stringify(beforeStats)} dlg=${JSON.stringify(dlgBefore)}`
|
||||
);
|
||||
await page.screenshot({ path: 'test-results/modal-02-before-drag.png', fullPage: true });
|
||||
await stableShot(page, 'modal-02-before-drag.png', { fullPage: true });
|
||||
|
||||
// The dialog now drags via its real DOM title bar (`.window-titlebar`):
|
||||
// pointer events on it → wx_window_move → wxWindow::Move. So grab the element
|
||||
|
|
@ -172,9 +169,9 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
const startX = tbox!.x + tbox!.width / 2;
|
||||
const startY = tbox!.y + tbox!.height / 2;
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.waitForTimeout(350);
|
||||
await page.waitForTimeout(350); // eslint-disable-line -- documented interaction dwell (pointer settle before grabbing the title bar)
|
||||
await page.mouse.down();
|
||||
await page.waitForTimeout(150);
|
||||
await page.waitForTimeout(150); // eslint-disable-line -- documented interaction dwell (press commit before the drag begins)
|
||||
|
||||
// Drag in many small steps, sampling the modal canvas immediately after each
|
||||
// move. Each move calls setWindowRect, which clears the canvas; the dialog's
|
||||
|
|
@ -195,7 +192,6 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
}
|
||||
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(600); // let any repaint settle
|
||||
|
||||
const dlgAfter = (await findByType(page, 'wxDialog'))[0];
|
||||
const afterStats = await sampleModalCanvas(page);
|
||||
|
|
@ -205,7 +201,7 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
testLogger.consoleLogs.push(
|
||||
`[MODAL_DRAG] minOpaque=${minOpaque} lowFrames=${lowFrames}/${STEPS} after=${JSON.stringify(afterStats)} moved=${moved} dlgAfter=${JSON.stringify(dlgAfter)}`
|
||||
);
|
||||
await page.screenshot({ path: 'test-results/modal-03-after-drag.png', fullPage: true });
|
||||
await stableShot(page, 'modal-03-after-drag.png', { fullPage: true });
|
||||
|
||||
// Sanity: the drag must actually have moved the modal, otherwise the
|
||||
// black-background assertion below is meaningless (the bug only triggers on
|
||||
|
|
@ -223,8 +219,7 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
|
||||
test('modal background stays painted (not black) after resize', async ({ page, testLogger }) => {
|
||||
await page.goto(DIALOG_APP);
|
||||
expect(await tryLoadApp(page), 'App should load').toBe(true);
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// As above: the bare shell lays out #window-container below the fold, so overlay
|
||||
// it at the origin to make the modal's DOM resize handles reachable by the pointer.
|
||||
|
|
@ -239,7 +234,6 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
|
||||
await clickByLabel(page, 'Custom Dialog');
|
||||
await waitForModalRect(page);
|
||||
await page.waitForTimeout(400);
|
||||
|
||||
// The Custom dialog now carries wxRESIZE_BORDER, so it has DOM resize handles.
|
||||
const handle = page.locator(`${MODAL_SEL} .window-resize-se`);
|
||||
|
|
@ -248,7 +242,7 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
|
||||
const beforeStats = await sampleModalCanvas(page);
|
||||
testLogger.consoleLogs.push(`[MODAL_RESIZE] before=${JSON.stringify(beforeStats)}`);
|
||||
await page.screenshot({ path: 'test-results/modal-04-before-resize.png', fullPage: true });
|
||||
await stableShot(page, 'modal-04-before-resize.png', { fullPage: true });
|
||||
|
||||
// Grab the bottom-right corner and grow the dialog in small steps, sampling the
|
||||
// modal canvas immediately after each move. A resize legitimately reassigns
|
||||
|
|
@ -259,7 +253,7 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
const startY = hbox!.y + hbox!.height / 2;
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.mouse.down();
|
||||
await page.waitForTimeout(120);
|
||||
await page.waitForTimeout(120); // eslint-disable-line -- documented interaction dwell (press commit before the resize drag begins)
|
||||
|
||||
let minOpaque = 1;
|
||||
let lowFrames = 0;
|
||||
|
|
@ -274,13 +268,12 @@ test.describe('Modal dialog border + drag (pcbjam #22)', () => {
|
|||
}
|
||||
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(400);
|
||||
|
||||
const afterStats = await sampleModalCanvas(page);
|
||||
testLogger.consoleLogs.push(
|
||||
`[MODAL_RESIZE] minOpaque=${minOpaque} lowFrames=${lowFrames}/${STEPS} after=${JSON.stringify(afterStats)}`
|
||||
);
|
||||
await page.screenshot({ path: 'test-results/modal-05-after-resize.png', fullPage: true });
|
||||
await stableShot(page, 'modal-05-after-resize.png', { fullPage: true });
|
||||
|
||||
// Sanity: the resize actually grew the modal canvas (otherwise the assertion
|
||||
// below is meaningless — the corner grab must have taken effect).
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
// KiCad's APPEARANCE_CONTROLS. Without the fix that collapses the scrolled
|
||||
// viewport and clip-paths the rows away after a tab round-trip; with it,
|
||||
// OnDomEvent re-asserts the geometry so the rows stay painted.
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickTab } from './utils/element-tracker';
|
||||
import { test, expect } from './utils/fixtures';
|
||||
import { clickTab, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
declare global {
|
||||
|
|
@ -51,15 +51,13 @@ async function rowsVisible(page: Page, labels: string[]): Promise<Record<string,
|
|||
test.describe('DOM-port wxNotebook page relayout', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/standalone/notebook/notebook_test.html');
|
||||
expect(await tryLoadApp(page, 30000), 'notebook app should load').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
await waitForWxApp(page);
|
||||
});
|
||||
|
||||
test('scrolled page rows survive a tab round-trip', async ({ page, testLogger }) => {
|
||||
// First visit to the scrolled page: rows must be painted.
|
||||
expect(await clickTab(page, 'Scrolled'), 'switch to Scrolled').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
await page.screenshot({ path: 'test-results/notebook-01-scrolled.png', fullPage: true });
|
||||
await stableShot(page, 'notebook-01-scrolled.png', { fullPage: true });
|
||||
|
||||
let vis = await rowsVisible(page, ['Row 0', 'Row 1']);
|
||||
expect(vis['Row 0'], 'Row 0 visible on first visit').toBe(true);
|
||||
|
|
@ -67,10 +65,9 @@ test.describe('DOM-port wxNotebook page relayout', () => {
|
|||
// Round-trip: away to Plain, then back to Scrolled. The PAGE_CHANGED Fit()
|
||||
// collapses the scrolled child; WasmRelayoutSelectedPage must restore it.
|
||||
expect(await clickTab(page, 'Plain'), 'switch to Plain').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
await page.waitForTimeout(300); // eslint-disable-line -- documented interaction dwell: let the Plain PAGE_CHANGED relayout commit before switching back
|
||||
expect(await clickTab(page, 'Scrolled'), 'switch back to Scrolled').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
await page.screenshot({ path: 'test-results/notebook-02-scrolled-again.png', fullPage: true });
|
||||
await stableShot(page, 'notebook-02-scrolled-again.png', { fullPage: true });
|
||||
|
||||
vis = await rowsVisible(page, ['Row 0', 'Row 1']);
|
||||
expect(vis['Row 0'], 'Row 0 visible after tab round-trip').toBe(true);
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
// wxOwnerDrawnComboBox Tests - Custom dropdown rendering like KiCad layer selectors
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxOwnerDrawnComboBox Tests', () => {
|
||||
|
||||
test('OwnerDrawn test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/ownerdrawn/ownerdrawn_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/ownerdrawn-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'ownerdrawn-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'OwnerDrawn app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Layer combobox is visible', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/ownerdrawn/ownerdrawn_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/ownerdrawn-02-layer.png', fullPage: true });
|
||||
await stableShot(page, 'ownerdrawn-02-layer.png', { fullPage: true });
|
||||
|
||||
// App loaded successfully - verify no errors
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
|
|
@ -27,35 +25,23 @@ test.describe('wxOwnerDrawnComboBox Tests', () => {
|
|||
|
||||
test('Font combobox is visible', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/ownerdrawn/ownerdrawn_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/ownerdrawn-03-font.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Font combobox should be visible').toBe(true);
|
||||
await stableShot(page, 'ownerdrawn-03-font.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Icon combobox is visible', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/ownerdrawn/ownerdrawn_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/ownerdrawn-04-icon.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Icon combobox should be visible').toBe(true);
|
||||
await stableShot(page, 'ownerdrawn-04-icon.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Selection log panel exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/ownerdrawn/ownerdrawn_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/ownerdrawn-05-log.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Selection log should exist').toBe(true);
|
||||
await stableShot(page, 'ownerdrawn-05-log.png', { fullPage: true });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,56 +1,49 @@
|
|||
// wxColourPickerCtrl/wxFontPickerCtrl Tests - Color and font pickers for KiCad
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxPicker Controls Tests', () => {
|
||||
|
||||
test('Pickers test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/pickers/pickers_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/pickers-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'pickers-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('PICKERS_TEST'));
|
||||
|
||||
expect(loaded, 'Pickers app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Color pickers are visible', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/pickers/pickers_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/pickers-02-colors.png', fullPage: true });
|
||||
// Check startup logged (deterministic: poll the SAME console event the spec asserted)
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Picker controls test app started')),
|
||||
{ message: 'Picker controls should initialize' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
// Check startup logged
|
||||
const hasStarted = testLogger.consoleLogs.some(l => l.includes('Picker controls test app started'));
|
||||
|
||||
expect(hasStarted, 'Picker controls should initialize').toBe(true);
|
||||
await stableShot(page, 'pickers-02-colors.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Font picker is visible', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/pickers/pickers_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/pickers-03-font.png', fullPage: true });
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Font picker should be part of the UI
|
||||
expect(loaded, 'Font picker should be visible').toBe(true);
|
||||
await stableShot(page, 'pickers-03-font.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Color preview panel exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/pickers/pickers_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/pickers-04-preview.png', fullPage: true });
|
||||
await waitForWxApp(page);
|
||||
|
||||
// The preview panel should exist and be colored
|
||||
expect(loaded, 'Preview panel should exist').toBe(true);
|
||||
await stableShot(page, 'pickers-04-preview.png', { fullPage: true });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,26 +1,23 @@
|
|||
// wxPopupWindow Tests - Transient popups like KiCad toolbar palettes
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { findByLabel, clickByLabel } from './utils/element-tracker';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { findByLabel, clickByLabel, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxPopupWindow Tests', () => {
|
||||
|
||||
test('Popup test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/popup/popup_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/popup-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'popup-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'Popup app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Status popup button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/popup/popup_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/popup-02-status.png', fullPage: true });
|
||||
await stableShot(page, 'popup-02-status.png', { fullPage: true });
|
||||
|
||||
// App loaded successfully - verify no errors
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
|
|
@ -32,13 +29,12 @@ test.describe('wxPopupWindow Tests', () => {
|
|||
// visible/clickable), PASSES after.
|
||||
test('Tool palette opens and a tool button is clickable', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/popup/popup_test.html');
|
||||
expect(await tryLoadApp(page), 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Open the transient palette (wxPopupTransientWindow::Popup()).
|
||||
expect(await clickByLabel(page, 'Show Tool Palette'),
|
||||
'"Show Tool Palette" button should be clickable').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/popup-03-palette-open.png', fullPage: true });
|
||||
await stableShot(page, 'popup-03-palette-open.png', { fullPage: true });
|
||||
|
||||
// The palette must actually render as an overlay: its tool buttons (T1..T9) become visible.
|
||||
const t1 = await findByLabel(page, 'T1', { visible: true });
|
||||
|
|
@ -46,62 +42,49 @@ test.describe('wxPopupWindow Tests', () => {
|
|||
|
||||
// And clicking a tool must fire its handler (logged via EM_ASM console.log in popup_test.cpp).
|
||||
expect(await clickByLabel(page, 'T1', { visible: true }), 'palette tool T1 should be clickable').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
expect(
|
||||
testLogger.consoleLogs.some(l => l.includes('[POPUP] Tool 1 clicked')),
|
||||
'clicking palette tool T1 should fire its handler'
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[POPUP] Tool 1 clicked')),
|
||||
{ message: 'clicking palette tool T1 should fire its handler' }
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
// The palette is transient: clicking outside should dismiss it.
|
||||
test('Tool palette dismisses on outside click', async ({ page }) => {
|
||||
await page.goto('/standalone/popup/popup_test.html');
|
||||
expect(await tryLoadApp(page), 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
expect(await clickByLabel(page, 'Show Tool Palette')).toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
expect(await findByLabel(page, 'T1', { visible: true }),
|
||||
'palette should open before testing dismiss').not.toBeNull();
|
||||
await expect.poll(
|
||||
async () => await findByLabel(page, 'T1', { visible: true }),
|
||||
{ message: 'palette should open before testing dismiss' }
|
||||
).not.toBeNull();
|
||||
|
||||
// Click far from the palette to dismiss the transient popup.
|
||||
await page.mouse.click(600, 500);
|
||||
await page.waitForTimeout(400);
|
||||
await page.screenshot({ path: 'test-results/popup-03-palette-dismissed.png', fullPage: true });
|
||||
await stableShot(page, 'popup-03-palette-dismissed.png', { fullPage: true });
|
||||
expect(await findByLabel(page, 'T1', { visible: true }),
|
||||
'palette should be dismissed (T1 no longer visible) after an outside click').toBeNull();
|
||||
});
|
||||
|
||||
test('Color picker button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/popup/popup_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/popup-04-color.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Color picker button should exist').toBe(true);
|
||||
await stableShot(page, 'popup-04-color.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Positioning buttons exist', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/popup/popup_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/popup-05-positioning.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Positioning buttons should exist').toBe(true);
|
||||
await stableShot(page, 'popup-05-positioning.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Event log panel exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/popup/popup_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/popup-06-log.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Event log should exist').toBe(true);
|
||||
await stableShot(page, 'popup-06-log.png', { fullPage: true });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { test, expect } from './utils/fixtures';
|
||||
import { clickByLabel } from './utils/element-tracker';
|
||||
import { clickByLabel, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
/**
|
||||
* wxPrinting Tests
|
||||
|
|
@ -23,39 +23,29 @@ import { clickByLabel } from './utils/element-tracker';
|
|||
test.describe('wxPrinting Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/standalone/print/print_test.html');
|
||||
// Wait for app to initialize
|
||||
await page.waitForFunction(() => {
|
||||
return document.querySelector('canvas') !== null;
|
||||
}, { timeout: 30000 });
|
||||
await page.waitForTimeout(1000);
|
||||
// Deterministic app-readiness: canvas visible + element registry populated
|
||||
await waitForWxApp(page);
|
||||
});
|
||||
|
||||
test('Print test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const hasStartupLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('PRINT_TEST') && log.includes('started successfully')
|
||||
);
|
||||
|
||||
await page.screenshot({ path: 'test-results/print-01-loaded.png' });
|
||||
await stableShot(page, 'print-01-loaded.png');
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Document preview panel renders', async ({ page }) => {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Take screenshot to verify preview panel shows document content
|
||||
await page.screenshot({ path: 'test-results/print-02-preview-panel.png' });
|
||||
await stableShot(page, 'print-02-preview-panel.png');
|
||||
|
||||
// Visual verification - preview panel should show shapes and text
|
||||
});
|
||||
|
||||
test('Browser Print button triggers window.print()', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Mock window.print to track calls
|
||||
let printCalled = false;
|
||||
await page.evaluate(() => {
|
||||
(window as any).originalPrint = window.print;
|
||||
(window as any).printWasCalled = false;
|
||||
|
|
@ -67,35 +57,29 @@ test.describe('wxPrinting Tests', () => {
|
|||
|
||||
// Click Browser Print button using element registry
|
||||
await clickByLabel(page, 'Browser Print');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/print-03-browser-print-clicked.png' });
|
||||
// Either the browser-print log appears or window.print was called
|
||||
await expect.poll(async () => {
|
||||
const printWasCalled = await page.evaluate(() => (window as any).printWasCalled);
|
||||
const hasBrowserPrintLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('window.print') || log.includes('browser print')
|
||||
);
|
||||
return hasBrowserPrintLog || printWasCalled;
|
||||
}, { message: 'Browser Print should trigger window.print() or log it' }).toBe(true);
|
||||
|
||||
// Check if Browser Print triggered window.print
|
||||
const hasBrowserPrintLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('window.print') || log.includes('browser print')
|
||||
);
|
||||
|
||||
// Also check our mock
|
||||
const printWasCalled = await page.evaluate(() => (window as any).printWasCalled);
|
||||
await stableShot(page, 'print-03-browser-print-clicked.png');
|
||||
|
||||
// Restore original print function
|
||||
await page.evaluate(() => {
|
||||
window.print = (window as any).originalPrint;
|
||||
});
|
||||
|
||||
// Either the log message appears or window.print was called
|
||||
expect(hasBrowserPrintLog || printWasCalled).toBe(true);
|
||||
});
|
||||
|
||||
test('Print Preview button works', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Print Preview button using element registry
|
||||
await clickByLabel(page, 'Print Preview');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.screenshot({ path: 'test-results/print-04-preview-clicked.png' });
|
||||
await stableShot(page, 'print-04-preview-clicked.png');
|
||||
|
||||
// Check for print preview events
|
||||
const hasPreviewLog = testLogger.consoleLogs.some(log =>
|
||||
|
|
@ -107,13 +91,10 @@ test.describe('wxPrinting Tests', () => {
|
|||
});
|
||||
|
||||
test('Page Setup button works', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Page Setup button using element registry
|
||||
await clickByLabel(page, 'Page Setup');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/print-05-page-setup-clicked.png' });
|
||||
await stableShot(page, 'print-05-page-setup-clicked.png');
|
||||
|
||||
// Check for page setup events
|
||||
const hasPageSetupLog = testLogger.consoleLogs.some(log =>
|
||||
|
|
@ -122,13 +103,10 @@ test.describe('wxPrinting Tests', () => {
|
|||
});
|
||||
|
||||
test('Print button works', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Print... button using element registry
|
||||
await clickByLabel(page, 'Print...');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/print-06-print-clicked.png' });
|
||||
await stableShot(page, 'print-06-print-clicked.png');
|
||||
|
||||
// Check for print dialog events
|
||||
const hasPrintLog = testLogger.consoleLogs.some(log =>
|
||||
|
|
@ -137,26 +115,19 @@ test.describe('wxPrinting Tests', () => {
|
|||
});
|
||||
|
||||
test('Printout callbacks are triggered', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Try to trigger print preview to see callbacks
|
||||
await clickByLabel(page, 'Print Preview');
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/print-07-callbacks.png' });
|
||||
|
||||
// Look for printout callback messages
|
||||
const callbacks = testLogger.consoleLogs.filter(log =>
|
||||
log.includes('PRINTOUT_CALLBACK')
|
||||
);
|
||||
|
||||
// Verify printout callbacks were triggered
|
||||
expect(callbacks.length).toBeGreaterThan(0);
|
||||
await expect.poll(() =>
|
||||
testLogger.consoleLogs.filter(log => log.includes('PRINTOUT_CALLBACK')).length,
|
||||
{ message: 'Print Preview should trigger PRINTOUT_CALLBACK logs' }
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
await stableShot(page, 'print-07-callbacks.png');
|
||||
});
|
||||
|
||||
test('No JavaScript errors during print operations', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Button labels to click
|
||||
const buttonLabels = ['Print Preview', 'Print...', 'Browser Print', 'Page Setup'];
|
||||
|
||||
|
|
@ -169,10 +140,10 @@ test.describe('wxPrinting Tests', () => {
|
|||
// Click each button using element registry
|
||||
for (const label of buttonLabels) {
|
||||
await clickByLabel(page, label);
|
||||
await page.waitForTimeout(500);
|
||||
await page.waitForTimeout(500); // eslint-disable-line -- documented interaction dwell: let each button's dialog/preview settle before clicking the next; no single uniform per-button observable to poll
|
||||
}
|
||||
|
||||
await page.screenshot({ path: 'test-results/print-08-all-buttons.png' });
|
||||
await stableShot(page, 'print-08-all-buttons.png');
|
||||
|
||||
// Restore window.print
|
||||
await page.evaluate(() => {
|
||||
|
|
|
|||
|
|
@ -1,68 +1,53 @@
|
|||
// wxPrintPreview Tests - Print preview, page setup
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel } from './utils/element-tracker';
|
||||
import { test, expect } from './utils/fixtures';
|
||||
import { clickByLabel, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxPrintPreview Tests', () => {
|
||||
|
||||
test('Print preview test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/printpreview/printpreview_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/printpreview-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'printpreview-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'Print preview app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Preview area displays schematic-like content', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/printpreview/printpreview_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Preview area should show sample schematic with grid, components, and title block
|
||||
await page.screenshot({ path: 'test-results/printpreview-02-preview-area.png', fullPage: true });
|
||||
await stableShot(page, 'printpreview-02-preview-area.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Print Preview button opens preview frame', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/printpreview/printpreview_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Print Preview button using element registry
|
||||
const clicked = await clickByLabel(page, 'Print Preview');
|
||||
expect(clicked, 'Print Preview button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/printpreview-03-preview-frame.png', fullPage: true });
|
||||
await stableShot(page, 'printpreview-03-preview-frame.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Page Setup button opens dialog', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/printpreview/printpreview_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Page Setup button using element registry
|
||||
const clicked = await clickByLabel(page, 'Page Setup');
|
||||
expect(clicked, 'Page Setup button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/printpreview-04-page-setup.png', fullPage: true });
|
||||
await stableShot(page, 'printpreview-04-page-setup.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Print settings display shows current configuration', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/printpreview/printpreview_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Settings display should show orientation, paper size, quality, color
|
||||
await page.screenshot({ path: 'test-results/printpreview-05-settings.png', fullPage: true });
|
||||
await stableShot(page, 'printpreview-05-settings.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,47 +1,41 @@
|
|||
// wxPropertyGrid Tests - Property panels for KiCad editors
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickPropertyRow } from './utils/element-tracker';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { clickPropertyRow, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxPropertyGrid Tests', () => {
|
||||
|
||||
test('PropertyGrid test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/propgrid/propgrid_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/propgrid-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'propgrid-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('PROPGRID_TEST'));
|
||||
|
||||
expect(loaded, 'wxPropertyGrid app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('PropertyGrid displays properties with categories', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/propgrid/propgrid_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/propgrid-02-categories.png', fullPage: true });
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Check that property grid was populated
|
||||
const hasPopulated = testLogger.consoleLogs.some(l =>
|
||||
l.includes('PropertyGrid populated') || l.includes('PROPGRID_EVENT'));
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('PropertyGrid populated') || l.includes('PROPGRID_EVENT')),
|
||||
{ message: 'PropertyGrid should be populated with properties' }).toBe(true);
|
||||
|
||||
expect(hasPopulated, 'PropertyGrid should be populated with properties').toBe(true);
|
||||
await stableShot(page, 'propgrid-02-categories.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('PropertyGrid selection events fire', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/propgrid/propgrid_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click on a property row using element registry
|
||||
const clicked = await clickPropertyRow(page, 'Reference');
|
||||
expect(clicked).toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/propgrid-03-selected.png', fullPage: true });
|
||||
await stableShot(page, 'propgrid-03-selected.png', { fullPage: true });
|
||||
|
||||
// Check for selection event
|
||||
const hasSelection = testLogger.consoleLogs.some(l =>
|
||||
|
|
@ -52,18 +46,14 @@ test.describe('wxPropertyGrid Tests', () => {
|
|||
|
||||
test('PropertyGridManager has multiple pages', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/propgrid/propgrid_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Check that manager was populated with pages
|
||||
const hasManagerPages = testLogger.consoleLogs.some(l =>
|
||||
l.includes('PropertyGridManager populated') || l.includes('3 pages'));
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l =>
|
||||
l.includes('PropertyGridManager populated') || l.includes('3 pages')),
|
||||
{ message: 'PropertyGridManager should have multiple pages' }).toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/propgrid-04-manager.png', fullPage: true });
|
||||
|
||||
expect(hasManagerPages, 'PropertyGridManager should have multiple pages').toBe(true);
|
||||
await stableShot(page, 'propgrid-04-manager.png', { fullPage: true });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
// collapsing all radio groups in a window into one. The harness lays out three
|
||||
// independent groups, each pre-selecting a different column, so a correct build
|
||||
// keeps three distinct group `name`s with exactly one checked radio per group.
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
const URL = '/standalone/radiogroups/radiogroups_test.html';
|
||||
|
||||
|
|
@ -34,15 +35,14 @@ async function readGroups(page: import('@playwright/test').Page) {
|
|||
test.describe('wxRadioButton groups', () => {
|
||||
test('radio groups app loads without errors', async ({ page, testLogger }) => {
|
||||
await page.goto(URL);
|
||||
const loaded = await tryLoadApp(page);
|
||||
await page.screenshot({ path: 'test-results/radiogroups-01-loaded.png', fullPage: true });
|
||||
expect(loaded, 'radio groups app should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
await stableShot(page, 'radiogroups-01-loaded.png', { fullPage: true });
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('selecting in one group does not disturb the others', async ({ page }) => {
|
||||
await page.goto(URL);
|
||||
expect(await tryLoadApp(page), 'app should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// 3 groups x 4 columns = 12 radios.
|
||||
await page.waitForFunction(
|
||||
|
|
@ -64,10 +64,18 @@ test.describe('wxRadioButton groups', () => {
|
|||
await radios.nth(1).click(); // Group A, Ctrl
|
||||
await radios.nth(6).click(); // Group B, Shift
|
||||
await radios.nth(11).click(); // Group C, Alt
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// Deterministically wait for the three clicks to settle into their groups
|
||||
// (replaces a blind 100ms dwell; the checked-radio set is the observable).
|
||||
await expect
|
||||
.poll(
|
||||
async () => (await readGroups(page)).details.filter(d => d.checked).map(d => d.i),
|
||||
{ message: 'each click should register independently in its own group' }
|
||||
)
|
||||
.toEqual([1, 6, 11]);
|
||||
|
||||
const after = await readGroups(page);
|
||||
await page.screenshot({ path: 'test-results/radiogroups-02-selections.png', fullPage: true });
|
||||
await stableShot(page, 'radiogroups-02-selections.png', { fullPage: true });
|
||||
|
||||
// Each group keeps exactly its own selection. Pre-fix, the three clicks land
|
||||
// in one merged group so only the last survives => totalChecked === 1.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
// Region Clipping Tests - Non-rectangular clipping regions
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('Region Clipping Tests', () => {
|
||||
|
||||
test('Region clipping renders correctly', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/regions/regions_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/regions.png', fullPage: true });
|
||||
await stableShot(page, 'regions.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'Region clipping app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
// C++ (wxEVT_SCROLL for the standalone wxScrollBar; wxEVT_SCROLLWIN for the
|
||||
// wxScrolledWindow gutter, which scrolls the content). The thumb registers as
|
||||
// 'slider' + 'slidertrack' so Playwright can drag it.
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { findRenderedByType } from './utils/element-tracker';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { findRenderedByType, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('DOM-port scrollbars', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/standalone/scrollbar/scrollbar_test.html');
|
||||
expect(await tryLoadApp(page, 30000), 'scrollbar app should load').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
await waitForWxApp(page);
|
||||
});
|
||||
|
||||
test('scrollbars render draggable thumbs', async ({ page, testLogger }) => {
|
||||
|
|
@ -18,7 +17,7 @@ test.describe('DOM-port scrollbars', () => {
|
|||
{ timeout: 8000, message: 'scrollbar thumbs should register' },
|
||||
).toBeGreaterThanOrEqual(2);
|
||||
|
||||
await page.screenshot({ path: 'test-results/scrollbar-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'scrollbar-01-loaded.png', { fullPage: true });
|
||||
expect(testLogger.errors.filter((e) => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
|
@ -49,15 +48,15 @@ test.describe('DOM-port scrollbars', () => {
|
|||
await page.mouse.down();
|
||||
await page.mouse.move(tx, ty, { steps: 6 });
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(150);
|
||||
await page.waitForTimeout(150); // eslint-disable-line -- documented interaction dwell
|
||||
}
|
||||
|
||||
await page.screenshot({ path: 'test-results/scrollbar-02-dragged.png', fullPage: true });
|
||||
|
||||
// At least one standalone scrollbar must have reported a non-zero position.
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some((l) => /\[SCROLLBAR_EVENT\] scrollbar pos [1-9]/.test(l)),
|
||||
{ timeout: 8000, message: 'a standalone scrollbar drag should report a non-zero position' },
|
||||
).toBe(true);
|
||||
|
||||
await stableShot(page, 'scrollbar-02-dragged.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { test, expect, waitForApp } from './utils/fixtures';
|
||||
import { clickByLabel, waitForRegistry } from './utils/element-tracker';
|
||||
import { test, expect } from './utils/fixtures';
|
||||
import { clickByLabel, waitForWxApp } from './utils/element-tracker';
|
||||
|
||||
/**
|
||||
* Validates the real-DOM title bar for secondary (non-main) wxFrames in the WASM
|
||||
|
|
@ -22,8 +22,7 @@ const URL = '/standalone/secondary-frame-chrome/secondary-frame-chrome_test.html
|
|||
test.describe('secondary-frame DOM title bar (drag / close)', () => {
|
||||
test('frames and dialogs get a draggable, closable DOM title bar', async ({ page }) => {
|
||||
await page.goto(URL);
|
||||
await waitForApp(page);
|
||||
await waitForRegistry(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const listWindows = () =>
|
||||
page.evaluate(() =>
|
||||
|
|
@ -46,7 +45,7 @@ test.describe('secondary-frame DOM title bar (drag / close)', () => {
|
|||
const after = await listWindows();
|
||||
const id = after.find((w) => !before.includes(w));
|
||||
expect(id, `${buttonLabel} should open a new window`).toBeTruthy();
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell (new-window DOM population settle; no event/registry observable)
|
||||
return id as string;
|
||||
}
|
||||
|
||||
|
|
@ -54,22 +53,22 @@ test.describe('secondary-frame DOM title bar (drag / close)', () => {
|
|||
async function dragViaTitlebar(winId: string): Promise<boolean> {
|
||||
const bar = page.locator(`#${winId} .window-titlebar`);
|
||||
const box = await bar.boundingBox();
|
||||
if (!box) return false;
|
||||
expect(box, `#${winId} .window-titlebar should have a bounding box`).not.toBeNull();
|
||||
const before = await styleRect(winId);
|
||||
const sx = box.x + box.width / 2;
|
||||
const sy = box.y + box.height / 2;
|
||||
const sx = box!.x + box!.width / 2;
|
||||
const sy = box!.y + box!.height / 2;
|
||||
await page.mouse.move(sx, sy);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(sx, sy + 90, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(250);
|
||||
await page.waitForTimeout(250); // eslint-disable-line -- documented interaction dwell (title-bar drag commit; no event/registry observable)
|
||||
const after = await styleRect(winId);
|
||||
return !!before && !!after && (Math.abs(after.top - before.top) > 5 || Math.abs(after.left - before.left) > 5);
|
||||
}
|
||||
|
||||
async function closeViaTitlebar(winId: string): Promise<boolean> {
|
||||
await page.locator(`#${winId} .window-titlebar-close`).click();
|
||||
await page.waitForTimeout(400);
|
||||
await page.waitForTimeout(400); // eslint-disable-line -- documented interaction dwell (× close / modal EndModal commit; no event/registry observable)
|
||||
return page.evaluate((wid) => {
|
||||
const el = document.getElementById(wid);
|
||||
return !el || getComputedStyle(el).display === 'none';
|
||||
|
|
@ -80,15 +79,15 @@ test.describe('secondary-frame DOM title bar (drag / close)', () => {
|
|||
async function resizeViaCorner(winId: string): Promise<boolean> {
|
||||
const handle = page.locator(`#${winId} .window-resize-se`);
|
||||
const box = await handle.boundingBox();
|
||||
if (!box) return false;
|
||||
expect(box, `#${winId} .window-resize-se should have a bounding box`).not.toBeNull();
|
||||
const before = await styleRect(winId);
|
||||
const sx = box.x + box.width / 2;
|
||||
const sy = box.y + box.height / 2;
|
||||
const sx = box!.x + box!.width / 2;
|
||||
const sy = box!.y + box!.height / 2;
|
||||
await page.mouse.move(sx, sy);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(sx + 60, sy + 60, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(250);
|
||||
await page.waitForTimeout(250); // eslint-disable-line -- documented interaction dwell (se-corner resize drag commit; no event/registry observable)
|
||||
const after = await styleRect(winId);
|
||||
return !!before && !!after
|
||||
&& (after.width - before.width > 20) && (after.height - before.height > 20);
|
||||
|
|
|
|||
|
|
@ -3,32 +3,39 @@
|
|||
// before layout; the DOM used to report ~0 height, collapsing the control. The
|
||||
// fix floors the height in wxChoice::DoGetBestSize(). The C++ app queries one
|
||||
// wxChoice's GetBestSize() in its constructor (before Show()) and logs it.
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
//
|
||||
// Determinism: no waitForTimeout. Readiness via waitForWxApp (loud). The init-dwell
|
||||
// sleep before checking the best-size log is replaced by polling for the exact
|
||||
// "Choice best size:" console event the test asserts on. Static loaded/result states
|
||||
// use stableShot.
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('Select Height Tests', () => {
|
||||
|
||||
test('Select height test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/selectheight/selectheight_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/selectheight-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'selectheight-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('[SELECTHEIGHT_TEST] Select height test app started'));
|
||||
|
||||
expect(loaded, 'Select height app should load').toBe(true);
|
||||
expect(hasStartup, 'Startup log should be present').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('wxChoice best size has a real height before layout', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/selectheight/selectheight_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Wait for the app to finish initialization
|
||||
await page.waitForTimeout(500);
|
||||
// Wait for the app to finish initialization: the constructor logs the best size.
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[SELECTHEIGHT_TEST] Choice best size:')),
|
||||
{ message: 'Choice best size log should appear after init' },
|
||||
).toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/selectheight-02-result.png', fullPage: true });
|
||||
await stableShot(page, 'selectheight-02-result.png', { fullPage: true });
|
||||
|
||||
// Parse the best size logged from the constructor (before Show()/layout)
|
||||
const bestSizeLogs = testLogger.consoleLogs.filter(l => l.includes('[SELECTHEIGHT_TEST] Choice best size:'));
|
||||
|
|
|
|||
|
|
@ -1,114 +1,87 @@
|
|||
// Specialized wxWidgets Controls Tests - Treebook, RearrangeCtrl, BitmapComboBox
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel, clickComboButton, selectComboItem, clickListboxItem, clickTreeItem } from './utils/element-tracker';
|
||||
import { test, expect } from './utils/fixtures';
|
||||
import { clickByLabel, clickComboButton, selectComboItem, clickListboxItem, clickTreeItem, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('Specialized wxWidgets Controls Tests', () => {
|
||||
|
||||
test('Specialized controls test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/specialized/specialized_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/specialized-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'specialized-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'Specialized controls app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('wxTreebook displays tree with pages', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/specialized/specialized_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Treebook should show General page by default with tree on left
|
||||
await page.screenshot({ path: 'test-results/specialized-02-treebook-initial.png', fullPage: true });
|
||||
await stableShot(page, 'specialized-02-treebook-initial.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('wxTreebook can navigate between pages', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/specialized/specialized_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click on Display page using element registry
|
||||
await clickTreeItem(page, 'Display');
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: treebook page-switch settle before the next tree click (no console observable in this app)
|
||||
|
||||
// Click on Printing page using element registry
|
||||
await clickTreeItem(page, 'Printing');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/specialized-03-treebook-subpage.png', fullPage: true });
|
||||
await stableShot(page, 'specialized-03-treebook-subpage.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('wxTreebook can expand tree nodes', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/specialized/specialized_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click on Editing page using element registry
|
||||
await clickTreeItem(page, 'Editing');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/specialized-04-treebook-expand.png', fullPage: true });
|
||||
await stableShot(page, 'specialized-04-treebook-expand.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('wxBitmapComboBox displays layer swatches', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/specialized/specialized_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click on layer combo box to open dropdown using element tracking
|
||||
const opened = await clickComboButton(page);
|
||||
expect(opened, 'Should be able to open BitmapComboBox dropdown').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/specialized-05-bitmapcombo-dropdown.png', fullPage: true });
|
||||
await stableShot(page, 'specialized-05-bitmapcombo-dropdown.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('wxBitmapComboBox can select different layer', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/specialized/specialized_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Select B.Cu layer using element tracking
|
||||
const selected = await selectComboItem(page, 'B.Cu');
|
||||
expect(selected, 'Should be able to select B.Cu from dropdown').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/specialized-06-bitmapcombo-select.png', fullPage: true });
|
||||
await stableShot(page, 'specialized-06-bitmapcombo-select.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('wxRearrangeCtrl displays layer order', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/specialized/specialized_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// RearrangeCtrl should show list of layers with checkboxes
|
||||
await page.screenshot({ path: 'test-results/specialized-07-rearrange-list.png', fullPage: true });
|
||||
await stableShot(page, 'specialized-07-rearrange-list.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('wxRearrangeCtrl Get Layer Status button works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/specialized/specialized_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Get Layer Status button using element registry
|
||||
await clickByLabel(page, 'Get Layer Status');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/specialized-08-get-order.png', fullPage: true });
|
||||
await stableShot(page, 'specialized-08-get-order.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { test, expect } from './utils/fixtures';
|
||||
import { clickByLabel } from './utils/element-tracker';
|
||||
import { clickByLabel, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
/**
|
||||
* wxStyledTextCtrl Tests
|
||||
|
|
@ -26,167 +26,156 @@ import { clickByLabel } from './utils/element-tracker';
|
|||
test.describe('wxStyledTextCtrl Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/standalone/stc/stc_test.html');
|
||||
// Wait for app to initialize
|
||||
await page.waitForFunction(() => {
|
||||
return document.querySelector('canvas') !== null;
|
||||
}, { timeout: 30000 });
|
||||
await page.waitForTimeout(1000);
|
||||
// Deterministic app-readiness: canvas visible + wx element registry populated.
|
||||
await waitForWxApp(page);
|
||||
});
|
||||
|
||||
test('STC test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
// Readiness (canvas + registry populated) is handled by beforeEach's waitForWxApp,
|
||||
// which is the load gate. The original only asserted no init errors (its startup-log
|
||||
// check was computed but never asserted — the app logs [STC_EVENT], not a start line).
|
||||
await stableShot(page, 'stc-01-loaded.png');
|
||||
|
||||
const hasStartupLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('STC_TEST') && log.includes('started successfully')
|
||||
);
|
||||
|
||||
await page.screenshot({ path: 'test-results/stc-01-loaded.png' });
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
expect(testLogger.errors.filter((e) => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Python syntax highlighting is enabled by default', async ({ page }) => {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Take screenshot to verify Python code with syntax highlighting
|
||||
await page.screenshot({ path: 'test-results/stc-02-python-default.png' });
|
||||
|
||||
// Visual verification - the editor should show Python code with colors
|
||||
// (import, def, for keywords should be highlighted)
|
||||
// (import, def, for keywords should be highlighted). Static default state.
|
||||
await stableShot(page, 'stc-02-python-default.png');
|
||||
});
|
||||
|
||||
test('DRC Rules mode can be activated', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click DRC Rules button using element registry
|
||||
await clickByLabel(page, 'DRC Rules');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/stc-03-drc-mode.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some((log) => log.includes('DRC rules lexer configured')),
|
||||
{ message: 'DRC rules lexer configured is logged' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasDrcLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('DRC rules lexer configured')
|
||||
);
|
||||
expect(hasDrcLog).toBe(true);
|
||||
await stableShot(page, 'stc-03-drc-mode.png');
|
||||
});
|
||||
|
||||
test('Plain text mode can be activated', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Plain button using element registry
|
||||
await clickByLabel(page, 'Plain');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/stc-04-plain-mode.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some((log) => log.includes('Plain text mode enabled')),
|
||||
{ message: 'Plain text mode enabled is logged' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasPlainLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Plain text mode enabled')
|
||||
);
|
||||
expect(hasPlainLog).toBe(true);
|
||||
await stableShot(page, 'stc-04-plain-mode.png');
|
||||
});
|
||||
|
||||
test('Insert Sample button adds code', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Insert Sample button using element registry
|
||||
await clickByLabel(page, 'Insert Sample');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/stc-05-insert-sample.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some((log) => log.includes('Inserted sample code')),
|
||||
{ message: 'Inserted sample code is logged' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasInsertLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Inserted sample code')
|
||||
);
|
||||
expect(hasInsertLog).toBe(true);
|
||||
await stableShot(page, 'stc-05-insert-sample.png');
|
||||
});
|
||||
|
||||
test('Clear button clears editor content', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Clear button using element registry
|
||||
await clickByLabel(page, 'Clear');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/stc-06-cleared.png' });
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some((log) => log.includes('Text cleared')), {
|
||||
message: 'Text cleared is logged',
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
const hasClearLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Text cleared')
|
||||
);
|
||||
expect(hasClearLog).toBe(true);
|
||||
await stableShot(page, 'stc-06-cleared.png');
|
||||
});
|
||||
|
||||
test('Line numbers can be toggled', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Line Numbers button using element registry
|
||||
await clickByLabel(page, 'Line Numbers');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/stc-07-line-numbers-toggle.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
testLogger.consoleLogs.some(
|
||||
(log) => log.includes('Line numbers hidden') || log.includes('Line numbers shown')
|
||||
),
|
||||
{ message: 'Line numbers toggle is logged' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasLineNumLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Line numbers hidden') || log.includes('Line numbers shown')
|
||||
);
|
||||
expect(hasLineNumLog).toBe(true);
|
||||
await stableShot(page, 'stc-07-line-numbers-toggle.png');
|
||||
});
|
||||
|
||||
test('Fold All button works', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Fold All button using element registry
|
||||
await clickByLabel(page, 'Fold All');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/stc-08-folded.png' });
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some((log) => log.includes('All code folded')), {
|
||||
message: 'All code folded is logged',
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
const hasFoldLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('All code folded')
|
||||
);
|
||||
expect(hasFoldLog).toBe(true);
|
||||
await stableShot(page, 'stc-08-folded.png');
|
||||
});
|
||||
|
||||
test('Editor can receive text input', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const canvas = page.locator('canvas');
|
||||
|
||||
// Clear the editor first using element registry
|
||||
// Clear the editor first using element registry, and wait deterministically
|
||||
// for the clear to commit before typing.
|
||||
await clickByLabel(page, 'Clear');
|
||||
await page.waitForTimeout(300);
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some((log) => log.includes('Text cleared')), {
|
||||
message: 'editor reports "Text cleared" before typing',
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
// Click in the editor area to focus it
|
||||
// STC (Scintilla) has complex internal windowing - click anywhere in editor area
|
||||
await canvas.click({ position: { x: 400, y: 300 } });
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: Scintilla focus commit before keystrokes (no observable)
|
||||
|
||||
// Type some text
|
||||
await page.keyboard.type('# Test input\nprint("Hello WASM!")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/stc-09-typed.png' });
|
||||
await stableShot(page, 'stc-09-typed.png');
|
||||
|
||||
// The text should have triggered change events (logged every 10 changes)
|
||||
});
|
||||
|
||||
test('Switching between modes preserves content structure', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Start with Python mode (default)
|
||||
await page.screenshot({ path: 'test-results/stc-10a-python.png' });
|
||||
await stableShot(page, 'stc-10a-python.png');
|
||||
|
||||
// Switch to DRC mode using element registry
|
||||
await clickByLabel(page, 'DRC Rules');
|
||||
await page.waitForTimeout(300);
|
||||
await page.screenshot({ path: 'test-results/stc-10b-drc.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some((log) => log.includes('DRC rules lexer configured')),
|
||||
{ message: 'DRC rules lexer configured is logged' }
|
||||
)
|
||||
.toBe(true);
|
||||
await stableShot(page, 'stc-10b-drc.png');
|
||||
|
||||
// Switch back to Python using element registry
|
||||
await clickByLabel(page, 'Python');
|
||||
await page.waitForTimeout(300);
|
||||
await page.screenshot({ path: 'test-results/stc-10c-python-again.png' });
|
||||
await stableShot(page, 'stc-10c-python-again.png');
|
||||
|
||||
// Multiple mode switches should work (at least DRC mode change should be logged)
|
||||
const modeChanges = testLogger.consoleLogs.filter(log =>
|
||||
log.includes('lexer configured') || log.includes('mode enabled')
|
||||
const modeChanges = testLogger.consoleLogs.filter(
|
||||
(log) => log.includes('lexer configured') || log.includes('mode enabled')
|
||||
).length;
|
||||
expect(modeChanges).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
// Text Decorations Tests - Underline and Strikethrough rendering
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('Text Decorations Tests', () => {
|
||||
|
||||
test('Text decorations render correctly', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/textdecor/textdecor_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/textdecor.png', fullPage: true });
|
||||
await stableShot(page, 'textdecor.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'Text decorations app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,117 +1,91 @@
|
|||
// wxTimer Tests - Timer functionality for KiCad animations, auto-save, periodic updates
|
||||
// Uses element registry for semantic element identification
|
||||
import { test, expect, tryLoadApp, waitForRegistry, clickByLabel, findByLabel } from './utils/fixtures';
|
||||
// Uses element registry for semantic element identification.
|
||||
//
|
||||
// Determinism: no waitForTimeout. Readiness via waitForWxApp; each button click's effect
|
||||
// is the console event it emits, so we poll for that event (deterministic) instead of
|
||||
// sleeping. The timer *tick* is a genuine scheduled event — the app logs each tick, so we
|
||||
// poll for the tick log rather than guessing a duration. Mid-run "ticking"/"running"
|
||||
// screenshots (which asserted nothing and whose pixels are timing-dependent) are dropped;
|
||||
// the static loaded/started/stopped/reset states use stableShot.
|
||||
import { test, expect, waitForWxApp, findByLabel, clickByLabel } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxTimer Tests', () => {
|
||||
|
||||
test('Timer test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/timer/timer_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/timer-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'timer-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'Timer app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Slow timer can be started and stopped', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/timer/timer_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await waitForRegistry(page);
|
||||
|
||||
// Click Start button for slow timer
|
||||
// Note: There are two "Start" buttons, we need the first one in "Slow Timer" section
|
||||
// Click Start for the slow timer (the first "Start" button, in the Slow Timer section).
|
||||
const startButton = await findByLabel(page, 'Start', { exact: true });
|
||||
if (startButton) {
|
||||
await page.mouse.click(startButton.centerX, startButton.centerY);
|
||||
}
|
||||
await page.waitForTimeout(500);
|
||||
expect(startButton, 'Slow-timer Start button should exist').not.toBeNull();
|
||||
await page.mouse.click(startButton!.centerX, startButton!.centerY);
|
||||
|
||||
await page.screenshot({ path: 'test-results/timer-02-started.png', fullPage: true });
|
||||
// The click's effect is the console event — poll for it (replaces waitForTimeout(500)).
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l => l.includes('Slow timer started')),
|
||||
{ message: 'Should log slow timer started' }).toBe(true);
|
||||
await stableShot(page, 'timer-02-started.png', { fullPage: true });
|
||||
|
||||
const hasStartEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Slow timer started')
|
||||
);
|
||||
expect(hasStartEvent, 'Should log slow timer started').toBe(true);
|
||||
// Wait for the timer to actually tick (the app logs each tick — deterministic,
|
||||
// replaces waitForTimeout(1500)).
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l => l.includes('Slow timer tick')),
|
||||
{ message: 'slow timer should tick', timeout: 8000 }).toBe(true);
|
||||
|
||||
// Wait for timer tick
|
||||
await page.waitForTimeout(1500);
|
||||
await page.screenshot({ path: 'test-results/timer-03-ticked.png', fullPage: true });
|
||||
|
||||
// Click Stop button for slow timer
|
||||
// Click Stop for the slow timer.
|
||||
const stopButton = await findByLabel(page, 'Stop', { exact: true });
|
||||
if (stopButton) {
|
||||
await page.mouse.click(stopButton.centerX, stopButton.centerY);
|
||||
}
|
||||
await page.waitForTimeout(500);
|
||||
expect(stopButton, 'Slow-timer Stop button should exist').not.toBeNull();
|
||||
await page.mouse.click(stopButton!.centerX, stopButton!.centerY);
|
||||
|
||||
await page.screenshot({ path: 'test-results/timer-04-stopped.png', fullPage: true });
|
||||
|
||||
const hasStopEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Slow timer stopped')
|
||||
);
|
||||
expect(hasStopEvent, 'Should log slow timer stopped').toBe(true);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l => l.includes('Slow timer stopped')),
|
||||
{ message: 'Should log slow timer stopped' }).toBe(true);
|
||||
await stableShot(page, 'timer-04-stopped.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Fast timer can be started and updates gauge', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/timer/timer_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await waitForRegistry(page);
|
||||
|
||||
// Click "Start Fast" button
|
||||
await clickByLabel(page, 'Start Fast');
|
||||
await page.waitForTimeout(500);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l => l.includes('Fast timer started')),
|
||||
{ message: 'Should log fast timer started' }).toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/timer-05-fast-started.png', fullPage: true });
|
||||
// Let the fast timer tick (deterministic). No screenshot while it runs: the fast
|
||||
// timer continuously animates the gauge, so no two frames are ever identical and
|
||||
// stableShot can't stabilize — and the original mid-run shots asserted nothing.
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l => l.includes('Fast timer tick')),
|
||||
{ message: 'fast timer should tick', timeout: 8000 }).toBe(true);
|
||||
|
||||
const hasFastStartEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Fast timer started')
|
||||
);
|
||||
expect(hasFastStartEvent, 'Should log fast timer started').toBe(true);
|
||||
|
||||
// Wait for multiple fast ticks
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: 'test-results/timer-06-fast-running.png', fullPage: true });
|
||||
|
||||
// Click "Stop Fast" button
|
||||
await clickByLabel(page, 'Stop Fast');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/timer-07-fast-stopped.png', fullPage: true });
|
||||
|
||||
const hasFastStopEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Fast timer stopped')
|
||||
);
|
||||
expect(hasFastStopEvent, 'Should log fast timer stopped').toBe(true);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l => l.includes('Fast timer stopped')),
|
||||
{ message: 'Should log fast timer stopped' }).toBe(true);
|
||||
// Screenshot the stopped (now-static) state.
|
||||
await stableShot(page, 'timer-07-fast-stopped.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Reset counters button works', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/timer/timer_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await waitForRegistry(page);
|
||||
|
||||
// Start slow timer briefly
|
||||
// Start the slow timer and let it run briefly (poll for a tick) so there are counters
|
||||
// to reset — replaces waitForTimeout(1500).
|
||||
const startButton = await findByLabel(page, 'Start', { exact: true });
|
||||
if (startButton) {
|
||||
await page.mouse.click(startButton.centerX, startButton.centerY);
|
||||
}
|
||||
await page.waitForTimeout(1500);
|
||||
expect(startButton, 'Slow-timer Start button should exist').not.toBeNull();
|
||||
await page.mouse.click(startButton!.centerX, startButton!.centerY);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l => l.includes('Slow timer tick')),
|
||||
{ message: 'slow timer should tick before reset', timeout: 8000 }).toBe(true);
|
||||
|
||||
// Click "Reset All Counters" button
|
||||
await clickByLabel(page, 'Reset All Counters');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/timer-08-reset.png', fullPage: true });
|
||||
|
||||
const hasResetEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Counters reset')
|
||||
);
|
||||
expect(hasResetEvent, 'Should log counters reset').toBe(true);
|
||||
await expect.poll(() => testLogger.consoleLogs.some(l => l.includes('Counters reset')),
|
||||
{ message: 'Should log counters reset' }).toBe(true);
|
||||
await stableShot(page, 'timer-08-reset.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,100 +1,110 @@
|
|||
// 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';
|
||||
import { test, expect, MAIN_CANVAS, getCanvasBox, waitForWxApp } from './utils/fixtures';
|
||||
import { clickToolbarTool, findRenderedByType, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxToolBar & wxStatusBar Tests', () => {
|
||||
|
||||
test('Toolbar test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/toolbar/toolbar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/toolbar-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'toolbar-01-loaded.png', { fullPage: true });
|
||||
|
||||
const hasStartup = testLogger.consoleLogs.some(l => l.includes('Toolbar test app started'));
|
||||
|
||||
expect(loaded, 'Toolbar app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Toolbar buttons are visible', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/toolbar/toolbar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/toolbar-02-buttons.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Toolbar created')),
|
||||
{ message: 'Toolbar-created event should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
const hasToolbarLog = testLogger.consoleLogs.some(l => l.includes('Toolbar created'));
|
||||
|
||||
expect(hasToolbarLog).toBe(true);
|
||||
await stableShot(page, 'toolbar-02-buttons.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('New tool button can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/toolbar/toolbar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// 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 expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Toolbar: New clicked')),
|
||||
{ message: 'New-clicked event should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/toolbar-03-new-clicked.png', fullPage: true });
|
||||
await stableShot(page, 'toolbar-03-new-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Zoom tools can be clicked', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/toolbar/toolbar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// 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);
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Toolbar: Zoom In clicked')),
|
||||
{ message: 'Zoom-In-clicked event should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
// 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 expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Toolbar: Zoom Out clicked')),
|
||||
{ message: 'Zoom-Out-clicked event should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/toolbar-04-zoom.png', fullPage: true });
|
||||
await stableShot(page, 'toolbar-04-zoom.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Toggle tool changes state', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/toolbar/toolbar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// 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 expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Toolbar: Toggle ON')),
|
||||
{ message: 'Toggle-ON event should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/toolbar-05-toggle-on.png', fullPage: true });
|
||||
await stableShot(page, 'toolbar-05-toggle-on.png', { fullPage: true });
|
||||
|
||||
// Click again to toggle off
|
||||
const toggleClicked2 = await clickToolbarTool(page, 'Toggle');
|
||||
expect(toggleClicked2, 'Toggle tool should be found and clicked again').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Toolbar: Toggle OFF')),
|
||||
{ message: 'Toggle-OFF event should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/toolbar-06-toggle-off.png', fullPage: true });
|
||||
await stableShot(page, 'toolbar-06-toggle-off.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Status bar shows messages', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/toolbar/toolbar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/toolbar-07-statusbar.png', fullPage: true });
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('Status bar created')),
|
||||
{ message: 'Status-bar-created event should be logged' }
|
||||
).toBe(true);
|
||||
|
||||
const hasStatusBarLog = testLogger.consoleLogs.some(l => l.includes('Status bar created'));
|
||||
|
||||
expect(hasStatusBarLog).toBe(true);
|
||||
await stableShot(page, 'toolbar-07-statusbar.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('All toolbar buttons accessible', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/toolbar/toolbar_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Verify all tools are registered
|
||||
const tools = await findRenderedByType(page, 'tool');
|
||||
|
|
@ -102,13 +112,24 @@ test.describe('wxToolBar & wxStatusBar Tests', () => {
|
|||
|
||||
// Click all toolbar buttons by label
|
||||
const toolLabels = ['New', 'Open', 'Save', 'Zoom In', 'Zoom Out', 'Toggle'];
|
||||
const clickEvent: Record<string, string> = {
|
||||
'New': 'Toolbar: New clicked',
|
||||
'Open': 'Toolbar: Open clicked',
|
||||
'Save': 'Toolbar: Save clicked',
|
||||
'Zoom In': 'Toolbar: Zoom In clicked',
|
||||
'Zoom Out': 'Toolbar: Zoom Out clicked',
|
||||
'Toggle': 'Toolbar: 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);
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes(clickEvent[label])),
|
||||
{ message: `Tool "${label}" click event should be logged` }
|
||||
).toBe(true);
|
||||
}
|
||||
|
||||
await page.screenshot({ path: 'test-results/toolbar-08-all-buttons.png', fullPage: true });
|
||||
await stableShot(page, 'toolbar-08-all-buttons.png', { fullPage: true });
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,139 +1,142 @@
|
|||
import { test, expect } from './utils/fixtures';
|
||||
import { clickByLabel, clickTreeItem, findAllTreeItems } from './utils/element-tracker';
|
||||
import { clickByLabel, clickTreeItem, findAllTreeItems, waitForWxApp, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxTreeCtrl Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/standalone/tree/tree_test.html');
|
||||
// Wait for app to initialize
|
||||
await page.waitForFunction(() => {
|
||||
return document.querySelector('canvas') !== null;
|
||||
}, { timeout: 30000 });
|
||||
await page.waitForTimeout(1000);
|
||||
// Deterministic app readiness: canvas visible + wx element registry populated (fails loudly).
|
||||
await waitForWxApp(page);
|
||||
});
|
||||
|
||||
test('Tree test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const hasStartupLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('TREE_TEST') && log.includes('started successfully')
|
||||
);
|
||||
|
||||
await page.screenshot({ path: 'test-results/tree-01-loaded.png' });
|
||||
await stableShot(page, 'tree-01-loaded.png');
|
||||
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Tree is populated with KiCad-like hierarchy', async ({ page, testLogger }) => {
|
||||
// Wait a bit longer for async console events to be captured
|
||||
await page.waitForTimeout(1000);
|
||||
// Capture the boot hierarchy first (matches the original's tree-02-hierarchy, taken
|
||||
// before any interaction). The app logs "populated" only to its on-page event div,
|
||||
// not the browser console, so the original's console check was always false and it fell
|
||||
// through to clicking Expand All. Prove population deterministically the same way:
|
||||
// Expand All emits console expand events that only fire when the tree is populated.
|
||||
await stableShot(page, 'tree-02-hierarchy.png');
|
||||
|
||||
// Check for the populated message - it should be in console logs OR
|
||||
// we verify tree rendered correctly by checking for expected tree events
|
||||
const hasPopulatedLog = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Tree populated with KiCad-like hierarchy') ||
|
||||
log.includes('TREE_EVENT')
|
||||
);
|
||||
|
||||
await page.screenshot({ path: 'test-results/tree-02-hierarchy.png' });
|
||||
|
||||
// If no console logs captured during init, verify tree exists by checking
|
||||
// that subsequent tree operations work (expand events prove tree is populated)
|
||||
if (!hasPopulatedLog) {
|
||||
// Click Expand All button to verify tree is actually populated
|
||||
await clickByLabel(page, 'Expand All');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const hasExpandEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Expanding') || log.includes('All items expanded')
|
||||
);
|
||||
expect(hasExpandEvent).toBe(true);
|
||||
} else {
|
||||
expect(hasPopulatedLog).toBe(true);
|
||||
}
|
||||
expect(await clickByLabel(page, 'Expand All'), 'Expand All button should be clickable').toBe(true);
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
testLogger.consoleLogs.some(log =>
|
||||
log.includes('Expanding') || log.includes('All items expanded')
|
||||
),
|
||||
{ message: 'expanding the tree should emit expand events (proves it is populated)' }
|
||||
)
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
test('Tree item can be selected', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click on a tree item using element registry
|
||||
const clicked = await clickTreeItem(page, 'Schematic');
|
||||
expect(clicked).toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/tree-03-selected.png' });
|
||||
// Deterministically wait for the selection to commit (was a blind 300ms dwell).
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some(log => log.includes('Selection changed')),
|
||||
{ message: 'tree should emit a "Selection changed" log after clicking an item' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasSelectionEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Selection changed')
|
||||
);
|
||||
await stableShot(page, 'tree-03-selected.png');
|
||||
});
|
||||
|
||||
test('Expand All button works', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Expand All button using element registry
|
||||
await clickByLabel(page, 'Expand All');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/tree-04-expanded.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some(log => log.includes('All items expanded')),
|
||||
{ message: 'tree should emit "All items expanded" after Expand All' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasExpandEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('All items expanded')
|
||||
);
|
||||
expect(hasExpandEvent).toBe(true);
|
||||
await stableShot(page, 'tree-04-expanded.png');
|
||||
});
|
||||
|
||||
test('Collapse All button works', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Collapse All button using element registry
|
||||
await clickByLabel(page, 'Collapse All');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/tree-05-collapsed.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some(log => log.includes('All items collapsed')),
|
||||
{ message: 'tree should emit "All items collapsed" after Collapse All' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasCollapseEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('All items collapsed')
|
||||
);
|
||||
expect(hasCollapseEvent).toBe(true);
|
||||
await stableShot(page, 'tree-05-collapsed.png');
|
||||
});
|
||||
|
||||
test('Add Item button works with selection', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// First select an item using element registry
|
||||
const clicked = await clickTreeItem(page, 'Schematic');
|
||||
expect(clicked).toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Deterministically wait for the selection to commit (was a blind 300ms dwell).
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some(log => log.includes('Selection changed')),
|
||||
{ message: 'tree should emit a "Selection changed" log after clicking an item' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
// Click Add Item button using element registry
|
||||
await clickByLabel(page, 'Add Item');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/tree-06-added.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
testLogger.consoleLogs.some(log =>
|
||||
log.includes('Added new item') || log.includes('No item selected')
|
||||
),
|
||||
{ message: 'tree should emit "Added new item" / "No item selected" after Add Item' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasAddEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Added new item') || log.includes('No item selected')
|
||||
);
|
||||
expect(hasAddEvent).toBe(true);
|
||||
await stableShot(page, 'tree-06-added.png');
|
||||
});
|
||||
|
||||
test('Delete Item button works with selection', async ({ page, testLogger }) => {
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// First select an item (not root) using element registry
|
||||
const clicked = await clickTreeItem(page, 'Libraries');
|
||||
expect(clicked).toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Deterministically wait for the selection to commit (was a blind 300ms dwell).
|
||||
await expect
|
||||
.poll(
|
||||
() => testLogger.consoleLogs.some(log => log.includes('Selection changed')),
|
||||
{ message: 'tree should emit a "Selection changed" log after clicking an item' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
// Click Delete Selected button using element registry
|
||||
await clickByLabel(page, 'Delete Selected');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/tree-07-deleted.png' });
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
testLogger.consoleLogs.some(log =>
|
||||
log.includes('Deleted item') || log.includes('Cannot delete')
|
||||
),
|
||||
{ message: 'tree should emit "Deleted item" / "Cannot delete" after Delete Selected' }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const hasDeleteEvent = testLogger.consoleLogs.some(log =>
|
||||
log.includes('Deleted item') || log.includes('Cannot delete')
|
||||
);
|
||||
expect(hasDeleteEvent).toBe(true);
|
||||
await stableShot(page, 'tree-07-deleted.png');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1655,3 +1655,350 @@ export async function clickStyledTextCtrl(
|
|||
export async function findAllStyledTextCtrls(page: Page): Promise<WxRenderedElement[]> {
|
||||
return findRenderedByType(page, 'styledtext');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Deterministic waits — the "hard" (throw-on-miss) primitives that replace
|
||||
// waitForTimeout sleeps and `if (element exists)` branches in the specs.
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Poll a page-side predicate until it returns truthy, or throw a readable error.
|
||||
*
|
||||
* This is the general "loop until something actually happens" utility: it replaces
|
||||
* `await page.waitForTimeout(n)` (a blind timer) with a wait on a real condition, and
|
||||
* fails LOUDLY with `desc` instead of a bare Playwright timeout — so a test that no
|
||||
* longer reaches its expected state reports *what* it was waiting for.
|
||||
*
|
||||
* await waitUntil(page, () => !!window.wxElementRegistry, 'registry defined');
|
||||
* await waitUntil(page, (t) => document.title === t, 'title set', { arg: 'KiCad' });
|
||||
*/
|
||||
export async function waitUntil<Arg = undefined>(
|
||||
page: Page,
|
||||
pageFunction: (arg: Arg) => unknown,
|
||||
desc: string,
|
||||
options: { timeout?: number; arg?: Arg; polling?: number | 'raf' } = {}
|
||||
): Promise<void> {
|
||||
const timeout = options.timeout ?? 15000;
|
||||
try {
|
||||
await page.waitForFunction(pageFunction, options.arg as Arg, {
|
||||
timeout,
|
||||
polling: options.polling ?? 'raf',
|
||||
});
|
||||
} catch {
|
||||
throw new Error(`waitUntil: timed out after ${timeout}ms waiting for: ${desc}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a KiCad/wx editor app is interactive: the emscripten canvas is visible,
|
||||
* the element registry is populated, and at least one toolbar has been laid out.
|
||||
*
|
||||
* App-agnostic (no app-specific frame name) so it can gate every editor spec. Replaces
|
||||
* the per-spec `completeWizard()` copies and their fixed 1500–2500ms "let it settle"
|
||||
* sleeps — the seeded config already skips the first-run wizard, so readiness is purely
|
||||
* "is the editor painted and registered".
|
||||
*/
|
||||
export async function waitForEditorReady(
|
||||
page: Page,
|
||||
options: { timeout?: number } = {}
|
||||
): Promise<void> {
|
||||
const timeout = options.timeout ?? 90000;
|
||||
|
||||
await page.locator('#canvas').waitFor({ state: 'visible', timeout });
|
||||
|
||||
await waitUntil(
|
||||
page,
|
||||
() => {
|
||||
const r = window.wxElementRegistry;
|
||||
if (!r) return false;
|
||||
const visible = r.findAll({ visible: true });
|
||||
const hasToolbar = visible.some((el) => /ToolBar/.test(el.typeName));
|
||||
return visible.length > 10 && hasToolbar;
|
||||
},
|
||||
'editor registry populated + a toolbar laid out',
|
||||
{ timeout }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a rendered element (menu item, toolbar tool, …) to appear, then return it;
|
||||
* throw if it never appears. The throwing counterpart to findRenderedByLabel — use it to
|
||||
* replace `await page.waitForTimeout(n)` before reading/clicking a popup that renders async.
|
||||
*/
|
||||
export async function waitForRenderedByLabel(
|
||||
page: Page,
|
||||
label: string,
|
||||
options: RenderedFindOptions & { timeout?: number } = {}
|
||||
): Promise<WxRenderedElement> {
|
||||
const timeout = options.timeout ?? 15000;
|
||||
const { timeout: _t, ...find } = options;
|
||||
await waitUntil(
|
||||
page,
|
||||
([label, opts]: [string, RenderedFindOptions]) => {
|
||||
const r = window.wxElementRegistry;
|
||||
if (!r || !r.findRenderedByLabel) return false;
|
||||
return r.findRenderedByLabel(label, opts).length > 0;
|
||||
},
|
||||
`rendered element "${label}"`,
|
||||
{ timeout, arg: [label, find] as [string, RenderedFindOptions] }
|
||||
);
|
||||
const el = await findRenderedByLabel(page, label, find);
|
||||
if (!el) throw new Error(`waitForRenderedByLabel: "${label}" vanished after appearing`);
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a canvas's pixels stop changing across consecutive animation frames —
|
||||
* a deterministic "the drawing has settled" signal for interaction specs (drawing a
|
||||
* wire, dragging an item) where the visual effect must land before it can be asserted.
|
||||
* Replaces fixed post-action sleeps. Works on WebGL canvases: KiCad's GAL sets
|
||||
* preserveDrawingBuffer=true, so drawImage() can read the buffer back.
|
||||
*/
|
||||
export async function waitForCanvasStable(
|
||||
page: Page,
|
||||
selector: string,
|
||||
options: { stableFrames?: number; timeout?: number; sampleSize?: number } = {}
|
||||
): Promise<void> {
|
||||
const stableFrames = options.stableFrames ?? 3;
|
||||
const timeout = options.timeout ?? 15000;
|
||||
const sampleSize = options.sampleSize ?? 48;
|
||||
|
||||
// Clear any accumulator left by a previous wait on this selector.
|
||||
await page.evaluate((sel) => {
|
||||
const w = window as unknown as { __canvasStable?: Record<string, unknown> };
|
||||
if (w.__canvasStable) delete w.__canvasStable[sel];
|
||||
}, selector);
|
||||
|
||||
await waitUntil(
|
||||
page,
|
||||
([sel, need, size]: [string, number, number]) => {
|
||||
const w = window as unknown as { __canvasStable?: Record<string, { sig: number; count: number }> };
|
||||
const el = document.querySelector(sel) as HTMLCanvasElement | null;
|
||||
if (!el) return false;
|
||||
const off = document.createElement('canvas');
|
||||
off.width = size;
|
||||
off.height = size;
|
||||
const ctx = off.getContext('2d', { willReadFrequently: true });
|
||||
if (!ctx) return false;
|
||||
try {
|
||||
ctx.drawImage(el, 0, 0, size, size);
|
||||
} catch {
|
||||
return false; // canvas not yet readable
|
||||
}
|
||||
const data = ctx.getImageData(0, 0, size, size).data;
|
||||
let sig = 0;
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
sig = (sig * 31 + data[i] + data[i + 1] * 7 + data[i + 2] * 13) | 0;
|
||||
}
|
||||
const store = (w.__canvasStable ??= {});
|
||||
const prev = store[sel];
|
||||
if (prev && prev.sig === sig) prev.count += 1;
|
||||
else store[sel] = { sig, count: 1 };
|
||||
return store[sel].count >= need;
|
||||
},
|
||||
`canvas "${selector}" stable for ${stableFrames} frames`,
|
||||
{ timeout, arg: [selector, stableFrames, sampleSize] as [string, number, number], polling: 'raf' }
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render-settle + capture — feeds the offline tools/screenshots gate
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* page.screenshot options we forward, plus stabilization knobs. Every capture is taken at
|
||||
* scale:'css' (1:1 CSS pixels, hardcoded below — see SHOT_OPTS), uniformly across both suites, to
|
||||
* match the DPR-1 CI Linux baselines and avoid the past device-scale mismatch bug. So there's no
|
||||
* per-call `scale`: it's always 'css'.
|
||||
*/
|
||||
type StableShotOptions = {
|
||||
fullPage?: boolean;
|
||||
/** Stabilization budget (ms). Animating states never settle: we proceed and capture anyway. */
|
||||
timeout?: number;
|
||||
/** Consecutive identical animation frames required before the render is "settled". */
|
||||
stableFrames?: number;
|
||||
/** Canvas whose pixels signal render activity (the wx main surface). Default '#canvas'. */
|
||||
canvas?: string;
|
||||
};
|
||||
|
||||
/** Screenshot params for the final capture — pinned scale keeps every shot at 1:1 CSS pixels. */
|
||||
const SHOT_OPTS = { scale: 'css', animations: 'disabled', caret: 'hide' } as const;
|
||||
|
||||
/**
|
||||
* Wait until the render has SETTLED, then let the caller capture. The "wait" half of
|
||||
* toHaveScreenshot, decoupled so pixel COMPARISON stays in the offline tools/screenshots gate
|
||||
* (which diffs test-results/ against tests/baseline-screenshots/ on CI's deterministic Linux render).
|
||||
*
|
||||
* Stabilization runs entirely IN-PAGE (one page.evaluate): it hashes a 64px downscale of the wx
|
||||
* canvas once per animation frame and resolves after `stableFrames` consecutive identical frames,
|
||||
* or when `timeout` elapses. There are NO repeated CDP screenshots — an earlier screenshot-probe
|
||||
* version saturated CDP on the heavy kicad canvases and timed the whole suite out. Genuinely-
|
||||
* animating states (timers, mid-slide) never converge: we resolve at the deadline and capture the
|
||||
* current frame, exactly as the old raw screenshots did. With no canvas we just wait `stableFrames`
|
||||
* paint frames — the spec's deterministic waits have already reached the target state.
|
||||
*/
|
||||
export async function waitForRenderStable(page: Page, options: StableShotOptions = {}): Promise<void> {
|
||||
await page.evaluate(
|
||||
({ sel, need, timeout }: { sel: string; need: number; timeout: number }) =>
|
||||
new Promise<void>((resolve) => {
|
||||
const el = document.querySelector(sel) as HTMLCanvasElement | null;
|
||||
if (!el || typeof el.getContext !== 'function') {
|
||||
let i = 0;
|
||||
const paint = () => (++i >= need ? resolve() : requestAnimationFrame(paint));
|
||||
requestAnimationFrame(paint);
|
||||
return;
|
||||
}
|
||||
const size = 64;
|
||||
const off = document.createElement('canvas');
|
||||
off.width = size;
|
||||
off.height = size;
|
||||
const ctx = off.getContext('2d', { willReadFrequently: true });
|
||||
const start = performance.now();
|
||||
let prev = NaN;
|
||||
let stable = 0;
|
||||
const tick = () => {
|
||||
let sig = prev;
|
||||
try {
|
||||
ctx!.drawImage(el, 0, 0, size, size);
|
||||
const d = ctx!.getImageData(0, 0, size, size).data;
|
||||
sig = 0;
|
||||
for (let i = 0; i < d.length; i += 4) sig = (sig * 31 + d[i] + d[i + 1] * 7 + d[i + 2] * 13) | 0;
|
||||
} catch {
|
||||
/* canvas not yet readable — treat as unchanged */
|
||||
}
|
||||
if (sig === prev) stable += 1;
|
||||
else {
|
||||
prev = sig;
|
||||
stable = 1;
|
||||
}
|
||||
if (stable >= need || performance.now() - start > timeout) resolve();
|
||||
else requestAnimationFrame(tick);
|
||||
};
|
||||
requestAnimationFrame(tick);
|
||||
}),
|
||||
{ sel: options.canvas ?? '#canvas', need: options.stableFrames ?? 3, timeout: options.timeout ?? 3000 }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stabilize the render, then write a raw PNG to test-results/<name> for the offline screenshot gate
|
||||
* (tools/screenshots/compare.ts vs tests/baseline-screenshots/). Drop-in replacement for
|
||||
* `expect(page).toHaveScreenshot(name, opts)`: keeps toHaveScreenshot's render-settle but NOT its
|
||||
* inline comparison (that's the calibrated offline pipeline's job) nor its per-platform baseline
|
||||
* files. Non-asserting — a render regression is caught by `npm run screenshots:check`, not the test.
|
||||
*/
|
||||
export async function stableShot(page: Page, name: string, options: StableShotOptions = {}): Promise<void> {
|
||||
await waitForRenderStable(page, options);
|
||||
await page.screenshot({
|
||||
path: name.includes('/') ? name : `test-results/${name}`,
|
||||
fullPage: options.fullPage ?? false,
|
||||
...SHOT_OPTS,
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Label normalization + app-readiness (shared by the kicad and e2e/widget suites)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Normalize a wx label / menu item / tooltip for matching: drop the '&' accelerator
|
||||
* marker and any trailing "..." / "…" / whitespace, so "Open", "Open...", "Open…" and
|
||||
* "&Open..." all compare equal. Lets a spec name the ONE canonical label and match it
|
||||
* regardless of the build's ellipsis rendering — replacing "try A, else A…, else A"
|
||||
* fallback chains (which silently mask a real label regression).
|
||||
*/
|
||||
export function labelBase(s: string | undefined | null): string {
|
||||
return (s ?? '').replace(/&/g, '').replace(/[.…\s]+$/u, '').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a wx widget-harness app is interactive: the emscripten canvas is visible and
|
||||
* the element registry has registered its widgets. The e2e counterpart to
|
||||
* waitForEditorReady (no toolbar requirement — widget demos have no toolbar). Replaces the
|
||||
* `tryLoadApp()` + fixed 500ms settle + `waitForRegistry()` trio with one deterministic
|
||||
* wait that FAILS LOUDLY (not a swallowed boolean). Only for registry-backed harnesses.
|
||||
*/
|
||||
export async function waitForWxApp(
|
||||
page: Page,
|
||||
options: { timeout?: number; selector?: string } = {}
|
||||
): Promise<void> {
|
||||
const timeout = options.timeout ?? 30000;
|
||||
const selector = options.selector ?? '#canvas';
|
||||
await page.locator(selector).waitFor({ state: 'visible', timeout });
|
||||
await waitUntil(
|
||||
page,
|
||||
() => {
|
||||
const r = window.wxElementRegistry;
|
||||
return !!r && typeof r.findAll === 'function' && r.findAll({}).length > 0;
|
||||
},
|
||||
'wx element registry populated',
|
||||
{ timeout }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until a registry-LESS wx canvas app (e.g. the drag-drop / GAL harnesses that draw to
|
||||
* #canvas but register no widgets) is interactive: the canvas is visible and its pixels have
|
||||
* settled. The deterministic counterpart to waitForWxApp for apps whose wxElementRegistry
|
||||
* never populates — replaces `tryLoadApp` + a fixed 500ms settle with a real paint-settle.
|
||||
*/
|
||||
export async function waitForCanvasApp(
|
||||
page: Page,
|
||||
options: { timeout?: number; selector?: string } = {}
|
||||
): Promise<void> {
|
||||
const timeout = options.timeout ?? 30000;
|
||||
const selector = options.selector ?? '#canvas';
|
||||
await page.locator(selector).waitFor({ state: 'visible', timeout });
|
||||
await waitForCanvasStable(page, selector, { timeout });
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a rendered menu item whose text matches `base` (ignoring '&' and a trailing
|
||||
* "..."/"…"), then click it; throw if it never appears. Collapses the
|
||||
* `clickMenuItem('Open...') || clickMenuItem('Open…') || clickMenuItem('Open')` fallback
|
||||
* chains AND the fixed post-menu-open sleep into one deterministic, loud call.
|
||||
*/
|
||||
export async function clickMenuItemByText(
|
||||
page: Page,
|
||||
base: string,
|
||||
options: { timeout?: number } = {}
|
||||
): Promise<void> {
|
||||
const timeout = options.timeout ?? 15000;
|
||||
const want = labelBase(base);
|
||||
// Match an ENABLED menu item (mirrors the old clickMenuItem, which returned false for a
|
||||
// present-but-disabled item — so a disabled target still fails loudly).
|
||||
await waitUntil(
|
||||
page,
|
||||
(w: string) => {
|
||||
const norm = (s: string) => (s || '').replace(/&/g, '').replace(/[.…\s]+$/u, '').trim();
|
||||
const r = window.wxElementRegistry;
|
||||
if (!r || !r.findAllRendered) return false;
|
||||
return r.findAllRendered({ elementType: 'menuitem' })
|
||||
.some((m) => norm(m.label || '') === w && m.enabled !== false);
|
||||
},
|
||||
`enabled menu item "${base}"`,
|
||||
{ timeout, arg: want }
|
||||
);
|
||||
const pos = await page.evaluate((w: string) => {
|
||||
const norm = (s: string) => (s || '').replace(/&/g, '').replace(/[.…\s]+$/u, '').trim();
|
||||
const r = window.wxElementRegistry!;
|
||||
const hit = r.findAllRendered!({ elementType: 'menuitem' })
|
||||
.find((m) => norm(m.label || '') === w && m.enabled !== false);
|
||||
return hit ? { x: hit.centerX, y: hit.centerY } : null;
|
||||
}, want);
|
||||
if (!pos) throw new Error(`clickMenuItemByText: "${base}" vanished after appearing`);
|
||||
await page.mouse.click(pos.x, pos.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the emscripten canvas by clicking its centre; asserts the canvas is visible and
|
||||
* has a layout box (throws loudly) instead of the `if (box) click` defensive skip that was
|
||||
* copy-pasted across specs.
|
||||
*/
|
||||
export async function focusCanvas(page: Page, selector = '#canvas'): Promise<void> {
|
||||
const loc = page.locator(selector);
|
||||
await loc.waitFor({ state: 'visible', timeout: 30000 });
|
||||
const box = await loc.boundingBox();
|
||||
if (!box) throw new Error(`focusCanvas: ${selector} has no bounding box`);
|
||||
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
// wxValidator Tests - Input validation like KiCad's dialog validators
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxValidator Tests', () => {
|
||||
|
||||
test('Validators test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/validators/validators_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/validators-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'validators-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'Validators app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Text validator input exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/validators/validators_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/validators-02-text.png', fullPage: true });
|
||||
await stableShot(page, 'validators-02-text.png', { fullPage: true });
|
||||
|
||||
// App loaded successfully - verify no errors
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
|
|
@ -27,46 +25,30 @@ test.describe('wxValidator Tests', () => {
|
|||
|
||||
test('Integer validator input exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/validators/validators_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/validators-03-integer.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Integer validator input should exist').toBe(true);
|
||||
await stableShot(page, 'validators-03-integer.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Floating point validator input exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/validators/validators_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/validators-04-float.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Float validator input should exist').toBe(true);
|
||||
await stableShot(page, 'validators-04-float.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Custom net name validator input exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/validators/validators_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/validators-05-netname.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Net name validator input should exist').toBe(true);
|
||||
await stableShot(page, 'validators-05-netname.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Validate all button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/validators/validators_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/validators-06-button.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Validate All button should exist').toBe(true);
|
||||
await stableShot(page, 'validators-06-button.png', { fullPage: true });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
// WASM Edge Cases Tests - Browser-specific limitations and behaviors
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('WASM Edge Cases Tests', () => {
|
||||
|
||||
test('WASM edge cases test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/wasmedge/wasmedge_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/wasmedge-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'wasmedge-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'WASM edge cases app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('File system test button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/wasmedge/wasmedge_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/wasmedge-02-filesystem.png', fullPage: true });
|
||||
await stableShot(page, 'wasmedge-02-filesystem.png', { fullPage: true });
|
||||
|
||||
// App loaded successfully - verify no errors
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
|
|
@ -27,68 +25,44 @@ test.describe('WASM Edge Cases Tests', () => {
|
|||
|
||||
test('Threading test button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/wasmedge/wasmedge_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/wasmedge-03-threading.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Threading test button should exist').toBe(true);
|
||||
await stableShot(page, 'wasmedge-03-threading.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Font enumeration test button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/wasmedge/wasmedge_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/wasmedge-04-fonts.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Font enumeration button should exist').toBe(true);
|
||||
await stableShot(page, 'wasmedge-04-fonts.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Clipboard test button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/wasmedge/wasmedge_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/wasmedge-05-clipboard.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Clipboard test button should exist').toBe(true);
|
||||
await stableShot(page, 'wasmedge-05-clipboard.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Memory test button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/wasmedge/wasmedge_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/wasmedge-06-memory.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Memory test button should exist').toBe(true);
|
||||
await stableShot(page, 'wasmedge-06-memory.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Run all tests button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/wasmedge/wasmedge_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/wasmedge-07-runall.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Run all tests button should exist').toBe(true);
|
||||
await stableShot(page, 'wasmedge-07-runall.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Test results log exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/wasmedge/wasmedge_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/wasmedge-08-log.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Test results log should exist').toBe(true);
|
||||
await stableShot(page, 'wasmedge-08-log.png', { fullPage: true });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,96 +1,86 @@
|
|||
// wxWizard Tests - Footprint Wizard simulation
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
import { clickByLabel } from './utils/element-tracker';
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { clickByLabel, waitForElement, stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxWizard Tests', () => {
|
||||
|
||||
test('Wizard test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/wizard/wizard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/wizard-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'wizard-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'wxWizard app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Wizard dialog can be launched', async ({ page, testLogger }) => {
|
||||
test('Wizard dialog can be launched', async ({ page }) => {
|
||||
await page.goto('/standalone/wizard/wizard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click Launch button using element registry
|
||||
await waitForElement(page, 'Launch Footprint Wizard');
|
||||
const clicked = await clickByLabel(page, 'Launch Footprint Wizard');
|
||||
expect(clicked, 'Launch Wizard button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/wizard-02-launch.png', fullPage: true });
|
||||
await stableShot(page, 'wizard-02-launch.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Wizard can navigate to next page', async ({ page, testLogger }) => {
|
||||
test('Wizard can navigate to next page', async ({ page }) => {
|
||||
await page.goto('/standalone/wizard/wizard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Launch wizard using element registry
|
||||
await waitForElement(page, 'Launch Footprint Wizard');
|
||||
const launchClicked = await clickByLabel(page, 'Launch Footprint Wizard');
|
||||
expect(launchClicked, 'Launch Wizard button should be found').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Next using element registry
|
||||
await waitForElement(page, 'Next');
|
||||
const nextClicked = await clickByLabel(page, 'Next');
|
||||
expect(nextClicked, 'Next button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/wizard-03-next-page.png', fullPage: true });
|
||||
await stableShot(page, 'wizard-03-next-page.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Wizard can navigate back', async ({ page, testLogger }) => {
|
||||
test('Wizard can navigate back', async ({ page }) => {
|
||||
await page.goto('/standalone/wizard/wizard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Launch wizard using element registry
|
||||
await waitForElement(page, 'Launch Footprint Wizard');
|
||||
const launchClicked = await clickByLabel(page, 'Launch Footprint Wizard');
|
||||
expect(launchClicked, 'Launch Wizard button should be found').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Next using element registry
|
||||
await waitForElement(page, 'Next');
|
||||
const nextClicked = await clickByLabel(page, 'Next');
|
||||
expect(nextClicked, 'Next button should be found').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Let the Next page-transition commit before clicking Back (the Back/Next
|
||||
// buttons persist across pages, so there is no registry delta to poll on).
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell
|
||||
|
||||
// Click Back using element registry
|
||||
const backClicked = await clickByLabel(page, 'Back');
|
||||
expect(backClicked, 'Back button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/wizard-04-back-page.png', fullPage: true });
|
||||
await stableShot(page, 'wizard-04-back-page.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Wizard can be cancelled', async ({ page, testLogger }) => {
|
||||
test('Wizard can be cancelled', async ({ page }) => {
|
||||
await page.goto('/standalone/wizard/wizard_test.html');
|
||||
const loaded = await tryLoadApp(page);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Launch wizard using element registry
|
||||
await waitForElement(page, 'Launch Footprint Wizard');
|
||||
const launchClicked = await clickByLabel(page, 'Launch Footprint Wizard');
|
||||
expect(launchClicked, 'Launch Wizard button should be found').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click Cancel using element registry
|
||||
await waitForElement(page, 'Cancel');
|
||||
const cancelClicked = await clickByLabel(page, 'Cancel');
|
||||
expect(cancelClicked, 'Cancel button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({ path: 'test-results/wizard-05-cancel.png', fullPage: true });
|
||||
await stableShot(page, 'wizard-05-cancel.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { test, expect, MAIN_CANVAS, waitForApp } from './utils/fixtures';
|
||||
import { test, expect, MAIN_CANVAS, waitForWxApp } from './utils/fixtures';
|
||||
import { Page } from '@playwright/test';
|
||||
import {
|
||||
clickTab,
|
||||
|
|
@ -10,8 +10,7 @@ import {
|
|||
clickTextCtrl,
|
||||
findSingleLineTextCtrl,
|
||||
findMultiLineTextCtrl,
|
||||
clickListboxItemByIndex
|
||||
} from './utils/element-tracker';
|
||||
clickListboxItemByIndex, stableShot } from './utils/element-tracker';
|
||||
|
||||
// Capture console events with [EVENT] prefix
|
||||
function captureEvents(page: Page): string[] {
|
||||
|
|
@ -68,93 +67,70 @@ test.describe('wxWidgets WASM - Diagnostics', () => {
|
|||
|
||||
await page.goto('/minimal_test.html');
|
||||
|
||||
// Screenshot 1: During loading
|
||||
await page.screenshot({ path: 'test-results/01-loading.png', fullPage: true });
|
||||
|
||||
// Wait for canvas
|
||||
try {
|
||||
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:', testLogger.consoleLogs);
|
||||
console.log('Errors:', testLogger.errors);
|
||||
throw e;
|
||||
}
|
||||
|
||||
await page.waitForTimeout(1000); // Let it settle
|
||||
// Deterministic app readiness: canvas visible + wx registry populated.
|
||||
// Fails loudly (replaces the try/catch waitForSelector + 1s settle).
|
||||
await waitForWxApp(page);
|
||||
|
||||
const canvas = page.locator('#canvas');
|
||||
const box = await canvas.boundingBox();
|
||||
if (!box) throw new Error('Canvas not found');
|
||||
|
||||
// Screenshot after load
|
||||
await page.screenshot({ path: 'test-results/03-after-load.png', fullPage: true });
|
||||
await stableShot(page, '03-after-load.png', { fullPage: true });
|
||||
|
||||
// === TAB 1: Controls ===
|
||||
// Click Controls tab using element registry
|
||||
await clickTab(page, 'Controls');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Click "Click Me" button using element registry
|
||||
await clickByLabel(page, 'Click Me');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Click "Toggle" button using element registry
|
||||
await clickByLabel(page, 'Toggle');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Click checkbox "Enable feature" using element registry
|
||||
await clickByLabel(page, 'Enable feature');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Click radio button "Option B" using element registry
|
||||
await clickByLabel(page, 'Option B');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Click radio button "Option C" using element registry
|
||||
await clickByLabel(page, 'Option C');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Interact with slider using element tracking
|
||||
await clickSlider(page);
|
||||
await page.waitForTimeout(200);
|
||||
// Drag slider to 80% position
|
||||
await dragSliderTo(page, 0.8);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/04-controls-tab.png', fullPage: true });
|
||||
await stableShot(page, '04-controls-tab.png', { fullPage: true });
|
||||
|
||||
// === TAB 2: Text Input ===
|
||||
// Click Text Input tab using element registry
|
||||
await clickTab(page, 'Text Input');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/05-text-input-tab.png', fullPage: true });
|
||||
await stableShot(page, '05-text-input-tab.png', { fullPage: true });
|
||||
|
||||
// Click in single-line text field using element tracking
|
||||
const singleLine = await findSingleLineTextCtrl(page);
|
||||
expect(singleLine, 'Single-line text control should be tracked').not.toBeNull();
|
||||
await page.mouse.click(singleLine!.centerX, singleLine!.centerY);
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: let focus land before typing (no registry/console observable for focus commit)
|
||||
await page.keyboard.type('Hello World');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Click multiline text area using element tracking
|
||||
const multiLine = await findMultiLineTextCtrl(page);
|
||||
expect(multiLine, 'Multi-line text control should be tracked').not.toBeNull();
|
||||
await page.mouse.click(multiLine!.centerX, multiLine!.centerY);
|
||||
await page.waitForTimeout(200);
|
||||
await page.waitForTimeout(200); // eslint-disable-line -- documented interaction dwell: let focus land before typing (no registry/console observable for focus commit)
|
||||
await page.keyboard.type('Line 1\nLine 2\nLine 3');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/06-text-input-typed.png', fullPage: true });
|
||||
await stableShot(page, '06-text-input-typed.png', { fullPage: true });
|
||||
|
||||
// === TAB 3: Drawing ===
|
||||
// Click Drawing tab using element registry
|
||||
await clickTab(page, 'Drawing');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/07-drawing-tab.png', fullPage: true });
|
||||
await stableShot(page, '07-drawing-tab.png', { fullPage: true });
|
||||
|
||||
// Draw on the canvas - multiple strokes
|
||||
for (let i = 0; i < 3; i++) {
|
||||
|
|
@ -164,25 +140,22 @@ test.describe('wxWidgets WASM - Diagnostics', () => {
|
|||
await page.mouse.down();
|
||||
await page.mouse.move(box.x + startX + 100, box.y + startY + 50, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
await page.screenshot({ path: 'test-results/08-drawing-done.png', fullPage: true });
|
||||
await stableShot(page, '08-drawing-done.png', { fullPage: true });
|
||||
|
||||
// === TAB 4: Lists ===
|
||||
// Click Lists tab using element registry
|
||||
await clickTab(page, 'Lists');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/09-lists-tab.png', fullPage: true });
|
||||
await stableShot(page, '09-lists-tab.png', { fullPage: true });
|
||||
|
||||
// Click on listbox items using element registry (wxListBox uses listboxitem type)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await clickListboxItemByIndex(page, i);
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
await page.screenshot({ path: 'test-results/10-lists-clicked.png', fullPage: true });
|
||||
await stableShot(page, '10-lists-clicked.png', { fullPage: true });
|
||||
|
||||
// Test the wxChoice dropdown. It renders as a native <select> (the
|
||||
// browser owns the popup, so it cannot be driven by coordinate
|
||||
|
|
@ -194,30 +167,25 @@ test.describe('wxWidgets WASM - Diagnostics', () => {
|
|||
expect(await choice.count(), 'Choice <select> should exist').toBe(1);
|
||||
|
||||
await choice.selectOption({ label: 'Green' });
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/10b-choice-selected.png', fullPage: true });
|
||||
await stableShot(page, '10b-choice-selected.png', { fullPage: true });
|
||||
|
||||
expect(await choice.inputValue(), 'Choice should now be Green').toBe('Green');
|
||||
|
||||
// === Menu interaction ===
|
||||
// Click File menu using element registry
|
||||
await clickMenuBarItem(page, 'File');
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/11-file-menu.png', fullPage: true });
|
||||
await stableShot(page, '11-file-menu.png', { fullPage: true });
|
||||
|
||||
// Close menu with Escape
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Click Help menu using element registry
|
||||
await clickMenuBarItem(page, 'Help');
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/12-help-menu.png', fullPage: true });
|
||||
await stableShot(page, '12-help-menu.png', { fullPage: true });
|
||||
|
||||
// Close menu with Escape
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// === Rapid interactions ===
|
||||
// Click rapidly on various areas
|
||||
|
|
@ -225,17 +193,16 @@ test.describe('wxWidgets WASM - Diagnostics', () => {
|
|||
const x = 50 + (i * 37) % 400;
|
||||
const y = 50 + (i * 23) % 350;
|
||||
await page.mouse.click(box.x + x, box.y + y);
|
||||
await page.waitForTimeout(50);
|
||||
}
|
||||
|
||||
await page.screenshot({ path: 'test-results/13-final.png', fullPage: true });
|
||||
await stableShot(page, '13-final.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('wxWidgets WASM - Loading', () => {
|
||||
test('app loads without JavaScript errors', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Filter out known non-critical errors
|
||||
const criticalErrors = testLogger.errors.filter(e =>
|
||||
|
|
@ -274,7 +241,7 @@ test.describe('wxWidgets WASM - Loading', () => {
|
|||
|
||||
test('WASM module initializes successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const moduleExists = await page.evaluate(() => {
|
||||
return typeof (window as any).Module !== 'undefined';
|
||||
|
|
@ -285,12 +252,15 @@ test.describe('wxWidgets WASM - Loading', () => {
|
|||
|
||||
test('application started event is logged', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Wait for the startup event to be logged
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
expect(testLogger.consoleLogs.some(e => e.includes('Application started'))).toBe(true);
|
||||
// Deterministically wait for the startup event to be logged (same assertion,
|
||||
// no blind settle).
|
||||
await expect
|
||||
.poll(() => testLogger.consoleLogs.some(e => e.includes('Application started')), {
|
||||
message: 'Application started event should be logged'
|
||||
})
|
||||
.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -298,20 +268,21 @@ test.describe('wxWidgets WASM - Canvas Interaction', () => {
|
|||
test('canvas receives click events', async ({ page, testLogger }) => {
|
||||
const events = captureEvents(page);
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Click on the canvas (somewhere in the middle)
|
||||
await clickCanvas(page, 320, 240);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Should have received the startup event at minimum
|
||||
// Additional click events may or may not be logged depending on what's clicked
|
||||
expect(events.length).toBeGreaterThanOrEqual(1);
|
||||
await expect
|
||||
.poll(() => events.length, { message: 'At least one [EVENT] should be logged' })
|
||||
.toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test('canvas receives keyboard events', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Focus the canvas
|
||||
const canvas = page.locator(MAIN_CANVAS);
|
||||
|
|
@ -319,7 +290,7 @@ test.describe('wxWidgets WASM - Canvas Interaction', () => {
|
|||
|
||||
// Type some text
|
||||
await page.keyboard.type('test');
|
||||
await page.waitForTimeout(300);
|
||||
await page.waitForTimeout(300); // eslint-disable-line -- documented interaction dwell: keystroke commit; test asserts no errors surface during keyboard input, no observable to poll
|
||||
|
||||
// The test passes if no errors occur during keyboard input
|
||||
});
|
||||
|
|
@ -329,27 +300,33 @@ test.describe('wxWidgets WASM - Mouse Drawing', () => {
|
|||
test('mouse drag creates drawing stroke', async ({ page, testLogger }) => {
|
||||
const events = captureEvents(page);
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// First, switch to the Drawing tab (Tab 3)
|
||||
// Use element registry to click the tab reliably
|
||||
await clickTab(page, 'Drawing');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check if we're on the Drawing tab
|
||||
const onDrawingTab = events.some(e => e.includes('Tab changed to: Drawing'));
|
||||
// Deterministically confirm we're on the Drawing tab before drawing
|
||||
// (replaces the blind 500ms sleep + the defensive `if (onDrawingTab)`
|
||||
// guard that used to silently skip the assertion).
|
||||
await expect
|
||||
.poll(() => events.some(e => e.includes('Tab changed to: Drawing')), {
|
||||
message: 'Should switch to the Drawing tab'
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
if (onDrawingTab) {
|
||||
// Now do a mouse drag in the drawing area
|
||||
await dragCanvas(page, 100, 150, 300, 250);
|
||||
await page.waitForTimeout(300);
|
||||
// Now do a mouse drag in the drawing area
|
||||
await dragCanvas(page, 100, 150, 300, 250);
|
||||
|
||||
// Should have mouse down and mouse up events
|
||||
const hasMouseDown = events.some(e => e.includes('Mouse down'));
|
||||
const hasMouseUp = events.some(e => e.includes('Mouse up'));
|
||||
|
||||
expect(hasMouseDown || hasMouseUp).toBe(true);
|
||||
}
|
||||
// Should have mouse down and mouse up events
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
events.some(e => e.includes('Mouse down')) ||
|
||||
events.some(e => e.includes('Mouse up')),
|
||||
{ message: 'Drag should produce a mouse down or mouse up event' }
|
||||
)
|
||||
.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -357,40 +334,38 @@ test.describe('wxWidgets WASM - Event Logging', () => {
|
|||
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);
|
||||
|
||||
// Wait for startup
|
||||
await page.waitForTimeout(500);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Should have at least the startup event
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
await expect
|
||||
.poll(() => events.length, { message: 'Startup event should be logged' })
|
||||
.toBeGreaterThan(0);
|
||||
expect(events[0]).toContain('Application started');
|
||||
});
|
||||
|
||||
test('multiple interactions produce multiple log entries', async ({ page, testLogger }) => {
|
||||
const events = captureEvents(page);
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const initialCount = events.length;
|
||||
|
||||
// Perform multiple clicks
|
||||
await clickCanvas(page, 100, 100);
|
||||
await page.waitForTimeout(200);
|
||||
await clickCanvas(page, 200, 100);
|
||||
await page.waitForTimeout(200);
|
||||
await clickCanvas(page, 300, 100);
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Should have more events now
|
||||
expect(events.length).toBeGreaterThanOrEqual(initialCount);
|
||||
await expect
|
||||
.poll(() => events.length, { message: 'Interactions should not lose log entries' })
|
||||
.toBeGreaterThanOrEqual(initialCount);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('wxWidgets WASM - Visual Rendering', () => {
|
||||
test('frame renders with visible content', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Take a screenshot for visual verification
|
||||
const screenshot = await page.screenshot();
|
||||
|
|
@ -406,7 +381,7 @@ test.describe('wxWidgets WASM - Visual Rendering', () => {
|
|||
|
||||
test('window has reasonable dimensions', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const canvas = page.locator(MAIN_CANVAS);
|
||||
const box = await canvas.boundingBox();
|
||||
|
|
@ -421,12 +396,11 @@ test.describe('wxWidgets WASM - Visual Rendering', () => {
|
|||
test.describe('wxWidgets WASM - Stability', () => {
|
||||
test('app remains stable after multiple interactions', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Perform many rapid interactions
|
||||
for (let i = 0; i < 20; i++) {
|
||||
await clickCanvas(page, 100 + i * 20, 100 + i * 10);
|
||||
await page.waitForTimeout(50);
|
||||
}
|
||||
|
||||
// App should still be responsive
|
||||
|
|
@ -444,7 +418,7 @@ test.describe('wxWidgets WASM - Stability', () => {
|
|||
|
||||
test('app handles rapid mouse movements', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
await waitForWxApp(page);
|
||||
|
||||
const canvas = page.locator(MAIN_CANVAS);
|
||||
const box = await canvas.boundingBox();
|
||||
|
|
|
|||
|
|
@ -1,25 +1,28 @@
|
|||
// wxXmlDocument Tests - XML parsing like KiCad's config and project files
|
||||
import { test, expect, tryLoadApp } from './utils/fixtures';
|
||||
//
|
||||
// Determinism: no waitForTimeout. Readiness via waitForWxApp (canvas visible + registry
|
||||
// populated, fails loudly) replaces the tryLoadApp + expect(loaded).toBe(true) dance. Each
|
||||
// test screenshots a STATIC loaded state, so the 500ms settle sleeps are dropped and the
|
||||
// screenshots become stableShot (its stabilization is the settle). Same base names.
|
||||
import { test, expect, waitForWxApp } from './utils/fixtures';
|
||||
import { stableShot } from './utils/element-tracker';
|
||||
|
||||
test.describe('wxXmlDocument Tests', () => {
|
||||
|
||||
test('XML test app loads successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/xml/xml_test.html');
|
||||
const loaded = await tryLoadApp(page, 30000);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.screenshot({ path: 'test-results/xml-01-loaded.png', fullPage: true });
|
||||
await stableShot(page, 'xml-01-loaded.png', { fullPage: true });
|
||||
|
||||
expect(loaded, 'XML app should load').toBe(true);
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Sample XML input exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/xml/xml_test.html');
|
||||
const loaded = await tryLoadApp(page, 30000);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/xml-02-input.png', fullPage: true });
|
||||
await stableShot(page, 'xml-02-input.png', { fullPage: true });
|
||||
|
||||
// App loaded successfully - verify no errors
|
||||
expect(testLogger.errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||
|
|
@ -27,46 +30,30 @@ test.describe('wxXmlDocument Tests', () => {
|
|||
|
||||
test('Parse button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/xml/xml_test.html');
|
||||
const loaded = await tryLoadApp(page, 30000);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/xml-03-parse.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Parse button should exist').toBe(true);
|
||||
await stableShot(page, 'xml-03-parse.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Traverse button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/xml/xml_test.html');
|
||||
const loaded = await tryLoadApp(page, 30000);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/xml-04-traverse.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Traverse button should exist').toBe(true);
|
||||
await stableShot(page, 'xml-04-traverse.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Create XML button exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/xml/xml_test.html');
|
||||
const loaded = await tryLoadApp(page, 30000);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/xml-05-create.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Create XML button should exist').toBe(true);
|
||||
await stableShot(page, 'xml-05-create.png', { fullPage: true });
|
||||
});
|
||||
|
||||
test('Results output panel exists', async ({ page, testLogger }) => {
|
||||
await page.goto('/standalone/xml/xml_test.html');
|
||||
const loaded = await tryLoadApp(page, 30000);
|
||||
expect(loaded, 'App should load').toBe(true);
|
||||
await waitForWxApp(page);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: 'test-results/xml-06-results.png', fullPage: true });
|
||||
|
||||
expect(loaded, 'Results panel should exist').toBe(true);
|
||||
await stableShot(page, 'xml-06-results.png', { fullPage: true });
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue