Files
Portfolio/tests/browser/fault-matrix.spec.ts

118 lines
6.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from '../helpers/serve-dist';
import {
assertSemanticFaultOracle,
authorizedFaultUrl,
collectBrowserErrors,
missingTokenFaultUrl,
readFaultSnapshot,
readUnhandledRejections,
wrongTokenFaultUrl,
} from '../helpers/test-build-auth';
import { installSceneInstrumentation, readSceneInstrumentation } from '../helpers/scene-instrumentation';
import type { PortfolioFaultId } from '@/testing/fault-hooks';
const assertNoEscapedErrors = async (
page: Parameters<typeof readUnhandledRejections>[0],
pageErrors: readonly string[],
): Promise<void> => {
expect(pageErrors).toEqual([]);
expect(await readUnhandledRejections(page)).toEqual([]);
expect((await readSceneInstrumentation(page)).unhandledRejections).toEqual([]);
};
test.describe('authenticated fault-build boundary', () => {
test.beforeEach(async ({}, testInfo) => {
test.skip(testInfo.project.name !== 'desktop-chromium', 'The complete F1–F13 matrix runs once on current desktop Chromium.');
});
test('missing token exposes no fault API and leaves the portfolio operational', async ({ page, distUrl }) => {
await page.goto(missingTokenFaultUrl(distUrl, 'F1-scene-chunk'), { waitUntil: 'domcontentloaded' });
expect(await page.evaluate(() => '__PORTFOLIO_TEST_HOOKS__' in window)).toBe(false);
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running');
await assertSemanticFaultOracle(page);
});
test('wrong token exposes no fault API and leaves the portfolio operational', async ({ page, distUrl }) => {
await page.goto(wrongTokenFaultUrl(distUrl, 'F1-scene-chunk'), { waitUntil: 'domcontentloaded' });
expect(await page.evaluate(() => '__PORTFOLIO_TEST_HOOKS__' in window)).toBe(false);
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running');
await assertSemanticFaultOracle(page);
});
test('correct token exposes only the frozen named fault interface', async ({ page, distUrl }) => {
await page.goto(authorizedFaultUrl(distUrl, 'F9-hydration-failure'), { waitUntil: 'domcontentloaded' });
await expect.poll(() => page.evaluate(() => '__PORTFOLIO_TEST_HOOKS__' in window)).toBe(true);
const shape = await page.evaluate(() => {
const hooks = (window as unknown as { __PORTFOLIO_TEST_HOOKS__: object }).__PORTFOLIO_TEST_HOOKS__;
return { frozen: Object.isFrozen(hooks), keys: Object.keys(hooks).sort() };
});
expect(shape).toEqual({ frozen: true, keys: ['fault', 'snapshot', 'trigger', 'version'] });
expect((await readFaultSnapshot(page)).fault).toBe('F9-hydration-failure');
await expect(page.locator('.drive-controls')).toHaveCount(1);
await assertSemanticFaultOracle(page);
});
});
test.describe('scene failure matrix', () => {
test.beforeEach(async ({}, testInfo) => {
test.skip(testInfo.project.name !== 'desktop-chromium', 'The complete F1–F13 matrix runs once on current desktop Chromium.');
});
for (const fault of [
'F1-scene-chunk',
'F1-shader',
'F1-car',
'F1-texture',
] as const satisfies readonly PortfolioFaultId[]) {
test(`${fault} enters one static fallback without escaping an error`, async ({ page, distUrl }) => {
await installSceneInstrumentation(page);
const errors = await collectBrowserErrors(page);
await page.goto(authorizedFaultUrl(distUrl, fault), { waitUntil: 'domcontentloaded' });
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'static');
await expect(page.locator('.journey-world')).toHaveAttribute('data-static-fallback', 'true');
await expect(page.locator('.journey-poster')).toHaveCount(1);
await expect(page.locator('.journey-canvas')).toHaveCount(0);
await expect(page.locator('.drive-controls__status')).toHaveText('Visuals unavailable');
await assertSemanticFaultOracle(page);
const resources = await readSceneInstrumentation(page);
expect(resources.activeAnimationFrames).toBe(0);
expect(resources.maximumCanvasCount).toBeLessThanOrEqual(1);
expect((await readFaultSnapshot(page)).staticFallbackCount).toBe(1);
await assertNoEscapedErrors(page, errors.pageErrors);
});
}
test('F2 enforces the literal five-second first-frame deadline', async ({ page, distUrl }) => {
test.setTimeout(12_000);
await installSceneInstrumentation(page);
const errors = await collectBrowserErrors(page);
const startedAt = Date.now();
await page.goto(authorizedFaultUrl(distUrl, 'F2-first-frame-timeout'), { waitUntil: 'domcontentloaded' });
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'static', { timeout: 7_000 });
expect(Date.now() - startedAt).toBeGreaterThanOrEqual(4_800);
await expect(page.locator('.journey-world')).toHaveAttribute('data-static-fallback', 'true');
await expect(page.locator('.journey-canvas')).toHaveCount(0);
await assertSemanticFaultOracle(page);
const fault = await readFaultSnapshot(page);
expect(fault.events).toContain('first-frame-suppressed');
expect(fault.events).not.toContain('first-frame-deadline');
expect(fault.staticFallbackCount).toBe(1);
expect((await readSceneInstrumentation(page)).activeAnimationFrames).toBe(0);
await assertNoEscapedErrors(page, errors.pageErrors);
});
test('F3 contains a post-first-frame exception on the next frame', async ({ page, distUrl }) => {
await installSceneInstrumentation(page);
const errors = await collectBrowserErrors(page);
await page.goto(authorizedFaultUrl(distUrl, 'F3-post-first-frame-exception'), { waitUntil: 'domcontentloaded' });
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'static');
await expect(page.locator('.journey-world')).toHaveAttribute('data-static-fallback', 'true');
await expect(page.locator('.journey-canvas')).toHaveCount(0);
await assertSemanticFaultOracle(page);
expect((await readSceneInstrumentation(page)).activeAnimationFrames).toBe(0);
await assertNoEscapedErrors(page, errors.pageErrors);
});
});