const debugPort = process.env.CHROMIUM_DEBUG_PORT || '9226'; const syncId = process.env.AZON_E2E_SYNC_ID; const expectFailure = process.env.AZON_EXPECT_SYNC_FAILURE === '1'; if (!syncId) throw new Error('AZON_E2E_SYNC_ID is required.'); let worker; for (let attempt = 0; attempt < 60 && !worker; attempt++) { const targets = await fetch(`http://127.0.0.1:${debugPort}/json/list`).then((response) => response.json(), ); worker = targets.find( (target) => target.type === 'service_worker' && target.url.endsWith('/dist/background/index.mjs'), ); if (!worker) await new Promise((resolve) => setTimeout(resolve, 250)); } if (!worker) throw new Error('Azon Seeker background service worker was not found.'); const socket = new WebSocket(worker.webSocketDebuggerUrl); const pending = new Map(); let nextId = 1; socket.addEventListener('message', (event) => { const message = JSON.parse(event.data); if (!message.id || !pending.has(message.id)) return; const { resolve, reject } = pending.get(message.id); pending.delete(message.id); message.error ? reject(new Error(message.error.message)) : resolve(message.result); }); await new Promise((resolve, reject) => { socket.addEventListener('open', resolve, { once: true }); socket.addEventListener('error', reject, { once: true }); }); function request(method, params = {}) { return new Promise((resolve, reject) => { const id = nextId++; pending.set(id, { resolve, reject }); socket.send(JSON.stringify({ id, method, params })); }); } async function evaluate(expression) { const result = await request('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true, }); if (result.exceptionDetails) throw new Error(result.exceptionDetails.text); return result.result.value; } const searchItem = { syncId, asin: 'B000000098', keywords: 'extension queue e2e', rank: 1, page: 1, pageRank: 1, link: 'https://www.amazon.com/dp/B000000098', title: 'Extension queue end-to-end fixture', price: '$33.93', strikethroughPrice: '$39.99', adType: 'sponsoredDisplay', adRank: 1, placement: 'sidebar', placementRank: 1, placementId: 'extension-e2e-placement', adProductIndex: 1, adFormat: 'standard', imageSrc: 'https://example.com/extension-e2e.jpg', createTime: '2026/08/31 11:45:00', }; await evaluate(`(async () => { await chrome.storage.local.set({ 'amazon-sync-queue-v1': ${JSON.stringify({ searchItems: [searchItem], detailItems: [], reviews: [], })}, 'amazon-sync-state-v1': JSON.stringify({status: 'pending', pendingCount: 1}) }); await chrome.alarms.create('amazon-sync-retry', { when: Date.now() + 200 }); return true; })()`); let state; for (let attempt = 0; attempt < 30; attempt++) { await new Promise((resolve) => setTimeout(resolve, 500)); state = await evaluate(`(async () => { const stored = await chrome.storage.local.get(['amazon-sync-queue-v1', 'amazon-sync-state-v1']); return { queue: stored['amazon-sync-queue-v1'], state: JSON.parse(stored['amazon-sync-state-v1']) }; })()`); if (state.state.status === 'failed') { if (expectFailure) break; throw new Error(state.state.lastError); } if (state.state.status === 'synced' && state.state.pendingCount === 0) break; } socket.close(); if (expectFailure) { if ( state.state.status !== 'failed' || state.state.pendingCount !== 1 || state.queue.searchItems.length !== 1 ) { throw new Error(`Failed synchronization was not retained: ${JSON.stringify(state)}`); } console.log(JSON.stringify({ syncId, retained: true, ...state.state }, null, 2)); process.exit(0); } if (state.state.status !== 'synced' || state.queue.searchItems.length !== 0) { throw new Error(`Queue did not drain: ${JSON.stringify(state)}`); } console.log(JSON.stringify({ syncId, ...state.state }, null, 2));