@cybermem/dashboard 0.5.10 → 0.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * UI Elements E2E Tests
3
+ *
4
+ * Comprehensive tests for all dashboard UI components:
5
+ * - Visibility: Elements exist on page
6
+ * - Functional: Interactions work (clicks, modals)
7
+ * - Data: Values are displayed correctly
8
+ *
9
+ * Run with: npm run test:e2e -- ui-elements.spec.ts
10
+ */
11
+
12
+ import { expect, test } from "@playwright/test";
13
+
14
+ const BASE_URL = process.env.BASE_URL || "http://localhost:3000";
15
+
16
+ // Helper to login
17
+ async function login(page: any) {
18
+ await page.goto(BASE_URL);
19
+ const passwordInput = page.getByPlaceholder("Enter admin password");
20
+ if (await passwordInput.isVisible({ timeout: 3000 }).catch(() => false)) {
21
+ await passwordInput.fill("admin");
22
+ await page.keyboard.press("Enter");
23
+ await expect(page.getByRole("heading", { name: "CyberMem" })).toBeVisible({
24
+ timeout: 10000,
25
+ });
26
+ }
27
+ // Dismiss password warning if present
28
+ const dontShowBtn = page.locator('button:has-text("Don\'t show again")');
29
+ if (await dontShowBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
30
+ await dontShowBtn.click();
31
+ }
32
+ }
33
+
34
+ test.describe("UI Elements - Visibility", () => {
35
+ test.beforeEach(async ({ page }) => {
36
+ await login(page);
37
+ });
38
+
39
+ test("Header: Logo, Title, and All Systems badge visible", async ({
40
+ page,
41
+ }) => {
42
+ // Logo
43
+ await expect(page.locator('img[alt="CyberMem Logo"]')).toBeVisible();
44
+
45
+ // Title
46
+ await expect(page.getByRole("heading", { name: "CyberMem" })).toBeVisible();
47
+
48
+ // All Systems badge should transition from shimmer to status
49
+ // Wait for shimmer to disappear (loading complete)
50
+ await page.waitForTimeout(3000);
51
+
52
+ // Should show one of: "All Systems OK", "Degraded", "System Error"
53
+ const badge = page.locator("text=/All Systems OK|Degraded|System Error/");
54
+ await expect(badge).toBeVisible({ timeout: 10000 });
55
+ console.log("✅ Header: All elements visible");
56
+ });
57
+
58
+ test("MetricCards: All 4 numeric cards visible with labels", async ({
59
+ page,
60
+ }) => {
61
+ const cards = [
62
+ "Memory Records",
63
+ "Total Clients",
64
+ "Success Rate",
65
+ "Total Requests",
66
+ ];
67
+
68
+ for (const label of cards) {
69
+ await expect(page.getByText(label)).toBeVisible();
70
+ }
71
+ console.log("✅ MetricCards: All 4 labels visible");
72
+ });
73
+
74
+ test("MetricCards: Values are not stuck on shimmer", async ({ page }) => {
75
+ // Wait for data to load
76
+ await page.waitForTimeout(3000);
77
+
78
+ // Check that Memory Records shows a number (not shimmer placeholder)
79
+ const memoryCard = page
80
+ .locator("text=Memory Records")
81
+ .locator("..")
82
+ .locator("..");
83
+ const valueText = await memoryCard.locator(".text-4xl").textContent();
84
+ expect(valueText).not.toBe("");
85
+ expect(valueText).not.toContain("...");
86
+ console.log(`✅ Memory Records value: ${valueText}`);
87
+ });
88
+
89
+ test("ClientCards: Top/Last Writer/Reader visible", async ({ page }) => {
90
+ const cards = ["Top Writer", "Top Reader", "Last Writer", "Last Reader"];
91
+
92
+ for (const label of cards) {
93
+ await expect(page.getByText(label)).toBeVisible();
94
+ }
95
+ console.log("✅ ClientCards: All 4 visible");
96
+ });
97
+
98
+ test("ChartCards: All 4 time series charts present", async ({ page }) => {
99
+ const charts = [
100
+ "Creates by Client",
101
+ "Reads by Client",
102
+ "Updates by Client",
103
+ "Deletes by Client",
104
+ ];
105
+
106
+ for (const title of charts) {
107
+ await expect(page.getByText(title)).toBeVisible();
108
+ }
109
+ console.log("✅ ChartCards: All 4 visible");
110
+ });
111
+
112
+ test("ChartCards: Period selector works", async ({ page }) => {
113
+ // Find first chart's period button
114
+ const periodBtn = page.locator('button:has-text("24 Hours")').first();
115
+ await expect(periodBtn).toBeVisible();
116
+ await periodBtn.click();
117
+
118
+ // Dropdown should appear
119
+ await expect(page.getByText("7 Days")).toBeVisible();
120
+ console.log("✅ Chart period selector works");
121
+ });
122
+
123
+ test("AuditLog: Table and headers visible", async ({ page }) => {
124
+ // Scroll to audit log
125
+ const auditHeader = page.getByRole("heading", { name: "Audit Log" });
126
+ await auditHeader.scrollIntoViewIfNeeded();
127
+ await expect(auditHeader).toBeVisible();
128
+
129
+ // Check column headers
130
+ const headers = [
131
+ "Timestamp",
132
+ "Client",
133
+ "Operation",
134
+ "Description",
135
+ "Status",
136
+ ];
137
+ for (const header of headers) {
138
+ await expect(page.locator(`th:has-text("${header}")`)).toBeVisible();
139
+ }
140
+ console.log("✅ AuditLog: Table headers visible");
141
+ });
142
+
143
+ test("AuditLog: Sorting works", async ({ page }) => {
144
+ const timestampHeader = page.locator('th:has-text("Timestamp")');
145
+ await timestampHeader.scrollIntoViewIfNeeded();
146
+
147
+ // Click to sort
148
+ await timestampHeader.click();
149
+ // Second click to reverse
150
+ await timestampHeader.click();
151
+
152
+ // If no error, sorting works
153
+ console.log("✅ AuditLog: Sorting works");
154
+ });
155
+ });
156
+
157
+ test.describe("UI Elements - Functional", () => {
158
+ test.beforeEach(async ({ page }) => {
159
+ await login(page);
160
+ });
161
+
162
+ test("Settings Modal: Opens and shows system info", async ({ page }) => {
163
+ // Click settings button
164
+ const settingsBtn = page.locator("button:has(svg.lucide-settings)");
165
+ await settingsBtn.click();
166
+
167
+ // Modal should open
168
+ await expect(page.getByText("Dashboard Settings")).toBeVisible({
169
+ timeout: 5000,
170
+ });
171
+
172
+ // System info should be present
173
+ await expect(page.getByText("System")).toBeVisible();
174
+
175
+ // Close modal
176
+ await page.keyboard.press("Escape");
177
+ console.log("✅ Settings Modal: Opens and shows system info");
178
+ });
179
+
180
+ test("Settings Modal: Data Management buttons visible", async ({ page }) => {
181
+ const settingsBtn = page.locator("button:has(svg.lucide-settings)");
182
+ await settingsBtn.click();
183
+
184
+ await expect(page.getByText("Dashboard Settings")).toBeVisible({
185
+ timeout: 5000,
186
+ });
187
+
188
+ // Check for data management buttons
189
+ await expect(page.getByRole("button", { name: /Backup/i })).toBeVisible();
190
+ await expect(page.getByRole("button", { name: /Restore/i })).toBeVisible();
191
+ await expect(page.getByRole("button", { name: /Reset/i })).toBeVisible();
192
+
193
+ await page.keyboard.press("Escape");
194
+ console.log("✅ Settings Modal: Data Management buttons visible");
195
+ });
196
+
197
+ test("MCP Config Modal: Opens and shows instructions", async ({ page }) => {
198
+ // Click Connect MCP button
199
+ const mcpBtn = page.getByRole("button", { name: "Connect MCP" });
200
+ await mcpBtn.click();
201
+
202
+ // Modal should open with instructions
203
+ await expect(page.getByText(/MCP Configuration|Connect MCP/i)).toBeVisible({
204
+ timeout: 5000,
205
+ });
206
+
207
+ // Should show config instructions
208
+ await expect(page.locator("code, pre")).toBeVisible();
209
+
210
+ // Close modal
211
+ await page.keyboard.press("Escape");
212
+ console.log("✅ MCP Config Modal: Opens and shows instructions");
213
+ });
214
+
215
+ test("Docs button: Links to documentation", async ({ page }) => {
216
+ const docsBtn = page.getByRole("link", { name: "Docs" });
217
+ const href = await docsBtn.getAttribute("href");
218
+ expect(href).toContain("docs.cybermem.dev");
219
+ console.log("✅ Docs button: Correct link");
220
+ });
221
+ });
222
+
223
+ test.describe("UI Elements - Data Validation", () => {
224
+ test.beforeEach(async ({ page }) => {
225
+ await login(page);
226
+ });
227
+
228
+ test("API returns valid stats structure", async ({ request }) => {
229
+ const res = await request.get(`${BASE_URL}/api/metrics`);
230
+ expect(res.ok()).toBeTruthy();
231
+
232
+ const data = await res.json();
233
+ expect(data.stats).toBeDefined();
234
+ expect(typeof data.stats.memoryRecords).toBe("number");
235
+ expect(typeof data.stats.totalClients).toBe("number");
236
+ expect(typeof data.stats.successRate).toBe("number");
237
+ expect(typeof data.stats.totalRequests).toBe("number");
238
+ expect(data.stats.topWriter).toBeDefined();
239
+ expect(data.stats.topReader).toBeDefined();
240
+ console.log("✅ API: Valid stats structure");
241
+ });
242
+
243
+ test("Dashboard displays consistent data with API", async ({
244
+ page,
245
+ request,
246
+ }) => {
247
+ // Get API data
248
+ const res = await request.get(`${BASE_URL}/api/metrics`);
249
+ const apiData = await res.json();
250
+
251
+ // Wait for dashboard to load
252
+ await page.waitForTimeout(3000);
253
+
254
+ // Memory Records should match API
255
+ const memoryValue = await page
256
+ .locator("text=Memory Records")
257
+ .locator("..")
258
+ .locator("..")
259
+ .locator(".text-4xl")
260
+ .textContent();
261
+ const apiMemory = apiData.stats.memoryRecords.toLocaleString();
262
+
263
+ // Values should match (allowing for locale formatting)
264
+ expect(memoryValue?.replace(/,/g, "")).toBe(apiMemory.replace(/,/g, ""));
265
+ console.log(`✅ Dashboard data matches API: ${memoryValue}`);
266
+ });
267
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cybermem/dashboard",
3
- "version": "0.5.10",
3
+ "version": "0.5.14",
4
4
  "description": "CyberMem Monitoring Dashboard",
5
5
  "homepage": "https://cybermem.dev",
6
6
  "repository": {