diff --git a/packages/app/e2e/actions.ts b/packages/app/e2e/actions.ts index 90af177ed1..efd370d395 100644 --- a/packages/app/e2e/actions.ts +++ b/packages/app/e2e/actions.ts @@ -465,10 +465,13 @@ export async function waitSession(page: Page, input: { directory: string; sessio if (!slug) return false const resolved = await resolveSlug(slug).catch(() => undefined) if (!resolved || resolved.directory !== target) return false - if (input.sessionID && sessionIDFromUrl(page.url()) !== input.sessionID) return false + const current = sessionIDFromUrl(page.url()) + if (input.sessionID && current !== input.sessionID) return false + if (!input.sessionID && current) return false const state = await probeSession(page) if (input.sessionID && (!state || state.sessionID !== input.sessionID)) return false + if (!input.sessionID && state?.sessionID) return false if (state?.dir) { const dir = await resolveDirectory(state.dir).catch(() => state.dir ?? "") if (dir !== target) return false diff --git a/packages/app/e2e/session/session-composer-dock.spec.ts b/packages/app/e2e/session/session-composer-dock.spec.ts index 5b2e8a8c6f..f083bf3597 100644 --- a/packages/app/e2e/session/session-composer-dock.spec.ts +++ b/packages/app/e2e/session/session-composer-dock.spec.ts @@ -93,7 +93,7 @@ async function todoDock(page: any, sessionID: string) { const write = async (driver: ComposerDriverState | undefined) => { await page.evaluate( - (input) => { + (input: { event: string; sessionID: string; driver: ComposerDriverState | undefined }) => { const win = window as ComposerWindow const composer = win.__opencode_e2e?.composer if (!composer?.enabled) throw new Error("Composer e2e driver is not enabled") @@ -118,7 +118,7 @@ async function todoDock(page: any, sessionID: string) { } const read = () => - page.evaluate((sessionID) => { + page.evaluate((sessionID: string) => { const win = window as ComposerWindow return win.__opencode_e2e?.composer?.sessions?.[sessionID]?.probe ?? null }, sessionID) as Promise @@ -186,6 +186,8 @@ async function withMockPermission( opts: { child?: any } | undefined, fn: (state: { resolved: () => Promise }) => Promise, ) { + const listUrl = /\/permission(?:\?.*)?$/ + const replyUrls = [/\/session\/[^/]+\/permissions\/[^/?]+(?:\?.*)?$/, /\/permission\/[^/]+\/reply(?:\?.*)?$/] let pending = [ { ...request, @@ -204,7 +206,8 @@ async function withMockPermission( const reply = async (route: any) => { const url = new URL(route.request().url()) - const id = url.pathname.split("/").pop() + const parts = url.pathname.split("/").filter(Boolean) + const id = parts.at(-1) === "reply" ? parts.at(-2) : parts.at(-1) pending = pending.filter((item) => item.id !== id) await route.fulfill({ status: 200, @@ -213,8 +216,10 @@ async function withMockPermission( }) } - await page.route("**/permission", list) - await page.route("**/session/*/permissions/*", reply) + await page.route(listUrl, list) + for (const item of replyUrls) { + await page.route(item, reply) + } const sessionList = opts?.child ? async (route: any) => { @@ -242,8 +247,10 @@ async function withMockPermission( try { return await fn(state) } finally { - await page.unroute("**/permission", list) - await page.unroute("**/session/*/permissions/*", reply) + await page.unroute(listUrl, list) + for (const item of replyUrls) { + await page.unroute(item, reply) + } if (sessionList) await page.unroute("**/session?*", sessionList) } } diff --git a/packages/app/e2e/session/session-model-persistence.spec.ts b/packages/app/e2e/session/session-model-persistence.spec.ts index b758a3b3d8..36cbb0fbf1 100644 --- a/packages/app/e2e/session/session-model-persistence.spec.ts +++ b/packages/app/e2e/session/session-model-persistence.spec.ts @@ -28,7 +28,17 @@ type Footer = { type Probe = { dir?: string sessionID?: string - model?: { providerID: string; modelID: string } + agent?: string + model?: { providerID: string; modelID: string; name?: string } + variant?: string | null + pick?: { + agent?: string + model?: { providerID: string; modelID: string } + variant?: string | null + } + variants?: string[] + models?: Array<{ providerID: string; modelID: string; name: string }> + agents?: Array<{ name: string }> } const escape = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") @@ -50,6 +60,86 @@ async function probe(page: Page): Promise { }) } +async function currentModel(page: Page) { + await expect.poll(() => probe(page).then(modelKey), { timeout: 30_000 }).not.toBe(null) + const value = await probe(page).then(modelKey) + if (!value) throw new Error("Failed to resolve current model key") + return value +} + +async function waitControl(page: Page, key: "setAgent" | "setModel" | "setVariant") { + await expect + .poll( + () => + page.evaluate((key) => { + const win = window as Window & { + __opencode_e2e?: { + model?: { + controls?: Record + } + } + } + return !!win.__opencode_e2e?.model?.controls?.[key] + }, key), + { timeout: 30_000 }, + ) + .toBe(true) +} + +async function pickAgent(page: Page, value: string) { + await waitControl(page, "setAgent") + await page.evaluate((value) => { + const win = window as Window & { + __opencode_e2e?: { + model?: { + controls?: { + setAgent?: (value: string | undefined) => void + } + } + } + } + const fn = win.__opencode_e2e?.model?.controls?.setAgent + if (!fn) throw new Error("Model e2e agent control is not enabled") + fn(value) + }, value) +} + +async function pickModel(page: Page, value: { providerID: string; modelID: string }) { + await waitControl(page, "setModel") + await page.evaluate((value) => { + const win = window as Window & { + __opencode_e2e?: { + model?: { + controls?: { + setModel?: (value: { providerID: string; modelID: string } | undefined) => void + } + } + } + } + const fn = win.__opencode_e2e?.model?.controls?.setModel + if (!fn) throw new Error("Model e2e model control is not enabled") + fn(value) + }, value) +} + +async function pickVariant(page: Page, value: string) { + await waitControl(page, "setVariant") + await page.evaluate((value) => { + const win = window as Window & { + __opencode_e2e?: { + model?: { + controls?: { + setVariant?: (value: string | undefined) => void + } + } + } + } + const fn = win.__opencode_e2e?.model?.controls?.setVariant + if (!fn) throw new Error("Model e2e variant control is not enabled") + fn(value) + }, value) +} + async function read(page: Page): Promise