Skip to content

Data & Storage Model

BrowseGenius uses a local-first architecture with optional cloud sync. All data is stored in your browser and works offline. When you provide an Extension API Key, your projects and test plans automatically sync to the cloud for cross-device access.

Zustand slices

The extension relies on Zustand with Immer for structured state slices:

  • settings (src/state/settings.ts) Stores OpenAI API keys and the selected model. Persisted to localStorage so keys survive reloads.
  • ui Holds transient UI fields like instruction text boxes.
  • currentTask Tracks the legacy Taxy free-form execution mode (still available for debugging).
  • testPlanner (src/state/testPlanner.ts) Drives the new workflow: captures, generated cases, suite status, active report, artifacts, and test recordings.
  • project (src/state/project.ts) Organises saved plans by hostname, keeps track of the active project, and records metadata such as descriptions and last-accessed timestamps.

Persistence is configured via createJSONStorage in src/state/store.ts. Only the following fields are persisted:

ts
{
  ui: { instructions },
  settings: { openAIKey, extensionApiKey, selectedModel },
  testPlanner: {
    captures,      // Screenshot + description + DOM
    testCases,     // Generated test flows
    recordings,    // JSON recordings for replay
    savedPlans     // Named plan snapshots
  },
  project: {
    projects,        // Project metadata, plan associations
    activeProjectId, // Currently selected project in the popup UI
    syncStatus,      // 'idle' | 'syncing' | 'synced' | 'error'
    lastSyncError    // Error message if sync failed
  }
}

Backend Sync

When an Extension API Key is configured:

Automatic Background Sync

  • Runs every 5 minutes automatically
  • Syncs projects and test plans bidirectionally
  • Uploads screenshots to Cloudflare R2
  • Updates user info (subscription tier, API credits)

Sync Flow

Extension Creates Project

localStorage Updated (Instant)

API Call to Backend

Cloudflare D1 Database

Mark as Synced in State

Conflict Resolution

  • Backend is source of truth
  • Local changes always pushed to backend
  • Backend data merged into local state
  • Last-write-wins strategy
  • No data loss on sync failures

Sync Status Tracking

typescript
interface ProjectState {
  syncStatus: 'idle' | 'syncing' | 'synced' | 'error';
  lastSyncError?: string;
}

Projects have a synced flag:

typescript
interface Project {
  id: string;
  name: string;
  synced: boolean;  // true if saved to backend
}

Indexed data

Captures, test cases, and recordings are stored as plain objects:

  • ScreenCapture: { id, imageDataUrl, url, title, notes, capturedAt, domSnapshot, imageDescription }

    • imageDescription: AI-generated description from OpenAI Vision API
    • Binary screenshots stored inline as base64 data URLs
  • FlowTestCase: { id, flowName, title, narrative, priority, steps, status, lastRunAt, failureReason }

    • Each step: { id, action, expectation }
    • Status: idle, queued, running, passed, failed, blocked, skipped
  • Project (src/state/project.ts):

    typescript
    {
      id: string;
      name: string;
      hostname: string;
      description?: string;
      planIds: string[];      // Saved plan references
      createdAt: string;
      lastAccessedAt: string;
    }
    • The active project ID is stored alongside the collection so the popup home screen can auto-load the correct plan.
  • TestRecording (src/types/testRecording.ts):

    typescript
    {
      id: string;
      testCaseId: string;
      testCaseName: string;
      flowName: string;
      recordedAt: string;
      browserInfo: { userAgent, viewport };
      startUrl: string;
      actions: RecordedAction[];
      summary: { totalActions, successfulActions, failedActions, duration, networkRequestCount };
      allNetworkRequests: NetworkRequest[];
    }

RecordedAction Structure

Each recorded action captures comprehensive execution details:

typescript
{
  id: string;
  stepIndex: number;
  timestamp: string;
  actionType: 'click' | 'input' | 'select' | 'navigate' | 'wait' | 'assertion';
  description: string;

  // Multiple selector strategies for robustness
  target?: {
    id?: string;
    css?: string;
    xpath?: string;
    dataTestId?: string;
    ariaLabel?: string;
    text?: string;
    coordinates?: { x, y, width, height };
  };

  value?: string | number | boolean;
  url: string;
  pageTitle: string;

  // Network activity
  networkRequests?: NetworkRequest[];

  // Visual state
  screenshotBefore?: string;
  screenshotAfter?: string;

  // Validation
  assertion?: {
    type: 'elementExists' | 'textContains' | 'urlContains' | 'networkStatus';
    expected: any;
    actual?: any;
    passed?: boolean;
  };

  executionMethod: 'vision' | 'dom';
  success: boolean;
}

Reports

SuiteReport objects, defined in testPlanner.ts, contain:

  • summary counters (total, passed, failed, blocked, skipped).
  • Case-level snapshots including durations and failure reasons.
  • Artifact arrays with timestamps.

The report is held entirely in memory until you click Download report—then it is serialised into JSON/HTML and offered through the download API.

Storage size considerations

Test recordings can become large due to:

  • Base64-encoded screenshots (before/after each action)
  • Network request/response bodies
  • Complete DOM snapshots

Best practices:

  • Limit screenshot capture to key validation points
  • Consider omitting screenshots from recordings for faster replays
  • Export and archive recordings externally for long-term storage
  • Clear old recordings periodically

Simplified export option:

typescript
const simplified = {
  ...recording,
  actions: recording.actions.map(a => ({
    ...a,
    screenshotBefore: undefined,
    screenshotAfter: undefined
  }))
};

Security considerations

  • API keys stored in localStorage only, never sent to external servers
  • All data remains local unless explicitly exported
  • DOM snapshots and recordings cleared when you reset or uninstall
  • Downloaded bundles contain sensitive application data
  • Network request/response bodies may include tokens, credentials, PII

Chrome extension storage model

BrowseGenius intentionally uses window.localStorage (via Zustand's JSON storage adapter) instead of chrome.storage.sync:

  • The popup and panel both run in the extension origin, so localStorage gives instant read/write performance without async APIs.
  • Captures, plans, and recordings can exceed the 100 KB per-item quota imposed by chrome.storage.
  • Sensitive API keys stay local to the machine—nothing is synced across Chrome profiles unless you export data manually.

If you prefer managed storage, you can swap the storage adapter in src/state/store.ts for chrome.storage.local, but be mindful of quota enforcement and the need to adapt the persistence middleware to async semantics.

Important: Test recordings capture full network traffic including authentication tokens, API responses, and user data. Follow your organization's data handling policies when:
  • Sharing recordings with team members
  • Storing recordings in version control
  • Exporting reports containing screenshots
  • Running tests against production environments
  1. Sanitize recordings before sharing:

    • Remove authentication tokens from network requests
    • Redact sensitive data from request/response bodies
    • Strip PII from screenshots
  2. Use test data in non-production environments whenever possible

  3. Encrypt exported recordings when storing externally

  4. Set short retention periods for recordings in localStorage

Resetting state

Clear individual items:

  • Remove captures using the delete icon in the UI
  • Delete test cases with the trash button
  • Clear recordings via state actions: deleteRecording(recordingId)

Clear all data:

typescript
// Reset entire test planner state
useAppState.getState().testPlanner.actions.reset();

// Clear localStorage completely
localStorage.clear();

Managing storage:

typescript
// Check current storage usage
const state = useAppState.getState();
const storageSize = JSON.stringify(state).length;
console.log(`Storage size: ${(storageSize / 1024).toFixed(2)} KB`);

// Export and clear old recordings
const recordings = state.testPlanner.recordings;
recordings.forEach(rec => {
  actionRecorder.downloadRecording(rec);
});
state.testPlanner.actions.recordings = [];

Export formats

JSON Recording - Complete test execution data:

bash
recording_tc_login_1673520601000.json

Report Bundle - Test results with artifacts:

bash
browsegenius-report-2025-01-12T10-30-00.json
browsegenius-report-2025-01-12T10-30-00.html

See the Recording & Replay documentation for complete JSON schema details.

Released under the MIT License.