Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Dax Raad <d@ironbay.co> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Nabs <nabil@instafork.com> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Victor Navarro <vn4varro@gmail.com> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com> Co-authored-by: AidenGeunGeun <eastlandwyvern@gmail.com> Co-authored-by: Mark <geraint0923@users.noreply.github.com> Co-authored-by: Aiden Cline <aidenpcline@gmail.com> Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com> Co-authored-by: Jay <air@live.ca> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: BB84 <110078428+BB-84C@users.noreply.github.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: Sebastian <hasta84@gmail.com> Co-authored-by: Jérôme Benoit <jerome.benoit@sap.com> Co-authored-by: Test User <test@test.com> Co-authored-by: Simon Klee <hello@simonklee.dk> Co-authored-by: Rahul A Mistry <149420892+ProdigyRahul@users.noreply.github.com> Co-authored-by: Qiping Li <liqiping1991@gmail.com> Co-authored-by: liqiping <liqiping@msh.team> Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Co-authored-by: Matthias Reso <13337103+mreso@users.noreply.github.com> Co-authored-by: tobwen <1864057+tobwen@users.noreply.github.com> Co-authored-by: Daniel Polito <danielbpolito@gmail.com> Co-authored-by: opencode <noreply@opencode.ai>
116 lines
4.8 KiB
TypeScript
116 lines
4.8 KiB
TypeScript
import { expect, test } from "@playwright/test"
|
|
import {
|
|
assistantMessage,
|
|
partUpdated,
|
|
setupTimeline,
|
|
status,
|
|
textPart,
|
|
userMessage,
|
|
} from "../performance/timeline-stability/fixture"
|
|
|
|
test("keeps one connection open while delivering multiple events", async ({ page }) => {
|
|
const timeline = await setupTimeline(page)
|
|
|
|
const first = await timeline.transport.send(partUpdated(textPart("prt_transport_first", "first event")))
|
|
const second = await timeline.transport.send(partUpdated(textPart("prt_transport_second", "second event")))
|
|
|
|
await timeline.waitForPart("prt_transport_first")
|
|
await timeline.waitForPart("prt_transport_second")
|
|
expect(first.connectionID).toBe(second.connectionID)
|
|
expect(await timeline.transport.connections()).toHaveLength(1)
|
|
expect(await timeline.transport.acknowledgements()).toHaveLength(2)
|
|
})
|
|
|
|
test("delivers a burst from one stream chunk", async ({ page }) => {
|
|
const timeline = await setupTimeline(page)
|
|
const acknowledgements = await timeline.transport.burst([
|
|
partUpdated(textPart("prt_transport_burst_a", "burst a")),
|
|
partUpdated(textPart("prt_transport_burst_b", "burst b")),
|
|
])
|
|
|
|
await timeline.waitForPart("prt_transport_burst_a")
|
|
await timeline.waitForPart("prt_transport_burst_b")
|
|
expect(acknowledgements.map((item) => item.chunkCount)).toEqual([1, 1])
|
|
expect(new Set(acknowledgements.map((item) => item.deliveryID)).size).toBe(2)
|
|
})
|
|
|
|
test("parses split JSON and a split multibyte code point", async ({ page }) => {
|
|
const timeline = await setupTimeline(page)
|
|
const payload = partUpdated(textPart("prt_transport_split", "split snowman \u2603\u2603\u2603"))
|
|
const encoded = new TextEncoder().encode(`data: ${JSON.stringify(payload)}\n\n`)
|
|
const snowman = new TextEncoder().encode("\u2603")[0]!
|
|
const multibyte = encoded.indexOf(snowman)
|
|
|
|
const acknowledgement = await timeline.transport.split(payload, [9, multibyte + 1, multibyte + 2])
|
|
|
|
await timeline.waitForPart("prt_transport_split")
|
|
await expect(page.locator('[data-timeline-part-id="prt_transport_split"]')).toContainText(
|
|
"split snowman \u2603\u2603\u2603",
|
|
)
|
|
expect(acknowledgement.chunkCount).toBe(4)
|
|
})
|
|
|
|
test("delivers server heartbeat without mutating the timeline", async ({ page }) => {
|
|
const timeline = await setupTimeline(page, {
|
|
messages: [userMessage(), assistantMessage([textPart("prt_transport_steady", "steady")])],
|
|
})
|
|
const before = await page.locator("[data-timeline-row]").allTextContents()
|
|
|
|
await timeline.transport.heartbeat()
|
|
await timeline.settle()
|
|
|
|
expect(await page.locator("[data-timeline-row]").allTextContents()).toEqual(before)
|
|
expect(await timeline.transport.connections()).toHaveLength(1)
|
|
})
|
|
|
|
test("reconnects after a clean close", async ({ page }) => {
|
|
const timeline = await setupTimeline(page, { eventRetry: 10 })
|
|
const first = await timeline.transport.waitForConnection()
|
|
|
|
await timeline.transport.close()
|
|
const second = await timeline.transport.waitForConnection({ after: first.id })
|
|
await timeline.transport.send(partUpdated(textPart("prt_transport_close", "after close")))
|
|
|
|
await timeline.waitForPart("prt_transport_close")
|
|
expect(second.id).toBeGreaterThan(first.id)
|
|
expect((await timeline.transport.connections())[0]?.endedBy).toBe("close")
|
|
})
|
|
|
|
test("reconnects after a stream error", async ({ page }) => {
|
|
const timeline = await setupTimeline(page, { eventRetry: 10 })
|
|
const first = await timeline.transport.waitForConnection()
|
|
|
|
await timeline.transport.error("contract failure")
|
|
const second = await timeline.transport.waitForConnection({ after: first.id })
|
|
await timeline.transport.send(status("busy"))
|
|
|
|
await expect.poll(async () => (await timeline.transport.connections()).length).toBe(2)
|
|
expect(second.id).toBeGreaterThan(first.id)
|
|
expect((await timeline.transport.connections())[0]?.endedBy).toBe("error")
|
|
})
|
|
|
|
test("does not request replay when reconnecting the volatile V2 event stream", async ({ page }) => {
|
|
const timeline = await setupTimeline(page, { eventRetry: 10, protocol: "v2" })
|
|
const first = await timeline.transport.send(partUpdated(textPart("prt_transport_id", "event with id")), {
|
|
id: "timeline-event-7",
|
|
})
|
|
await timeline.waitForPart("prt_transport_id")
|
|
|
|
await timeline.transport.error("retry with event id")
|
|
const connection = await timeline.transport.waitForConnection({ after: first.connectionID })
|
|
|
|
expect(first.eventID).toBe("timeline-event-7")
|
|
expect(connection.headers["last-event-id"]).toBeUndefined()
|
|
})
|
|
|
|
test("passes through non-event fetches", async ({ page }) => {
|
|
const timeline = await setupTimeline(page)
|
|
|
|
const health = await page.evaluate(async () => {
|
|
const response = await fetch("/global/health")
|
|
return response.json()
|
|
})
|
|
|
|
expect(health).toEqual({ healthy: true })
|
|
expect(await timeline.transport.connections()).toHaveLength(1)
|
|
})
|