From 511c7abacaebb5e007139395078b6a848e5e9ea5 Mon Sep 17 00:00:00 2001 From: Filip <34747899+neriousy@users.noreply.github.com> Date: Sat, 31 Jan 2026 13:42:47 +0100 Subject: [PATCH] test(app): session actions (#11386) --- packages/app/e2e/actions.ts | 111 +++++++++++++++++ packages/app/e2e/app/server-default.spec.ts | 30 ++--- packages/app/e2e/app/session.spec.ts | 13 +- packages/app/e2e/app/titlebar-history.spec.ts | 56 ++++----- packages/app/e2e/files/file-open.spec.ts | 6 +- packages/app/e2e/files/file-viewer.spec.ts | 10 +- packages/app/e2e/models/model-picker.spec.ts | 5 +- .../app/e2e/models/models-visibility.spec.ts | 2 +- .../app/e2e/projects/project-edit.spec.ts | 7 +- .../app/e2e/projects/projects-close.spec.ts | 19 +-- packages/app/e2e/prompt/context.spec.ts | 17 +-- packages/app/e2e/prompt/prompt.spec.ts | 2 +- packages/app/e2e/selectors.ts | 18 +++ packages/app/e2e/session/session.spec.ts | 115 ++++++++++++++++++ .../e2e/settings/settings-providers.spec.ts | 2 +- .../e2e/sidebar/sidebar-session-links.spec.ts | 2 +- packages/app/script/e2e-local.ts | 2 +- turbo.json | 2 + 18 files changed, 313 insertions(+), 106 deletions(-) create mode 100644 packages/app/e2e/session/session.spec.ts diff --git a/packages/app/e2e/actions.ts b/packages/app/e2e/actions.ts index 3da16d3171..7308adc7e6 100644 --- a/packages/app/e2e/actions.ts +++ b/packages/app/e2e/actions.ts @@ -4,6 +4,17 @@ import os from "node:os" import path from "node:path" import { execSync } from "node:child_process" import { modKey, serverUrl } from "./utils" +import { + sessionItemSelector, + dropdownMenuTriggerSelector, + dropdownMenuContentSelector, + titlebarRightSelector, + popoverBodySelector, + listItemSelector, + listItemKeySelector, + listItemKeyStartsWithSelector, +} from "./selectors" +import type { createSdk } from "./utils" export async function defocus(page: Page) { await page.mouse.click(5, 5) @@ -158,3 +169,103 @@ export function sessionIDFromUrl(url: string) { const match = /\/session\/([^/?#]+)/.exec(url) return match?.[1] } + +export async function hoverSessionItem(page: Page, sessionID: string) { + const sessionEl = page.locator(sessionItemSelector(sessionID)).first() + await expect(sessionEl).toBeVisible() + await sessionEl.hover() + return sessionEl +} + +export async function openSessionMoreMenu(page: Page, sessionID: string) { + const sessionEl = await hoverSessionItem(page, sessionID) + + const menuTrigger = sessionEl.locator(dropdownMenuTriggerSelector).first() + await expect(menuTrigger).toBeVisible() + await menuTrigger.click() + + const menu = page.locator(dropdownMenuContentSelector).first() + await expect(menu).toBeVisible() + return menu +} + +export async function clickMenuItem(menu: Locator, itemName: string | RegExp, options?: { force?: boolean }) { + const item = menu.getByRole("menuitem").filter({ hasText: itemName }).first() + await expect(item).toBeVisible() + await item.click({ force: options?.force }) +} + +export async function confirmDialog(page: Page, buttonName: string | RegExp) { + const dialog = page.getByRole("dialog").first() + await expect(dialog).toBeVisible() + + const button = dialog.getByRole("button").filter({ hasText: buttonName }).first() + await expect(button).toBeVisible() + await button.click() +} + +export async function openSharePopover(page: Page) { + const rightSection = page.locator(titlebarRightSelector) + const shareButton = rightSection.getByRole("button", { name: "Share" }).first() + await expect(shareButton).toBeVisible() + + const popoverBody = page + .locator(popoverBodySelector) + .filter({ has: page.getByRole("button", { name: /^(Publish|Unpublish)$/ }) }) + .first() + + const opened = await popoverBody + .isVisible() + .then((x) => x) + .catch(() => false) + + if (!opened) { + await shareButton.click() + await expect(popoverBody).toBeVisible() + } + return { rightSection, popoverBody } +} + +export async function clickPopoverButton(page: Page, buttonName: string | RegExp) { + const button = page.getByRole("button").filter({ hasText: buttonName }).first() + await expect(button).toBeVisible() + await button.click() +} + +export async function clickListItem( + container: Locator | Page, + filter: string | RegExp | { key?: string; text?: string | RegExp; keyStartsWith?: string }, +): Promise { + let item: Locator + + if (typeof filter === "string" || filter instanceof RegExp) { + item = container.locator(listItemSelector).filter({ hasText: filter }).first() + } else if (filter.keyStartsWith) { + item = container.locator(listItemKeyStartsWithSelector(filter.keyStartsWith)).first() + } else if (filter.key) { + item = container.locator(listItemKeySelector(filter.key)).first() + } else if (filter.text) { + item = container.locator(listItemSelector).filter({ hasText: filter.text }).first() + } else { + throw new Error("Invalid filter provided to clickListItem") + } + + await expect(item).toBeVisible() + await item.click() + return item +} + +export async function withSession( + sdk: ReturnType, + title: string, + callback: (session: { id: string; title: string }) => Promise, +): Promise { + const session = await sdk.session.create({ title }).then((r) => r.data) + if (!session?.id) throw new Error("Session create did not return an id") + + try { + return await callback(session) + } finally { + await sdk.session.delete({ sessionID: session.id }).catch(() => undefined) + } +} diff --git a/packages/app/e2e/app/server-default.spec.ts b/packages/app/e2e/app/server-default.spec.ts index 6f44ded1a2..adbc83473b 100644 --- a/packages/app/e2e/app/server-default.spec.ts +++ b/packages/app/e2e/app/server-default.spec.ts @@ -1,5 +1,6 @@ import { test, expect } from "../fixtures" import { serverName, serverUrl } from "../utils" +import { clickListItem, closeDialog, clickMenuItem } from "../actions" const DEFAULT_SERVER_URL_KEY = "opencode.settings.dat:defaultServerUrl" @@ -33,31 +34,18 @@ test("can set a default server on web", async ({ page, gotoSession }) => { const row = dialog.locator('[data-slot="list-item"]').filter({ hasText: serverName }).first() await expect(row).toBeVisible() - const menu = row.locator('[data-component="icon-button"]').last() - await menu.click() - await page.getByRole("menuitem", { name: "Set as default" }).click() + const menuTrigger = row.locator('[data-slot="dropdown-menu-trigger"]').first() + await expect(menuTrigger).toBeVisible() + await menuTrigger.click({ force: true }) + + const menu = page.locator('[data-component="dropdown-menu-content"]').first() + await expect(menu).toBeVisible() + await clickMenuItem(menu, /set as default/i) await expect.poll(() => page.evaluate((key) => localStorage.getItem(key), DEFAULT_SERVER_URL_KEY)).toBe(serverUrl) await expect(row.getByText("Default", { exact: true })).toBeVisible() - await page.keyboard.press("Escape") - const closed = await dialog - .waitFor({ state: "detached", timeout: 1500 }) - .then(() => true) - .catch(() => false) - - if (!closed) { - await page.keyboard.press("Escape") - const closedSecond = await dialog - .waitFor({ state: "detached", timeout: 1500 }) - .then(() => true) - .catch(() => false) - - if (!closedSecond) { - await page.locator('[data-component="dialog-overlay"]').click({ position: { x: 5, y: 5 } }) - await expect(dialog).toHaveCount(0) - } - } + await closeDialog(page, dialog) await ensurePopoverOpen() diff --git a/packages/app/e2e/app/session.spec.ts b/packages/app/e2e/app/session.spec.ts index d35af7ef77..c7fdfdc542 100644 --- a/packages/app/e2e/app/session.spec.ts +++ b/packages/app/e2e/app/session.spec.ts @@ -1,21 +1,16 @@ import { test, expect } from "../fixtures" import { promptSelector } from "../selectors" +import { withSession } from "../actions" test("can open an existing session and type into the prompt", async ({ page, sdk, gotoSession }) => { const title = `e2e smoke ${Date.now()}` - const created = await sdk.session.create({ title }).then((r) => r.data) - if (!created?.id) throw new Error("Session create did not return an id") - const sessionID = created.id - - try { - await gotoSession(sessionID) + await withSession(sdk, title, async (session) => { + await gotoSession(session.id) const prompt = page.locator(promptSelector) await prompt.click() await page.keyboard.type("hello from e2e") await expect(prompt).toContainText("hello from e2e") - } finally { - await sdk.session.delete({ sessionID }).catch(() => undefined) - } + }) }) diff --git a/packages/app/e2e/app/titlebar-history.spec.ts b/packages/app/e2e/app/titlebar-history.spec.ts index c7ff6566c1..ec65dca0b3 100644 --- a/packages/app/e2e/app/titlebar-history.spec.ts +++ b/packages/app/e2e/app/titlebar-history.spec.ts @@ -1,48 +1,42 @@ import { test, expect } from "../fixtures" -import { openSidebar } from "../actions" +import { openSidebar, withSession } from "../actions" import { promptSelector } from "../selectors" test("titlebar back/forward navigates between sessions", async ({ page, slug, sdk, gotoSession }) => { await page.setViewportSize({ width: 1400, height: 800 }) const stamp = Date.now() - const one = await sdk.session.create({ title: `e2e titlebar history 1 ${stamp}` }).then((r) => r.data) - const two = await sdk.session.create({ title: `e2e titlebar history 2 ${stamp}` }).then((r) => r.data) - if (!one?.id) throw new Error("Session create did not return an id") - if (!two?.id) throw new Error("Session create did not return an id") + await withSession(sdk, `e2e titlebar history 1 ${stamp}`, async (one) => { + await withSession(sdk, `e2e titlebar history 2 ${stamp}`, async (two) => { + await gotoSession(one.id) - try { - await gotoSession(one.id) + await openSidebar(page) - await openSidebar(page) + const link = page.locator(`[data-session-id="${two.id}"] a`).first() + await expect(link).toBeVisible() + await link.scrollIntoViewIfNeeded() + await link.click() - const link = page.locator(`[data-session-id="${two.id}"] a`).first() - await expect(link).toBeVisible() - await link.scrollIntoViewIfNeeded() - await link.click() + await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`)) + await expect(page.locator(promptSelector)).toBeVisible() - await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`)) - await expect(page.locator(promptSelector)).toBeVisible() + const back = page.getByRole("button", { name: "Back" }) + const forward = page.getByRole("button", { name: "Forward" }) - const back = page.getByRole("button", { name: "Back" }) - const forward = page.getByRole("button", { name: "Forward" }) + await expect(back).toBeVisible() + await expect(back).toBeEnabled() + await back.click() - await expect(back).toBeVisible() - await expect(back).toBeEnabled() - await back.click() + await expect(page).toHaveURL(new RegExp(`/${slug}/session/${one.id}(?:\\?|#|$)`)) + await expect(page.locator(promptSelector)).toBeVisible() - await expect(page).toHaveURL(new RegExp(`/${slug}/session/${one.id}(?:\\?|#|$)`)) - await expect(page.locator(promptSelector)).toBeVisible() + await expect(forward).toBeVisible() + await expect(forward).toBeEnabled() + await forward.click() - await expect(forward).toBeVisible() - await expect(forward).toBeEnabled() - await forward.click() - - await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`)) - await expect(page.locator(promptSelector)).toBeVisible() - } finally { - await sdk.session.delete({ sessionID: one.id }).catch(() => undefined) - await sdk.session.delete({ sessionID: two.id }).catch(() => undefined) - } + await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`)) + await expect(page.locator(promptSelector)).toBeVisible() + }) + }) }) diff --git a/packages/app/e2e/files/file-open.spec.ts b/packages/app/e2e/files/file-open.spec.ts index dea35d25ba..3c636d748a 100644 --- a/packages/app/e2e/files/file-open.spec.ts +++ b/packages/app/e2e/files/file-open.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from "../fixtures" -import { openPalette } from "../actions" +import { openPalette, clickListItem } from "../actions" test("can open a file tab from the search palette", async ({ page, gotoSession }) => { await gotoSession() @@ -9,9 +9,7 @@ test("can open a file tab from the search palette", async ({ page, gotoSession } const input = dialog.getByRole("textbox").first() await input.fill("package.json") - const fileItem = dialog.locator('[data-slot="list-item"][data-key^="file:"]').first() - await expect(fileItem).toBeVisible() - await fileItem.click() + await clickListItem(dialog, { keyStartsWith: "file:" }) await expect(dialog).toHaveCount(0) diff --git a/packages/app/e2e/files/file-viewer.spec.ts b/packages/app/e2e/files/file-viewer.spec.ts index 3dc0dead2d..5283844975 100644 --- a/packages/app/e2e/files/file-viewer.spec.ts +++ b/packages/app/e2e/files/file-viewer.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from "../fixtures" -import { openPalette } from "../actions" +import { openPalette, clickListItem } from "../actions" test("smoke file viewer renders real file content", async ({ page, gotoSession }) => { await gotoSession() @@ -12,13 +12,7 @@ test("smoke file viewer renders real file content", async ({ page, gotoSession } const input = dialog.getByRole("textbox").first() await input.fill(file) - const fileItem = dialog - .locator( - '[data-slot="list-item"][data-key^="file:"][data-key*="packages"][data-key*="app"][data-key$="package.json"]', - ) - .first() - await expect(fileItem).toBeVisible() - await fileItem.click() + await clickListItem(dialog, { text: /packages.*app.*package.json/ }) await expect(dialog).toHaveCount(0) diff --git a/packages/app/e2e/models/model-picker.spec.ts b/packages/app/e2e/models/model-picker.spec.ts index df95e04d22..01e72464cc 100644 --- a/packages/app/e2e/models/model-picker.spec.ts +++ b/packages/app/e2e/models/model-picker.spec.ts @@ -1,5 +1,6 @@ import { test, expect } from "../fixtures" import { promptSelector } from "../selectors" +import { clickListItem } from "../actions" test("smoke model selection updates prompt footer", async ({ page, gotoSession }) => { await gotoSession() @@ -32,9 +33,7 @@ test("smoke model selection updates prompt footer", async ({ page, gotoSession } await input.fill(model) - const item = dialog.locator(`[data-slot="list-item"][data-key="${key}"]`) - await expect(item).toBeVisible() - await item.click() + await clickListItem(dialog, { key }) await expect(dialog).toHaveCount(0) diff --git a/packages/app/e2e/models/models-visibility.spec.ts b/packages/app/e2e/models/models-visibility.spec.ts index 36f14596dd..c699111793 100644 --- a/packages/app/e2e/models/models-visibility.spec.ts +++ b/packages/app/e2e/models/models-visibility.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from "../fixtures" import { promptSelector } from "../selectors" -import { closeDialog, openSettings } from "../actions" +import { closeDialog, openSettings, clickListItem } from "../actions" test("hiding a model removes it from the model picker", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/projects/project-edit.spec.ts b/packages/app/e2e/projects/project-edit.spec.ts index 22d053f3d9..772c259517 100644 --- a/packages/app/e2e/projects/project-edit.spec.ts +++ b/packages/app/e2e/projects/project-edit.spec.ts @@ -14,7 +14,12 @@ test("dialog edit project updates name and startup script", async ({ page, gotoS await expect(trigger).toBeVisible() await trigger.click({ force: true }) - await page.getByRole("menuitem", { name: "Edit" }).click() + const menu = page.locator('[data-component="dropdown-menu-content"]').first() + await expect(menu).toBeVisible() + + const editItem = menu.getByRole("menuitem", { name: "Edit" }).first() + await expect(editItem).toBeVisible() + await editItem.click({ force: true }) const dialog = page.getByRole("dialog") await expect(dialog).toBeVisible() diff --git a/packages/app/e2e/projects/projects-close.spec.ts b/packages/app/e2e/projects/projects-close.spec.ts index c3618740dd..bd323b90c6 100644 --- a/packages/app/e2e/projects/projects-close.spec.ts +++ b/packages/app/e2e/projects/projects-close.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from "../fixtures" -import { createTestProject, seedProjects, cleanupTestProject, openSidebar } from "../actions" +import { createTestProject, seedProjects, cleanupTestProject, openSidebar, clickMenuItem } from "../actions" import { projectCloseHoverSelector, projectCloseMenuSelector, projectSwitchSelector } from "../selectors" import { dirSlug } from "../utils" @@ -33,7 +33,7 @@ test("can close a project via project header more options menu", async ({ page, await page.setViewportSize({ width: 1400, height: 800 }) const other = await createTestProject() - const otherName = other.split("/").pop() + const otherName = other.split("/").pop() ?? other const otherSlug = dirSlug(other) await seedProjects(page, { directory, extra: [other] }) @@ -59,17 +59,10 @@ test("can close a project via project header more options menu", async ({ page, await trigger.focus() await page.keyboard.press("Enter") - const close = page - .locator(projectCloseMenuSelector(otherSlug)) - .or(page.getByRole("menuitem", { name: "Close" })) - .or( - page - .locator('[data-component="dropdown-menu-content"] [data-slot="dropdown-menu-item"]') - .filter({ hasText: "Close" }), - ) - .first() - await expect(close).toBeVisible({ timeout: 10_000 }) - await close.click({ force: true }) + const menu = page.locator('[data-component="dropdown-menu-content"]').first() + await expect(menu).toBeVisible({ timeout: 10_000 }) + + await clickMenuItem(menu, /^Close$/i, { force: true }) await expect(otherButton).toHaveCount(0) } finally { await cleanupTestProject(other) diff --git a/packages/app/e2e/prompt/context.spec.ts b/packages/app/e2e/prompt/context.spec.ts index 9e8f998f27..80aa9ea334 100644 --- a/packages/app/e2e/prompt/context.spec.ts +++ b/packages/app/e2e/prompt/context.spec.ts @@ -1,16 +1,13 @@ import { test, expect } from "../fixtures" import { promptSelector } from "../selectors" +import { withSession } from "../actions" test("context panel can be opened from the prompt", async ({ page, sdk, gotoSession }) => { const title = `e2e smoke context ${Date.now()}` - const created = await sdk.session.create({ title }).then((r) => r.data) - if (!created?.id) throw new Error("Session create did not return an id") - const sessionID = created.id - - try { + await withSession(sdk, title, async (session) => { await sdk.session.promptAsync({ - sessionID, + sessionID: session.id, noReply: true, parts: [ { @@ -22,12 +19,12 @@ test("context panel can be opened from the prompt", async ({ page, sdk, gotoSess await expect .poll(async () => { - const messages = await sdk.session.messages({ sessionID, limit: 1 }).then((r) => r.data ?? []) + const messages = await sdk.session.messages({ sessionID: session.id, limit: 1 }).then((r) => r.data ?? []) return messages.length }) .toBeGreaterThan(0) - await gotoSession(sessionID) + await gotoSession(session.id) const contextButton = page .locator('[data-component="button"]') @@ -39,7 +36,5 @@ test("context panel can be opened from the prompt", async ({ page, sdk, gotoSess const tabs = page.locator('[data-component="tabs"][data-variant="normal"]') await expect(tabs.getByRole("tab", { name: "Context" })).toBeVisible() - } finally { - await sdk.session.delete({ sessionID }).catch(() => undefined) - } + }) }) diff --git a/packages/app/e2e/prompt/prompt.spec.ts b/packages/app/e2e/prompt/prompt.spec.ts index 33f8d7ebc3..07d242c634 100644 --- a/packages/app/e2e/prompt/prompt.spec.ts +++ b/packages/app/e2e/prompt/prompt.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from "../fixtures" import { promptSelector } from "../selectors" -import { sessionIDFromUrl } from "../actions" +import { sessionIDFromUrl, withSession } from "../actions" test("can send a prompt and receive a reply", async ({ page, sdk, gotoSession }) => { test.setTimeout(120_000) diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts index 9179a6fd57..8beade5730 100644 --- a/packages/app/e2e/selectors.ts +++ b/packages/app/e2e/selectors.ts @@ -15,3 +15,21 @@ export const projectMenuTriggerSelector = (slug: string) => `${sidebarNavSelector} [data-action="project-menu"][data-project="${slug}"]` export const projectCloseMenuSelector = (slug: string) => `[data-action="project-close-menu"][data-project="${slug}"]` + +export const titlebarRightSelector = "#opencode-titlebar-right" + +export const popoverBodySelector = '[data-slot="popover-body"]' + +export const dropdownMenuTriggerSelector = '[data-slot="dropdown-menu-trigger"]' + +export const dropdownMenuContentSelector = '[data-component="dropdown-menu-content"]' + +export const inlineInputSelector = '[data-component="inline-input"]' + +export const sessionItemSelector = (sessionID: string) => `${sidebarNavSelector} [data-session-id="${sessionID}"]` + +export const listItemSelector = '[data-slot="list-item"]' + +export const listItemKeyStartsWithSelector = (prefix: string) => `${listItemSelector}[data-key^="${prefix}"]` + +export const listItemKeySelector = (key: string) => `${listItemSelector}[data-key="${key}"]` diff --git a/packages/app/e2e/session/session.spec.ts b/packages/app/e2e/session/session.spec.ts new file mode 100644 index 0000000000..05984bbeee --- /dev/null +++ b/packages/app/e2e/session/session.spec.ts @@ -0,0 +1,115 @@ +import { test, expect } from "../fixtures" +import { + openSidebar, + openSessionMoreMenu, + clickMenuItem, + confirmDialog, + openSharePopover, + withSession, +} from "../actions" +import { sessionItemSelector, inlineInputSelector } from "../selectors" + +const shareDisabled = process.env.OPENCODE_DISABLE_SHARE === "true" || process.env.OPENCODE_DISABLE_SHARE === "1" + +test("sidebar session can be renamed", async ({ page, sdk, gotoSession }) => { + const stamp = Date.now() + const originalTitle = `e2e rename test ${stamp}` + const newTitle = `e2e renamed ${stamp}` + + await withSession(sdk, originalTitle, async (session) => { + await gotoSession(session.id) + await openSidebar(page) + + const menu = await openSessionMoreMenu(page, session.id) + await clickMenuItem(menu, /rename/i) + + const input = page.locator(sessionItemSelector(session.id)).locator(inlineInputSelector).first() + await expect(input).toBeVisible() + await input.fill(newTitle) + await input.press("Enter") + + await expect(page.locator(sessionItemSelector(session.id)).locator("a").first()).toContainText(newTitle) + }) +}) + +test("sidebar session can be archived", async ({ page, sdk, gotoSession }) => { + const stamp = Date.now() + const title = `e2e archive test ${stamp}` + + await withSession(sdk, title, async (session) => { + await gotoSession(session.id) + await openSidebar(page) + + const sessionEl = page.locator(sessionItemSelector(session.id)) + const menu = await openSessionMoreMenu(page, session.id) + await clickMenuItem(menu, /archive/i) + + await expect(sessionEl).not.toBeVisible() + }) +}) + +test("sidebar session can be deleted", async ({ page, sdk, gotoSession }) => { + const stamp = Date.now() + const title = `e2e delete test ${stamp}` + + await withSession(sdk, title, async (session) => { + await gotoSession(session.id) + await openSidebar(page) + + const sessionEl = page.locator(sessionItemSelector(session.id)) + const menu = await openSessionMoreMenu(page, session.id) + await clickMenuItem(menu, /delete/i) + await confirmDialog(page, /delete/i) + + await expect(sessionEl).not.toBeVisible() + }) +}) + +test("session can be shared and unshared via header button", async ({ page, sdk, gotoSession }) => { + test.skip(shareDisabled, "Share is disabled in this environment (OPENCODE_DISABLE_SHARE).") + + const stamp = Date.now() + const title = `e2e share test ${stamp}` + + await withSession(sdk, title, async (session) => { + await gotoSession(session.id) + + const { rightSection, popoverBody } = await openSharePopover(page) + await popoverBody.getByRole("button", { name: "Publish" }).first().click() + + await expect + .poll( + async () => { + const data = await sdk.session.get({ sessionID: session.id }).then((r) => r.data) + return data?.share?.url || undefined + }, + { timeout: 30_000 }, + ) + .not.toBeUndefined() + + const copyButton = rightSection.locator('button[aria-label="Copy link"]').first() + await expect(copyButton).toBeVisible({ timeout: 30_000 }) + + const sharedPopover = await openSharePopover(page) + const unpublish = sharedPopover.popoverBody.getByRole("button", { name: "Unpublish" }).first() + await expect(unpublish).toBeVisible({ timeout: 30_000 }) + await unpublish.click() + + await expect + .poll( + async () => { + const data = await sdk.session.get({ sessionID: session.id }).then((r) => r.data) + return data?.share?.url || undefined + }, + { timeout: 30_000 }, + ) + .toBeUndefined() + + await expect(copyButton).not.toBeVisible({ timeout: 30_000 }) + + const unsharedPopover = await openSharePopover(page) + await expect(unsharedPopover.popoverBody.getByRole("button", { name: "Publish" }).first()).toBeVisible({ + timeout: 30_000, + }) + }) +}) diff --git a/packages/app/e2e/settings/settings-providers.spec.ts b/packages/app/e2e/settings/settings-providers.spec.ts index 4b3b178cc5..2bd2616bc9 100644 --- a/packages/app/e2e/settings/settings-providers.spec.ts +++ b/packages/app/e2e/settings/settings-providers.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from "../fixtures" import { promptSelector } from "../selectors" -import { closeDialog, openSettings } from "../actions" +import { closeDialog, openSettings, clickListItem } from "../actions" test("smoke providers settings opens provider selector", async ({ page, gotoSession }) => { await gotoSession() diff --git a/packages/app/e2e/sidebar/sidebar-session-links.spec.ts b/packages/app/e2e/sidebar/sidebar-session-links.spec.ts index 1c0f4fa71d..cda2278a95 100644 --- a/packages/app/e2e/sidebar/sidebar-session-links.spec.ts +++ b/packages/app/e2e/sidebar/sidebar-session-links.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from "../fixtures" -import { openSidebar } from "../actions" +import { openSidebar, withSession } from "../actions" import { promptSelector } from "../selectors" test("sidebar session links navigate to the selected session", async ({ page, slug, sdk, gotoSession }) => { diff --git a/packages/app/script/e2e-local.ts b/packages/app/script/e2e-local.ts index 2c7be2ad95..df2107f76d 100644 --- a/packages/app/script/e2e-local.ts +++ b/packages/app/script/e2e-local.ts @@ -58,7 +58,7 @@ const sandbox = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-e2e-")) const serverEnv = { ...process.env, - OPENCODE_DISABLE_SHARE: "true", + OPENCODE_DISABLE_SHARE: process.env.OPENCODE_DISABLE_SHARE ?? "true", OPENCODE_DISABLE_LSP_DOWNLOAD: "true", OPENCODE_DISABLE_DEFAULT_PLUGINS: "true", OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "true", diff --git a/turbo.json b/turbo.json index 5de1b8d751..f06ddb0e8b 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,7 @@ { "$schema": "https://turborepo.com/schema.json", + "globalEnv": ["CI", "OPENCODE_DISABLE_SHARE"], + "globalPassThroughEnv": ["CI", "OPENCODE_DISABLE_SHARE"], "tasks": { "typecheck": {}, "build": {