Automated IndexedDB Disaster Recovery Testing with Playwright: From 30% to 100% Coverage

python dev.to

It was 2 a.m. when someone pinged me in the user group: “Every time I refresh the app, the form I filled yesterday is completely gone. Where do you even store the data?” My gut reaction was impossible — we explicitly persist form drafts in IndexedDB, and a simple page refresh or tab close shouldn’t wipe them. After digging in, I discovered the user had been browsing in Chrome’s incognito mode, closed the tab, and the browser silently wiped the entire IndexedDB. What scared me even more: this exact scenario had never been covered by our manual tests.

If you’re using IndexedDB for frontend persistence, you probably share the same uneasy feeling: IndexedDB has a knack for making you believe your data is safe, right until a user loses it in a way you never tested. This article is a log of how I built a fully automated disaster recovery test suite with Playwright — taking every edge case that manual testing could never systematically cover and locking them straight into CI.

Breaking Down the Problem: Just How Fragile Is IndexedDB Persistence?

First, let’s align on a fundamental truth: IndexedDB may be the most “database-like” storage option in the browser, but it still lives strictly inside the browser sandbox. Data loss isn’t a bug — it’s a design reality. Common scenarios where data vaporises include:

  • The user explicitly clears site data (“Settings → Privacy & Security → Clear browsing data”)
  • Storage quota is exhausted, and writes fail silently
  • In incognito/guest mode, closing the tab purges the database
  • On iOS Safari, low device storage can cause the OS to quietly delete IndexedDB
  • A Service Worker update triggers an IndexedDB version upgrade failure, clearing all data

For our application to feel robust, it must handle all of this gracefully: the user should never face a blank screen or lose critical state. The app needs to rebuild the database automatically, re-fetch from the server, or at the very least show a clear error message.

Manual testing for these scenarios is almost impossible to systematise. You can’t ask QA to manually clear browser storage a dozen times, simulate quota exhaustion, jump into incognito, and then run a full user flow before every release. The result? We only ever verified “IndexedDB reads and writes work under normal conditions.” We never truly tested what happens after the data disappears. That’s exactly what left me in hot water at 2 a.m.

Design Decisions: Why Playwright Over Cypress or Puppeteer?

The challenge boils down to this: simulate storage failures inside a real browser and then verify the UI layer’s disaster recovery behaviour. Plenty of tools can dip their toes here, but Playwright stood out for three reasons:

  1. Native multi-browser + multi-context support: Test Chrome, Firefox, and Safari (WebKit) with practically no overhead. One script covers the IndexedDB implementation quirks of each engine.
  2. page.evaluate() for direct IndexedDB control: I can inject scripts at any point to delete databases, force write failures, or check for leftover data. Cypress can’t reach this layer from its sandbox (it only has access to the page’s exposed APIs).
  3. storageState for perfect isolation: I can precisely simulate the “close tab and reopen” storage condition without manually wrangling userDataDir like in Puppeteer.

My architectural decision was clear: don’t mix IndexedDB tests with standard E2E flow tests. Instead, build a dedicated “storage disaster recovery test suite” managed by Playwright Test Runner. Every test case follows the same structure:

Prepare state (insert data) → Simulate fault (clear database / tamper with storage quota / switch to incognito context) → Trigger a page action → Assert the UI hasn’t broken and data recovers correctly

Core Implementation: Pushing IndexedDB to the Brink with Playwright

Below are three core test scenarios that cover the most typical disaster recovery cases. I’ll explain the problem each one solves before the code.

1. Simulating Cleared Browser Storage – Verifying Automatic Recovery

Problem: After a user clears site data from the browser settings, re-opening the app must not show a white screen. It should pull essential data back from the server.

// 测试 case:存储被清空后,应用能否自动重建数据库并展示默认状态
import { test, expect } from '@playwright/test';

test('IndexedDB 被清空后,页面可以自动恢复', async ({ page }) => {
  // 步骤1:先正常打开页面,写入一些模拟数据
  await page.goto('https://your-app.com/dashboard');
  await page.evaluate(() => {
    return new Promise<void>((resolve) => {
      const request = indexedDB.open('myAppDB', 1);
      request.onsuccess = () => {
        const db = request.result;
        const tx = db.transaction('drafts', 'readwrite');
        tx.objectStore('drafts').add({ id: 1, content: '草稿内容' });
        tx.oncomplete = () => resolve();
      };
    });
  });

  // 步骤2:关闭当前上下文(模拟关闭标签页),新建一个无痕 context 模拟“存储被清空”
  await page.context().close();

  const newContext = await browser.newContext({ storageState: undefined }); // 不继承任何存储状态
  const newPage = await newContext.newPage();

  // 步骤3:再次打开同一页面,断言:不应白屏,且展示“暂无草稿”默认态
  await newPage.goto('https://your-app.com/dashboard');

  // 容灾的UI标志:页面正常渲染,不会因取不到本地数据而崩溃
  await expect(newPage.locator('.empty-state')).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

Why storageState: undefined? Because it precisely mimics a fresh browser profile — IndexedDB, localStorage, everything wiped. It’s far closer to a real user scenario than manually clearing storage.

2. Simulating a Write Failure – Avoiding Silent Data Loss

Problem: When IndexedDB refuses a write due to quota exhaustion or a disk error, the app must catch the exception and warn the user instead of pretending everything is fine.

test('IndexedDB 写入失败时,前端给出错误提示', async ({ page }) => {
  await page.goto('https://your-app.com/dashboard');

  // 注入脚本:劫持 IDBObjectStore 的
Enter fullscreen mode Exit fullscreen mode

Source: dev.to

arrow_back Back to Tutorials