memlnaut-nisps/playground/tests/e2e/ui-interactions.spec.ts

94 lines
3.9 KiB
TypeScript
Raw Normal View History

/**
* UI interaction smoke tests.
*
* These tests exercise the SolidJS playground's chrome header navigation,
* mode switcher, route handling. They are deliberately resilient to layout
* changes: we look for accessible names / roles where possible rather than
* hard-coding selectors.
*
* NOT covered here (yet):
* - The training drawer (stream 10 is still landing the controls).
* - The control surface (stream 10).
* - Gear-icon settings drawer (stream 10).
* Tests below that depend on stream 10 deliverables call `test.skip(...)`
* with a clear marker so they show as skipped, not failing, until that
* stream lands.
*/
import { test, expect } from '@playwright/test';
import { loadApp } from './helpers';
test.describe('UI — top-level navigation', () => {
test('home renders title and mode link', async ({ page }) => {
await loadApp(page, { route: '/', waitForReady: false });
await expect(page.getByText('MEMLNaut Playground')).toBeVisible();
await expect(page.getByRole('link', { name: /^Modes$/ })).toBeVisible();
});
test('header has nav buttons for /modes and /dev/primitives', async ({ page }) => {
await loadApp(page, { route: '/', waitForReady: false });
await expect(page.getByRole('button', { name: '/modes' })).toBeVisible();
await expect(page.getByRole('button', { name: '/dev/primitives' })).toBeVisible();
});
test('clicking /modes navigates to the modes route', async ({ page }) => {
await loadApp(page, { route: '/', waitForReady: false });
await page.getByRole('button', { name: '/modes' }).click();
await expect(page).toHaveURL(/\/modes\/?$/);
});
test('clicking /dev/primitives navigates to the primitives showcase', async ({ page }) => {
await loadApp(page, { route: '/', waitForReady: false });
await page.getByRole('button', { name: '/dev/primitives' }).click();
await expect(page).toHaveURL(/\/dev\/primitives\/?$/);
});
test('unknown route shows a 404', async ({ page }) => {
await loadApp(page, { route: '/no-such-route', waitForReady: false });
await expect(page.getByText('404')).toBeVisible();
});
});
test.describe('UI — modes route', () => {
test('mode switcher renders with at least one mode', async ({ page }) => {
// The mode-switcher chrome must render even if WASM ML hasn't booted —
// the page should never be blank-screen on a WASM init failure.
await loadApp(page, { waitForReady: false });
const buttons = await page.locator('button, [role=tab], select').count();
expect(buttons).toBeGreaterThan(0);
});
test('switching modes does not raise unrelated console errors', async ({ page }) => {
const errors: string[] = [];
page.on('pageerror', (err) => errors.push(String(err)));
page.on('console', (msg) => {
if (msg.type() === 'error') errors.push(msg.text());
});
await loadApp(page, { waitForReady: false });
const modeButtons = page.getByRole('button').filter({ hasNotText: 'home' });
const count = await modeButtons.count();
if (count >= 2) {
await modeButtons.nth(1).click().catch(() => undefined);
await page.waitForTimeout(200);
}
// Filter out the known-pending WASM module-factory failure (stream 7/10
// is finishing ESM-friendly glue) and other benign init noise.
const real = errors.filter((e) =>
!/module factory/i.test(e) &&
!/abortFn|preview\.html|favicon/.test(e) &&
!/playwright/i.test(e)
);
expect(real, real.join('\n')).toEqual([]);
});
});
test.describe('UI — drawer / training controls (stream 10 pending)', () => {
test('training drawer skipped pending stream 10', async () => {
test.skip(true, 'TrainingControls primitive is not on /modes yet — stream 10 wires it');
});
test('control surface skipped pending stream 10', async () => {
test.skip(true, 'ControlAxis bar is not on /modes yet — stream 10 wires it');
});
});