|
| 1 | +// Tier-3 E2E: startup settings restore must not hang the app when the settings |
| 2 | +// IndexedDB never opens. This happens for real when another tab holds a |
| 3 | +// versionchange that blocks our open request (the open just sits there, firing |
| 4 | +// neither success nor error), which would otherwise leave the app wedged on an |
| 5 | +// unconfigured state forever. The restore watchdog (App.jsx, |
| 6 | +// SETTINGS_LOAD_TIMEOUT_MS) must give up, log it, and boot on defaults. |
| 7 | +// |
| 8 | +// We simulate the blocked DB by stubbing indexedDB.open for the settings DB so |
| 9 | +// its open request never settles. Unlike transcription.spec.js this needs no |
| 10 | +// model weights, so it is quick. Built with Claude Code. |
| 11 | + |
| 12 | +import { test, expect } from '@playwright/test'; |
| 13 | + |
| 14 | +const SETTINGS_DB = 'parakeetweb-settings-db'; |
| 15 | + |
| 16 | +test('boots on defaults when the settings DB open hangs', async ({ page }) => { |
| 17 | + // Before any app code runs, make the settings DB open hang forever: return a |
| 18 | + // request object whose onsuccess/onerror/onupgradeneeded are never fired (a |
| 19 | + // plain object accepts the assignments openIdb makes but invokes nothing), so |
| 20 | + // the open() promise never settles. Other DBs open normally. |
| 21 | + await page.addInitScript((dbName) => { |
| 22 | + const realOpen = indexedDB.open.bind(indexedDB); |
| 23 | + indexedDB.open = function (name, version) { |
| 24 | + if (name === dbName) return {}; |
| 25 | + return realOpen(name, version); |
| 26 | + }; |
| 27 | + }, SETTINGS_DB); |
| 28 | + |
| 29 | + const warnings = []; |
| 30 | + page.on('console', (m) => { if (m.type() === 'warning') warnings.push(m.text()); }); |
| 31 | + |
| 32 | + await page.goto('/'); |
| 33 | + |
| 34 | + // The app shell renders immediately; confirm it is interactive (the model-load |
| 35 | + // button is present and clickable) rather than stuck on a blank screen. |
| 36 | + const loadBtn = page.locator('[data-umami-event="load_model_button"]'); |
| 37 | + await expect(loadBtn).toBeVisible({ timeout: 15000 }); |
| 38 | + await expect(loadBtn).toBeEnabled(); |
| 39 | + |
| 40 | + // The watchdog must fire and boot on defaults. Its warning is emitted in the |
| 41 | + // same branch that calls setSettingsLoaded(true), so seeing it is proof the |
| 42 | + // timeout path ran and the app booted instead of hanging on the dead DB. |
| 43 | + await expect |
| 44 | + .poll(() => warnings.some((w) => w.includes('Settings restore timed out')), { |
| 45 | + timeout: 20000, |
| 46 | + message: 'expected the settings-restore watchdog to fire and log a timeout', |
| 47 | + }) |
| 48 | + .toBe(true); |
| 49 | +}); |
0 commit comments