opencode/packages/app/e2e/regression/settings-loading.spec.ts

279 lines
13 KiB
TypeScript

import { expect, test } from "@playwright/test"
import { mockOpenCodeServer } from "../utils/mock-server"
const directory = "C:/Projects/settings-demo"
const sandboxes = Array.from({ length: 12 }, (_, index) => `${directory}/workspace-${index + 1}`)
test.use({ viewport: { width: 1440, height: 1000 }, colorScheme: "dark" })
test.beforeEach(async ({ page }) => {
await mockOpenCodeServer(page, {
directory,
project: {
id: "proj_settings_demo",
canonical: directory,
name: "Settings demo",
vcs: "git",
time: { created: 1700000000000, updated: 1700000000000 },
sandboxes,
},
provider: { all: [], connected: [], default: {} },
sessions: sandboxes.map((directory, index) => ({
id: `ses_settings_${index + 1}`,
title: `Workspace ${index + 1} session`,
directory,
projectID: "proj_settings_demo",
time: { created: 1700000000000, updated: 1700000000000 },
})),
pageMessages: () => ({ items: [] }),
})
await page.addInitScript((directory) => {
localStorage.setItem(
"opencode.global.dat:server",
JSON.stringify({ projects: { local: [{ worktree: directory, expanded: true }] } }),
)
}, directory)
await page.goto("/")
await page.getByRole("button", { name: "Settings", exact: true }).click()
await expect(page.getByTestId("settings-screen").getByRole("tab", { name: "Preferences" })).toBeVisible()
})
test("settings has its own route and returns through app history", async ({ page }) => {
const settings = page.getByTestId("settings-screen")
const home = page.getByRole("button", { name: "Home", exact: true })
await expect(page).toHaveURL("/settings")
await expect(home).toHaveAttribute("aria-pressed", "false")
await settings.getByRole("button", { name: "Back to app", exact: true }).click()
await expect(page).toHaveURL("/")
await expect(home).toHaveAttribute("aria-pressed", "true")
await page.keyboard.press("Control+]")
await expect(page).toHaveURL("/settings")
await expect(settings.getByRole("tab", { name: "Preferences", exact: true })).toBeVisible()
await expect(home).toHaveAttribute("aria-pressed", "false")
await home.click()
await expect(page).toHaveURL("/")
await expect(settings).toBeHidden()
await expect(home).toHaveAttribute("aria-pressed", "true")
})
test("new session shortcut leaves settings and opens a new session screen", async ({ page }) => {
const settings = page.getByTestId("settings-screen")
await expect(settings).toBeFocused()
await page.keyboard.press("Control+t")
await expect(page).toHaveURL(/\/new-session\?draftId=.+$/)
await expect(settings).toBeHidden()
await expect(page.locator('[data-component="composer-editor"]')).toBeEditable()
await expect(page.locator('[data-titlebar-tab][data-active="true"]')).toHaveCount(1)
})
test("recording a new session shortcut stays in settings until recording finishes", async ({ page }) => {
const settings = page.getByTestId("settings-screen")
await settings.getByRole("tab", { name: "Shortcuts", exact: true }).click()
const binding = settings.locator('[data-keybind-id="tab.new"]')
await binding.click()
await expect(binding).toHaveText("Press keys")
await page.keyboard.press("Control+t")
await expect(binding).toHaveText("Ctrl+T")
await expect(page).toHaveURL("/settings")
await expect(page.locator("[data-titlebar-tab]")).toHaveCount(0)
await page.keyboard.press("Control+t")
await expect(page).toHaveURL(/\/new-session\?draftId=.+$/)
await expect(settings).toBeHidden()
await expect(page.locator('[data-component="composer-editor"]')).toBeEditable()
})
test("workspaces opens without waiting for inventory or sessions", async ({ page }) => {
const inventory = Promise.withResolvers<void>()
const sessions = Promise.withResolvers<void>()
await page.route(
(url) => url.pathname === "/api/worktree",
async (route) => {
await inventory.promise
await route.fallback()
},
)
await page.route("**/api/session?*", async (route) => {
if (new URL(route.request().url()).searchParams.has("directory")) await sessions.promise
await route.fallback()
})
const settings = page.getByTestId("settings-screen")
const requested = page.waitForRequest(
(request) =>
new URL(request.url()).pathname === "/api/worktree" &&
new URL(request.url()).searchParams.get("location[directory]") === directory &&
request.method() === "GET",
)
await settings.getByRole("tab", { name: "Worktrees", exact: true }).click()
await requested
await expect(settings.getByRole("heading", { name: "Worktrees", exact: true })).toBeVisible()
await expect(settings.getByRole("button", { name: "Back to app" })).toBeVisible()
await expect(settings.getByText("No worktrees", { exact: true })).toHaveCount(0)
inventory.resolve()
await expect(settings.getByText(sandboxes[0], { exact: true })).toBeVisible()
await expect(settings.getByText("12 worktrees", { exact: true })).toBeVisible()
sessions.resolve()
await expect(settings.getByText("Workspace 1 session", { exact: true })).toBeVisible()
const refresh = Promise.withResolvers<void>()
await page.route(
(url) => url.pathname === "/api/worktree",
async (route) => {
await refresh.promise
await route.fallback()
},
)
await settings.getByRole("tab", { name: "Preferences", exact: true }).click()
await settings.getByRole("tab", { name: "Worktrees", exact: true }).click()
await expect(settings.getByText("Workspace 1 session", { exact: true })).toBeVisible()
refresh.resolve()
})
test("worktree deletion sends the project location separately from the target", async ({ page }) => {
const removed = new Set<string>()
await page.route(
(url) => url.pathname === "/api/worktree",
async (route) => {
if (route.request().method() === "GET") {
return route.fulfill({
json: [
{ directory },
...sandboxes.filter((item) => !removed.has(item)).map((directory) => ({ directory, strategy: "git" })),
],
})
}
if (route.request().method() === "DELETE") {
removed.add(route.request().postDataJSON().directory)
return route.fulfill({ status: 204 })
}
return route.fallback()
},
)
const settings = page.getByTestId("settings-screen")
await settings.getByRole("tab", { name: "Worktrees", exact: true }).click()
await expect(settings.getByText(sandboxes[0], { exact: true })).toBeVisible()
await settings.getByRole("button", { name: 'Delete worktree "workspace-1"?', exact: true }).click()
const confirmation = page.getByRole("dialog", { name: "Delete worktree", exact: true })
const remove = confirmation.getByRole("button", { name: "Delete worktree", exact: true })
await expect(remove).toBeEnabled()
const deleting = page.waitForRequest(
(request) => new URL(request.url()).pathname === "/api/worktree" && request.method() === "DELETE",
)
await remove.click()
const request = await deleting
expect(new URL(request.url()).searchParams.get("location[directory]")).toBe(directory)
expect(request.postDataJSON()).toEqual({ directory: sandboxes[0], force: true })
await expect(settings.getByText(sandboxes[0], { exact: true })).toHaveCount(0)
await expect(settings.getByText("11 worktrees", { exact: true })).toBeVisible()
})
test("extensions opens without waiting for MCPs", async ({ page }) => {
const mcps = Promise.withResolvers<void>()
await page.route("**/api/mcp", async (route) => {
await mcps.promise
await route.fulfill({
json: { location: { directory }, data: [{ name: "demo-mcp", status: { status: "connected" } }] },
})
})
const settings = page.getByTestId("settings-screen")
const requested = page.waitForRequest((request) => new URL(request.url()).pathname === "/api/mcp")
await settings.getByRole("tab", { name: "Extensions", exact: true }).click()
await requested
await expect(settings.getByRole("heading", { name: "Extensions", exact: true })).toBeVisible()
await expect(settings.getByRole("button", { name: "Back to app" })).toBeVisible()
mcps.resolve()
await settings.getByRole("tab", { name: "MCPs", exact: true }).click()
await expect(settings.getByRole("switch", { name: "demo-mcp" })).toBeChecked()
})
test("about opens without waiting for contributors", async ({ page }) => {
const contributors = Promise.withResolvers<void>()
const url = "https://api.github.com/repos/anomalyco/opencode/contributors?anon=1&per_page=1"
await page.route(url, async (route) => {
await contributors.promise
await route.fulfill({
json: [],
headers: {
"access-control-allow-origin": "*",
"access-control-expose-headers": "Link",
Link: `<${url}&page=1004>; rel="last"`,
},
})
})
const settings = page.getByTestId("settings-screen")
const requested = page.waitForRequest(url)
await settings.getByRole("tab", { name: "About", exact: true }).click()
await requested
await expect(settings.getByRole("tab", { name: "About", exact: true })).toHaveAttribute("aria-selected", "true")
await expect(settings.getByText("Released under the MIT License", { exact: true })).toBeVisible()
await expect(settings.getByText(/^Version /)).toBeVisible()
await expect(settings.getByText("OpenCode Desktop", { exact: true })).toHaveCount(0)
await expect(settings.getByText(/^v\d+\./)).toHaveCount(0)
await expect(settings.getByRole("link", { name: "935 others", exact: true })).toBeVisible()
await expect(settings.getByRole("button", { name: "Back to app" })).toBeVisible()
await settings.getByRole("tab", { name: "Preferences", exact: true }).click()
await expect(settings.getByRole("tab", { name: "Preferences", exact: true })).toHaveAttribute("aria-selected", "true")
await settings.getByRole("tab", { name: "About", exact: true }).click()
await expect(settings.getByRole("link", { name: "935 others", exact: true })).toBeVisible()
const website = settings.getByRole("link", { name: "www.opencode.ai", exact: true })
await website.focus()
contributors.resolve()
await expect(settings.getByRole("link", { name: "988 others", exact: true })).toBeVisible()
await expect(website).toBeFocused()
})
test("about is available in the mobile settings menu", async ({ page }) => {
await page.route("https://api.github.com/repos/anomalyco/opencode/contributors?*", (route) => route.abort("failed"))
await page.setViewportSize({ width: 390, height: 844 })
const settings = page.getByTestId("settings-screen")
await settings.getByRole("button", { name: "Preferences", exact: true }).click()
await page.getByRole("menuitemradio", { name: "About", exact: true }).click()
await expect(settings.getByRole("button", { name: "About", exact: true })).toBeVisible()
await expect(settings.getByText("Released under the MIT License", { exact: true })).toBeVisible()
await expect(settings.getByRole("link", { name: "935 others", exact: true })).toBeVisible()
await expect(settings.getByText("OpenCode Desktop", { exact: true })).toHaveCount(0)
await settings.getByRole("button", { name: "About", exact: true }).click()
await expect(page.getByRole("menuitemradio", { name: "About", exact: true })).toBeChecked()
})
test("about keeps its fallback when the contributor request fails", async ({ page }) => {
const contributors = Promise.withResolvers<void>()
const url = "https://api.github.com/repos/anomalyco/opencode/contributors?anon=1&per_page=1"
await page.route(url, async (route) => {
await contributors.promise
await route.abort("failed")
})
const settings = page.getByTestId("settings-screen")
const requested = page.waitForRequest(url)
await settings.getByRole("tab", { name: "About", exact: true }).click()
await requested
await expect(settings.getByRole("link", { name: "935 others", exact: true })).toBeVisible()
const failed = page.waitForEvent("requestfailed", (request) => request.url() === url)
contributors.resolve()
await failed
await expect(settings.getByRole("link", { name: "935 others", exact: true })).toBeVisible()
await expect(settings.getByText("Released under the MIT License", { exact: true })).toBeVisible()
await expect(settings.getByRole("tab", { name: "About", exact: true })).toHaveAttribute("aria-selected", "true")
})
test("workspace inventory uses the settings panel scroll area", async ({ page }) => {
const settings = page.getByTestId("settings-screen")
await settings.getByRole("tab", { name: "Worktrees", exact: true }).click()
await expect(settings.getByText("Workspace 1 session", { exact: true })).toBeVisible()
const list = settings.locator('[data-component="settings-list"]')
await expect(list).toHaveCSS("max-height", "none")
await expect(list).toHaveCSS("overflow-y", "visible")
await settings.getByText("Workspace 12 session", { exact: true }).scrollIntoViewIfNeeded()
await expect(settings.getByText("Workspace 12 session", { exact: true })).toBeInViewport()
await expect(settings.getByRole("button", { name: "Back to app" })).toBeInViewport()
await page.setViewportSize({ width: 390, height: 844 })
await expect(list).toHaveCSS("max-height", "none")
await expect(list).toHaveCSS("overflow-y", "visible")
await settings.getByText("Workspace 12 session", { exact: true }).scrollIntoViewIfNeeded()
await expect(settings.getByText("Workspace 12 session", { exact: true })).toBeInViewport()
})