import { expect, test, type Page } from "../support/fixtures"; import { expectComposerVisible } from "../support/helpers/composer"; import { openAgentRoute, seedMockAgentWorkspace } from "../support/helpers/mock-agent"; const MOBILE_VIEWPORT = { width: 390, height: 844 }; async function openMockAgentAtMobileBreakpoint(page: Page) { await page.setViewportSize(MOBILE_VIEWPORT); const session = await seedMockAgentWorkspace({ repoPrefix: "bottom-sheet-reopen-", title: "Bottom sheet reopen e2e", initialPrompt: "Prepare a bottom sheet reopen test agent.", }); await openAgentRoute(page, session); await expect(page.getByTestId("workspace-tab-switcher-trigger")).toBeVisible({ timeout: 30_000, }); await expectComposerVisible(page); await expect(page.getByRole("button", { name: /Select model/ })).toBeVisible({ timeout: 30_000, }); return session; } async function withMobileMockAgent(page: Page, run: () => Promise) { const session = await openMockAgentAtMobileBreakpoint(page); try { await run(); } finally { await session.cleanup(); } } function bottomSheetBackdrop(page: Page) { return page.getByRole("button", { name: "Bottom sheet backdrop" }).first(); } function bottomSheetHandle(page: Page) { return page.getByRole("slider", { name: "Bottom sheet handle" }).first(); } async function expectBottomSheetOpen(page: Page) { await expect(bottomSheetBackdrop(page)).toBeVisible({ timeout: 10_000 }); } async function closeBottomSheetWithBackdrop(page: Page) { const backdrop = bottomSheetBackdrop(page); const handle = bottomSheetHandle(page); // Tapping the backdrop is the close path under test, but on a loaded CI runner // the model-selector sheet re-renders as its model list settles and Gorhom // drops backdrop presses during that churn — so a tap (even retried) can fail // to dismiss. Tap the backdrop first; if it survives, drag the handle down, // which drives Gorhom's pan-to-close directly and is unaffected by the churn. // The post-close guard below still protects the regression this test exists // for: a sheet that dismisses, then re-presents. await expect(async () => { if (!(await backdrop.isVisible())) { return; } const box = await backdrop.boundingBox(); if (box) { await page.mouse.click(box.x + box.width / 2, box.y + 24); } await page.waitForTimeout(150); if (await backdrop.isVisible()) { const handleBox = await handle.boundingBox(); if (handleBox) { const startX = handleBox.x + handleBox.width / 2; const startY = handleBox.y + handleBox.height / 2; await page.mouse.move(startX, startY); await page.mouse.down(); await page.mouse.move(startX, startY + 400, { steps: 8 }); await page.mouse.up(); } } await expect(backdrop).not.toBeVisible({ timeout: 1_000 }); }).toPass({ timeout: 15_000 }); // Guard against the regression where the sheet starts dismissing, then re-presents. await page.waitForTimeout(500); await expect(backdrop).not.toBeVisible({ timeout: 1_000 }); } async function openTabSwitcher(page: Page) { const trigger = page.getByRole("button", { name: /Switch tabs/ }); await trigger.click(); await expectBottomSheetOpen(page); } async function openModelSelector(page: Page) { await page.getByRole("button", { name: /Select model/ }).click(); await expectBottomSheetOpen(page); await expect(page.getByTestId("agent-controls-settings-list")).toBeVisible({ timeout: 10_000 }); } async function openAndCloseTabSwitcherTwice(page: Page) { await openTabSwitcher(page); await closeBottomSheetWithBackdrop(page); await openTabSwitcher(page); await closeBottomSheetWithBackdrop(page); } async function openAndCloseModelSelectorTwice(page: Page) { await openModelSelector(page); await closeBottomSheetWithBackdrop(page); await openModelSelector(page); await closeBottomSheetWithBackdrop(page); } test.describe("mobile bottom sheet reopen", () => { test("tab switcher can open, close, reopen, and close again", async ({ page }) => { await withMobileMockAgent(page, async () => { await openAndCloseTabSwitcherTwice(page); }); }); test("model selector can open, close, reopen, and close again", async ({ page }) => { await withMobileMockAgent(page, async () => { await openAndCloseModelSelectorTwice(page); }); }); test("search selection returns to configuration options", async ({ page }) => { await withMobileMockAgent(page, async () => { await openModelSelector(page); const sheet = page.getByTestId("agent-controls-model-sheet"); await page.getByTestId("model-search-all-input").click(); const model = page.getByRole("button", { name: /^Ten second stream/ }); await expect(model).toBeVisible({ timeout: 10_000, }); await model.click(); await expect(sheet).toBeVisible(); await expect(page.getByTestId("agent-controls-settings-list")).toBeVisible(); await expect(page.getByTestId("agent-controls-model")).toContainText("Ten second stream"); await expect(page.getByTestId("agent-controls-model-browser-sheet")).not.toBeVisible(); }); }); });