/** * Widgets E2E Tests * * Tests widget area management at /widgets. * Covers creating widget areas, adding widgets, and deleting areas. * * The fixture starts with no widget areas, so tests create their own. */ import { test, expect } from "../fixtures"; // API helper function apiHeaders(token: string, baseUrl: string) { return { "Content-Type": "application/json", Authorization: `Bearer ${token}`, "X-EmDash-Request": "1", Origin: baseUrl, }; } test.describe("Widgets", () => { let headers: Record; let baseUrl: string; test.beforeEach(async ({ admin, serverInfo }) => { await admin.devBypassAuth(); baseUrl = serverInfo.baseUrl; headers = apiHeaders(serverInfo.token, baseUrl); }); test.describe("Widget Areas Page", () => { test("renders the widgets page", async ({ admin, page }) => { await admin.goto("/widgets"); await admin.waitForShell(); await admin.waitForLoading(); // Should show the page title await expect(page.locator("h1").first()).toContainText("Widgets"); // Should show the "Add Widget Area" button await expect(page.getByRole("button", { name: "Add Widget Area" })).toBeVisible(); // Should show the available widgets palette await expect(page.locator("h2", { hasText: "Available Widgets" })).toBeVisible({ timeout: 10000, }); }); test("shows empty state when no widget areas exist", async ({ admin, page }) => { // Clean up any existing areas first const res = await fetch(`${baseUrl}/_emdash/api/widget-areas`, { headers }); if (res.ok) { const data: any = await res.json(); const areas = data.data?.areas ?? []; for (const area of areas) { await fetch(`${baseUrl}/_emdash/api/widget-areas/${area.name}`, { method: "DELETE", headers, }).catch(() => {}); } } await admin.goto("/widgets"); await admin.waitForShell(); await admin.waitForLoading(); // Should show empty state await expect(page.locator("text=No widget areas yet")).toBeVisible({ timeout: 10000 }); }); test("shows built-in widget types in the palette", async ({ admin, page }) => { await admin.goto("/widgets"); await admin.waitForShell(); await admin.waitForLoading(); // Should show Content Block and Menu in the palette await expect(page.locator("text=Content Block").first()).toBeVisible({ timeout: 10000 }); await expect(page.locator("text=Menu").first()).toBeVisible(); }); }); test.describe("Create Widget Area", () => { test("opens and closes the create widget area dialog", async ({ admin, page }) => { await admin.goto("/widgets"); await admin.waitForShell(); await admin.waitForLoading(); // Click "Add Widget Area" await page.getByRole("button", { name: "Add Widget Area" }).click(); // Dialog should appear const dialog = page.getByRole("dialog", { name: "Create Widget Area" }); await expect(dialog).toBeVisible({ timeout: 5000 }); // Should have Name, Label, Description fields await expect(dialog.getByLabel("Name")).toBeVisible(); await expect(dialog.getByLabel("Label")).toBeVisible(); // Cancel should close the dialog await dialog.getByRole("button", { name: "Cancel" }).click(); await expect(dialog).not.toBeVisible({ timeout: 5000 }); }); test("creates a new widget area", async ({ admin, page }) => { const areaName = `e2e-area-${Date.now()}`; const areaLabel = "E2E Test Area"; await admin.goto("/widgets"); await admin.waitForShell(); await admin.waitForLoading(); // Open create dialog await page.getByRole("button", { name: "Add Widget Area" }).click(); const dialog = page.getByRole("dialog", { name: "Create Widget Area" }); await expect(dialog).toBeVisible({ timeout: 5000 }); // Fill form await dialog.getByLabel("Name").fill(areaName); await dialog.getByLabel("Label").fill(areaLabel); await dialog.getByLabel("Description").fill("Area for E2E testing"); // Submit await dialog.getByRole("button", { name: "Create" }).click(); // Dialog should close await expect(dialog).not.toBeVisible({ timeout: 10000 }); // New area should appear in the list await expect(page.locator("h3", { hasText: areaLabel })).toBeVisible({ timeout: 10000 }); // Clean up via API await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}`, { method: "DELETE", headers, }).catch(() => {}); }); }); test.describe("Add Widget to Area", () => { test("adds a content widget to an area via API and verifies in UI", async ({ admin, page }) => { const areaName = `e2e-widget-${Date.now()}`; const areaLabel = "Widget Test Area"; // Create area via API await fetch(`${baseUrl}/_emdash/api/widget-areas`, { method: "POST", headers, body: JSON.stringify({ name: areaName, label: areaLabel }), }); // Add a content widget via API await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}/widgets`, { method: "POST", headers, body: JSON.stringify({ type: "content", title: "Test Content Widget" }), }); // Navigate to widgets page await admin.goto("/widgets"); await admin.waitForShell(); await admin.waitForLoading(); // Area should be visible await expect(page.locator("h3", { hasText: areaLabel })).toBeVisible({ timeout: 10000 }); // Widget should be visible in the area await expect(page.locator("text=Test Content Widget").first()).toBeVisible({ timeout: 10000, }); // Clean up await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}`, { method: "DELETE", headers, }).catch(() => {}); }); test("deletes a widget from an area", async ({ admin, page }) => { const areaName = `e2e-del-widget-${Date.now()}`; const areaLabel = "Delete Widget Area"; // Create area and widget via API await fetch(`${baseUrl}/_emdash/api/widget-areas`, { method: "POST", headers, body: JSON.stringify({ name: areaName, label: areaLabel }), }); const widgetRes = await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}/widgets`, { method: "POST", headers, body: JSON.stringify({ type: "content", title: "Widget To Delete" }), }); const widgetData: any = await widgetRes.json(); const widgetTitle = widgetData.data?.widget?.title ?? "Widget To Delete"; // Navigate to widgets page await admin.goto("/widgets"); await admin.waitForShell(); await admin.waitForLoading(); // Widget should be visible await expect(page.locator(`text=${widgetTitle}`).first()).toBeVisible({ timeout: 10000 }); // Click the delete button on the widget const deleteButton = page.getByRole("button", { name: `Delete ${widgetTitle}` }); if (await deleteButton.isVisible({ timeout: 3000 }).catch(() => false)) { await deleteButton.click(); await admin.waitForLoading(); // Widget should disappear await expect(page.locator(`text=${widgetTitle}`).first()).not.toBeVisible({ timeout: 10000, }); } // Clean up area await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}`, { method: "DELETE", headers, }).catch(() => {}); }); }); test.describe("Delete Widget Area", () => { test("deletes a widget area with confirmation", async ({ admin, page }) => { const areaName = `e2e-del-area-${Date.now()}`; const areaLabel = "Area To Delete"; // Create area via API await fetch(`${baseUrl}/_emdash/api/widget-areas`, { method: "POST", headers, body: JSON.stringify({ name: areaName, label: areaLabel }), }); await admin.goto("/widgets"); await admin.waitForShell(); await admin.waitForLoading(); // Area should be visible await expect(page.locator("h3", { hasText: areaLabel })).toBeVisible({ timeout: 10000 }); // Click the delete button on the area header const deleteAreaButton = page.getByRole("button", { name: `Delete ${areaLabel} widget area`, }); await deleteAreaButton.click(); // ConfirmDialog should appear const confirmDialog = page.getByRole("dialog", { name: "Delete Widget Area" }); await expect(confirmDialog).toBeVisible({ timeout: 5000 }); // Confirm deletion await confirmDialog.getByRole("button", { name: "Delete" }).click(); // Area should disappear await expect(page.locator("h3", { hasText: areaLabel })).not.toBeVisible({ timeout: 10000, }); }); test("cancel delete keeps the widget area", async ({ admin, page }) => { const areaName = `e2e-keep-area-${Date.now()}`; const areaLabel = "Area To Keep"; // Create area via API await fetch(`${baseUrl}/_emdash/api/widget-areas`, { method: "POST", headers, body: JSON.stringify({ name: areaName, label: areaLabel }), }); await admin.goto("/widgets"); await admin.waitForShell(); await admin.waitForLoading(); await expect(page.locator("h3", { hasText: areaLabel })).toBeVisible({ timeout: 10000 }); // Click delete await page.getByRole("button", { name: `Delete ${areaLabel} widget area` }).click(); // Dialog appears const confirmDialog = page.getByRole("dialog", { name: "Delete Widget Area" }); await expect(confirmDialog).toBeVisible({ timeout: 5000 }); // Cancel await confirmDialog.getByRole("button", { name: "Cancel" }).click(); // Dialog closes await expect(confirmDialog).not.toBeVisible({ timeout: 5000 }); // Area should still be there await expect(page.locator("h3", { hasText: areaLabel })).toBeVisible(); // Clean up await fetch(`${baseUrl}/_emdash/api/widget-areas/${areaName}`, { method: "DELETE", headers, }).catch(() => {}); }); }); });