From ab0c6a34e3af005dfa74a41ffe2f43ddebc92c83 Mon Sep 17 00:00:00 2001 From: Jhoan Lopez Date: Wed, 8 Jul 2026 18:15:16 -0500 Subject: [PATCH 1/3] fix: storage restore double-JSON-encoding bug --- clicker/internal/agent/handlers.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/clicker/internal/agent/handlers.go b/clicker/internal/agent/handlers.go index 96aeae0..c7464f7 100644 --- a/clicker/internal/agent/handlers.go +++ b/clicker/internal/agent/handlers.go @@ -3949,11 +3949,21 @@ func (h *Handlers) browserStorageState(args map[string]interface{}) (*ToolsCallR })() })` - storageResult, err := h.client.Evaluate("", script) + storageJSON, err := h.client.Evaluate("", script) if err != nil { return nil, fmt.Errorf("failed to get storage: %w", err) } + storageStr, ok := storageJSON.(string) + if !ok { + return nil, fmt.Errorf("unexpected storage result type: %T", storageJSON) + } + + var storageResult interface{} + if err := json.Unmarshal([]byte(storageStr), &storageResult); err != nil { + return nil, fmt.Errorf("failed to parse storage data: %w", err) + } + // Build combined state state := map[string]interface{}{ "cookies": cookies, From bed6330a5ff2820bfe382812b4f802dbc53ff638 Mon Sep 17 00:00:00 2001 From: Jhoan Lopez Date: Wed, 8 Jul 2026 18:19:06 -0500 Subject: [PATCH 2/3] Renaming variable --- clicker/internal/agent/handlers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clicker/internal/agent/handlers.go b/clicker/internal/agent/handlers.go index c7464f7..23f75d7 100644 --- a/clicker/internal/agent/handlers.go +++ b/clicker/internal/agent/handlers.go @@ -3949,14 +3949,14 @@ func (h *Handlers) browserStorageState(args map[string]interface{}) (*ToolsCallR })() })` - storageJSON, err := h.client.Evaluate("", script) + storageEvalResult, err := h.client.Evaluate("", script) if err != nil { return nil, fmt.Errorf("failed to get storage: %w", err) } - storageStr, ok := storageJSON.(string) + storageStr, ok := storageEvalResult.(string) if !ok { - return nil, fmt.Errorf("unexpected storage result type: %T", storageJSON) + return nil, fmt.Errorf("unexpected storage result type: %T", storageEvalResult) } var storageResult interface{} From 23c068875ef50e20b002f16f9e144700b28db176 Mon Sep 17 00:00:00 2001 From: Jhoan Lopez Date: Wed, 8 Jul 2026 18:48:00 -0500 Subject: [PATCH 3/3] Test added --- Makefile | 2 +- tests/cli/storage.test.js | 90 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/cli/storage.test.js diff --git a/Makefile b/Makefile index effcfa9..374a243 100644 --- a/Makefile +++ b/Makefile @@ -196,7 +196,7 @@ test-cli: build-go @echo "--- CLI Tests ---" @$(CURDIR)/clicker/bin/vibium$(EXE) daemon stop 2>/dev/null || true @$(CURDIR)/clicker/bin/vibium$(EXE) daemon start --headless - $(TIMEOUT_CMD) node --test $(TEST_FLAGS) --test-concurrency=1 tests/cli/navigation.test.js tests/cli/elements.test.js tests/cli/actionability.test.js tests/cli/page-reading.test.js tests/cli/input-tools.test.js tests/cli/pages.test.js tests/cli/page-context.test.js tests/cli/find-refs.test.js + $(TIMEOUT_CMD) node --test $(TEST_FLAGS) --test-concurrency=1 tests/cli/navigation.test.js tests/cli/elements.test.js tests/cli/actionability.test.js tests/cli/page-reading.test.js tests/cli/input-tools.test.js tests/cli/pages.test.js tests/cli/page-context.test.js tests/cli/find-refs.test.js tests/cli/storage.test.js @$(CURDIR)/clicker/bin/vibium$(EXE) daemon stop 2>/dev/null || true @echo "--- CLI Process Tests (sequential) ---" $(TIMEOUT_CMD) node --test $(TEST_FLAGS) --test-concurrency=1 tests/cli/process.test.js diff --git a/tests/cli/storage.test.js b/tests/cli/storage.test.js new file mode 100644 index 0000000..5da5e77 --- /dev/null +++ b/tests/cli/storage.test.js @@ -0,0 +1,90 @@ +/** + * CLI Tests: Storage export/restore + * Verifies `vibium storage` / `vibium storage restore` round-trip + * localStorage and sessionStorage correctly. + */ + +const { test, describe, before, after } = require('node:test'); +const assert = require('node:assert'); +const { execSync, spawn } = require('node:child_process'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { VIBIUM } = require('../helpers'); + +let serverProcess, baseURL, statePath; + +before(async () => { + serverProcess = spawn('node', [path.join(__dirname, '../helpers/test-server.js')], { + stdio: ['pipe', 'pipe', 'pipe'], + }); + baseURL = await new Promise((resolve) => { + serverProcess.stdout.once('data', (data) => { + resolve(data.toString().trim()); + }); + }); + execSync(`${VIBIUM} go ${baseURL}/`, { encoding: 'utf-8', timeout: 30000 }); + statePath = path.join(os.tmpdir(), `vibium-storage-test-${Date.now()}.json`); +}); + +after(() => { + if (statePath && fs.existsSync(statePath)) fs.unlinkSync(statePath); + if (serverProcess) serverProcess.kill(); +}); + +describe('CLI: storage export/restore', () => { + test('export produces a real nested JSON object, not a double-encoded string', () => { + execSync(`${VIBIUM} eval "localStorage.setItem('user', 'user_vibium')"`, { + encoding: 'utf-8', + timeout: 30000, + }); + execSync(`${VIBIUM} eval "sessionStorage.setItem('session_id', 'session_vibium')"`, { + encoding: 'utf-8', + timeout: 30000, + }); + + execSync(`${VIBIUM} storage -o ${statePath}`, { encoding: 'utf-8', timeout: 30000 }); + const state = JSON.parse(fs.readFileSync(statePath, 'utf-8')); + + // Guard + assert.strictEqual( + typeof state.storage, + 'object', + '"storage" must be a nested object, not a double-encoded string' + ); + assert.strictEqual(state.storage.localStorage.user, 'user_vibium'); + assert.strictEqual(state.storage.sessionStorage.session_id, 'session_vibium'); + }); + + test('restore round-trip repopulates localStorage and sessionStorage', () => { + execSync(`${VIBIUM} eval "localStorage.clear(); sessionStorage.clear();"`, { + encoding: 'utf-8', + timeout: 30000, + }); + + // Sanity check: confirm storage is actually empty before restoring + const before = execSync(`${VIBIUM} eval "localStorage.getItem('user')"`, { + encoding: 'utf-8', + timeout: 30000, + }).trim(); + assert.strictEqual(before, 'null'); + + const result = execSync(`${VIBIUM} storage restore ${statePath}`, { + encoding: 'utf-8', + timeout: 30000, + }); + assert.match(result, /restored/i, 'Should confirm storage was restored'); + + const user = execSync(`${VIBIUM} eval "localStorage.getItem('user')"`, { + encoding: 'utf-8', + timeout: 30000, + }).trim(); + const sessionId = execSync(`${VIBIUM} eval "sessionStorage.getItem('session_id')"`, { + encoding: 'utf-8', + timeout: 30000, + }).trim(); + + assert.strictEqual(user, 'user_vibium', 'localStorage should be restored'); + assert.strictEqual(sessionId, 'session_vibium', 'sessionStorage should be restored'); + }); +});