summaryrefslogtreecommitdiffhomepage
path: root/packages/app/e2e/projects
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-02 14:17:28 -0400
committerGitHub <[email protected]>2026-04-02 18:17:28 +0000
commitc3ef69c8664ab84464d720f75eb9bf35289c1847 (patch)
treed7b1aa78355c39472804bdfbbd9db87722d0587b /packages/app/e2e/projects
parent363891126c9b2a909db0e724eb6377bd3d12b38b (diff)
downloadopencode-c3ef69c8664ab84464d720f75eb9bf35289c1847.tar.gz
opencode-c3ef69c8664ab84464d720f75eb9bf35289c1847.zip
test(app): add a golden path for mocked e2e prompts (#20593)
Diffstat (limited to 'packages/app/e2e/projects')
-rw-r--r--packages/app/e2e/projects/project-edit.spec.ts80
-rw-r--r--packages/app/e2e/projects/projects-close.spec.ts73
-rw-r--r--packages/app/e2e/projects/projects-switch.spec.ts139
-rw-r--r--packages/app/e2e/projects/workspace-new-session.spec.ts51
-rw-r--r--packages/app/e2e/projects/workspaces.spec.ts507
5 files changed, 400 insertions, 450 deletions
diff --git a/packages/app/e2e/projects/project-edit.spec.ts b/packages/app/e2e/projects/project-edit.spec.ts
index 7c20f29ec..1ffe4219d 100644
--- a/packages/app/e2e/projects/project-edit.spec.ts
+++ b/packages/app/e2e/projects/project-edit.spec.ts
@@ -1,43 +1,49 @@
import { test, expect } from "../fixtures"
import { clickMenuItem, openProjectMenu, openSidebar } from "../actions"
-test("dialog edit project updates name and startup script", async ({ page, withProject }) => {
+test("dialog edit project updates name and startup script", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
- await withProject(async ({ slug }) => {
- await openSidebar(page)
-
- const open = async () => {
- const menu = await openProjectMenu(page, slug)
- await clickMenuItem(menu, /^Edit$/i, { force: true })
-
- const dialog = page.getByRole("dialog")
- await expect(dialog).toBeVisible()
- await expect(dialog.getByRole("heading", { level: 2 })).toHaveText("Edit project")
- return dialog
- }
-
- const name = `e2e project ${Date.now()}`
- const startup = `echo e2e_${Date.now()}`
-
- const dialog = await open()
-
- const nameInput = dialog.getByLabel("Name")
- await nameInput.fill(name)
-
- const startupInput = dialog.getByLabel("Workspace startup script")
- await startupInput.fill(startup)
-
- await dialog.getByRole("button", { name: "Save" }).click()
- await expect(dialog).toHaveCount(0)
-
- const header = page.locator(".group\\/project").first()
- await expect(header).toContainText(name)
-
- const reopened = await open()
- await expect(reopened.getByLabel("Name")).toHaveValue(name)
- await expect(reopened.getByLabel("Workspace startup script")).toHaveValue(startup)
- await reopened.getByRole("button", { name: "Cancel" }).click()
- await expect(reopened).toHaveCount(0)
- })
+ await project.open()
+ await openSidebar(page)
+
+ const open = async () => {
+ const menu = await openProjectMenu(page, project.slug)
+ await clickMenuItem(menu, /^Edit$/i, { force: true })
+
+ const dialog = page.getByRole("dialog")
+ await expect(dialog).toBeVisible()
+ await expect(dialog.getByRole("heading", { level: 2 })).toHaveText("Edit project")
+ return dialog
+ }
+
+ const name = `e2e project ${Date.now()}`
+ const startup = `echo e2e_${Date.now()}`
+
+ const dialog = await open()
+
+ const nameInput = dialog.getByLabel("Name")
+ await nameInput.fill(name)
+
+ const startupInput = dialog.getByLabel("Workspace startup script")
+ await startupInput.fill(startup)
+
+ await dialog.getByRole("button", { name: "Save" }).click()
+ await expect(dialog).toHaveCount(0)
+
+ await expect
+ .poll(
+ async () => {
+ await page.reload()
+ await openSidebar(page)
+ const reopened = await open()
+ const value = await reopened.getByLabel("Name").inputValue()
+ const next = await reopened.getByLabel("Workspace startup script").inputValue()
+ await reopened.getByRole("button", { name: "Cancel" }).click()
+ await expect(reopened).toHaveCount(0)
+ return `${value}\n${next}`
+ },
+ { timeout: 30_000 },
+ )
+ .toBe(`${name}\n${startup}`)
})
diff --git a/packages/app/e2e/projects/projects-close.spec.ts b/packages/app/e2e/projects/projects-close.spec.ts
index 9454d683f..75e6f2ce6 100644
--- a/packages/app/e2e/projects/projects-close.spec.ts
+++ b/packages/app/e2e/projects/projects-close.spec.ts
@@ -3,51 +3,46 @@ import { createTestProject, cleanupTestProject, openSidebar, clickMenuItem, open
import { projectSwitchSelector } from "../selectors"
import { dirSlug } from "../utils"
-test("closing active project navigates to another open project", async ({ page, withProject }) => {
+test("closing active project navigates to another open project", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
const other = await createTestProject()
const otherSlug = dirSlug(other)
try {
- await withProject(
- async ({ slug }) => {
- await openSidebar(page)
-
- const otherButton = page.locator(projectSwitchSelector(otherSlug)).first()
- await expect(otherButton).toBeVisible()
- await otherButton.click()
-
- await expect(page).toHaveURL(new RegExp(`/${otherSlug}/session`))
-
- const menu = await openProjectMenu(page, otherSlug)
-
- await clickMenuItem(menu, /^Close$/i, { force: true })
-
- await expect
- .poll(
- () => {
- const pathname = new URL(page.url()).pathname
- if (new RegExp(`^/${slug}/session(?:/[^/]+)?/?$`).test(pathname)) return "project"
- if (pathname === "/") return "home"
- return ""
- },
- { timeout: 15_000 },
- )
- .toMatch(/^(project|home)$/)
-
- await expect(page).not.toHaveURL(new RegExp(`/${otherSlug}/session(?:[/?#]|$)`))
- await expect
- .poll(
- async () => {
- return await page.locator(projectSwitchSelector(otherSlug)).count()
- },
- { timeout: 15_000 },
- )
- .toBe(0)
- },
- { extra: [other] },
- )
+ await project.open({ extra: [other] })
+ await openSidebar(page)
+
+ const otherButton = page.locator(projectSwitchSelector(otherSlug)).first()
+ await expect(otherButton).toBeVisible()
+ await otherButton.click()
+
+ await expect(page).toHaveURL(new RegExp(`/${otherSlug}/session`))
+
+ const menu = await openProjectMenu(page, otherSlug)
+ await clickMenuItem(menu, /^Close$/i, { force: true })
+
+ await expect
+ .poll(
+ () => {
+ const pathname = new URL(page.url()).pathname
+ if (new RegExp(`^/${project.slug}/session(?:/[^/]+)?/?$`).test(pathname)) return "project"
+ if (pathname === "/") return "home"
+ return ""
+ },
+ { timeout: 15_000 },
+ )
+ .toMatch(/^(project|home)$/)
+
+ await expect(page).not.toHaveURL(new RegExp(`/${otherSlug}/session(?:[/?#]|$)`))
+ await expect
+ .poll(
+ async () => {
+ return await page.locator(projectSwitchSelector(otherSlug)).count()
+ },
+ { timeout: 15_000 },
+ )
+ .toBe(0)
} finally {
await cleanupTestProject(other)
}
diff --git a/packages/app/e2e/projects/projects-switch.spec.ts b/packages/app/e2e/projects/projects-switch.spec.ts
index f87a47cf0..67d09afd1 100644
--- a/packages/app/e2e/projects/projects-switch.spec.ts
+++ b/packages/app/e2e/projects/projects-switch.spec.ts
@@ -5,114 +5,89 @@ import {
createTestProject,
cleanupTestProject,
openSidebar,
- sessionIDFromUrl,
setWorkspacesEnabled,
waitSession,
- waitSessionSaved,
waitSlug,
- withNoReplyPrompt,
} from "../actions"
-import { projectSwitchSelector, promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
+import { projectSwitchSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
import { dirSlug, resolveDirectory } from "../utils"
-test("can switch between projects from sidebar", async ({ page, withProject }) => {
+test("can switch between projects from sidebar", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
const other = await createTestProject()
const otherSlug = dirSlug(other)
try {
- await withProject(
- async ({ directory }) => {
- await defocus(page)
+ await project.open({ extra: [other] })
+ await defocus(page)
- const currentSlug = dirSlug(directory)
- const otherButton = page.locator(projectSwitchSelector(otherSlug)).first()
- await expect(otherButton).toBeVisible()
- await otherButton.click()
+ const currentSlug = dirSlug(project.directory)
+ const otherButton = page.locator(projectSwitchSelector(otherSlug)).first()
+ await expect(otherButton).toBeVisible()
+ await otherButton.click()
- await expect(page).toHaveURL(new RegExp(`/${otherSlug}/session`))
+ await expect(page).toHaveURL(new RegExp(`/${otherSlug}/session`))
- const currentButton = page.locator(projectSwitchSelector(currentSlug)).first()
- await expect(currentButton).toBeVisible()
- await currentButton.click()
+ const currentButton = page.locator(projectSwitchSelector(currentSlug)).first()
+ await expect(currentButton).toBeVisible()
+ await currentButton.click()
- await expect(page).toHaveURL(new RegExp(`/${currentSlug}/session`))
- },
- { extra: [other] },
- )
+ await expect(page).toHaveURL(new RegExp(`/${currentSlug}/session`))
} finally {
await cleanupTestProject(other)
}
})
-test("switching back to a project opens the latest workspace session", async ({ page, withProject }) => {
+test("switching back to a project opens the latest workspace session", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
const other = await createTestProject()
const otherSlug = dirSlug(other)
try {
- await withProject(
- async ({ directory, slug, trackSession, trackDirectory }) => {
- await defocus(page)
- await setWorkspacesEnabled(page, slug, true)
- await openSidebar(page)
- await expect(page.getByRole("button", { name: "New workspace" }).first()).toBeVisible()
-
- await page.getByRole("button", { name: "New workspace" }).first().click()
-
- const raw = await waitSlug(page, [slug])
- const dir = base64Decode(raw)
- if (!dir) throw new Error(`Failed to decode workspace slug: ${raw}`)
- const space = await resolveDirectory(dir)
- const next = dirSlug(space)
- trackDirectory(space)
- await openSidebar(page)
-
- const item = page.locator(`${workspaceItemSelector(next)}, ${workspaceItemSelector(raw)}`).first()
- await expect(item).toBeVisible()
- await item.hover()
-
- const btn = page.locator(`${workspaceNewSessionSelector(next)}, ${workspaceNewSessionSelector(raw)}`).first()
- await expect(btn).toBeVisible()
- await btn.click({ force: true })
-
- await waitSession(page, { directory: space })
-
- // Create a session by sending a prompt
- const prompt = page.locator(promptSelector)
- await expect(prompt).toBeVisible()
- await withNoReplyPrompt(page, async () => {
- await prompt.fill("test")
- await page.keyboard.press("Enter")
- })
-
- // Wait for the URL to update with the new session ID
- await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 15_000 }).not.toBe("")
-
- const created = sessionIDFromUrl(page.url())
- if (!created) throw new Error(`Failed to get session ID from url: ${page.url()}`)
- trackSession(created, space)
- await waitSessionSaved(space, created)
-
- await expect(page).toHaveURL(new RegExp(`/${next}/session/${created}(?:[/?#]|$)`))
-
- await openSidebar(page)
-
- const otherButton = page.locator(projectSwitchSelector(otherSlug)).first()
- await expect(otherButton).toBeVisible()
- await otherButton.click({ force: true })
- await waitSession(page, { directory: other })
-
- const rootButton = page.locator(projectSwitchSelector(slug)).first()
- await expect(rootButton).toBeVisible()
- await rootButton.click({ force: true })
-
- await waitSession(page, { directory: space, sessionID: created })
- await expect(page).toHaveURL(new RegExp(`/session/${created}(?:[/?#]|$)`))
- },
- { extra: [other] },
- )
+ await project.open({ extra: [other] })
+ await defocus(page)
+ await setWorkspacesEnabled(page, project.slug, true)
+ await openSidebar(page)
+ await expect(page.getByRole("button", { name: "New workspace" }).first()).toBeVisible()
+
+ await page.getByRole("button", { name: "New workspace" }).first().click()
+
+ const raw = await waitSlug(page, [project.slug])
+ const dir = base64Decode(raw)
+ if (!dir) throw new Error(`Failed to decode workspace slug: ${raw}`)
+ const space = await resolveDirectory(dir)
+ const next = dirSlug(space)
+ project.trackDirectory(space)
+ await openSidebar(page)
+
+ const item = page.locator(`${workspaceItemSelector(next)}, ${workspaceItemSelector(raw)}`).first()
+ await expect(item).toBeVisible()
+ await item.hover()
+
+ const btn = page.locator(`${workspaceNewSessionSelector(next)}, ${workspaceNewSessionSelector(raw)}`).first()
+ await expect(btn).toBeVisible()
+ await btn.click({ force: true })
+
+ await waitSession(page, { directory: space })
+
+ const created = await project.user("test")
+
+ await expect(page).toHaveURL(new RegExp(`/${next}/session/${created}(?:[/?#]|$)`))
+
+ await openSidebar(page)
+
+ const otherButton = page.locator(projectSwitchSelector(otherSlug)).first()
+ await expect(otherButton).toBeVisible()
+ await otherButton.click({ force: true })
+ await waitSession(page, { directory: other })
+
+ const rootButton = page.locator(projectSwitchSelector(project.slug)).first()
+ await expect(rootButton).toBeVisible()
+ await rootButton.click({ force: true })
+
+ await waitSession(page, { directory: space, sessionID: created })
+ await expect(page).toHaveURL(new RegExp(`/session/${created}(?:[/?#]|$)`))
} finally {
await cleanupTestProject(other)
}
diff --git a/packages/app/e2e/projects/workspace-new-session.spec.ts b/packages/app/e2e/projects/workspace-new-session.spec.ts
index 835c8c99e..d9d010b4d 100644
--- a/packages/app/e2e/projects/workspace-new-session.spec.ts
+++ b/packages/app/e2e/projects/workspace-new-session.spec.ts
@@ -7,12 +7,9 @@ import {
setWorkspacesEnabled,
waitDir,
waitSession,
- waitSessionSaved,
waitSlug,
- withNoReplyPrompt,
} from "../actions"
-import { promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
-import { createSdk } from "../utils"
+import { workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
function item(space: { slug: string; raw: string }) {
return `${workspaceItemSelector(space.slug)}, ${workspaceItemSelector(space.raw)}`
@@ -51,47 +48,31 @@ async function openWorkspaceNewSession(page: Page, space: { slug: string; raw: s
}
async function createSessionFromWorkspace(
+ project: Parameters<typeof test>[0]["project"],
page: Page,
space: { slug: string; raw: string; directory: string },
text: string,
) {
await openWorkspaceNewSession(page, space)
-
- const prompt = page.locator(promptSelector)
- await expect(prompt).toBeVisible()
- await withNoReplyPrompt(page, async () => {
- await prompt.fill(text)
- await page.keyboard.press("Enter")
- })
-
- await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 15_000 }).not.toBe("")
- const sessionID = sessionIDFromUrl(page.url())
- if (!sessionID) throw new Error(`Failed to parse session id from url: ${page.url()}`)
-
- await waitSessionSaved(space.directory, sessionID)
- await createSdk(space.directory)
- .session.abort({ sessionID })
- .catch(() => undefined)
- return sessionID
+ return project.user(text)
}
-test("new sessions from sidebar workspace actions stay in selected workspace", async ({ page, withProject }) => {
+test("new sessions from sidebar workspace actions stay in selected workspace", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
- await withProject(async ({ slug: root, trackDirectory, trackSession }) => {
- await openSidebar(page)
- await setWorkspacesEnabled(page, root, true)
+ await project.open()
+ await openSidebar(page)
+ await setWorkspacesEnabled(page, project.slug, true)
- const first = await createWorkspace(page, root, [])
- trackDirectory(first.directory)
- await waitWorkspaceReady(page, first)
+ const first = await createWorkspace(page, project.slug, [])
+ project.trackDirectory(first.directory)
+ await waitWorkspaceReady(page, first)
- const second = await createWorkspace(page, root, [first.slug])
- trackDirectory(second.directory)
- await waitWorkspaceReady(page, second)
+ const second = await createWorkspace(page, project.slug, [first.slug])
+ project.trackDirectory(second.directory)
+ await waitWorkspaceReady(page, second)
- trackSession(await createSessionFromWorkspace(page, first, `workspace one ${Date.now()}`), first.directory)
- trackSession(await createSessionFromWorkspace(page, second, `workspace two ${Date.now()}`), second.directory)
- trackSession(await createSessionFromWorkspace(page, first, `workspace one again ${Date.now()}`), first.directory)
- })
+ await createSessionFromWorkspace(project, page, first, `workspace one ${Date.now()}`)
+ await createSessionFromWorkspace(project, page, second, `workspace two ${Date.now()}`)
+ await createSessionFromWorkspace(project, page, first, `workspace one again ${Date.now()}`)
})
diff --git a/packages/app/e2e/projects/workspaces.spec.ts b/packages/app/e2e/projects/workspaces.spec.ts
index 297cdb9fc..206baa47c 100644
--- a/packages/app/e2e/projects/workspaces.spec.ts
+++ b/packages/app/e2e/projects/workspaces.spec.ts
@@ -19,10 +19,10 @@ import {
waitDir,
waitSlug,
} from "../actions"
-import { dropdownMenuContentSelector, inlineInputSelector, workspaceItemSelector } from "../selectors"
-import { createSdk, dirSlug } from "../utils"
+import { inlineInputSelector, workspaceItemSelector } from "../selectors"
+import { dirSlug } from "../utils"
-async function setupWorkspaceTest(page: Page, project: { slug: string }) {
+async function setupWorkspaceTest(page: Page, project: { slug: string; trackDirectory: (directory: string) => void }) {
const rootSlug = project.slug
await openSidebar(page)
@@ -31,6 +31,7 @@ async function setupWorkspaceTest(page: Page, project: { slug: string }) {
await page.getByRole("button", { name: "New workspace" }).first().click()
const next = await resolveSlug(await waitSlug(page, [rootSlug]))
await waitDir(page, next.directory)
+ project.trackDirectory(next.directory)
await openSidebar(page)
@@ -52,62 +53,59 @@ async function setupWorkspaceTest(page: Page, project: { slug: string }) {
return { rootSlug, slug: next.slug, directory: next.directory }
}
-test("can enable and disable workspaces from project menu", async ({ page, withProject }) => {
+test("can enable and disable workspaces from project menu", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
+ await project.open()
- await withProject(async ({ slug }) => {
- await openSidebar(page)
+ await openSidebar(page)
- await expect(page.getByRole("button", { name: "New session" }).first()).toBeVisible()
- await expect(page.getByRole("button", { name: "New workspace" })).toHaveCount(0)
+ await expect(page.getByRole("button", { name: "New session" }).first()).toBeVisible()
+ await expect(page.getByRole("button", { name: "New workspace" })).toHaveCount(0)
- await setWorkspacesEnabled(page, slug, true)
- await expect(page.getByRole("button", { name: "New workspace" }).first()).toBeVisible()
- await expect(page.locator(workspaceItemSelector(slug)).first()).toBeVisible()
+ await setWorkspacesEnabled(page, project.slug, true)
+ await expect(page.getByRole("button", { name: "New workspace" }).first()).toBeVisible()
+ await expect(page.locator(workspaceItemSelector(project.slug)).first()).toBeVisible()
- await setWorkspacesEnabled(page, slug, false)
- await expect(page.getByRole("button", { name: "New session" }).first()).toBeVisible()
- await expect(page.locator(workspaceItemSelector(slug))).toHaveCount(0)
- })
+ await setWorkspacesEnabled(page, project.slug, false)
+ await expect(page.getByRole("button", { name: "New session" }).first()).toBeVisible()
+ await expect(page.locator(workspaceItemSelector(project.slug))).toHaveCount(0)
})
-test("can create a workspace", async ({ page, withProject }) => {
+test("can create a workspace", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
+ await project.open()
- await withProject(async ({ slug }) => {
- await openSidebar(page)
- await setWorkspacesEnabled(page, slug, true)
-
- await expect(page.getByRole("button", { name: "New workspace" }).first()).toBeVisible()
+ await openSidebar(page)
+ await setWorkspacesEnabled(page, project.slug, true)
- await page.getByRole("button", { name: "New workspace" }).first().click()
- const next = await resolveSlug(await waitSlug(page, [slug]))
- await waitDir(page, next.directory)
+ await expect(page.getByRole("button", { name: "New workspace" }).first()).toBeVisible()
- await openSidebar(page)
+ await page.getByRole("button", { name: "New workspace" }).first().click()
+ const next = await resolveSlug(await waitSlug(page, [project.slug]))
+ await waitDir(page, next.directory)
+ project.trackDirectory(next.directory)
- await expect
- .poll(
- async () => {
- const item = page.locator(workspaceItemSelector(next.slug)).first()
- try {
- await item.hover({ timeout: 500 })
- return true
- } catch {
- return false
- }
- },
- { timeout: 60_000 },
- )
- .toBe(true)
+ await openSidebar(page)
- await expect(page.locator(workspaceItemSelector(next.slug)).first()).toBeVisible()
+ await expect
+ .poll(
+ async () => {
+ const item = page.locator(workspaceItemSelector(next.slug)).first()
+ try {
+ await item.hover({ timeout: 500 })
+ return true
+ } catch {
+ return false
+ }
+ },
+ { timeout: 60_000 },
+ )
+ .toBe(true)
- await cleanupTestProject(next.directory)
- })
+ await expect(page.locator(workspaceItemSelector(next.slug)).first()).toBeVisible()
})
-test("non-git projects keep workspace mode disabled", async ({ page, withProject }) => {
+test("non-git projects keep workspace mode disabled", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
const nonGit = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-e2e-project-nongit-"))
@@ -116,260 +114,255 @@ test("non-git projects keep workspace mode disabled", async ({ page, withProject
await fs.writeFile(path.join(nonGit, "README.md"), "# e2e nongit\n")
try {
- await withProject(async () => {
- await page.goto(`/${nonGitSlug}/session`)
-
- await expect.poll(() => slugFromUrl(page.url()), { timeout: 30_000 }).not.toBe("")
+ await project.open({ extra: [nonGit] })
+ await page.goto(`/${nonGitSlug}/session`)
- const activeDir = await resolveSlug(slugFromUrl(page.url())).then((item) => item.directory)
- expect(path.basename(activeDir)).toContain("opencode-e2e-project-nongit-")
+ await expect.poll(() => slugFromUrl(page.url()), { timeout: 30_000 }).not.toBe("")
- await openSidebar(page)
- await expect(page.getByRole("button", { name: "New workspace" })).toHaveCount(0)
-
- const trigger = page.locator('[data-action="project-menu"]').first()
- const hasMenu = await trigger
- .isVisible()
- .then((x) => x)
- .catch(() => false)
- if (!hasMenu) return
+ const activeDir = await resolveSlug(slugFromUrl(page.url())).then((item) => item.directory)
+ expect(path.basename(activeDir)).toContain("opencode-e2e-project-nongit-")
- await trigger.click({ force: true })
-
- const menu = page.locator(dropdownMenuContentSelector).first()
- await expect(menu).toBeVisible()
-
- const toggle = menu.locator('[data-action="project-workspaces-toggle"]').first()
-
- await expect(toggle).toBeVisible()
- await expect(toggle).toBeDisabled()
- await expect(menu.getByRole("menuitem", { name: "New workspace" })).toHaveCount(0)
- })
+ await openSidebar(page)
+ await expect(page.getByRole("button", { name: "New workspace" })).toHaveCount(0)
+ await expect(page.getByRole("button", { name: "Create Git repository" })).toBeVisible()
} finally {
await cleanupTestProject(nonGit)
}
})
-test("can rename a workspace", async ({ page, withProject }) => {
+test("can rename a workspace", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
-
- await withProject(async (project) => {
- const { slug } = await setupWorkspaceTest(page, project)
-
- const rename = `e2e workspace ${Date.now()}`
- const menu = await openWorkspaceMenu(page, slug)
- await clickMenuItem(menu, /^Rename$/i, { force: true })
-
- await expect(menu).toHaveCount(0)
-
- const item = page.locator(workspaceItemSelector(slug)).first()
- await expect(item).toBeVisible()
- const input = item.locator(inlineInputSelector).first()
- await expect(input).toBeVisible()
- await input.fill(rename)
- await input.press("Enter")
- await expect(item).toContainText(rename)
- })
+ await project.open()
+
+ const { slug } = await setupWorkspaceTest(page, project)
+
+ const rename = `e2e workspace ${Date.now()}`
+ const menu = await openWorkspaceMenu(page, slug)
+ await clickMenuItem(menu, /^Rename$/i, { force: true })
+
+ await expect(menu).toHaveCount(0)
+
+ const item = page.locator(workspaceItemSelector(slug)).first()
+ await expect(item).toBeVisible()
+ const input = item.locator(inlineInputSelector).first()
+ const shown = await input
+ .isVisible()
+ .then((x) => x)
+ .catch(() => false)
+ if (!shown) {
+ const retry = await openWorkspaceMenu(page, slug)
+ await clickMenuItem(retry, /^Rename$/i, { force: true })
+ await expect(retry).toHaveCount(0)
+ }
+ await expect(input).toBeVisible()
+ await input.fill(rename)
+ await input.press("Enter")
+ await expect(item).toContainText(rename)
})
-test("can reset a workspace", async ({ page, sdk, withProject }) => {
+test("can reset a workspace", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
+ await project.open()
- await withProject(async (project) => {
- const { slug, directory: createdDir } = await setupWorkspaceTest(page, project)
+ const { slug, directory: createdDir } = await setupWorkspaceTest(page, project)
- const readme = path.join(createdDir, "README.md")
- const extra = path.join(createdDir, `e2e_reset_${Date.now()}.txt`)
- const original = await fs.readFile(readme, "utf8")
- const dirty = `${original.trimEnd()}\n\nchange_${Date.now()}\n`
- await fs.writeFile(readme, dirty, "utf8")
- await fs.writeFile(extra, `created_${Date.now()}\n`, "utf8")
+ const readme = path.join(createdDir, "README.md")
+ const extra = path.join(createdDir, `e2e_reset_${Date.now()}.txt`)
+ const original = await fs.readFile(readme, "utf8")
+ const dirty = `${original.trimEnd()}\n\nchange_${Date.now()}\n`
+ await fs.writeFile(readme, dirty, "utf8")
+ await fs.writeFile(extra, `created_${Date.now()}\n`, "utf8")
- await expect
- .poll(async () => {
- return await fs
- .stat(extra)
- .then(() => true)
- .catch(() => false)
- })
- .toBe(true)
+ await expect
+ .poll(async () => {
+ return await fs
+ .stat(extra)
+ .then(() => true)
+ .catch(() => false)
+ })
+ .toBe(true)
- await expect
- .poll(async () => {
- const files = await sdk.file
+ await expect
+ .poll(async () => {
+ const files = await project.sdk.file
+ .status({ directory: createdDir })
+ .then((r) => r.data ?? [])
+ .catch(() => [])
+ return files.length
+ })
+ .toBeGreaterThan(0)
+
+ const menu = await openWorkspaceMenu(page, slug)
+ await clickMenuItem(menu, /^Reset$/i, { force: true })
+ await confirmDialog(page, /^Reset workspace$/i)
+
+ await expect
+ .poll(
+ async () => {
+ const files = await project.sdk.file
.status({ directory: createdDir })
.then((r) => r.data ?? [])
.catch(() => [])
return files.length
- })
- .toBeGreaterThan(0)
-
- const menu = await openWorkspaceMenu(page, slug)
- await clickMenuItem(menu, /^Reset$/i, { force: true })
- await confirmDialog(page, /^Reset workspace$/i)
-
- await expect
- .poll(
- async () => {
- const files = await sdk.file
- .status({ directory: createdDir })
- .then((r) => r.data ?? [])
- .catch(() => [])
- return files.length
- },
- { timeout: 60_000 },
- )
- .toBe(0)
+ },
+ { timeout: 120_000 },
+ )
+ .toBe(0)
- await expect.poll(() => fs.readFile(readme, "utf8"), { timeout: 60_000 }).toBe(original)
+ await expect.poll(() => fs.readFile(readme, "utf8"), { timeout: 120_000 }).toBe(original)
- await expect
- .poll(async () => {
- return await fs
- .stat(extra)
- .then(() => true)
- .catch(() => false)
- })
- .toBe(false)
- })
+ await expect
+ .poll(async () => {
+ return await fs
+ .stat(extra)
+ .then(() => true)
+ .catch(() => false)
+ })
+ .toBe(false)
})
-test("can delete a workspace", async ({ page, withProject }) => {
+test("can reorder workspaces by drag and drop", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
+ await project.open()
+ const rootSlug = project.slug
- await withProject(async (project) => {
- const sdk = createSdk(project.directory)
- const { rootSlug, slug, directory } = await setupWorkspaceTest(page, project)
+ const listSlugs = async () => {
+ const nodes = page.locator('[data-component="sidebar-nav-desktop"] [data-component="workspace-item"]')
+ const slugs = await nodes.evaluateAll((els) => {
+ return els.map((el) => el.getAttribute("data-workspace") ?? "").filter((x) => x.length > 0)
+ })
+ return slugs
+ }
+ const waitReady = async (slug: string) => {
await expect
.poll(
async () => {
- const worktrees = await sdk.worktree
- .list()
- .then((r) => r.data ?? [])
- .catch(() => [] as string[])
- return worktrees.includes(directory)
+ const item = page.locator(workspaceItemSelector(slug)).first()
+ try {
+ await item.hover({ timeout: 500 })
+ return true
+ } catch {
+ return false
+ }
},
- { timeout: 30_000 },
+ { timeout: 60_000 },
)
.toBe(true)
+ }
- const menu = await openWorkspaceMenu(page, slug)
- await clickMenuItem(menu, /^Delete$/i, { force: true })
- await confirmDialog(page, /^Delete workspace$/i)
+ const drag = async (from: string, to: string) => {
+ const src = page.locator(workspaceItemSelector(from)).first()
+ const dst = page.locator(workspaceItemSelector(to)).first()
- await expect.poll(() => base64Decode(slugFromUrl(page.url()))).toBe(project.directory)
+ const a = await src.boundingBox()
+ const b = await dst.boundingBox()
+ if (!a || !b) throw new Error("Failed to resolve workspace drag bounds")
- await expect
- .poll(
- async () => {
- const worktrees = await sdk.worktree
- .list()
- .then((r) => r.data ?? [])
- .catch(() => [] as string[])
- return worktrees.includes(directory)
- },
- { timeout: 60_000 },
- )
- .toBe(false)
+ await page.mouse.move(a.x + a.width / 2, a.y + a.height / 2)
+ await page.mouse.down()
+ await page.mouse.move(b.x + b.width / 2, b.y + b.height / 2, { steps: 12 })
+ await page.mouse.up()
+ }
+
+ await openSidebar(page)
- await project.gotoSession()
+ await setWorkspacesEnabled(page, rootSlug, true)
+
+ const workspaces = [] as { directory: string; slug: string }[]
+ for (const _ of [0, 1]) {
+ const prev = slugFromUrl(page.url())
+ await page.getByRole("button", { name: "New workspace" }).first().click()
+ const next = await resolveSlug(await waitSlug(page, [rootSlug, prev]))
+ await waitDir(page, next.directory)
+ project.trackDirectory(next.directory)
+ workspaces.push(next)
await openSidebar(page)
- await expect(page.locator(workspaceItemSelector(slug))).toHaveCount(0, { timeout: 60_000 })
- await expect(page.locator(workspaceItemSelector(rootSlug)).first()).toBeVisible()
- })
+ }
+
+ if (workspaces.length !== 2) throw new Error("Expected two created workspaces")
+
+ const a = workspaces[0].slug
+ const b = workspaces[1].slug
+
+ await waitReady(a)
+ await waitReady(b)
+
+ const list = async () => {
+ const slugs = await listSlugs()
+ return slugs.filter((s) => s !== rootSlug && (s === a || s === b)).slice(0, 2)
+ }
+
+ await expect
+ .poll(async () => {
+ const slugs = await list()
+ return slugs.length === 2
+ })
+ .toBe(true)
+
+ const before = await list()
+ const from = before[1]
+ const to = before[0]
+ if (!from || !to) throw new Error("Failed to resolve initial workspace order")
+
+ await drag(from, to)
+
+ await expect.poll(async () => await list()).toEqual([from, to])
})
-test("can reorder workspaces by drag and drop", async ({ page, withProject }) => {
+test("can delete a workspace", async ({ page, project }) => {
await page.setViewportSize({ width: 1400, height: 800 })
- await withProject(async ({ slug: rootSlug }) => {
- const workspaces = [] as { directory: string; slug: string }[]
-
- const listSlugs = async () => {
- const nodes = page.locator('[data-component="sidebar-nav-desktop"] [data-component="workspace-item"]')
- const slugs = await nodes.evaluateAll((els) => {
- return els.map((el) => el.getAttribute("data-workspace") ?? "").filter((x) => x.length > 0)
- })
- return slugs
- }
-
- const waitReady = async (slug: string) => {
- await expect
- .poll(
- async () => {
- const item = page.locator(workspaceItemSelector(slug)).first()
- try {
- await item.hover({ timeout: 500 })
- return true
- } catch {
- return false
- }
- },
- { timeout: 60_000 },
- )
- .toBe(true)
- }
-
- const drag = async (from: string, to: string) => {
- const src = page.locator(workspaceItemSelector(from)).first()
- const dst = page.locator(workspaceItemSelector(to)).first()
-
- const a = await src.boundingBox()
- const b = await dst.boundingBox()
- if (!a || !b) throw new Error("Failed to resolve workspace drag bounds")
-
- await page.mouse.move(a.x + a.width / 2, a.y + a.height / 2)
- await page.mouse.down()
- await page.mouse.move(b.x + b.width / 2, b.y + b.height / 2, { steps: 12 })
- await page.mouse.up()
- }
-
- try {
- await openSidebar(page)
-
- await setWorkspacesEnabled(page, rootSlug, true)
-
- for (const _ of [0, 1]) {
- const prev = slugFromUrl(page.url())
- await page.getByRole("button", { name: "New workspace" }).first().click()
- const next = await resolveSlug(await waitSlug(page, [rootSlug, prev]))
- await waitDir(page, next.directory)
- workspaces.push(next)
-
- await openSidebar(page)
- }
-
- if (workspaces.length !== 2) throw new Error("Expected two created workspaces")
-
- const a = workspaces[0].slug
- const b = workspaces[1].slug
-
- await waitReady(a)
- await waitReady(b)
-
- const list = async () => {
- const slugs = await listSlugs()
- return slugs.filter((s) => s !== rootSlug && (s === a || s === b)).slice(0, 2)
- }
-
- await expect
- .poll(async () => {
- const slugs = await list()
- return slugs.length === 2
- })
- .toBe(true)
-
- const before = await list()
- const from = before[1]
- const to = before[0]
- if (!from || !to) throw new Error("Failed to resolve initial workspace order")
-
- await drag(from, to)
-
- await expect.poll(async () => await list()).toEqual([from, to])
- } finally {
- await Promise.all(workspaces.map((w) => cleanupTestProject(w.directory)))
- }
- })
+ await project.open()
+
+ const rootSlug = project.slug
+ await openSidebar(page)
+ await setWorkspacesEnabled(page, rootSlug, true)
+
+ const created = await project.sdk.worktree.create({ directory: project.directory }).then((res) => res.data)
+ if (!created?.directory) throw new Error("Failed to create workspace for delete test")
+
+ const directory = created.directory
+ const slug = dirSlug(directory)
+ project.trackDirectory(directory)
+
+ await page.reload()
+ await openSidebar(page)
+ await expect(page.locator(workspaceItemSelector(slug)).first()).toBeVisible({ timeout: 60_000 })
+
+ await expect
+ .poll(
+ async () => {
+ const worktrees = await project.sdk.worktree
+ .list()
+ .then((r) => r.data ?? [])
+ .catch(() => [] as string[])
+ return worktrees.includes(directory)
+ },
+ { timeout: 30_000 },
+ )
+ .toBe(true)
+
+ const menu = await openWorkspaceMenu(page, slug)
+ await clickMenuItem(menu, /^Delete$/i, { force: true })
+ await confirmDialog(page, /^Delete workspace$/i)
+
+ await expect.poll(() => base64Decode(slugFromUrl(page.url()))).toBe(project.directory)
+
+ await expect
+ .poll(
+ async () => {
+ const worktrees = await project.sdk.worktree
+ .list()
+ .then((r) => r.data ?? [])
+ .catch(() => [] as string[])
+ return worktrees.includes(directory)
+ },
+ { timeout: 60_000 },
+ )
+ .toBe(false)
+
+ await openSidebar(page)
+ await expect(page.locator(workspaceItemSelector(slug))).toHaveCount(0, { timeout: 60_000 })
+ await expect(page.locator(workspaceItemSelector(rootSlug)).first()).toBeVisible()
})