BangSo/apps/web/e2e/bot-organization.spec.ts

280 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { expect, test } from "@playwright/test";
import { activeBotId, captureScreenshot, completeOnboarding, rpc, signup } from "./helpers";
test("pinned bots and sidebar sections persist", async ({ page }, testInfo) => {
const stamp = Date.now();
await signup(page, `bot-organize-${stamp}@rakazo.test`, "password12", "Test User");
await completeOnboarding(page);
await page.goto("/app");
await page.waitForURL(/\/app\/[^/]+$/);
const sidebar = page.locator("aside").first();
const bot = sidebar.getByRole("button", { name: /^Chief/ });
await bot.click({ button: "right" });
await page.getByRole("menuitem", { name: "Pin", exact: true }).click();
await expect(sidebar.locator('[data-sidebar-group="pinned"]')).toContainText("Chief");
await captureScreenshot(page, testInfo, "pinned-bots");
await bot.click({ button: "right" });
await page.getByRole("menuitem", { name: "Unpin", exact: true }).click();
await expect(sidebar.locator('[data-sidebar-group="pinned"]')).toHaveCount(0);
await bot.click({ button: "right" });
await page.getByRole("menuitem", { name: "Move to", exact: true }).click();
await captureScreenshot(page, testInfo, "move-to-section-menu");
await page
.getByRole("menu", { name: /Move Chief to section/ })
.getByText("New section")
.click();
const dialog = page.getByRole("dialog", { name: "New section" });
await dialog.getByLabel("Name").fill("Projects");
await dialog.getByRole("button", { name: "Create" }).click();
const projects = sidebar.locator('[data-sidebar-group^="section:"]');
await expect(projects).toContainText("Projects");
await expect(projects).toContainText("Chief");
await captureScreenshot(page, testInfo, "bot-sections");
await page.reload();
await expect(projects).toContainText("Projects");
await expect(projects).toContainText("Chief");
await bot.click({ button: "right" });
await page.getByRole("menuitem", { name: "Move to", exact: true }).click();
await page
.getByRole("menu", { name: /Move Chief to section/ })
.getByRole("menuitem", { name: "Unassigned", exact: true })
.click();
await expect(sidebar.locator('[data-sidebar-group="unassigned"]')).toContainText("Chief");
});
test("bots can be reordered by drag or keyboard and keep that order", async ({ page }) => {
const stamp = Date.now();
await signup(page, `bot-reorder-${stamp}@rakazo.test`, "password12", "Bot Order");
await completeOnboarding(page);
await page.goto("/app");
await page.waitForURL(/\/app\/[^/]+$/);
const chiefId = activeBotId(page);
const alpha = await rpc<{ id: string }>(page, "bots/create", {
name: "Alpha",
title: "",
description: "",
instructions: "",
notifyOnFinish: true,
computerMode: "team",
});
const beta = await rpc<{ id: string }>(page, "bots/create", {
name: "Beta",
title: "",
description: "",
instructions: "",
notifyOnFinish: true,
computerMode: "team",
});
await page.reload();
const sidebar = page.locator("aside").first();
const rows = sidebar.locator("[data-roster-bot-id]");
const order = () =>
rows.evaluateAll((items) => items.map((item) => item.getAttribute("data-roster-bot-id")));
await expect.poll(order).toEqual([chiefId, alpha.id, beta.id]);
let releaseStaleList!: () => void;
let markStaleListReady!: () => void;
let markStaleListDelivered!: () => void;
const staleListReady = new Promise<void>((resolve) => {
markStaleListReady = resolve;
});
const staleListDelivered = new Promise<void>((resolve) => {
markStaleListDelivered = resolve;
});
const staleListGate = new Promise<void>((resolve) => {
releaseStaleList = resolve;
});
let interceptedList = false;
await page.route("**/rpc/spaces/list", async (route) => {
if (interceptedList) {
await route.continue();
return;
}
interceptedList = true;
const staleResponse = await route.fetch();
markStaleListReady();
await staleListGate;
await route.fulfill({ response: staleResponse });
markStaleListDelivered();
});
await page.evaluate(() => window.dispatchEvent(new Event("focus")));
await staleListReady;
const reorderSaved = page.waitForResponse(
(response) => response.url().includes("/rpc/bots/reorder") && response.ok(),
);
await sidebar
.locator(`[data-roster-bot-id="${beta.id}"]`)
.dragTo(sidebar.locator(`[data-roster-bot-id="${chiefId}"]`));
await reorderSaved;
releaseStaleList();
await staleListDelivered;
await expect.poll(order).toEqual([beta.id, chiefId, alpha.id]);
await page.reload();
await expect.poll(order).toEqual([beta.id, chiefId, alpha.id]);
const betaRow = sidebar.locator(`[data-roster-bot-id="${beta.id}"]`);
await betaRow.focus();
await page.keyboard.press("Alt+ArrowDown");
await expect.poll(order).toEqual([chiefId, beta.id, alpha.id]);
await page.reload();
await expect.poll(order).toEqual([chiefId, beta.id, alpha.id]);
let releaseRejectedReorder!: () => void;
let markRejectedReorderStarted!: () => void;
const rejectedReorderStarted = new Promise<void>((resolve) => {
markRejectedReorderStarted = resolve;
});
const rejectReorder = new Promise<void>((resolve) => {
releaseRejectedReorder = resolve;
});
await page.route(
"**/rpc/bots/reorder",
async (route) => {
markRejectedReorderStarted();
await rejectReorder;
await route.fulfill({ status: 500, contentType: "application/json", body: "{}" });
},
{ times: 1 },
);
await sidebar
.locator(`[data-roster-bot-id="${alpha.id}"]`)
.dragTo(sidebar.locator(`[data-roster-bot-id="${chiefId}"]`));
await rejectedReorderStarted;
const queuedReorderSaved = page.waitForResponse(
(response) => response.url().includes("/rpc/bots/reorder") && response.ok(),
);
await sidebar
.locator(`[data-roster-bot-id="${beta.id}"]`)
.dragTo(sidebar.locator(`[data-roster-bot-id="${alpha.id}"]`));
releaseRejectedReorder();
await queuedReorderSaved;
await expect.poll(order).toEqual([beta.id, alpha.id, chiefId]);
await page.reload();
await expect.poll(order).toEqual([beta.id, alpha.id, chiefId]);
});
test("chat composer controls are vertically centered", async ({ page }) => {
const stamp = Date.now();
await signup(page, `composer-layout-${stamp}@rakazo.test`, "password12", "Composer Layout");
await completeOnboarding(page);
const centers = await page.getByTestId("composer-bar").evaluate((composer) =>
["Attach file", "Dictate", "Message Chief", "Send"].map((label) => {
const element = composer.querySelector<HTMLElement>(`[aria-label="${label}"]`);
if (!element) throw new Error(`Missing composer control: ${label}`);
const box = element.getBoundingClientRect();
return box.top + box.height / 2;
}),
);
expect(Math.max(...centers) - Math.min(...centers)).toBeLessThanOrEqual(1);
});
test("group chats share every context-menu action", async ({ page }, testInfo) => {
const stamp = Date.now();
await signup(page, `group-organize-${stamp}@rakazo.test`, "password12", "Group Menu");
await completeOnboarding(page);
await page.goto("/app");
await page.waitForURL(/\/app\/[^/]+$/);
const chiefId = activeBotId(page);
const partner = await rpc<{ id: string }>(page, "bots/create", {
name: "Partner",
title: "",
description: "",
instructions: "",
notifyOnFinish: true,
computerMode: "team",
});
await rpc(page, "groups/create", {
name: "Group menu",
botIds: [chiefId, partner.id],
});
await page.reload();
const sidebar = page.locator("aside").first();
const group = sidebar.getByRole("button", { name: /^Group menu/ });
await group.click({ button: "right" });
for (const action of [
"Pin",
"Move to",
"Mark as Unread",
"Edit Profile",
"Duplicate",
"Clear conversation",
"Archive",
"Delete",
]) {
await expect(page.getByRole("menuitem", { name: action, exact: true })).toBeVisible();
}
await captureScreenshot(page, testInfo, "group-context-menu-desktop");
await page.getByRole("menuitem", { name: "Mark as Unread", exact: true }).click();
await group.click({ button: "right" });
await page.getByRole("menuitem", { name: "Mark as Read", exact: true }).click();
await group.click({ button: "right" });
await page.getByRole("menuitem", { name: "Pin", exact: true }).click();
await expect(sidebar.locator('[data-sidebar-group="pinned"]')).toContainText("Group menu");
await group.click({ button: "right" });
await page.getByRole("menuitem", { name: "Unpin", exact: true }).click();
await group.click({ button: "right" });
await page.getByRole("menuitem", { name: "Move to", exact: true }).click();
await page
.getByRole("menu", { name: /Move Group menu to section/ })
.getByRole("menuitem", { name: "New section", exact: true })
.click();
const sectionDialog = page.getByRole("dialog", { name: "New section" });
await sectionDialog.getByLabel("Name").fill("Teams");
await sectionDialog.getByRole("button", { name: "Create" }).click();
await expect(sidebar.locator('[data-sidebar-group^="section:"]')).toContainText("Group menu");
await group.click({ button: "right" });
await page.getByRole("menuitem", { name: "Clear conversation", exact: true }).click();
await expect(
page.getByRole("alertdialog", { name: "Clear Group menus conversation?" }),
).toBeVisible();
await page.getByRole("button", { name: "Clear", exact: true }).click();
await expect(
page.getByRole("alertdialog", { name: "Clear Group menus conversation?" }),
).toHaveCount(0);
await group.click({ button: "right" });
await page.getByRole("menuitem", { name: "Duplicate", exact: true }).click();
const copy = sidebar.getByRole("button", { name: /^Group menu copy/ });
await expect(copy).toBeVisible();
await copy.click({ button: "right" });
await page.getByRole("menuitem", { name: "Delete", exact: true }).click();
await expect(page.getByRole("alertdialog", { name: "Delete Group menu copy?" })).toBeVisible();
await page.getByRole("button", { name: "Cancel" }).click();
await copy.click({ button: "right" });
await page.getByRole("menuitem", { name: "Archive", exact: true }).click();
await expect(sidebar.getByRole("button", { name: /^Group menu copy/ })).toHaveCount(0);
await expect(sidebar.getByText("Archived", { exact: true })).toBeVisible();
await page.setViewportSize({ width: 390, height: 844 });
await page.getByRole("button", { name: "Open navigation" }).click();
await group.click({ button: "right" });
await captureScreenshot(page, testInfo, "group-context-menu-mobile");
await page.keyboard.press("Escape");
await page.setViewportSize({ width: 1280, height: 800 });
await group.click({ button: "right" });
await page.getByRole("menuitem", { name: "Edit Profile", exact: true }).click();
await expect(page.getByTestId("side-panel")).toHaveAttribute("data-panel", "group-settings");
});