mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 11:39:41 +03:00
251 lines
11 KiB
TypeScript
251 lines
11 KiB
TypeScript
import { base64Encode } from "@opencode/util/encode"
|
|
import { expect, test } from "@playwright/test"
|
|
import { NO_PROVIDER, REMOTE_SERVER, SERVER, project, provider, session } from "../utils/app"
|
|
import { mockOpenCodeServer, mockServers } from "../utils/mock-server"
|
|
import { expectSessionTitle } from "../utils/waits"
|
|
|
|
test.use({ viewport: { width: 1280, height: 900 } })
|
|
|
|
for (const mode of ["failed", "stopped", "ready"] as const) {
|
|
test(`manages a ${mode} configured WSL server from the Servers page`, async ({ page }) => {
|
|
await mockOpenCodeServer(page, {
|
|
directory: "/repo",
|
|
project: project({ id: "proj_wsl_settings", directory: "/repo", name: "WSL project" }),
|
|
provider: NO_PROVIDER,
|
|
sessions: [],
|
|
pageMessages: () => ({ items: [] }),
|
|
})
|
|
await page.goto(`/e2e/utils/settings-wsl.html?${new URLSearchParams({ server: SERVER, mode })}`)
|
|
const settings = page.getByTestId("settings-screen")
|
|
const switcher = settings.getByRole("button", { name: /^Server / })
|
|
await settings.getByRole("tab", { name: "Projects", exact: true }).click()
|
|
await switcher.click()
|
|
await page.getByRole("menuitemradio", { name: /^Ubuntu/ }).click()
|
|
const unavailable = settings.getByRole("status").filter({ hasText: "Can't reach Ubuntu" })
|
|
|
|
if (mode !== "ready") await expect(unavailable).toBeVisible()
|
|
|
|
await settings.getByRole("tab", { name: "Servers", exact: true }).click()
|
|
const connection = settings.getByRole("region", { name: "Ubuntu", exact: true })
|
|
|
|
if (mode !== "ready") {
|
|
await connection.getByRole("button", { name: "More options", exact: true }).click()
|
|
await page.getByRole("menuitem", { name: "Retry start", exact: true }).click()
|
|
await expect(page.getByLabel("WSL actions")).toHaveText("start:wsl:Ubuntu")
|
|
}
|
|
|
|
await settings.getByRole("tab", { name: "Projects", exact: true }).click()
|
|
await expect(switcher).toHaveText("ServerUbuntu")
|
|
await expect(unavailable).toHaveCount(0)
|
|
await settings.getByRole("tab", { name: "Servers", exact: true }).click()
|
|
await connection.getByRole("button", { name: "Update OpenCode", exact: true }).click()
|
|
await expect(page.getByLabel("WSL actions")).toContainText("update:Ubuntu")
|
|
await expect(connection.getByRole("button", { name: "Update OpenCode", exact: true })).toHaveCount(0)
|
|
|
|
await connection.getByRole("button", { name: "More options", exact: true }).click()
|
|
await page.getByRole("menuitem", { name: "Remove", exact: true }).click()
|
|
await expect(page.getByLabel("WSL actions")).toContainText("remove:wsl:Ubuntu")
|
|
await expect(connection).toHaveCount(0)
|
|
await expect(settings.getByRole("tab", { name: "Server", exact: true })).toBeEnabled()
|
|
})
|
|
}
|
|
|
|
test("adding a WSL server while the WSL plugin is down shows that WSL is unavailable", async ({ page }) => {
|
|
await mockOpenCodeServer(page, {
|
|
directory: "/repo",
|
|
project: project({ id: "proj_wsl_unavailable", directory: "/repo", name: "WSL project" }),
|
|
provider: NO_PROVIDER,
|
|
sessions: [],
|
|
pageMessages: () => ({ items: [] }),
|
|
})
|
|
await page.goto(`/e2e/utils/settings-wsl.html?${new URLSearchParams({ server: SERVER, mode: "ready" })}`)
|
|
const settings = page.getByTestId("settings-screen")
|
|
await settings.getByRole("tab", { name: "Servers", exact: true }).click()
|
|
await expect(settings.getByRole("region", { name: "Ubuntu", exact: true })).toHaveCount(1)
|
|
await page.getByRole("checkbox", { name: "WSL plugin" }).uncheck()
|
|
|
|
await settings.getByRole("button", { name: "Add server", exact: true }).press("Enter")
|
|
await page.getByRole("menuitem", { name: "Add WSL server", exact: true }).click()
|
|
const dialog = page.getByRole("dialog")
|
|
await expect(dialog.getByRole("heading", { name: "WSL unavailable", exact: true })).toBeVisible()
|
|
await expect(dialog.getByText("OpenCode could not verify WSL on this machine.", { exact: true })).toBeVisible()
|
|
await expect(dialog.getByText("WSL is unavailable", { exact: true })).toBeVisible()
|
|
})
|
|
|
|
test("adding an SSH server while the SSH plugin is down fails instead of connecting forever", async ({ page }) => {
|
|
await mockOpenCodeServer(page, {
|
|
directory: "/repo",
|
|
project: project({ id: "proj_ssh_unavailable", directory: "/repo", name: "SSH project" }),
|
|
provider: NO_PROVIDER,
|
|
sessions: [],
|
|
pageMessages: () => ({ items: [] }),
|
|
})
|
|
await page.goto(`/e2e/utils/settings-wsl.html?${new URLSearchParams({ server: SERVER, mode: "ready" })}`)
|
|
const settings = page.getByTestId("settings-screen")
|
|
await settings.getByRole("tab", { name: "Servers", exact: true }).click()
|
|
await expect(settings.getByRole("region", { name: "Ubuntu", exact: true })).toHaveCount(1)
|
|
await page.getByRole("checkbox", { name: "SSH plugin" }).uncheck()
|
|
|
|
await settings.getByRole("button", { name: "Add server", exact: true }).press("Enter")
|
|
await page.getByRole("menuitem", { name: "Add SSH server", exact: true }).click()
|
|
const dialog = page.getByRole("dialog")
|
|
await dialog.getByRole("textbox", { name: "Host or SSH command" }).fill("ssh devbox")
|
|
await dialog.getByRole("button", { name: "Add server", exact: true }).click()
|
|
await expect(dialog.getByRole("alert")).toHaveText("Request failed")
|
|
await expect(dialog.getByRole("button", { name: "Add server", exact: true })).toBeEnabled()
|
|
})
|
|
|
|
test("WSL session and draft tabs outlive the plugin going away until the server is removed", async ({ page }) => {
|
|
const directory = "/home/ubuntu/project"
|
|
const wsl = session({ id: "ses_wsl_tabs", directory, title: "WSL tabs session" })
|
|
|
|
const config = {
|
|
directory,
|
|
project: project({ id: "proj_wsl_tabs", directory }),
|
|
provider: NO_PROVIDER,
|
|
sessions: [wsl],
|
|
pageMessages: () => ({ items: [] }),
|
|
}
|
|
|
|
await mockServers(page, { [SERVER]: { ...config, sessions: [] }, [REMOTE_SERVER]: config })
|
|
const href = `/server/${base64Encode("wsl:Ubuntu")}/session/${wsl.id}`
|
|
await page.goto(
|
|
`/e2e/utils/settings-wsl.html?${new URLSearchParams({ server: SERVER, mode: "ready", wsl: REMOTE_SERVER, path: href })}`,
|
|
)
|
|
await expectSessionTitle(page, wsl.title)
|
|
await page.getByRole("button", { name: "New session", exact: true }).click()
|
|
await expect(page.getByRole("heading", { name: wsl.title })).toHaveCount(0)
|
|
const editor = page.locator('[data-component="composer-editor"]')
|
|
await editor.fill("keep this draft")
|
|
await expect(editor).toHaveText("keep this draft")
|
|
const tabs = page.locator("a[data-titlebar-tab-link]")
|
|
|
|
const expectTabs = async () => {
|
|
await expect(tabs).toHaveCount(2)
|
|
await expect(tabs.nth(0)).toHaveAttribute("href", href)
|
|
await expect(tabs.nth(1)).toHaveAttribute("href", /^\/new-session\?draftId=/)
|
|
}
|
|
|
|
await expectTabs()
|
|
|
|
const plugin = page.getByRole("checkbox", { name: "WSL plugin" })
|
|
await plugin.uncheck()
|
|
await expectTabs()
|
|
await expect(editor).toHaveText("keep this draft")
|
|
await plugin.check()
|
|
await expectTabs()
|
|
await tabs.nth(0).click()
|
|
await expectSessionTitle(page, wsl.title)
|
|
|
|
await page.keyboard.press("Control+,")
|
|
const settings = page.getByTestId("settings-screen")
|
|
await settings.getByRole("tab", { name: "Servers", exact: true }).click()
|
|
const connection = settings.getByRole("region", { name: "Ubuntu", exact: true })
|
|
await connection.getByRole("button", { name: "More options", exact: true }).click()
|
|
await page.getByRole("menuitem", { name: "Remove", exact: true }).click()
|
|
await expect(page.getByLabel("WSL actions")).toHaveText("remove:wsl:Ubuntu")
|
|
await expect(tabs).toHaveCount(0)
|
|
})
|
|
|
|
test("an SSH host that asks for sign-in again opens the dialog once per selected tab", async ({ page }) => {
|
|
const directory = "/home/box/project"
|
|
const box = session({ id: "ses_ssh_offer", directory, title: "SSH offer session" })
|
|
|
|
const config = {
|
|
directory,
|
|
project: project({ id: "proj_ssh_offer", directory }),
|
|
provider: provider(),
|
|
sessions: [box],
|
|
pageMessages: () => ({ items: [] }),
|
|
}
|
|
|
|
await mockServers(page, { [SERVER]: { ...config, sessions: [] }, [REMOTE_SERVER]: config })
|
|
const path = `/server/${base64Encode("ssh:box")}/session/${box.id}`
|
|
await page.goto(
|
|
`/e2e/utils/settings-wsl.html?${new URLSearchParams({ server: SERVER, mode: "ready", ssh: REMOTE_SERVER, path })}`,
|
|
)
|
|
await expectSessionTitle(page, box.title)
|
|
const connects = page.getByLabel("SSH connects")
|
|
const signIn = page.getByRole("button", { name: "Require SSH sign-in" })
|
|
const authenticate = page.getByRole("button", { name: "Authenticate", exact: true })
|
|
|
|
// The first request on the tab opens sign-in by itself; the fixture's host connects without a prompt.
|
|
await signIn.click()
|
|
await expect(connects).toHaveText("1")
|
|
await expectSessionTitle(page, box.title)
|
|
// A later request on the same tab only offers the button.
|
|
await signIn.click()
|
|
await expect(authenticate).toBeVisible()
|
|
await expect(connects).toHaveText("1")
|
|
await authenticate.click()
|
|
await expect(connects).toHaveText("2")
|
|
await expectSessionTitle(page, box.title)
|
|
|
|
// Another tab selected and this one selected again is a new selection, even while the host was ready.
|
|
const tabs = page.locator("a[data-titlebar-tab-link]")
|
|
await page.getByRole("button", { name: "New session", exact: true }).click()
|
|
await expect(tabs).toHaveCount(2)
|
|
await expect(tabs.nth(1)).toHaveAttribute("href", /^\/new-session\?draftId=/)
|
|
await expect(page.getByRole("heading", { name: box.title })).toHaveCount(0)
|
|
await tabs.nth(0).click()
|
|
await expectSessionTitle(page, box.title)
|
|
await signIn.click()
|
|
await expect(connects).toHaveText("3")
|
|
})
|
|
|
|
test("an open session moves to the controller a new SSH sign-in creates", async ({ page }) => {
|
|
const directory = "/home/box/project"
|
|
const model = { id: "box-model", name: "Box Model" }
|
|
|
|
const box = session({
|
|
id: "ses_ssh",
|
|
directory,
|
|
title: "SSH session",
|
|
model: { id: model.id, providerID: "opencode" },
|
|
})
|
|
|
|
const remote = { password: "ssh-1" }
|
|
const prompts: unknown[] = []
|
|
|
|
const config = {
|
|
directory,
|
|
project: project({ id: "proj_ssh", directory }),
|
|
provider: provider(model),
|
|
sessions: [box],
|
|
pageMessages: () => ({ items: [] }),
|
|
}
|
|
|
|
const servers = await mockServers(page, {
|
|
[SERVER]: { ...config, sessions: [] },
|
|
[REMOTE_SERVER]: {
|
|
...config,
|
|
password: () => remote.password,
|
|
onPrompt: (input) => prompts.push(input.body.text),
|
|
},
|
|
})
|
|
|
|
const path = `/server/${base64Encode("ssh:box")}/session/${box.id}`
|
|
await page.goto(
|
|
`/e2e/utils/settings-wsl.html?${new URLSearchParams({ server: SERVER, mode: "ready", ssh: REMOTE_SERVER, path })}`,
|
|
)
|
|
await expectSessionTitle(page, box.title)
|
|
|
|
// The remote server restarts behind the open tunnel with a new password and a renamed session. Health checks run
|
|
// every 10 s; the first one it rejects disposes the session's controller, which ends its event stream.
|
|
remote.password = "ssh-2"
|
|
box.title = "SSH session after restart"
|
|
await expect
|
|
.poll(async () => (await servers[REMOTE_SERVER]!.transport.connections()).map((item) => !!item.endedBy), {
|
|
timeout: 20_000,
|
|
})
|
|
.toEqual([true])
|
|
|
|
await page.getByRole("button", { name: "Drop SSH tunnel" }).click()
|
|
await page.getByRole("button", { name: "Reconnect", exact: true }).click()
|
|
await expectSessionTitle(page, box.title)
|
|
const editor = page.locator('[data-component="composer-editor"]')
|
|
await editor.fill("after sign-in")
|
|
await editor.press("Enter")
|
|
await expect.poll(() => prompts).toEqual(["after sign-in"])
|
|
})
|