DEV Community

BAOFUFAN
BAOFUFAN

Posted on

"How a Refresh Wiped Out 237 Drafts — and How We Used Playwright to Stop It Forever"

At 2 AM, I was jolted awake by a call from operations. Our user community was on fire: someone had spent half an hour filling out a complex form, accidentally hit refresh, and all their drafts vanished. A backend check showed 237 drafts reduced to just three. The backend wasn't to blame — the database never received a single request. The culprit was our frontend memory storage, and we had zero automated tests covering it. Later, we added automated tests for localStorage and IndexedDB persistence using Playwright, and the same kind of incident never happened again. In this article, I'll walk you through the complete "memory storage health check" blueprint — code included.


Breaking Down the Problem: Why Memory Storage Fails Silently

Frontend memory storage broadly refers to using localStorage, sessionStorage, IndexedDB, or state management persistence (like Pinia persist plugins) to cache user input so data isn't lost when the page is refreshed or closed. It’s what lets you hit F5 by accident and still see what you were typing — a baseline experience for any modern web app.

But its fragility is often underestimated. In our incident, the root cause was simple: a code refactor changed the serialization key for drafts. The old key was no longer read, so after a refresh the app assumed “no draft exists” and wrote an empty state, wiping everything. Conventional manual testing never covered this path because testers always fill forms from scratch — they don't deliberately refresh a half-filled form and then check for restoration.

Why common approaches fell short:

  • Unit tests: mocking localStorage can’t replicate real browser storage behavior, storage quotas, or serialization quirks.
  • E2E tests (Cypress/Playwright): they typically follow the “happy path” — fill from a blank state and submit — without intentionally triggering refresh or crash-recovery scenarios.
  • Manual verification: you can’t guarantee someone clears storage, fills half a form, refreshes, and validates restoration on every regression cycle. It’s too costly and easy to miss.

We needed an automated, repeatable test suite that could assert storage contents — something purpose-built to guard memory storage reliability.


Designing the Solution: Why Playwright for Memory Storage Testing

We evaluated three approaches:

  1. Browser extensions + script injection: too hacky, impossible to integrate into CI, and can’t accurately simulate real user journeys.
  2. Cypress APIs like cy.clearLocalStorage(): they can manipulate storage, but IndexedDB support is weaker, and the execution model doesn’t produce a truly “native” refresh scenario.
  3. Playwright: native support for multiple browsers, isolated contexts, page.evaluate() to read localStorage/IndexedDB directly, and page.reload() that triggers a true page refresh. Plus, the test scripts are plain Node.js, seamlessly pluggable into existing CI pipelines.

Our final decision: write dedicated “memory storage regression cases” with Playwright, simulating the core path “fill → refresh → verify restoration” and running them automatically on every test pass.

The architecture is straightforward:

  • Each test case creates an isolated browser context (browser.newContext()) to guarantee a clean storage environment.
  • Each case follows three steps: write (simulate user input triggering auto-save) → refresh → read (assert that stored drafts match the form values after reload).
  • We designed reusable assertion helpers for both localStorage and IndexedDB.

Core Implementation: A Full-Body Checkup for Memory Storage

Let’s build a runnable memory storage test suite with Playwright. The core problem it solves: verifying that after a user fills out a form and it’s auto-saved to localStorage, the draft survives a page refresh intact.

1. Simulated Page Logic

To make the test runnable, here’s a minimal HTML page with auto-save to localStorage and restoration on load. Save it as test-app/index.html:

<!DOCTYPE html>
<html>
<body>
  <form id="draftForm">
    <input id="title" placeholder="标题" />
    <textarea id="content" placeholder="内容"></textarea>
  </form>
  <script>
    const form = document.getElementById('draftForm');
    const title = document.getElementById('title');
    const content = document.getElementById('content');
    const STORAGE_KEY = 'draft_v1';

    // 页面加载时恢复草稿
    function restore() {
      try {
        const saved = localStorage.getItem(STORAGE_KEY);
        if (saved) {
          const draft = JSON.parse(saved);
          title.value = draft.title || '';
          content.value = draft.content || '';
        }
      } catch (e) {}
    }

    // 输入变化自动保存
    function autoSave() {
      const draft = { title: title.value, content: content.value };
      localStorage.setItem(STORAGE_KEY, JSON.stringify(draft));
    }

    title.addEventListener('input', autoSave);
    content.addEventListener('input', autoSave);
    restore(); // 立刻恢复
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

2. Playwright Test: Verifying Drafts Survive a Refresh

Install Playwright: npm i -D playwright @playwright/test

Create a playwright.config.ts that points to the test directory and the base URL.

The test below directly verifies refresh recovery — if this case fails, your live memory storage is broken.

// tests/memory-storage.spec.ts
import { test, expect } from '@playwright/test';

const DRAFT_TEXT = '这是一段重要的草稿内容,不能丢';

test.describe('记忆存储 - 草稿恢复', () => {
  test.beforeEach(async ({ page
Enter fullscreen mode Exit fullscreen mode

Top comments (0)