From b9edd23608deb2692049fc70218b4b2b2b87e103 Mon Sep 17 00:00:00 2001 From: adamelmore <2363879+adamdottv@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:47:31 -0600 Subject: [PATCH] test(app): new e2e smoke tests --- packages/app/e2e/models-visibility.spec.ts | 86 +++++++++++++++++++++ packages/app/e2e/server-default.spec.ts | 67 ++++++++++++++++ packages/app/e2e/settings-providers.spec.ts | 56 ++++++++++++++ packages/app/e2e/titlebar-history.spec.ts | 52 +++++++++++++ 4 files changed, 261 insertions(+) create mode 100644 packages/app/e2e/models-visibility.spec.ts create mode 100644 packages/app/e2e/server-default.spec.ts create mode 100644 packages/app/e2e/settings-providers.spec.ts create mode 100644 packages/app/e2e/titlebar-history.spec.ts diff --git a/packages/app/e2e/models-visibility.spec.ts b/packages/app/e2e/models-visibility.spec.ts new file mode 100644 index 000000000..680ba96a3 --- /dev/null +++ b/packages/app/e2e/models-visibility.spec.ts @@ -0,0 +1,86 @@ +import { test, expect } from "./fixtures" +import { modKey, promptSelector } from "./utils" + +test("hiding a model removes it from the model picker", async ({ page, gotoSession }) => { + await gotoSession() + + await page.locator(promptSelector).click() + await page.keyboard.type("/model") + + const command = page.locator('[data-slash-id="model.choose"]') + await expect(command).toBeVisible() + await command.hover() + await page.keyboard.press("Enter") + + const picker = page.getByRole("dialog") + await expect(picker).toBeVisible() + + const target = picker.locator('[data-slot="list-item"]').first() + await expect(target).toBeVisible() + + const key = await target.getAttribute("data-key") + if (!key) throw new Error("Failed to resolve model key from list item") + + const name = (await target.locator("span").first().innerText()).trim() + if (!name) throw new Error("Failed to resolve model name from list item") + + await page.keyboard.press("Escape") + await expect(picker).toHaveCount(0) + + const settings = page.getByRole("dialog") + + await page.keyboard.press(`${modKey}+Comma`).catch(() => undefined) + const opened = await settings + .waitFor({ state: "visible", timeout: 3000 }) + .then(() => true) + .catch(() => false) + + if (!opened) { + await page.getByRole("button", { name: "Settings" }).first().click() + await expect(settings).toBeVisible() + } + + await settings.getByRole("tab", { name: "Models" }).click() + const search = settings.getByPlaceholder("Search models") + await expect(search).toBeVisible() + await search.fill(name) + + const toggle = settings.locator('[data-component="switch"]').filter({ hasText: name }).first() + const input = toggle.locator('[data-slot="switch-input"]') + await expect(toggle).toBeVisible() + await expect(input).toHaveAttribute("aria-checked", "true") + await toggle.locator('[data-slot="switch-control"]').click() + await expect(input).toHaveAttribute("aria-checked", "false") + + await page.keyboard.press("Escape") + const closed = await settings + .waitFor({ state: "detached", timeout: 1500 }) + .then(() => true) + .catch(() => false) + if (!closed) { + await page.keyboard.press("Escape") + const closedSecond = await settings + .waitFor({ state: "detached", timeout: 1500 }) + .then(() => true) + .catch(() => false) + if (!closedSecond) { + await page.locator('[data-component="dialog-overlay"]').click({ position: { x: 5, y: 5 } }) + await expect(settings).toHaveCount(0) + } + } + + await page.locator(promptSelector).click() + await page.keyboard.type("/model") + await expect(command).toBeVisible() + await command.hover() + await page.keyboard.press("Enter") + + const pickerAgain = page.getByRole("dialog") + await expect(pickerAgain).toBeVisible() + await expect(pickerAgain.locator('[data-slot="list-item"]').first()).toBeVisible() + + await expect(pickerAgain.locator(`[data-slot="list-item"][data-key="${key}"]`)).toHaveCount(0) + + await page.keyboard.press("Escape") + await expect(pickerAgain).toHaveCount(0) +}) diff --git a/packages/app/e2e/server-default.spec.ts b/packages/app/e2e/server-default.spec.ts new file mode 100644 index 000000000..b6b16f0bc --- /dev/null +++ b/packages/app/e2e/server-default.spec.ts @@ -0,0 +1,67 @@ +import { test, expect } from "./fixtures" +import { serverName, serverUrl } from "./utils" + +const DEFAULT_SERVER_URL_KEY = "opencode.settings.dat:defaultServerUrl" + +test("can set a default server on web", async ({ page, gotoSession }) => { + await page.addInitScript((key: string) => { + try { + localStorage.removeItem(key) + } catch { + return + } + }, DEFAULT_SERVER_URL_KEY) + + await gotoSession() + + const status = page.getByRole("button", { name: "Status" }) + await expect(status).toBeVisible() + const popover = page.locator('[data-component="popover-content"]').filter({ hasText: "Manage servers" }) + + const ensurePopoverOpen = async () => { + if (await popover.isVisible()) return + await status.click() + await expect(popover).toBeVisible() + } + + await ensurePopoverOpen() + await popover.getByRole("button", { name: "Manage servers" }).click() + + const dialog = page.getByRole("dialog") + await expect(dialog).toBeVisible() + + const row = dialog.locator('[data-slot="list-item"]').filter({ hasText: serverName }).first() + await expect(row).toBeVisible() + + const menu = row.locator('[data-component="icon-button"]').last() + await menu.click() + await page.getByRole("menuitem", { name: "Set as default" }).click() + + await expect.poll(() => page.evaluate((key) => localStorage.getItem(key), DEFAULT_SERVER_URL_KEY)).toBe(serverUrl) + await expect(row.getByText("Default", { exact: true })).toBeVisible() + + await page.keyboard.press("Escape") + const closed = await dialog + .waitFor({ state: "detached", timeout: 1500 }) + .then(() => true) + .catch(() => false) + + if (!closed) { + await page.keyboard.press("Escape") + const closedSecond = await dialog + .waitFor({ state: "detached", timeout: 1500 }) + .then(() => true) + .catch(() => false) + + if (!closedSecond) { + await page.locator('[data-component="dialog-overlay"]').click({ position: { x: 5, y: 5 } }) + await expect(dialog).toHaveCount(0) + } + } + + await ensurePopoverOpen() + + const serverRow = popover.locator("button").filter({ hasText: serverName }).first() + await expect(serverRow).toBeVisible() + await expect(serverRow.getByText("Default", { exact: true })).toBeVisible() +}) diff --git a/packages/app/e2e/settings-providers.spec.ts b/packages/app/e2e/settings-providers.spec.ts new file mode 100644 index 000000000..326a9fad1 --- /dev/null +++ b/packages/app/e2e/settings-providers.spec.ts @@ -0,0 +1,56 @@ +import { test, expect } from "./fixtures" +import { modKey, promptSelector } from "./utils" + +test("smoke providers settings opens provider selector", async ({ page, gotoSession }) => { + await gotoSession() + + const dialog = page.getByRole("dialog") + + await page.keyboard.press(`${modKey}+Comma`).catch(() => undefined) + + const opened = await dialog + .waitFor({ state: "visible", timeout: 3000 }) + .then(() => true) + .catch(() => false) + + if (!opened) { + await page.getByRole("button", { name: "Settings" }).first().click() + await expect(dialog).toBeVisible() + } + + await dialog.getByRole("tab", { name: "Providers" }).click() + await expect(dialog.getByText("Connected providers", { exact: true })).toBeVisible() + await expect(dialog.getByText("Popular providers", { exact: true })).toBeVisible() + + await dialog.getByRole("button", { name: "Show more providers" }).click() + + const providerDialog = page.getByRole("dialog").filter({ has: page.getByPlaceholder("Search providers") }) + + await expect(providerDialog).toBeVisible() + await expect(providerDialog.getByPlaceholder("Search providers")).toBeVisible() + await expect(providerDialog.locator('[data-slot="list-item"]').first()).toBeVisible() + + await page.keyboard.press("Escape") + await expect(providerDialog).toHaveCount(0) + await expect(page.locator(promptSelector)).toBeVisible() + + const stillOpen = await dialog.isVisible().catch(() => false) + if (!stillOpen) return + + await page.keyboard.press("Escape") + const closed = await dialog + .waitFor({ state: "detached", timeout: 1500 }) + .then(() => true) + .catch(() => false) + if (closed) return + + await page.keyboard.press("Escape") + const closedSecond = await dialog + .waitFor({ state: "detached", timeout: 1500 }) + .then(() => true) + .catch(() => false) + if (closedSecond) return + + await page.locator('[data-component="dialog-overlay"]').click({ position: { x: 5, y: 5 } }) + await expect(dialog).toHaveCount(0) +}) diff --git a/packages/app/e2e/titlebar-history.spec.ts b/packages/app/e2e/titlebar-history.spec.ts new file mode 100644 index 000000000..b8141b982 --- /dev/null +++ b/packages/app/e2e/titlebar-history.spec.ts @@ -0,0 +1,52 @@ +import { test, expect } from "./fixtures" +import { modKey, promptSelector } from "./utils" + +test("titlebar back/forward navigates between sessions", async ({ page, slug, sdk, gotoSession }) => { + await page.setViewportSize({ width: 1400, height: 800 }) + + const stamp = Date.now() + const one = await sdk.session.create({ title: `e2e titlebar history 1 ${stamp}` }).then((r) => r.data) + const two = await sdk.session.create({ title: `e2e titlebar history 2 ${stamp}` }).then((r) => r.data) + + if (!one?.id) throw new Error("Session create did not return an id") + if (!two?.id) throw new Error("Session create did not return an id") + + try { + await gotoSession(one.id) + + const main = page.locator("main") + const collapsed = ((await main.getAttribute("class")) ?? "").includes("xl:border-l") + if (collapsed) { + await page.keyboard.press(`${modKey}+B`) + await expect(main).not.toHaveClass(/xl:border-l/) + } + + const link = page.locator(`[data-session-id="${two.id}"] a`).first() + await expect(link).toBeVisible() + await link.scrollIntoViewIfNeeded() + await link.click() + + await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`)) + await expect(page.locator(promptSelector)).toBeVisible() + + const back = page.getByRole("button", { name: "Go back" }) + const forward = page.getByRole("button", { name: "Go forward" }) + + await expect(back).toBeVisible() + await expect(back).toBeEnabled() + await back.click() + + await expect(page).toHaveURL(new RegExp(`/${slug}/session/${one.id}(?:\\?|#|$)`)) + await expect(page.locator(promptSelector)).toBeVisible() + + await expect(forward).toBeVisible() + await expect(forward).toBeEnabled() + await forward.click() + + await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`)) + await expect(page.locator(promptSelector)).toBeVisible() + } finally { + await sdk.session.delete({ sessionID: one.id }).catch(() => undefined) + await sdk.session.delete({ sessionID: two.id }).catch(() => undefined) + } +})