prompt-shell.spec.ts 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import type { ToolPart } from "@opencode-ai/sdk/v2/client"
  2. import { test, expect } from "../fixtures"
  3. import { sessionIDFromUrl } from "../actions"
  4. import { promptSelector } from "../selectors"
  5. import { createSdk } from "../utils"
  6. const isBash = (part: unknown): part is ToolPart => {
  7. if (!part || typeof part !== "object") return false
  8. if (!("type" in part) || part.type !== "tool") return false
  9. if (!("tool" in part) || part.tool !== "bash") return false
  10. return "state" in part
  11. }
  12. test("shell mode runs a command in the project directory", async ({ page, withProject }) => {
  13. test.setTimeout(120_000)
  14. await withProject(async ({ directory, gotoSession, trackSession }) => {
  15. const sdk = createSdk(directory)
  16. const prompt = page.locator(promptSelector)
  17. const cmd = process.platform === "win32" ? "dir" : "ls"
  18. await gotoSession()
  19. await prompt.click()
  20. await page.keyboard.type("!")
  21. await expect(prompt).toHaveAttribute("aria-label", /enter shell command/i)
  22. await page.keyboard.type(cmd)
  23. await page.keyboard.press("Enter")
  24. await expect(page).toHaveURL(/\/session\/[^/?#]+/, { timeout: 30_000 })
  25. const id = sessionIDFromUrl(page.url())
  26. if (!id) throw new Error(`Failed to parse session id from url: ${page.url()}`)
  27. trackSession(id, directory)
  28. await expect
  29. .poll(
  30. async () => {
  31. const list = await sdk.session.messages({ sessionID: id, limit: 50 }).then((x) => x.data ?? [])
  32. const msg = list.findLast(
  33. (item) => item.info.role === "assistant" && "path" in item.info && item.info.path.cwd === directory,
  34. )
  35. if (!msg) return
  36. const part = msg.parts
  37. .filter(isBash)
  38. .find((item) => item.state.input?.command === cmd && item.state.status === "completed")
  39. if (!part || part.state.status !== "completed") return
  40. const output =
  41. typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
  42. if (!output.includes("README.md")) return
  43. return { cwd: directory, output }
  44. },
  45. { timeout: 90_000 },
  46. )
  47. .toEqual(expect.objectContaining({ cwd: directory, output: expect.stringContaining("README.md") }))
  48. await expect(prompt).toHaveText("")
  49. })
  50. })