2
0

prompt-shell.spec.ts 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import type { ToolPart } from "@opencode-ai/sdk/v2/client"
  2. import { test, expect } from "../fixtures"
  3. import { closeDialog, openSettings, withSession } from "../actions"
  4. import { promptModelSelector, promptSelector, promptVariantSelector } from "../selectors"
  5. const isBash = (part: unknown): part is ToolPart => {
  6. if (!part || typeof part !== "object") return false
  7. if (!("type" in part) || part.type !== "tool") return false
  8. if (!("tool" in part) || part.tool !== "bash") return false
  9. return "state" in part
  10. }
  11. test("shell mode runs a command in the project directory", async ({ page, project }) => {
  12. test.setTimeout(120_000)
  13. await project.open()
  14. const cmd = process.platform === "win32" ? "dir" : "command ls"
  15. await withSession(project.sdk, `e2e shell ${Date.now()}`, async (session) => {
  16. project.trackSession(session.id)
  17. await project.gotoSession(session.id)
  18. const dialog = await openSettings(page)
  19. const toggle = dialog.locator('[data-action="settings-auto-accept-permissions"]').first()
  20. const input = toggle.locator('[data-slot="switch-input"]').first()
  21. await expect(toggle).toBeVisible()
  22. if ((await input.getAttribute("aria-checked")) !== "true") {
  23. await toggle.locator('[data-slot="switch-control"]').click()
  24. await expect(input).toHaveAttribute("aria-checked", "true")
  25. }
  26. await closeDialog(page, dialog)
  27. await project.shell(cmd)
  28. await expect
  29. .poll(
  30. async () => {
  31. const list = await project.sdk.session
  32. .messages({ sessionID: session.id, limit: 50 })
  33. .then((x) => x.data ?? [])
  34. const msg = list.findLast(
  35. (item) => item.info.role === "assistant" && "path" in item.info && item.info.path.cwd === project.directory,
  36. )
  37. if (!msg) return
  38. const part = msg.parts
  39. .filter(isBash)
  40. .find((item) => item.state.input?.command === cmd && item.state.status === "completed")
  41. if (!part || part.state.status !== "completed") return
  42. const output =
  43. typeof part.state.metadata?.output === "string" ? part.state.metadata.output : part.state.output
  44. if (!output.includes("README.md")) return
  45. return { cwd: project.directory, output }
  46. },
  47. { timeout: 90_000 },
  48. )
  49. .toEqual(expect.objectContaining({ cwd: project.directory, output: expect.stringContaining("README.md") }))
  50. })
  51. })
  52. test("shell mode unmounts model and variant controls", async ({ page, project }) => {
  53. await project.open()
  54. const prompt = page.locator(promptSelector).first()
  55. await expect(page.locator(promptModelSelector)).toHaveCount(1)
  56. await expect(page.locator(promptVariantSelector)).toHaveCount(1)
  57. await prompt.click()
  58. await page.keyboard.type("!")
  59. await expect(prompt).toHaveAttribute("aria-label", /enter shell command/i)
  60. await expect(page.locator(promptModelSelector)).toHaveCount(0)
  61. await expect(page.locator(promptVariantSelector)).toHaveCount(0)
  62. })