const path = require('path'); const { app, BrowserWindow, ipcMain } = require('electron'); const guiDir = process.cwd(); app.setAppPath(guiDir); require(path.join(guiDir, 'dist-electron', 'main', 'index.js')); const streamTokens = ['Alpha', ' Beta', ' Gamma']; function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function registerDeterministicAppHandlers() { ipcMain.removeHandler('rex:getSetupStatus'); ipcMain.handle('rex:getSetupStatus', async () => ({ ok: true, needs_setup: false })); ipcMain.removeHandler('rex:getStatus'); ipcMain.handle('rex:getStatus', async () => ({ ok: true, status: 'ready' })); ipcMain.removeHandler('rex:startChatStream'); ipcMain.handle('rex:startChatStream', async (event, { message, streamId }) => { process.stdout.write(JSON.stringify({ handler: 'rex:startChatStream', message }) + '\n'); for (const token of streamTokens) { await sleep(240); event.sender.send('rex:chatToken', { streamId, token }); } event.sender.send('rex:chatDone', { streamId }); return { ok: true }; }); } async function waitForWindow() { for (let attempt = 0; attempt < 100; attempt += 1) { const win = BrowserWindow.getAllWindows()[0]; if (win && !win.isDestroyed()) { return win; } await sleep(200); } throw new Error('Timed out waiting for Electron window'); } app.whenReady().then(async () => { let ok = false; try { const win = await waitForWindow(); registerDeterministicAppHandlers(); await win.reload(); await sleep(1500); const result = await win.webContents.executeJavaScript(` (async () => { const prompt = 'Smoke test message'; const tokens = ${JSON.stringify(streamTokens)}; const finalReply = tokens.join(''); window.location.hash = '#/chat'; await new Promise((resolve) => setTimeout(resolve, 700)); window.__chatVerify = { partialSnapshots: [], finalReply, preloadStreamApiAvailable: typeof window.rex.sendChatStream === 'function' }; if (!window.__chatVerify.preloadStreamApiAvailable) { throw new Error('Preload sendChatStream API is unavailable'); } const textarea = document.querySelector('textarea[aria-label="Chat message input"]'); if (!textarea) { throw new Error('Could not find chat textarea'); } const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set; if (!nativeSetter) { throw new Error('Could not resolve textarea value setter'); } nativeSetter.call(textarea, prompt); textarea.dispatchEvent(new Event('input', { bubbles: true })); const sendButton = document.querySelector('button[aria-label="Send message"]'); if (!sendButton) { throw new Error('Could not find send button'); } for (let attempt = 0; attempt < 20 && sendButton.disabled; attempt += 1) { await new Promise((resolve) => setTimeout(resolve, 50)); } if (sendButton.disabled) { throw new Error('Send button did not enable after entering a message'); } sendButton.click(); let sawUserMessage = false; let sawFinal = false; let lastText = ''; for (let attempt = 0; attempt < 60; attempt += 1) { await new Promise((resolve) => setTimeout(resolve, 120)); lastText = document.body.innerText; window.__chatVerify.partialSnapshots.push(lastText); if (lastText.includes(prompt)) sawUserMessage = true; if (lastText.includes(finalReply)) { sawFinal = true; break; } } const partialSnapshots = window.__chatVerify?.partialSnapshots || []; const sawFirstPartial = partialSnapshots.some((text) => text.includes('Alpha') && !text.includes(finalReply)); const sawSecondPartial = partialSnapshots.some((text) => text.includes('Alpha Beta') && !text.includes(finalReply)); if (!sawUserMessage) { throw new Error('User message was not rendered in chat UI'); } if (!sawFirstPartial || !sawSecondPartial) { throw new Error( 'Streaming partial reply was not rendered token-by-token: ' + JSON.stringify({ partialSnapshots, lastText }) ); } if (!sawFinal) { throw new Error('Final streamed reply was not rendered'); } return { prompt, finalReply, sawUserMessage, sawFirstPartial, sawSecondPartial, sawFinal, excerpt: lastText.slice(0, 800) }; })(); `, true); process.stdout.write(JSON.stringify({ ok: true, result }) + '\n'); ok = true; } catch (error) { process.stdout.write(JSON.stringify({ ok: false, error: String(error) }) + '\n'); } finally { setTimeout(() => app.quit(), 500); if (!ok) { process.exitCode = 1; } } });