test: cover chat streaming browser contract (#766)

This commit is contained in:
Zhicheng Han
2026-05-15 12:43:57 +02:00
committed by GitHub
parent 312e7397eb
commit 09c554b446
2 changed files with 122 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
import { expect, test } from '@playwright/test'
import { authenticate, mockChatSocket, mockHermesApi, TEST_ACCESS_KEY } from './fixtures'
test('sends a chat run and renders streamed Socket.IO response events', async ({ page }) => {
await authenticate(page, TEST_ACCESS_KEY, 'research')
const api = await mockHermesApi(page)
await mockChatSocket(page)
await page.goto('/#/hermes/chat')
const input = page.getByPlaceholder('Type a message... (Enter to send, Shift+Enter for new line)')
await expect(input).toBeVisible()
await input.fill('Summarize the queue')
await page.getByRole('button', { name: 'Send' }).click()
await expect(page.locator('p').filter({ hasText: /^Summarize the queue$/ })).toBeVisible()
const socketState = await page.waitForFunction(() => {
const state = (window as any).__PW_CHAT_SOCKET__
return state?.emitted?.some((item: any) => item.event === 'run')
? {
socket: {
url: state.latest.url,
options: state.latest.options,
},
emitted: state.emitted,
}
: null
})
const { socket, emitted } = await socketState.jsonValue() as any
const run = emitted.find((item: any) => item.event === 'run')
expect(socket.url).toBe('/chat-run')
expect(socket.options.auth).toEqual({ token: TEST_ACCESS_KEY })
expect(socket.options.query).toEqual({ profile: 'research' })
expect(run.payload).toMatchObject({
input: 'Summarize the queue',
queue_id: expect.any(String),
session_id: expect.any(String),
source: 'api_server',
})
expect(run.payload.model).toBe('test-model')
const sessionId = run.payload.session_id
await page.evaluate((sid) => {
const socket = (window as any).__PW_CHAT_SOCKET__.latest
socket.__trigger('run.started', { event: 'run.started', session_id: sid, run_id: 'run-1' })
socket.__trigger('message.delta', { event: 'message.delta', session_id: sid, run_id: 'run-1', delta: 'Streaming ' })
socket.__trigger('message.delta', { event: 'message.delta', session_id: sid, run_id: 'run-1', delta: 'answer from Hermes' })
socket.__trigger('run.completed', {
event: 'run.completed',
session_id: sid,
run_id: 'run-1',
output: 'Streaming answer from Hermes',
inputTokens: 11,
outputTokens: 7,
})
}, sessionId)
await expect(page.getByText('Streaming answer from Hermes')).toBeVisible()
await expect(page.getByRole('button', { name: 'Send' })).toBeVisible()
expect(api.unexpectedRequests).toEqual([])
})
+59
View File
@@ -189,3 +189,62 @@ export async function authenticate(page: Page, accessKey = TEST_ACCESS_KEY, prof
}
}, { storedToken: accessKey, storedProfileName: profileName })
}
export async function mockChatSocket(page: Page) {
await page.route('**/node_modules/.vite/deps/socket__io-client.js*', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/javascript',
body: `
const state = window.__PW_CHAT_SOCKET__ || (window.__PW_CHAT_SOCKET__ = { sockets: [], emitted: [] })
function makeSocket(url, options) {
const listeners = new Map()
const onceListeners = new Map()
const socket = {
connected: true,
url,
options,
on(event, handler) {
const handlers = listeners.get(event) || []
handlers.push(handler)
listeners.set(event, handlers)
return this
},
once(event, handler) {
const handlers = onceListeners.get(event) || []
handlers.push(handler)
onceListeners.set(event, handlers)
return this
},
emit(event, payload) {
state.emitted.push({ event, payload })
return this
},
removeAllListeners() {
listeners.clear()
onceListeners.clear()
return this
},
disconnect() {
this.connected = false
return this
},
__trigger(event, payload) {
for (const handler of listeners.get(event) || []) handler(payload)
const handlers = onceListeners.get(event) || []
onceListeners.delete(event)
for (const handler of handlers) handler(payload)
},
}
state.sockets.push(socket)
state.latest = socket
return socket
}
export function io(url, options) {
return makeSocket(url, options)
}
export default { io }
`,
})
})
}