fixtures.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import { test as base, expect, type Page } from "@playwright/test"
  2. import type { E2EWindow } from "../src/testing/terminal"
  3. import {
  4. healthPhase,
  5. cleanupSession,
  6. cleanupTestProject,
  7. createTestProject,
  8. setHealthPhase,
  9. seedProjects,
  10. sessionIDFromUrl,
  11. waitSlug,
  12. waitSession,
  13. } from "./actions"
  14. import { createSdk, dirSlug, getWorktree, sessionPath } from "./utils"
  15. export const settingsKey = "settings.v3"
  16. type TestFixtures = {
  17. sdk: ReturnType<typeof createSdk>
  18. gotoSession: (sessionID?: string) => Promise<void>
  19. withProject: <T>(
  20. callback: (project: {
  21. directory: string
  22. slug: string
  23. gotoSession: (sessionID?: string) => Promise<void>
  24. trackSession: (sessionID: string, directory?: string) => void
  25. trackDirectory: (directory: string) => void
  26. }) => Promise<T>,
  27. options?: { extra?: string[] },
  28. ) => Promise<T>
  29. }
  30. type WorkerFixtures = {
  31. directory: string
  32. slug: string
  33. }
  34. export const test = base.extend<TestFixtures, WorkerFixtures>({
  35. page: async ({ page }, use) => {
  36. let boundary: string | undefined
  37. setHealthPhase(page, "test")
  38. const consoleHandler = (msg: { text(): string }) => {
  39. const text = msg.text()
  40. if (!text.includes("[e2e:error-boundary]")) return
  41. if (healthPhase(page) === "cleanup") {
  42. console.warn(`[e2e:error-boundary][cleanup-warning]\n${text}`)
  43. return
  44. }
  45. boundary ||= text
  46. console.log(text)
  47. }
  48. const pageErrorHandler = (err: Error) => {
  49. console.log(`[e2e:pageerror] ${err.stack || err.message}`)
  50. }
  51. page.on("console", consoleHandler)
  52. page.on("pageerror", pageErrorHandler)
  53. await use(page)
  54. page.off("console", consoleHandler)
  55. page.off("pageerror", pageErrorHandler)
  56. if (boundary) throw new Error(boundary)
  57. },
  58. directory: [
  59. async ({}, use) => {
  60. const directory = await getWorktree()
  61. await use(directory)
  62. },
  63. { scope: "worker" },
  64. ],
  65. slug: [
  66. async ({ directory }, use) => {
  67. await use(dirSlug(directory))
  68. },
  69. { scope: "worker" },
  70. ],
  71. sdk: async ({ directory }, use) => {
  72. await use(createSdk(directory))
  73. },
  74. gotoSession: async ({ page, directory }, use) => {
  75. await seedStorage(page, { directory })
  76. const gotoSession = async (sessionID?: string) => {
  77. await page.goto(sessionPath(directory, sessionID))
  78. await waitSession(page, { directory, sessionID })
  79. }
  80. await use(gotoSession)
  81. },
  82. withProject: async ({ page }, use) => {
  83. await use(async (callback, options) => {
  84. const root = await createTestProject()
  85. const sessions = new Map<string, string>()
  86. const dirs = new Set<string>()
  87. await seedStorage(page, { directory: root, extra: options?.extra })
  88. const gotoSession = async (sessionID?: string) => {
  89. await page.goto(sessionPath(root, sessionID))
  90. await waitSession(page, { directory: root, sessionID })
  91. const current = sessionIDFromUrl(page.url())
  92. if (current) trackSession(current)
  93. }
  94. const trackSession = (sessionID: string, directory?: string) => {
  95. sessions.set(sessionID, directory ?? root)
  96. }
  97. const trackDirectory = (directory: string) => {
  98. if (directory !== root) dirs.add(directory)
  99. }
  100. try {
  101. await gotoSession()
  102. const slug = await waitSlug(page)
  103. return await callback({ directory: root, slug, gotoSession, trackSession, trackDirectory })
  104. } finally {
  105. setHealthPhase(page, "cleanup")
  106. await Promise.allSettled(
  107. Array.from(sessions, ([sessionID, directory]) => cleanupSession({ sessionID, directory })),
  108. )
  109. await Promise.allSettled(Array.from(dirs, (directory) => cleanupTestProject(directory)))
  110. await cleanupTestProject(root)
  111. setHealthPhase(page, "test")
  112. }
  113. })
  114. },
  115. })
  116. async function seedStorage(page: Page, input: { directory: string; extra?: string[] }) {
  117. await seedProjects(page, input)
  118. await page.addInitScript(() => {
  119. const win = window as E2EWindow
  120. win.__opencode_e2e = {
  121. ...win.__opencode_e2e,
  122. model: {
  123. enabled: true,
  124. },
  125. prompt: {
  126. enabled: true,
  127. },
  128. terminal: {
  129. enabled: true,
  130. terminals: {},
  131. },
  132. }
  133. localStorage.setItem(
  134. "opencode.global.dat:model",
  135. JSON.stringify({
  136. recent: [{ providerID: "opencode", modelID: "big-pickle" }],
  137. user: [],
  138. variant: {},
  139. }),
  140. )
  141. })
  142. }
  143. export { expect }