import { expect, test } from '../helpers/serve-dist'; import { assertSemanticFaultOracle, authorizedFaultUrl, collectBrowserErrors, readUnhandledRejections, triggerFaultAction, } from '../helpers/test-build-auth'; import { installSceneInstrumentation, readSceneInstrumentation } from '../helpers/scene-instrumentation'; test.beforeEach(async ({ page }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'Lifecycle faults run once on current desktop Chromium.'); await installSceneInstrumentation(page); }); const assertContained = async (page: import('@playwright/test').Page, pageErrors: readonly string[]): Promise => { expect(pageErrors).toEqual([]); expect(await readUnhandledRejections(page)).toEqual([]); const resources = await readSceneInstrumentation(page); expect(resources.unhandledRejections).toEqual([]); expect(resources.maximumCanvasCount).toBeLessThanOrEqual(1); expect(resources.activeAnimationFrames).toBeLessThanOrEqual(1); }; test('F4 restores one visible lost context before the five-second cutoff', async ({ page, distUrl }) => { const errors = await collectBrowserErrors(page); await page.goto(authorizedFaultUrl(distUrl, 'F4-context-loss-restore'), { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); const firstGeneration = await page.locator('.journey-world').getAttribute('data-scene-generation'); await triggerFaultAction(page, 'context-loss'); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'loading'); await triggerFaultAction(page, 'context-restore'); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await expect(page.locator('.journey-world')).not.toHaveAttribute('data-scene-generation', firstGeneration ?? ''); await expect(page.locator('.journey-canvas')).toHaveCount(1); await assertSemanticFaultOracle(page); await assertContained(page, errors.pageErrors); }); test('F5 defers a hidden restoration until the first visible lifecycle task', async ({ page, distUrl }) => { const errors = await collectBrowserErrors(page); await page.goto(authorizedFaultUrl(distUrl, 'F5-hidden-context-loss'), { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await page.evaluate(() => window.dispatchEvent(new PageTransitionEvent('pagehide', { persisted: true }))); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'paused'); await triggerFaultAction(page, 'context-loss'); await triggerFaultAction(page, 'context-restore'); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'loading'); await page.evaluate(() => window.dispatchEvent(new PageTransitionEvent('pageshow', { persisted: true }))); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await expect(page.locator('.journey-canvas')).toHaveCount(1); await assertSemanticFaultOracle(page); await assertContained(page, errors.pageErrors); }); test('F6 makes a later context loss terminal for the session', async ({ page, distUrl }) => { const errors = await collectBrowserErrors(page); await page.goto(authorizedFaultUrl(distUrl, 'F6-repeated-context-loss'), { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await triggerFaultAction(page, 'context-loss'); await triggerFaultAction(page, 'context-restore'); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await triggerFaultAction(page, 'context-loss'); 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 expect(page.locator('.drive-controls__status')).toHaveText('Visuals unavailable'); await assertSemanticFaultOracle(page); await assertContained(page, errors.pageErrors); });