summaryrefslogtreecommitdiffhomepage
path: root/packages/app/e2e
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-03-26 13:41:22 -0500
committerGitHub <[email protected]>2026-03-26 13:41:22 -0500
commitc7760b433b1bdbcaed7e7cd55d53b5b331f0f0fa (patch)
tree4d1a865b8890dc30767b66293923c15e2b3f6d24 /packages/app/e2e
parent2e6ac8ff49eabcb1b62c1bd504338e7449f80c6e (diff)
downloadopencode-c7760b433b1bdbcaed7e7cd55d53b5b331f0f0fa.tar.gz
opencode-c7760b433b1bdbcaed7e7cd55d53b5b331f0f0fa.zip
fix(app): more startup perf (#19288)
Diffstat (limited to 'packages/app/e2e')
-rw-r--r--packages/app/e2e/actions.ts5
-rw-r--r--packages/app/e2e/session/session-composer-dock.spec.ts21
-rw-r--r--packages/app/e2e/session/session-model-persistence.spec.ts192
3 files changed, 137 insertions, 81 deletions
diff --git a/packages/app/e2e/actions.ts b/packages/app/e2e/actions.ts
index 90af177ed..efd370d39 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 5b2e8a8c6..f083bf359 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<ComposerProbeState | null>
@@ -186,6 +186,8 @@ async function withMockPermission<T>(
opts: { child?: any } | undefined,
fn: (state: { resolved: () => Promise<void> }) => Promise<T>,
) {
+ const listUrl = /\/permission(?:\?.*)?$/
+ const replyUrls = [/\/session\/[^/]+\/permissions\/[^/?]+(?:\?.*)?$/, /\/permission\/[^/]+\/reply(?:\?.*)?$/]
let pending = [
{
...request,
@@ -204,7 +206,8 @@ async function withMockPermission<T>(
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<T>(
})
}
- 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<T>(
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 b758a3b3d..36cbb0fbf 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<Probe | null> {
})
}
+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<string, unknown>
+ }
+ }
+ }
+ 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<Footer> {
return {
agent: await text(page.locator(`${promptAgentSelector} [data-slot="select-select-trigger-value"]`).first()),
@@ -82,31 +172,15 @@ async function waitModel(page: Page, value: string) {
async function choose(page: Page, root: string, value: string) {
const select = page.locator(root)
await expect(select).toBeVisible()
- await select.locator('[data-action], [data-slot="select-select-trigger"]').first().click()
- const item = page
- .locator('[data-slot="select-select-item"]')
- .filter({ hasText: new RegExp(`^\\s*${escape(value)}\\s*$`) })
- .first()
- await expect(item).toBeVisible()
- await item.click()
+ await pickAgent(page, value)
}
async function variantCount(page: Page) {
- const select = page.locator(promptVariantSelector)
- await expect(select).toBeVisible()
- await select.locator('[data-slot="select-select-trigger"]').click()
- const count = await page.locator('[data-slot="select-select-item"]').count()
- await page.keyboard.press("Escape")
- return count
+ return (await probe(page))?.variants?.length ?? 0
}
async function agents(page: Page) {
- const select = page.locator(promptAgentSelector)
- await expect(select).toBeVisible()
- await select.locator('[data-action], [data-slot="select-select-trigger"]').first().click()
- const labels = await page.locator('[data-slot="select-select-item-label"]').allTextContents()
- await page.keyboard.press("Escape")
- return labels.map((item) => item.trim()).filter(Boolean)
+ return ((await probe(page))?.agents ?? []).map((item) => item.name).filter(Boolean)
}
async function ensureVariant(page: Page, directory: string): Promise<Footer> {
@@ -132,48 +206,23 @@ async function ensureVariant(page: Page, directory: string): Promise<Footer> {
async function chooseDifferentVariant(page: Page): Promise<Footer> {
const current = await read(page)
- const select = page.locator(promptVariantSelector)
- await expect(select).toBeVisible()
- await select.locator('[data-slot="select-select-trigger"]').click()
-
- const items = page.locator('[data-slot="select-select-item"]')
- const count = await items.count()
- if (count < 2) throw new Error("Current model has no alternate variant to select")
-
- for (let i = 0; i < count; i++) {
- const item = items.nth(i)
- const next = await text(item.locator('[data-slot="select-select-item-label"]').first())
- if (!next || next === current.variant) continue
- await item.click()
- return waitFooter(page, { agent: current.agent, model: current.model, variant: next })
- }
+ const next = (await probe(page))?.variants?.find((item) => item !== current.variant)
+ if (!next) throw new Error("Current model has no alternate variant to select")
- throw new Error("Failed to choose a different variant")
+ await pickVariant(page, next)
+ return waitFooter(page, { agent: current.agent, model: current.model, variant: next })
}
-async function chooseOtherModel(page: Page): Promise<Footer> {
- const current = await read(page)
- const button = page.locator(`${promptModelSelector} [data-action="prompt-model"]`)
- await expect(button).toBeVisible()
- await button.click()
-
- const dialog = page.getByRole("dialog")
- await expect(dialog).toBeVisible()
- const items = dialog.locator('[data-slot="list-item"]')
- const count = await items.count()
- expect(count).toBeGreaterThan(1)
-
- for (let i = 0; i < count; i++) {
- const item = items.nth(i)
- const selected = (await item.getAttribute("data-selected")) === "true"
- if (selected) continue
- await item.click()
- await expect(dialog).toHaveCount(0)
- await expect.poll(async () => (await read(page)).model !== current.model, { timeout: 30_000 }).toBe(true)
- return read(page)
- }
-
- throw new Error("Failed to choose a different model")
+async function chooseOtherModel(page: Page, skip: string[] = []): Promise<Footer> {
+ const current = await currentModel(page)
+ const next = (await probe(page))?.models?.find((item) => {
+ const key = `${item.providerID}:${item.modelID}`
+ return key !== current && !skip.includes(key)
+ })
+ if (!next) throw new Error("Failed to choose a different model")
+ await pickModel(page, { providerID: next.providerID, modelID: next.modelID })
+ await expect.poll(async () => (await read(page)).model, { timeout: 30_000 }).toBe(next.name)
+ return read(page)
}
async function goto(page: Page, directory: string, sessionID?: string) {
@@ -249,17 +298,14 @@ async function newWorkspaceSession(page: Page, slug: string) {
return waitSession(page, { directory: next.directory }).then((item) => item.directory)
}
-test("session model and variant restore per session without leaking into new sessions", async ({
- page,
- withProject,
-}) => {
+test("session model restore per session without leaking into new sessions", async ({ page, withProject }) => {
await page.setViewportSize({ width: 1440, height: 900 })
await withProject(async ({ directory, gotoSession, trackSession }) => {
await gotoSession()
- await ensureVariant(page, directory)
- const firstState = await chooseDifferentVariant(page)
+ const firstState = await chooseOtherModel(page)
+ const firstKey = await currentModel(page)
const first = await submit(page, `session variant ${Date.now()}`)
trackSession(first)
await waitUser(directory, first)
@@ -269,10 +315,10 @@ test("session model and variant restore per session without leaking into new ses
await waitFooter(page, firstState)
await gotoSession()
- const fresh = await ensureVariant(page, directory)
- expect(fresh.variant).not.toBe(firstState.variant)
+ const fresh = await read(page)
+ expect(fresh.model).not.toBe(firstState.model)
- const secondState = await chooseOtherModel(page)
+ const secondState = await chooseOtherModel(page, [firstKey])
const second = await submit(page, `session model ${Date.now()}`)
trackSession(second)
await waitUser(directory, second)
@@ -294,8 +340,8 @@ test("session model restore across workspaces", async ({ page, withProject }) =>
await withProject(async ({ directory: root, slug, gotoSession, trackDirectory, trackSession }) => {
await gotoSession()
- await ensureVariant(page, root)
- const firstState = await chooseDifferentVariant(page)
+ const firstState = await chooseOtherModel(page)
+ const firstKey = await currentModel(page)
const first = await submit(page, `root session ${Date.now()}`)
trackSession(first, root)
await waitUser(root, first)
@@ -307,7 +353,8 @@ test("session model restore across workspaces", async ({ page, withProject }) =>
const oneDir = await newWorkspaceSession(page, one.slug)
trackDirectory(oneDir)
- const secondState = await chooseOtherModel(page)
+ const secondState = await chooseOtherModel(page, [firstKey])
+ const secondKey = await currentModel(page)
const second = await submit(page, `workspace one ${Date.now()}`)
trackSession(second, oneDir)
await waitUser(oneDir, second)
@@ -316,8 +363,7 @@ test("session model restore across workspaces", async ({ page, withProject }) =>
const twoDir = await newWorkspaceSession(page, two.slug)
trackDirectory(twoDir)
- await ensureVariant(page, twoDir)
- const thirdState = await chooseDifferentVariant(page)
+ const thirdState = await chooseOtherModel(page, [firstKey, secondKey])
const third = await submit(page, `workspace two ${Date.now()}`)
trackSession(third, twoDir)
await waitUser(twoDir, third)