import { expect, type Locator, test } from "@playwright/test"; import { captureScreenshot, completeOnboarding, openNewSpace, rpc, signup, unterhaltungenOnly, } from "./helpers"; function projectSpace(projekte: Locator, name: string) { return projekte.locator('[data-sidebar-group^="project-space:"]').filter({ hasText: name, exact: true, }); } test("Projekte + opens new project dialog instead of group form", async ({ page }) => { const stamp = Date.now(); await signup(page, `proj-create-${stamp}@rakazo.test`, "password12", "Project Create"); await completeOnboarding(page); await page.goto("/app"); await page.waitForURL(/\/app\/[^/]+$/); const projekte = page.locator(".sidebar-projekte"); await projekte.getByTestId("create-project-trigger").click(); await expect(page.getByRole("dialog", { name: "New project" })).toBeVisible(); await expect(page.getByRole("button", { name: "Create project", exact: true })).toBeVisible(); await expect(page.getByTestId("side-panel")).not.toHaveAttribute("data-panel", "create-group"); await expect(page.getByText("New group", { exact: true })).toHaveCount(0); const dialog = page.getByRole("dialog", { name: "New project" }); await dialog.getByLabel("Name").fill("Customer project"); await dialog.getByRole("button", { name: "Create project", exact: true }).click(); await expect(page).not.toHaveURL(/\/onboarding/); await page.waitForURL(/\/app/); await page.waitForLoadState("networkidle"); await expect(projekte.getByText("Customer project", { exact: true })).toBeVisible({ timeout: 30_000, }); await expect(unterhaltungenOnly(page).getByText("Customer project", { exact: true })).toHaveCount( 0, ); await expect(page.locator("main").getByRole("button", { name: "Create an Oo" })).toBeVisible(); }); test("spaces stay invisible by default and chat creation requires approval", async ({ page, }, testInfo) => { const stamp = Date.now(); await signup(page, `spaces-${stamp}@rakazo.test`, "password12", "Space Owner"); await completeOnboarding(page); const sidebar = page.locator("aside").first(); const personalSpaceId = await page.evaluate(() => window.localStorage.getItem("rakazo:space-id")); expect(personalSpaceId).toBeTruthy(); await expect(sidebar.getByText("Personal", { exact: true })).toHaveCount(0); await expect(sidebar.getByRole("button", { name: /^Chief/ })).toHaveCount(1); await captureScreenshot(page, testInfo, "single-space-sidebar"); await openNewSpace(page); const dialog = page.getByRole("dialog", { name: "New space" }); await expect(dialog.getByLabel("Name")).toBeVisible(); await dialog.getByLabel("Name").fill("Customer support"); await captureScreenshot(page, testInfo, "new-space-dialog"); await dialog.getByRole("button", { name: "Cancel" }).click(); const composer = page.getByRole("combobox", { name: "Message" }); await composer.fill("Create a space named Customer support"); await composer.press("Enter"); await expect(page.getByRole("button", { name: "Create space", exact: true })).toBeVisible({ timeout: 15_000, }); await expect(page.getByRole("button", { name: "Cancel", exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: "Always allow this tool" })).toHaveCount(0); await expect(sidebar.getByText("Customer support", { exact: true })).toHaveCount(0); await captureScreenshot(page, testInfo, "create-space-chat-approval"); await page.getByRole("button", { name: "Create space", exact: true }).click(); await expect(page.getByText("Created", { exact: true })).toBeVisible(); const projekte = page.locator(".sidebar-projekte"); await expect(projekte.getByText("Customer support", { exact: true })).toBeVisible({ timeout: 15_000, }); await expect(unterhaltungenOnly(page).getByText("Customer support", { exact: true })).toHaveCount( 0, ); await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1); const supportSpace = projekte.locator('[data-sidebar-group^="project-space:"]').filter({ hasText: "Customer support", }); const supportSpaceGroup = await supportSpace.getAttribute("data-sidebar-group"); const supportSpaceId = supportSpaceGroup?.match(/^project-space:([^:]+)/)?.[1]; expect(supportSpaceId).toBeTruthy(); await supportSpace.getByRole("button", { name: "Open Customer support" }).click(); await page.waitForURL(/\/onboarding/); await expect .poll(() => page.evaluate(() => window.localStorage.getItem("rakazo:space-id"))) .toBe(supportSpaceId); await completeOnboarding(page); await expect(projekte.getByText("Customer support", { exact: true })).toBeVisible(); await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1); await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(1); await captureScreenshot(page, testInfo, "spaces-sidebar"); await page.evaluate((id) => { window.localStorage.setItem("rakazo:space-id", id!); }, personalSpaceId); await page.reload(); await page.waitForURL(/\/app\/[^/]+$/); await expect .poll(() => page.evaluate(() => window.localStorage.getItem("rakazo:space-id"))) .toBe(personalSpaceId); await expect(projekte.getByText("Customer support", { exact: true })).toBeVisible(); }); test("a new space auto-completes onboarding and can be deleted from its menu", async ({ page, }, testInfo) => { const stamp = Date.now(); await signup(page, `spaces-delete-${stamp}@rakazo.test`, "password12", "Space Owner"); await completeOnboarding(page); const sidebar = page.locator("aside").first(); await openNewSpace(page); const dialog = page.getByRole("dialog", { name: "New space" }); await dialog.getByLabel("Name").fill("Temporary"); await dialog.getByRole("button", { name: "Create space", exact: true }).click(); await page.waitForURL(/\/onboarding/); // A new empty space walks onboarding again, then lands in Chief. await completeOnboarding(page); await expect(page).toHaveURL(/\/app\//); const projekte = page.locator(".sidebar-projekte"); await expect(projekte.getByText("Temporary", { exact: true })).toBeVisible(); await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1); await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(1); await captureScreenshot(page, testInfo, "new-space-after-onboarding"); // Spaces can only be deleted when empty — remove auto-created Chief first. const temporaryChief = projekte.getByRole("button", { name: /^Chief/ }); await temporaryChief.click({ button: "right" }); await page.getByRole("menuitem", { name: "Delete" }).click(); const deleteBotDialog = page.getByRole("alertdialog", { name: /Delete Chief/ }); await expect(deleteBotDialog).toBeVisible(); await deleteBotDialog.getByRole("button", { name: "Delete", exact: true }).click(); await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0); await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1); // The emptied space stays selectable instead of trapping the user. await expect(projekte.getByRole("button", { name: "Open Temporary" }).first()).toBeVisible(); // Deletion is discoverable through the visible Space actions menu. await projectSpace(projekte, "Temporary") .getByRole("button", { name: "Actions for Temporary" }) .click(); await captureScreenshot(page, testInfo, "space-actions-menu"); await expect(page.getByRole("menuitem", { name: "Delete space" })).toBeVisible(); await page.getByRole("menuitem", { name: "Delete space" }).click(); const deleteSpaceDialog = page.getByRole("alertdialog", { name: "Delete Temporary?" }); await expect(deleteSpaceDialog).toBeVisible(); await captureScreenshot(page, testInfo, "delete-space-dialog"); await deleteSpaceDialog.getByRole("button", { name: "Delete", exact: true }).click(); await expect(sidebar.getByText("Temporary", { exact: true })).toHaveCount(0); await expect(page).not.toHaveURL(/\/onboarding/); await expect(sidebar.getByRole("button", { name: /^Chief/ })).toHaveCount(1); // A stale onboarding URL recovers without duplicating the bot. await page.goto("/onboarding"); await page.waitForURL(/\/app/); await expect(page).not.toHaveURL(/\/onboarding/); await expect(sidebar.getByRole("button", { name: /^Chief/ })).toHaveCount(1); }); test("deleting the last bot in a space stays in the app after first use", async ({ page }) => { const stamp = Date.now(); await signup(page, `spaces-empty-${stamp}@rakazo.test`, "password12", "Space Owner"); await completeOnboarding(page); const [chief] = await rpc>(page, "bots/list", {}); expect(chief).toBeTruthy(); await rpc(page, "bots/archive", { botId: chief!.id }); await page.reload(); await expect(page).not.toHaveURL(/\/onboarding/); const sidebar = page.locator("aside").first(); await openNewSpace(page); const dialog = page.getByRole("dialog", { name: "New space" }); await dialog.getByLabel("Name").fill("Side"); await dialog.getByRole("button", { name: "Create space", exact: true }).click(); await page.waitForURL(/\/(onboarding|app)/); await completeOnboarding(page); const projekte = page.locator(".sidebar-projekte"); await expect(projekte.getByText("Side", { exact: true })).toBeVisible(); // Delete the only bot in the new space: the app must stay put instead of // forcing onboarding again. const sideBot = projekte.getByRole("button", { name: /^Chief/ }); await sideBot.click({ button: "right" }); await page.getByRole("menuitem", { name: "Delete" }).click(); const deleteDialog = page.getByRole("alertdialog", { name: /Delete Chief/ }); await expect(deleteDialog).toBeVisible(); await deleteDialog.getByRole("button", { name: "Delete", exact: true }).click(); await expect(sideBot).toHaveCount(0); await expect(page).not.toHaveURL(/\/onboarding/); await page.reload(); await expect(page).not.toHaveURL(/\/onboarding/); await expect(projekte.getByRole("button", { name: "Open Side" }).first()).toBeVisible(); await expect( page.locator("main").getByRole("button", { name: "Create new Bot", exact: true }), ).toBeVisible(); await projectSpace(projekte, "Side").getByRole("button", { name: "Actions for Side" }).click(); await expect(page.getByRole("menuitem", { name: "Delete space" })).toBeVisible(); await page.getByRole("menuitem", { name: "Delete space" }).click(); await page .getByRole("alertdialog", { name: "Delete Side?" }) .getByRole("button", { name: "Delete", exact: true }) .click(); await expect(sidebar.getByText("Side", { exact: true })).toHaveCount(0); await sidebar.getByRole("button", { name: /^Archived/ }).click(); await expect(sidebar.getByText("Chief", { exact: true })).toBeVisible(); });