test: cover chat streaming browser contract (#766)
This commit is contained in:
@@ -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([])
|
||||
})
|
||||
@@ -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 }
|
||||
`,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user