session-timeline-tool-state.spec.ts 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import { expect, test } from "@playwright/test"
  2. import {
  3. assistantMessage,
  4. partUpdated,
  5. setupTimeline,
  6. toolPart,
  7. userMessage,
  8. } from "../performance/timeline-stability/fixture"
  9. test("updates expanded web search links without resetting expansion", async ({ page }) => {
  10. const searchID = "prt_websearch_mutation"
  11. const input = { query: "timeline stability" }
  12. const timeline = await setupTimeline(page, {
  13. messages: [
  14. userMessage(),
  15. assistantMessage([toolPart(searchID, "websearch", "completed", input, { output: "https://example.com/one" })]),
  16. ],
  17. })
  18. const wrapper = page.locator(`[data-timeline-part-id="${searchID}"]`)
  19. const trigger = wrapper.locator('[data-slot="collapsible-trigger"]')
  20. await trigger.click()
  21. await expect(trigger).toHaveAttribute("aria-expanded", "true")
  22. await timeline.send(
  23. partUpdated(
  24. toolPart(searchID, "websearch", "completed", input, {
  25. output: "https://example.com/one\nhttps://example.com/two",
  26. }),
  27. ),
  28. 300,
  29. )
  30. await expect(trigger).toHaveAttribute("aria-expanded", "true")
  31. await expect(wrapper.locator('a[href="https://example.com/two"]')).toBeVisible()
  32. })
  33. test("preserves an expanded tool error card across duplicate delivery", async ({ page }) => {
  34. const toolID = "prt_duplicate_error"
  35. const failed = toolPart(toolID, "bash", "error", { command: "exit 1" }, { error: "Command failed visibly" })
  36. const timeline = await setupTimeline(page, { messages: [userMessage(), assistantMessage([failed])] })
  37. const wrapper = page.locator(`[data-timeline-part-id="${toolID}"]`)
  38. const trigger = wrapper.locator('[data-slot="collapsible-trigger"]')
  39. await trigger.click()
  40. await expect(trigger).toHaveAttribute("aria-expanded", "true")
  41. await timeline.send(partUpdated(failed), 150)
  42. await timeline.send(partUpdated(failed), 250)
  43. await expect(trigger).toHaveAttribute("aria-expanded", "true")
  44. await expect(wrapper).toContainText("Command failed visibly")
  45. })
  46. test("renders multiple question answers and preserves open state on answer updates", async ({ page }) => {
  47. const questionID = "prt_multi_question"
  48. const input = {
  49. questions: [
  50. { header: "First", question: "First choice?", options: [] },
  51. { header: "Second", question: "Second choice?", options: [], multiple: true },
  52. ],
  53. }
  54. const timeline = await setupTimeline(page, {
  55. messages: [
  56. userMessage(),
  57. assistantMessage([
  58. toolPart(questionID, "question", "completed", input, { metadata: { answers: [["A"], ["B", "C"]] } }),
  59. ]),
  60. ],
  61. })
  62. const wrapper = page.locator(`[data-timeline-part-id="${questionID}"]`)
  63. const trigger = wrapper.locator('[data-slot="collapsible-trigger"]')
  64. await expect(trigger).toHaveAttribute("aria-expanded", "true")
  65. await timeline.send(
  66. partUpdated(
  67. toolPart(questionID, "question", "completed", input, { metadata: { answers: [["Updated"], ["B", "C"]] } }),
  68. ),
  69. 300,
  70. )
  71. await expect(trigger).toHaveAttribute("aria-expanded", "true")
  72. await expect(wrapper).toContainText("Updated")
  73. await expect(wrapper).toContainText("B, C")
  74. })