97 lines
3.7 KiB
TypeScript
97 lines
3.7 KiB
TypeScript
/**
|
|
* H-01 · QW-3 regression — skip BootScreen on return visits.
|
|
*
|
|
* apps/web/src/pages/Index.tsx gates BootScreen on a `waggle-booted`
|
|
* localStorage key. First visit: key absent → BootScreen mounts, runs
|
|
* through phase animation, calls `onComplete` which writes the key and
|
|
* sets `booted=true`. Subsequent visits: key present → BootScreen never
|
|
* mounts; Desktop renders immediately.
|
|
*
|
|
* Guards three properties:
|
|
* 1. Fresh storage shows BootScreen (behavior under the gate).
|
|
* 2. Pre-seeded BOOT_KEY makes BootScreen skip entirely.
|
|
* 3. Completing the boot writes the key and persists across reload.
|
|
*
|
|
* Run: npx playwright test tests/e2e/boot-screen-skip.spec.ts --reporter=list
|
|
*/
|
|
import { test, expect, type Page } from '@playwright/test';
|
|
|
|
const BASE = process.env.WAGGLE_E2E_BASE_URL ?? 'http://127.0.0.1:3333';
|
|
const BOOT_KEY = 'waggle-booted';
|
|
const BOOT_SCREEN = '[data-testid="boot-screen"]';
|
|
|
|
async function clearBootFlag(page: Page) {
|
|
// Fresh-storage setup: wipe BOOT_KEY before React mounts. Using
|
|
// addInitScript so the removal lands before the useState initializer
|
|
// in Index.tsx reads localStorage. Wrap in try-catch because some
|
|
// browsers throw on localStorage access in file:// contexts.
|
|
await page.addInitScript(() => {
|
|
try {
|
|
window.localStorage.removeItem('waggle-booted');
|
|
} catch {
|
|
// ignore — localStorage unavailable
|
|
}
|
|
});
|
|
}
|
|
|
|
async function seedBootFlag(page: Page) {
|
|
// Return-visit setup: mark boot as completed before first navigation.
|
|
await page.addInitScript(() => {
|
|
try {
|
|
window.localStorage.setItem('waggle-booted', 'true');
|
|
} catch {
|
|
// ignore
|
|
}
|
|
});
|
|
}
|
|
|
|
test.describe('H-01 · QW-3 · BootScreen skip on return visits', () => {
|
|
test('fresh storage renders BootScreen', async ({ page }) => {
|
|
await clearBootFlag(page);
|
|
await page.goto(`${BASE}/`);
|
|
|
|
// BootScreen should mount immediately (before the ~2.5s auto-advance
|
|
// completes). 1s window is well inside the animation runtime.
|
|
await expect(page.locator(BOOT_SCREEN)).toBeVisible({ timeout: 1_000 });
|
|
});
|
|
|
|
test('pre-seeded BOOT_KEY skips BootScreen entirely', async ({ page }) => {
|
|
await seedBootFlag(page);
|
|
await page.goto(`${BASE}/`);
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// Assert BootScreen never mounted. toHaveCount(0) proves non-rendered,
|
|
// not merely off-screen — the AnimatePresence branch in Index.tsx
|
|
// conditionally renders on `!booted`.
|
|
await expect(page.locator(BOOT_SCREEN)).toHaveCount(0);
|
|
|
|
// Sanity: confirm the localStorage key survived into the page runtime.
|
|
const bootFlag = await page.evaluate(() => window.localStorage.getItem('waggle-booted'));
|
|
expect(bootFlag).not.toBeNull();
|
|
});
|
|
|
|
test('completing boot persists the flag across reload', async ({ page }) => {
|
|
await clearBootFlag(page);
|
|
await page.goto(`${BASE}/`);
|
|
|
|
// First visit: BootScreen visible.
|
|
await expect(page.locator(BOOT_SCREEN)).toBeVisible({ timeout: 1_000 });
|
|
|
|
// Click to skip — BootScreen listens for click + keydown and fires
|
|
// `onComplete`, which writes BOOT_KEY and flips the booted state.
|
|
await page.locator(BOOT_SCREEN).click();
|
|
|
|
// Wait for BootScreen to unmount (AnimatePresence exit anim ~500ms).
|
|
await expect(page.locator(BOOT_SCREEN)).toHaveCount(0, { timeout: 3_000 });
|
|
|
|
// The flag should now be persisted.
|
|
const bootFlag = await page.evaluate(() => window.localStorage.getItem('waggle-booted'));
|
|
expect(bootFlag).toBe('true');
|
|
|
|
// Reload — BootScreen must stay skipped.
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
await expect(page.locator(BOOT_SCREEN)).toHaveCount(0);
|
|
});
|
|
});
|