prompt-shell.spec.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. import type { ToolPart } from "@opencode-ai/sdk/v2/client"
  2. import { test, expect } from "../fixtures"
  3. import { withSession } from "../actions"
  4. const isBash = (part: unknown): part is ToolPart => {
  5. if (!part || typeof part !== "object") return false
  6. if (!("type" in part) || part.type !== "tool") return false
  7. if (!("tool" in part) || part.tool !== "bash") return false
  8. return "state" in part
  9. }
  10. async function setAutoAccept(page: Parameters<typeof test>[0]["page"], enabled: boolean) {
  11. const button = page.locator('[data-action="prompt-permissions"]').first()
  12. await expect(button).toBeVisible()
  13. const pressed = (await button.getAttribute("aria-pressed")) === "true"
  14. if (pressed === enabled) return
  15. await button.click()
  16. await expect(button).toHaveAttribute("aria-pressed", enabled ? "true" : "false")
  17. }
  18. test("shell mode runs a command in the project directory", async ({ page, project }) => {
  19. test.setTimeout(120_000)
  20. await project.open()
  21. const cmd = process.platform === "win32" ? "dir" : "command ls"
  22. await withSession(project.sdk, `e2e shell ${Date.now()}`, async (session) => {
  23. project.trackSession(session.id)
  24. await project.gotoSession(session.id)
  25. await setAutoAccept(page, true)
  26. await project.shell(cmd)
  27. await expect
  28. .poll(
  29. async () => {
  30. const list = await project.sdk.session
  31. .messages({ sessionID: session.id, limit: 50 })
  32. .then((x) => x.data ?? [])
  33. const msg = list.findLast(
  34. (item) => item.info.role === "assistant" && "path" in item.info && item.info.path.cwd === project.directory,
  35. )
  36. if (!msg) return
  37. const part = msg.parts
  38. .filter(isBash)
  39. .find((item) => item.state.input?.command === cmd && item.state.status === "completed")
  40. if (!part || part.state.status !== "completed") return
  41. const output =
  42. typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
  43. if (!output.includes("README.md")) return
  44. return { cwd: project.directory, output }
  45. },
  46. { timeout: 90_000 },
  47. )
  48. .toEqual(expect.objectContaining({ cwd: project.directory, output: expect.stringContaining("README.md") }))
  49. })
  50. })