evOo-local/apps/web/e2e/spaces.spec.ts
cynarAI 0474875142 feat(web): Unterhaltungen-IA, Bot-Picker und schlanker Chat-Header (#71)
- Sidebar: Unterhaltungen (1:1-Bots im Home-Space) und Projekte
  (Projekt-Spaces + Gruppen) getrennt; Projekt-Spaces erscheinen nicht
  mehr unter Team/Unterhaltungen
- + in Unterhaltungen öffnet Bot-Picker (bestehenden Bot öffnen oder
  neuen Bot erstellen)
- Chat-Header: Bot-Avatar wieder sichtbar, Status nur bei Aktivität,
  kein doppeltes „1:1 conversation“
- Composer: generisches „Nachricht…“ statt Name dreifach
- OoStage: kein doppelter Bot-Name bei laufendem Verlauf

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 12:18:23 +02:00

202 lines
10 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { captureScreenshot, completeOnboarding, openNewSpace, rpc, signup } from "./helpers";
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 sidebar = page.locator("aside").first();
const projekte = page.locator(".sidebar-projekte");
await projekte.getByRole("button", { name: "Create a project" }).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 expect(projekte.getByText("Customer project", { exact: true })).toBeVisible({
timeout: 15_000,
});
await expect(page.locator(".sidebar-unterhaltungen").getByText("Customer project")).toHaveCount(0);
await expect(
page.locator("main").getByRole("button", { name: "Create new Bot", exact: true }),
).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");
const unterhaltungen = page.locator(".sidebar-unterhaltungen");
await expect(projekte.getByText("Customer support", { exact: true })).toBeVisible({
timeout: 15_000,
});
await expect(unterhaltungen.getByText("Customer support", { exact: true })).toHaveCount(0);
await expect(unterhaltungen.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?.split(":")[2];
expect(supportSpaceId).toBeTruthy();
await supportSpace.getByRole("button", { name: "Open Customer support" }).click();
await page.waitForURL(/\/(onboarding|app)/);
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(unterhaltungen.getByRole("button", { name: /^Chief/ })).toHaveCount(1);
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
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();
// Model is already connected, so onboarding skips the form and 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(sidebar.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.
await sidebar.getByRole("button", { name: /^Chief/ }).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(sidebar.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
// The emptied space stays selectable instead of trapping the user.
await expect(projekte.getByRole("button", { name: "Open Temporary" })).toBeVisible();
// Deletion is discoverable through the visible Space actions menu.
await projekte.getByRole("button", { name: "Actions for Temporary" }).click();
await captureScreenshot(page, testInfo, "space-actions-menu");
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<Array<{ id: string }>>(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 = sidebar.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" })).toBeVisible();
await expect(
page.locator("main").getByRole("button", { name: "Create new Bot", exact: true }),
).toBeVisible();
await projekte.getByRole("button", { name: "Actions for Side" }).click();
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();
});