feat: ship production deploy, Playwright e2e, and agent implementer
Add Docker atomic deploy tooling, CI, Playwright smoke/visual suite, shared industrial theme, physics invariants, quality-mode wiring, and a worktree-isolated agent implementer MVP with hard safety limits. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
38
e2e/controls.spec.ts
Normal file
38
e2e/controls.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('controls', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('canvas')).toBeVisible({ timeout: 60_000 });
|
||||
await expect(page.getByTestId('demo-hud')).toBeVisible();
|
||||
});
|
||||
|
||||
test('seek next/prev and jump cases, speed, presentation', async ({ page }) => {
|
||||
await expect(page.getByTestId('demo-case-label')).toHaveText('1/10');
|
||||
|
||||
await page.getByTestId('demo-next').click();
|
||||
await expect(page.getByTestId('demo-case-label')).toHaveText('2/10');
|
||||
|
||||
await page.getByTestId('demo-prev').click();
|
||||
await expect(page.getByTestId('demo-case-label')).toHaveText('1/10');
|
||||
|
||||
await page.getByTestId('demo-case-8').click();
|
||||
await expect(page.getByTestId('demo-case-label')).toHaveText('9/10');
|
||||
|
||||
await page.getByTestId('demo-case-0').click();
|
||||
await expect(page.getByTestId('demo-case-label')).toHaveText('1/10');
|
||||
|
||||
for (const speed of ['0.5', '1', '1.5', '2'] as const) {
|
||||
await page.getByTestId(`demo-speed-${speed}`).click();
|
||||
await expect(page.getByTestId(`demo-speed-${speed}`)).toHaveClass(/active/);
|
||||
}
|
||||
|
||||
await page.getByTestId('demo-presentation').click();
|
||||
await expect(page.getByTestId('demo-hud')).toBeHidden();
|
||||
await expect(page.locator('.presentation-exit')).toBeVisible();
|
||||
|
||||
await page.locator('.presentation-exit').click();
|
||||
await expect(page.getByTestId('demo-hud')).toBeVisible();
|
||||
await expect(page.getByTestId('demo-presentation')).toBeVisible();
|
||||
});
|
||||
});
|
||||
19
e2e/routes.spec.ts
Normal file
19
e2e/routes.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('routes', () => {
|
||||
test('/details opens, refresh stays, back to home', async ({ page }) => {
|
||||
await page.goto('/details');
|
||||
await expect(page).toHaveURL(/\/details\/?$/);
|
||||
await expect(page.getByRole('link', { name: /back to full-screen demo/i })).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
await expect(page).toHaveURL(/\/details\/?$/);
|
||||
await expect(page.getByRole('link', { name: /back to full-screen demo/i })).toBeVisible();
|
||||
|
||||
await page.getByRole('link', { name: /back to full-screen demo/i }).click();
|
||||
await expect(page).toHaveURL(/\/$/);
|
||||
await expect(page.getByTestId('demo-hud')).toBeVisible({ timeout: 60_000 });
|
||||
});
|
||||
});
|
||||
43
e2e/safety.spec.ts
Normal file
43
e2e/safety.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('safety', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('canvas')).toBeVisible({ timeout: 60_000 });
|
||||
await expect(page.getByTestId('demo-hud')).toBeVisible();
|
||||
// Speed up to reach fault phases sooner
|
||||
await page.getByTestId('demo-speed-2').click();
|
||||
});
|
||||
|
||||
test('jam case shows FAULT in status or command', async ({ page }) => {
|
||||
await page.getByTestId('demo-case-8').click();
|
||||
await expect(page.getByTestId('demo-case-label')).toHaveText('9/10');
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const status = (await page.getByTestId('demo-status').textContent()) ?? '';
|
||||
const command = (await page.getByTestId('demo-command').textContent()) ?? '';
|
||||
return `${status} ${command}`;
|
||||
},
|
||||
{ timeout: 25_000 },
|
||||
)
|
||||
.toMatch(/FAULT/i);
|
||||
});
|
||||
|
||||
test('emergency case shows EMERGENCY in status or command', async ({ page }) => {
|
||||
await page.getByTestId('demo-case-9').click();
|
||||
await expect(page.getByTestId('demo-case-label')).toHaveText('10/10');
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const status = (await page.getByTestId('demo-status').textContent()) ?? '';
|
||||
const command = (await page.getByTestId('demo-command').textContent()) ?? '';
|
||||
return `${status} ${command}`;
|
||||
},
|
||||
{ timeout: 25_000 },
|
||||
)
|
||||
.toMatch(/EMERGENCY/i);
|
||||
});
|
||||
});
|
||||
31
e2e/smoke.spec.ts
Normal file
31
e2e/smoke.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('smoke', () => {
|
||||
test('home opens, canvas loads, play works', async ({ page }) => {
|
||||
const pageErrors: Error[] = [];
|
||||
page.on('pageerror', (err) => pageErrors.push(err));
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const loading = page.locator('.three-loading');
|
||||
const canvas = page.locator('canvas');
|
||||
await expect(loading.or(canvas).first()).toBeVisible({ timeout: 30_000 });
|
||||
await expect(canvas).toBeVisible({ timeout: 60_000 });
|
||||
|
||||
await expect(page.getByTestId('demo-hud')).toBeVisible();
|
||||
await expect(page.getByTestId('demo-play')).toBeVisible();
|
||||
|
||||
await page.getByTestId('demo-play').click();
|
||||
await expect(page.getByTestId('demo-pause')).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByTestId('demo-status')).not.toHaveText('FINISHED');
|
||||
|
||||
const finished = page.getByTestId('demo-finished');
|
||||
if (await finished.isVisible().catch(() => false)) {
|
||||
await page.getByTestId('demo-play').click();
|
||||
await expect(finished).toBeHidden({ timeout: 10_000 });
|
||||
await expect(page.getByTestId('demo-pause')).toBeVisible({ timeout: 10_000 });
|
||||
}
|
||||
|
||||
expect(pageErrors, `pageerrors: ${pageErrors.map((e) => e.message).join('; ')}`).toEqual([]);
|
||||
});
|
||||
});
|
||||
20
e2e/visual.spec.ts
Normal file
20
e2e/visual.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('visual regression', () => {
|
||||
test('home idle HUD panel', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('canvas')).toBeVisible({ timeout: 60_000 });
|
||||
|
||||
const hud = page.getByTestId('demo-hud');
|
||||
await expect(hud).toBeVisible();
|
||||
// Wait for idle classification proof row / stable HUD text
|
||||
await expect(page.getByTestId('demo-command')).toHaveText('IDLE');
|
||||
await expect(page.getByTestId('demo-case-label')).toHaveText('1/10');
|
||||
|
||||
// Snapshot HUD only — avoids flaky WebGL canvas pixels
|
||||
await expect(hud).toHaveScreenshot('home-idle-hud.png', {
|
||||
threshold: 0.3,
|
||||
maxDiffPixelRatio: 0.05,
|
||||
});
|
||||
});
|
||||
});
|
||||
BIN
e2e/visual.spec.ts-snapshots/home-idle-hud-chromium-linux.png
Normal file
BIN
e2e/visual.spec.ts-snapshots/home-idle-hud-chromium-linux.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Reference in New Issue
Block a user