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 tolocalStorageso keys survive reloads.uiHolds transient UI fields like instruction text boxes.currentTaskTracks 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:
{
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 StateConflict 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
interface ProjectState {
syncStatus: 'idle' | 'syncing' | 'synced' | 'error';
lastSyncError?: string;
}Projects have a synced flag:
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
- Each step:
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:
{
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:
summarycounters (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:
const simplified = {
...recording,
actions: recording.actions.map(a => ({
...a,
screenshotBefore: undefined,
screenshotAfter: undefined
}))
};Security considerations
- API keys stored in
localStorageonly, 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
localStoragegives 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.
- Sharing recordings with team members
- Storing recordings in version control
- Exporting reports containing screenshots
- Running tests against production environments
Recommended security practices
Sanitize recordings before sharing:
- Remove authentication tokens from network requests
- Redact sensitive data from request/response bodies
- Strip PII from screenshots
Use test data in non-production environments whenever possible
Encrypt exported recordings when storing externally
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:
// Reset entire test planner state
useAppState.getState().testPlanner.actions.reset();
// Clear localStorage completely
localStorage.clear();Managing storage:
// 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:
recording_tc_login_1673520601000.jsonReport Bundle - Test results with artifacts:
browsegenius-report-2025-01-12T10-30-00.json
browsegenius-report-2025-01-12T10-30-00.htmlSee the Recording & Replay documentation for complete JSON schema details.