|
|
@@ -1,5 +1,8 @@
|
|
|
import { test as base, expect, type Page } from "@playwright/test"
|
|
|
+import { ManagedRuntime } from "effect"
|
|
|
import type { E2EWindow } from "../src/testing/terminal"
|
|
|
+import type { Item, Reply, Usage } from "../../opencode/test/lib/llm-server"
|
|
|
+import { TestLLMServer } from "../../opencode/test/lib/llm-server"
|
|
|
import {
|
|
|
healthPhase,
|
|
|
cleanupSession,
|
|
|
@@ -13,6 +16,24 @@ import {
|
|
|
} from "./actions"
|
|
|
import { createSdk, dirSlug, getWorktree, sessionPath } from "./utils"
|
|
|
|
|
|
+type LLMFixture = {
|
|
|
+ url: string
|
|
|
+ push: (...input: (Item | Reply)[]) => Promise<void>
|
|
|
+ text: (value: string, opts?: { usage?: Usage }) => Promise<void>
|
|
|
+ tool: (name: string, input: unknown) => Promise<void>
|
|
|
+ toolHang: (name: string, input: unknown) => Promise<void>
|
|
|
+ reason: (value: string, opts?: { text?: string; usage?: Usage }) => Promise<void>
|
|
|
+ fail: (message?: unknown) => Promise<void>
|
|
|
+ error: (status: number, body: unknown) => Promise<void>
|
|
|
+ hang: () => Promise<void>
|
|
|
+ hold: (value: string, wait: PromiseLike<unknown>) => Promise<void>
|
|
|
+ hits: () => Promise<Array<{ url: URL; body: Record<string, unknown> }>>
|
|
|
+ calls: () => Promise<number>
|
|
|
+ wait: (count: number) => Promise<void>
|
|
|
+ inputs: () => Promise<Record<string, unknown>[]>
|
|
|
+ pending: () => Promise<number>
|
|
|
+}
|
|
|
+
|
|
|
export const settingsKey = "settings.v3"
|
|
|
|
|
|
const seedModel = (() => {
|
|
|
@@ -26,6 +47,7 @@ const seedModel = (() => {
|
|
|
})()
|
|
|
|
|
|
type TestFixtures = {
|
|
|
+ llm: LLMFixture
|
|
|
sdk: ReturnType<typeof createSdk>
|
|
|
gotoSession: (sessionID?: string) => Promise<void>
|
|
|
withProject: <T>(
|
|
|
@@ -36,7 +58,11 @@ type TestFixtures = {
|
|
|
trackSession: (sessionID: string, directory?: string) => void
|
|
|
trackDirectory: (directory: string) => void
|
|
|
}) => Promise<T>,
|
|
|
- options?: { extra?: string[] },
|
|
|
+ options?: {
|
|
|
+ extra?: string[]
|
|
|
+ model?: { providerID: string; modelID: string }
|
|
|
+ setup?: (directory: string) => Promise<void>
|
|
|
+ },
|
|
|
) => Promise<T>
|
|
|
}
|
|
|
|
|
|
@@ -46,6 +72,31 @@ type WorkerFixtures = {
|
|
|
}
|
|
|
|
|
|
export const test = base.extend<TestFixtures, WorkerFixtures>({
|
|
|
+ llm: async ({}, use) => {
|
|
|
+ const rt = ManagedRuntime.make(TestLLMServer.layer)
|
|
|
+ try {
|
|
|
+ const svc = await rt.runPromise(TestLLMServer.asEffect())
|
|
|
+ await use({
|
|
|
+ url: svc.url,
|
|
|
+ push: (...input) => rt.runPromise(svc.push(...input)),
|
|
|
+ text: (value, opts) => rt.runPromise(svc.text(value, opts)),
|
|
|
+ tool: (name, input) => rt.runPromise(svc.tool(name, input)),
|
|
|
+ toolHang: (name, input) => rt.runPromise(svc.toolHang(name, input)),
|
|
|
+ reason: (value, opts) => rt.runPromise(svc.reason(value, opts)),
|
|
|
+ fail: (message) => rt.runPromise(svc.fail(message)),
|
|
|
+ error: (status, body) => rt.runPromise(svc.error(status, body)),
|
|
|
+ hang: () => rt.runPromise(svc.hang),
|
|
|
+ hold: (value, wait) => rt.runPromise(svc.hold(value, wait)),
|
|
|
+ hits: () => rt.runPromise(svc.hits),
|
|
|
+ calls: () => rt.runPromise(svc.calls),
|
|
|
+ wait: (count) => rt.runPromise(svc.wait(count)),
|
|
|
+ inputs: () => rt.runPromise(svc.inputs),
|
|
|
+ pending: () => rt.runPromise(svc.pending),
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ await rt.dispose()
|
|
|
+ }
|
|
|
+ },
|
|
|
page: async ({ page }, use) => {
|
|
|
let boundary: string | undefined
|
|
|
setHealthPhase(page, "test")
|
|
|
@@ -99,7 +150,8 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
|
|
|
const root = await createTestProject()
|
|
|
const sessions = new Map<string, string>()
|
|
|
const dirs = new Set<string>()
|
|
|
- await seedStorage(page, { directory: root, extra: options?.extra })
|
|
|
+ await options?.setup?.(root)
|
|
|
+ await seedStorage(page, { directory: root, extra: options?.extra, model: options?.model })
|
|
|
|
|
|
const gotoSession = async (sessionID?: string) => {
|
|
|
await page.goto(sessionPath(root, sessionID))
|
|
|
@@ -133,7 +185,14 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
|
|
|
},
|
|
|
})
|
|
|
|
|
|
-async function seedStorage(page: Page, input: { directory: string; extra?: string[] }) {
|
|
|
+async function seedStorage(
|
|
|
+ page: Page,
|
|
|
+ input: {
|
|
|
+ directory: string
|
|
|
+ extra?: string[]
|
|
|
+ model?: { providerID: string; modelID: string }
|
|
|
+ },
|
|
|
+) {
|
|
|
await seedProjects(page, input)
|
|
|
await page.addInitScript((model: { providerID: string; modelID: string }) => {
|
|
|
const win = window as E2EWindow
|
|
|
@@ -158,7 +217,7 @@ async function seedStorage(page: Page, input: { directory: string; extra?: strin
|
|
|
variant: {},
|
|
|
}),
|
|
|
)
|
|
|
- }, seedModel)
|
|
|
+ }, input.model ?? seedModel)
|
|
|
}
|
|
|
|
|
|
export { expect }
|