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>
175 lines
6.1 KiB
TypeScript
175 lines
6.1 KiB
TypeScript
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('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');
|
|
});
|
|
|
|
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,
|
|
});
|
|
});
|
|
});
|