more cleaning up
This commit is contained in:
parent
2c7d72a699
commit
560398fefb
110 changed files with 931 additions and 2645 deletions
190
e2e/app.spec.ts
190
e2e/app.spec.ts
|
|
@ -1,7 +1,5 @@
|
|||
import { expect, test, type Page } from '@playwright/test';
|
||||
|
||||
type WebGpuFailureMode = 'adapter-null' | 'adapter-rejects' | 'device-rejects';
|
||||
|
||||
const disableWebGpu = async (page: Page) => {
|
||||
await page.addInitScript(() => {
|
||||
Object.defineProperty(navigator, 'gpu', {
|
||||
|
|
@ -11,54 +9,6 @@ const disableWebGpu = async (page: Page) => {
|
|||
});
|
||||
};
|
||||
|
||||
const emulateWebGpuFailure = async (page: Page, mode: WebGpuFailureMode) => {
|
||||
await page.addInitScript((failureMode) => {
|
||||
const limits = {
|
||||
maxBufferSize: 256 * 1024 * 1024,
|
||||
maxComputeWorkgroupsPerDimension: 65_535,
|
||||
maxStorageBufferBindingSize: 128 * 1024 * 1024,
|
||||
};
|
||||
const adapter = {
|
||||
features: new Set(),
|
||||
info: {
|
||||
architecture: 'test',
|
||||
description: 'Playwright fake adapter',
|
||||
device: 'test-device',
|
||||
isFallbackAdapter: false,
|
||||
subgroupMaxSize: 0,
|
||||
subgroupMinSize: 0,
|
||||
vendor: 'test-vendor',
|
||||
},
|
||||
limits,
|
||||
requestDevice: async () => {
|
||||
if (failureMode === 'device-rejects') {
|
||||
throw new Error('Playwright fake device failure');
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
};
|
||||
|
||||
Object.defineProperty(navigator, 'gpu', {
|
||||
configurable: true,
|
||||
value: {
|
||||
getPreferredCanvasFormat: () => 'rgba8unorm',
|
||||
requestAdapter: async () => {
|
||||
if (failureMode === 'adapter-null') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (failureMode === 'adapter-rejects') {
|
||||
throw new Error('Playwright fake adapter failure');
|
||||
}
|
||||
|
||||
return adapter;
|
||||
},
|
||||
},
|
||||
});
|
||||
}, mode);
|
||||
};
|
||||
|
||||
const getFirstSwatchColor = (page: Page) =>
|
||||
page
|
||||
.locator('.color-swatch')
|
||||
|
|
@ -71,6 +21,18 @@ const getGardenBackground = (page: Page) =>
|
|||
);
|
||||
|
||||
test('loads the app shell and WebGPU fallback in Chromium', async ({ page }) => {
|
||||
const browserFailures: Array<string> = [];
|
||||
|
||||
page.on('requestfailed', (request) => {
|
||||
const failure = request.failure();
|
||||
browserFailures.push(`${request.method()} ${request.url()} ${failure?.errorText}`);
|
||||
});
|
||||
page.on('response', (response) => {
|
||||
if (response.status() >= 400) {
|
||||
browserFailures.push(`${response.status()} ${response.url()}`);
|
||||
}
|
||||
});
|
||||
|
||||
await disableWebGpu(page);
|
||||
|
||||
await page.goto('/');
|
||||
|
|
@ -85,6 +47,7 @@ test('loads the app shell and WebGPU fallback in Chromium', async ({ page }) =>
|
|||
|
||||
await page.getByRole('button', { name: 'About' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Fleeting Garden' })).toBeVisible();
|
||||
expect(browserFailures).toEqual([]);
|
||||
});
|
||||
|
||||
test('keeps fallback controls interactive and accessible', async ({ page }) => {
|
||||
|
|
@ -106,13 +69,21 @@ test('keeps fallback controls interactive and accessible', async ({ page }) => {
|
|||
await expect(aboutPanel).toHaveAttribute('aria-hidden', 'true');
|
||||
await expect(aboutPanel).toHaveAttribute('inert', '');
|
||||
|
||||
const settingsButton = page.getByRole('button', { name: 'Show config overlay' });
|
||||
const settingsButton = page.locator('button.settings');
|
||||
await expect(settingsButton).toHaveAttribute('aria-label', 'Show config overlay');
|
||||
await expect(settingsButton).toHaveAttribute('aria-expanded', 'false');
|
||||
await settingsButton.click();
|
||||
await expect(page.getByRole('button', { name: 'Hide config overlay' })).toHaveAttribute(
|
||||
'aria-expanded',
|
||||
'true'
|
||||
);
|
||||
await expect(settingsButton).toHaveAttribute('aria-expanded', 'true');
|
||||
await expect(settingsButton).toHaveAttribute('aria-label', 'Hide config overlay');
|
||||
await expect(page.locator('.config-pane')).toBeVisible();
|
||||
await expect(page.locator('.config-pane')).toContainText('Runtime');
|
||||
await expect(page.locator('.color-reaction-matrix')).toBeVisible();
|
||||
|
||||
const colorReaction = page.getByLabel('Color 1 agents reacting to color 2');
|
||||
await colorReaction.selectOption('-1');
|
||||
await expect(colorReaction).toHaveValue('-1');
|
||||
await settingsButton.click();
|
||||
await expect(settingsButton).toHaveAttribute('aria-expanded', 'false');
|
||||
|
||||
const soundButton = page.locator('button.sound');
|
||||
await expect(soundButton).toHaveAttribute('aria-pressed', 'false');
|
||||
|
|
@ -146,98 +117,35 @@ test('keeps fallback controls interactive and accessible', async ({ page }) => {
|
|||
await expect(page.locator('.mirror-segment-control')).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
(
|
||||
[
|
||||
{
|
||||
expectedCode: 'webgpu-adapter-unavailable',
|
||||
expectedMessage:
|
||||
'WebGPU is available, but this browser could not provide a compatible GPU adapter.',
|
||||
mode: 'adapter-null',
|
||||
},
|
||||
{
|
||||
expectedCode: 'webgpu-adapter-unavailable',
|
||||
expectedMessage: 'Could not request a WebGPU adapter.',
|
||||
mode: 'adapter-rejects',
|
||||
},
|
||||
{
|
||||
expectedCode: 'webgpu-device-unavailable',
|
||||
expectedMessage: 'Could not create a WebGPU device for this adapter.',
|
||||
mode: 'device-rejects',
|
||||
},
|
||||
] satisfies Array<{
|
||||
expectedCode: string;
|
||||
expectedMessage: string;
|
||||
mode: WebGpuFailureMode;
|
||||
}>
|
||||
).forEach(({ expectedCode, expectedMessage, mode }) => {
|
||||
test(`reports ${mode} startup failures without leaving the shell loading`, async ({
|
||||
page,
|
||||
}) => {
|
||||
await emulateWebGpuFailure(page, mode);
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page.locator('body')).not.toHaveClass(/is-loading/);
|
||||
await expect(page.getByRole('toolbar', { name: 'Garden toolbar' })).toBeVisible();
|
||||
await expect(page.getByRole('alert')).toContainText(expectedMessage);
|
||||
await expect(page.getByRole('alert')).toContainText(expectedCode);
|
||||
});
|
||||
});
|
||||
|
||||
test('serves the production bundle without missing browser assets', async ({ page }) => {
|
||||
const browserFailures: Array<string> = [];
|
||||
|
||||
page.on('requestfailed', (request) => {
|
||||
const failure = request.failure();
|
||||
browserFailures.push(`${request.method()} ${request.url()} ${failure?.errorText}`);
|
||||
});
|
||||
page.on('response', (response) => {
|
||||
if (response.status() >= 400) {
|
||||
browserFailures.push(`${response.status()} ${response.url()}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('keeps the fallback shell usable on mobile', async ({ page }) => {
|
||||
await page.setViewportSize({ height: 844, width: 390 });
|
||||
await disableWebGpu(page);
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.locator('body')).not.toHaveClass(/is-loading/);
|
||||
|
||||
expect(browserFailures).toEqual([]);
|
||||
});
|
||||
const canvasBox = await page
|
||||
.getByRole('img', { name: 'Interactive generative garden canvas' })
|
||||
.boundingBox();
|
||||
expect(canvasBox?.width).toBeGreaterThan(0);
|
||||
expect(canvasBox?.height).toBeGreaterThan(0);
|
||||
await expect(page.getByRole('toolbar', { name: 'Garden toolbar' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'About' })).toBeVisible();
|
||||
await expect(page.getByRole('alert')).toContainText('Fleeting Garden needs WebGPU');
|
||||
|
||||
[
|
||||
{ height: 720, name: 'desktop', width: 1280 },
|
||||
{ height: 844, name: 'mobile', width: 390 },
|
||||
].forEach(({ height, name, width }) => {
|
||||
test(`keeps the fallback shell usable on ${name}`, async ({ page }) => {
|
||||
await page.setViewportSize({ height, width });
|
||||
await disableWebGpu(page);
|
||||
const aboutButtonReceivesPointer = await page
|
||||
.getByRole('button', { name: 'About' })
|
||||
.evaluate((button) => {
|
||||
const rect = button.getBoundingClientRect();
|
||||
const target = document.elementFromPoint(
|
||||
rect.left + rect.width / 2,
|
||||
rect.top + rect.height / 2
|
||||
);
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.locator('body')).not.toHaveClass(/is-loading/);
|
||||
return button === target || button.contains(target);
|
||||
});
|
||||
|
||||
const canvasBox = await page
|
||||
.getByRole('img', { name: 'Interactive generative garden canvas' })
|
||||
.boundingBox();
|
||||
expect(canvasBox?.width).toBeGreaterThan(0);
|
||||
expect(canvasBox?.height).toBeGreaterThan(0);
|
||||
await expect(page.getByRole('toolbar', { name: 'Garden toolbar' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'About' })).toBeVisible();
|
||||
await expect(page.getByRole('alert')).toContainText('Fleeting Garden needs WebGPU');
|
||||
|
||||
const aboutButtonReceivesPointer = await page
|
||||
.getByRole('button', { name: 'About' })
|
||||
.evaluate((button) => {
|
||||
const rect = button.getBoundingClientRect();
|
||||
const target = document.elementFromPoint(
|
||||
rect.left + rect.width / 2,
|
||||
rect.top + rect.height / 2
|
||||
);
|
||||
|
||||
return button === target || button.contains(target);
|
||||
});
|
||||
|
||||
expect(aboutButtonReceivesPointer).toBe(true);
|
||||
});
|
||||
expect(aboutButtonReceivesPointer).toBe(true);
|
||||
});
|
||||
|
||||
test('hides the bottom dock after the cursor leaves fullscreen controls', async ({
|
||||
|
|
@ -305,5 +213,5 @@ test('keeps the bottom dock visible in mobile fullscreen', async ({ page }) => {
|
|||
await page.waitForTimeout(5200);
|
||||
|
||||
await expect(page.locator('aside.control-dock')).not.toHaveClass(/menu-hidden/);
|
||||
await expect(page.getByRole('button', { name: 'Show config overlay' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'About' })).toBeVisible();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue