fix: stabilize public demo and align sorter twins

Document arhipovdan.ru DNS/TLS blocker, add production smoke and GPU
perf tooling, unify twin layout, and expand visual regression coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Даня Архипов
2026-07-15 18:00:25 +00:00
parent b5985f33bb
commit 16e7930304
43 changed files with 1696 additions and 227 deletions

View File

@@ -0,0 +1,44 @@
import { test, expect } from '@playwright/test';
/**
* Production smoke — run manually:
* PLAYWRIGHT_BASE_URL=https://arhipovdan.ru/ npm run test:e2e:production
* or against the working tunnel / local prod:
* PLAYWRIGHT_BASE_URL=http://127.0.0.1:3100 npm run test:e2e:production
*/
test.describe('production smoke @production', () => {
test('serves current app shell and demo controls', async ({ page }) => {
const pageErrors: string[] = [];
const failed: string[] = [];
page.on('pageerror', (e) => pageErrors.push(String(e)));
page.on('response', (r) => {
if (r.status() >= 400 && r.url().includes(new URL(page.url()).host)) {
failed.push(`${r.status()} ${r.url()}`);
}
});
await page.goto('/');
const html = await page.content();
const bundle = html.match(/index-[A-Za-z0-9_-]+\.js/)?.[0] ?? '';
expect(bundle).toMatch(/^index-/);
expect(bundle).not.toBe('index-CCdZzxPJ.js');
await expect(page.locator('canvas')).toBeVisible({ timeout: 60_000 });
await expect(page.getByTestId('demo-play')).toBeVisible();
await expect(page.getByTestId('demo-case-9')).toBeVisible();
await page.getByTestId('demo-play').click();
await page.waitForTimeout(800);
await page.getByTestId('demo-case-8').click();
await expect(page.getByTestId('demo-case-label')).toHaveText('9/10');
await page.goto('/details');
await expect(page.locator('#root')).toBeVisible();
await page.reload();
await expect(page.locator('#root')).toBeVisible();
expect(pageErrors, pageErrors.join('\n')).toEqual([]);
const critical = failed.filter((f) => !f.includes('favicon'));
expect(critical, critical.join('\n')).toEqual([]);
});
});

View File

@@ -1,20 +1,174 @@
import { test, expect } from '@playwright/test';
import { test, expect, type Page } from '@playwright/test';
test.setTimeout(90_000);
async function waitScene(page: Page) {
await page.goto('/');
await expect(page.locator('canvas')).toBeVisible({ timeout: 60_000 });
await page.evaluate(() => document.fonts.ready);
await expect(page.getByTestId('demo-hud')).toBeVisible();
// Demo auto-starts as running — pin speed early
await page.getByTestId('demo-speed-2').click();
}
async function dismissFinishedIfNeeded(page: Page) {
const finished = page.getByTestId('demo-finished');
if (await finished.isVisible().catch(() => false)) {
await finished.getByRole('button', { name: /Replay/i }).click();
await expect(finished).toHaveCount(0);
}
}
async function jumpCase(page: Page, index: number) {
await dismissFinishedIfNeeded(page);
await page.getByTestId(`demo-case-${index}`).click();
await expect(page.getByTestId('demo-case-label')).toHaveText(`${index + 1}/10`, {
timeout: 10_000,
});
}
async function ensureRunning(page: Page) {
await dismissFinishedIfNeeded(page);
const pause = page.getByTestId('demo-pause');
if (await pause.isVisible().catch(() => false)) return;
await page.getByTestId('demo-play').click();
}
async function snapHud(page: Page, name: string) {
await dismissFinishedIfNeeded(page);
const hud = page.getByTestId('demo-hud');
await expect(hud).toBeVisible();
await expect(hud).toHaveScreenshot(name, {
threshold: 0.35,
maxDiffPixelRatio: 0.08,
});
}
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');
test('01 home idle HUD', async ({ page }) => {
await waitScene(page);
// Pause for a stable idle-ish HUD
const pause = page.getByTestId('demo-pause');
if (await pause.isVisible().catch(() => false)) {
await pause.click();
}
await expect(page.getByTestId('demo-case-label')).toHaveText('1/10');
await snapHud(page, '01-home-idle-hud.png');
});
// Snapshot HUD only — avoids flaky WebGL canvas pixels
await expect(hud).toHaveScreenshot('home-idle-hud.png', {
threshold: 0.3,
maxDiffPixelRatio: 0.05,
test('02 class B after seek case 0', async ({ page }) => {
await waitScene(page);
await jumpCase(page, 0);
await ensureRunning(page);
await expect(page.getByTestId('demo-category')).toHaveText('B', { timeout: 25_000 });
await page.getByTestId('demo-pause').click().catch(() => undefined);
await snapHud(page, '02-class-b-hud.png');
});
test('03 class C oversized case 2', async ({ page }) => {
await waitScene(page);
await jumpCase(page, 2);
await ensureRunning(page);
await expect(page.getByTestId('demo-category')).toHaveText('C', { timeout: 25_000 });
await page.getByTestId('demo-pause').click().catch(() => undefined);
await snapHud(page, '03-class-c-hud.png');
});
test('04 class D plate case 4', async ({ page }) => {
await waitScene(page);
await jumpCase(page, 4);
await ensureRunning(page);
await expect(page.getByTestId('demo-category')).toHaveText('D', { timeout: 25_000 });
await page.getByTestId('demo-pause').click().catch(() => undefined);
await snapHud(page, '04-class-d-hud.png');
});
test('05 jam fault HUD', async ({ page }) => {
await waitScene(page);
await jumpCase(page, 8);
await ensureRunning(page);
await expect
.poll(async () => {
const status = await page.getByTestId('demo-status').innerText();
const cmd = await page.getByTestId('demo-command').innerText();
return `${status} ${cmd}`;
}, { timeout: 30_000 })
.toMatch(/FAULT|JAM/i);
await page.getByTestId('demo-pause').click().catch(() => undefined);
await snapHud(page, '05-jam-fault-hud.png');
});
test('06 emergency stop HUD', async ({ page }) => {
await waitScene(page);
await jumpCase(page, 9);
await ensureRunning(page);
await expect
.poll(async () => {
const status = await page.getByTestId('demo-status').innerText();
const cmd = await page.getByTestId('demo-command').innerText();
return `${status} ${cmd}`;
}, { timeout: 30_000 })
.toMatch(/EMERGENCY/i);
await page.getByTestId('demo-pause').click().catch(() => undefined);
await snapHud(page, '06-emergency-hud.png');
});
test('07 presentation mode hides HUD', async ({ page }) => {
await waitScene(page);
await dismissFinishedIfNeeded(page);
await page.getByTestId('demo-presentation').click();
await expect(page.getByTestId('demo-hud')).toHaveCount(0);
await expect(page.locator('.main-page')).toHaveScreenshot('07-presentation-mode.png', {
threshold: 0.4,
maxDiffPixelRatio: 0.12,
animations: 'disabled',
});
});
test('08 details engineering page', async ({ page }) => {
await page.goto('/details');
await expect(page.locator('#root')).toBeVisible();
await page.waitForTimeout(1500);
await expect(page).toHaveScreenshot('08-details-page.png', {
threshold: 0.4,
maxDiffPixelRatio: 0.12,
fullPage: true,
animations: 'disabled',
});
});
test('09 recovery after jam stop', async ({ page }) => {
await waitScene(page);
await jumpCase(page, 8);
await ensureRunning(page);
await expect
.poll(async () => {
const status = await page.getByTestId('demo-status').innerText();
const cmd = await page.getByTestId('demo-command').innerText();
return `${status} ${cmd}`;
}, { timeout: 30_000 })
.toMatch(/FAULT|JAM/i);
// Stop resets toward recovery
const stop = page.getByTestId('demo-stop');
if (await stop.isVisible().catch(() => false)) {
await stop.click({ force: true });
} else {
await dismissFinishedIfNeeded(page);
await page.getByTestId('demo-case-0').click();
}
await expect(page.getByTestId('demo-case-label')).toBeVisible();
await snapHud(page, '09-recovery-hud.png');
});
test('10 perf overlay engineering snapshot', async ({ page }) => {
await page.goto('/?perf=1&quality=demo');
await expect(page.locator('canvas')).toBeVisible({ timeout: 60_000 });
await page.waitForTimeout(2000);
const overlay = page.locator('.perf-overlay');
await expect(overlay).toBeVisible({ timeout: 15_000 });
await expect(overlay).toHaveScreenshot('10-perf-overlay.png', {
threshold: 0.4,
maxDiffPixelRatio: 0.15,
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB