-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Expand file tree
/
Copy pathdashboard.spec.ts
More file actions
296 lines (254 loc) · 10.2 KB
/
dashboard.spec.ts
File metadata and controls
296 lines (254 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import type { Page } from "@playwright/test";
import {
cleanupFlowRuns,
createDeployment,
createFlow,
createFlowRun,
expect,
test,
waitForServerHealth,
} from "../fixtures";
const TEST_PREFIX = "e2e-dashboard-";
/**
* Wait for the dashboard page to be fully loaded.
* Handles both empty state and populated state.
*/
async function waitForDashboardReady(page: Page): Promise<void> {
// Wait for either the empty state or the dashboard cards to be visible
await expect(
page
.getByRole("heading", { name: /run a task or flow to get started/i })
.or(page.getByText("Flow Runs")),
).toBeVisible({ timeout: 10000 });
}
test.describe("Dashboard Page", () => {
test.beforeAll(async ({ apiClient }) => {
await waitForServerHealth(apiClient);
});
test.beforeEach(async ({ apiClient }) => {
// Cleanup is best-effort - don't fail tests if server is temporarily unavailable
try {
await cleanupFlowRuns(apiClient, TEST_PREFIX);
} catch {
// Ignore cleanup errors - test data uses unique timestamps so collisions are unlikely
}
});
test.afterEach(async ({ apiClient }) => {
// Cleanup is best-effort - don't fail tests if server is temporarily unavailable
try {
await cleanupFlowRuns(apiClient, TEST_PREFIX);
} catch {
// Ignore cleanup errors - test data uses unique timestamps so collisions are unlikely
}
});
test.describe("Dashboard with Data", () => {
test("should display dashboard with flow runs, task runs, and work pools cards after creating test data", async ({
page,
apiClient,
}) => {
// --- SETUP: Create test data via API ---
const flowName = `${TEST_PREFIX}main-flow-${Date.now()}`;
const flow = await createFlow(apiClient, flowName);
const deployment = await createDeployment(apiClient, {
name: `${TEST_PREFIX}deployment-${Date.now()}`,
flowId: flow.id,
});
// Create multiple flow runs with different states to populate dashboard
await createFlowRun(apiClient, {
flowId: flow.id,
deploymentId: deployment.id,
name: `${TEST_PREFIX}completed-run-${Date.now()}`,
state: { type: "COMPLETED", name: "Completed" },
});
await createFlowRun(apiClient, {
flowId: flow.id,
deploymentId: deployment.id,
name: `${TEST_PREFIX}failed-run-${Date.now()}`,
state: { type: "FAILED", name: "Failed" },
});
await createFlowRun(apiClient, {
flowId: flow.id,
deploymentId: deployment.id,
name: `${TEST_PREFIX}running-run-${Date.now()}`,
state: { type: "RUNNING", name: "Running" },
});
// --- NAVIGATE to dashboard and wait for data ---
await expect(async () => {
await page.goto("/dashboard");
await waitForDashboardReady(page);
await expect(page.getByText(/completed/i).first()).toBeVisible({
timeout: 2000,
});
}).toPass({ timeout: 15000 });
// --- VERIFY: Dashboard header and filters are visible ---
// Dashboard title is in a BreadcrumbItem (li element), not a heading
await expect(page.getByText("Dashboard").first()).toBeVisible();
await expect(page.getByLabel("Hide subflows")).toBeVisible();
await expect(page.getByText("All tags")).toBeVisible();
// --- VERIFY: Flow Runs card displays data ---
await expect(page.getByText("Flow Runs")).toBeVisible();
// Wait for state tabs to be visible (indicates data has loaded)
await expect(page.getByRole("tablist")).toBeVisible({ timeout: 10000 });
await expect(page.getByRole("tab", { name: /failed/i })).toBeVisible();
await expect(page.getByRole("tab", { name: /running/i })).toBeVisible();
await expect(page.getByRole("tab", { name: /completed/i })).toBeVisible();
// --- VERIFY: Task Runs card is visible ---
await expect(page.getByText("Task Runs")).toBeVisible();
// --- VERIFY: Work Pools card is visible ---
// Use locator for the card title specifically to avoid matching sidebar nav
// The card title has data-slot="card-title" attribute
await expect(
page.locator('[data-slot="card-title"]', {
hasText: "Active Work Pools",
}),
).toBeVisible();
// --- VERIFY: Clicking state tabs updates the displayed flow runs ---
// Default tab is Failed/Crashed - should show our failed run
await expect(page.getByRole("tab", { name: /failed/i })).toBeVisible();
// Click on Running tab
await page.getByRole("tab", { name: /running/i }).click();
// URL should update with tab parameter
await expect(page).toHaveURL(/tab=RUNNING/);
// Click on Completed tab
await page.getByRole("tab", { name: /completed/i }).click();
await expect(page).toHaveURL(/tab=COMPLETED/);
// Click back to Failed tab (should clear the tab param since it's default)
await page.getByRole("tab", { name: /failed/i }).click();
// Default tab doesn't need to be in URL
await expect(page).not.toHaveURL(/tab=FAILED/);
});
});
test.describe("Dashboard Filters", () => {
test("should filter dashboard data using hide subflows toggle and verify URL updates", async ({
page,
apiClient,
}) => {
// --- SETUP: Create test data including a subflow ---
const flowName = `${TEST_PREFIX}filter-flow-${Date.now()}`;
const flow = await createFlow(apiClient, flowName);
const deployment = await createDeployment(apiClient, {
name: `${TEST_PREFIX}filter-deployment-${Date.now()}`,
flowId: flow.id,
});
// Create a parent flow run
await createFlowRun(apiClient, {
flowId: flow.id,
deploymentId: deployment.id,
name: `${TEST_PREFIX}parent-run-${Date.now()}`,
state: { type: "COMPLETED", name: "Completed" },
});
// Note: We can't easily create a real subflow in E2E tests without a parent task run,
// but we can still test the toggle functionality by verifying URL updates
// --- NAVIGATE to dashboard ---
await page.goto("/dashboard");
await waitForDashboardReady(page);
// --- VERIFY: Hide subflows toggle is visible and unchecked by default ---
const hideSubflowsSwitch = page.getByLabel("Hide subflows");
await expect(hideSubflowsSwitch).toBeVisible();
await expect(hideSubflowsSwitch).not.toBeChecked();
// --- ACTION: Toggle hide subflows ON ---
await hideSubflowsSwitch.click();
// --- VERIFY: URL updates with hideSubflows parameter ---
await expect(page).toHaveURL(/hideSubflows=true/);
// Wait for the switch to reflect checked state before toggling again
await expect(hideSubflowsSwitch).toBeChecked();
// The dashboard should refresh and filter out subflows
// This is verified by the URL param change and the component re-rendering
// --- ACTION: Toggle hide subflows OFF ---
await hideSubflowsSwitch.click();
// --- VERIFY: Switch is unchecked and URL no longer has hideSubflows=true ---
await expect(hideSubflowsSwitch).not.toBeChecked();
// When false/default, the param should be removed or set to false
await expect(page).not.toHaveURL(/hideSubflows=true/, { timeout: 10000 });
});
test("should persist date range selection in URL and filter dashboard data", async ({
page,
apiClient,
}) => {
// --- SETUP: Create test data ---
const flowName = `${TEST_PREFIX}date-flow-${Date.now()}`;
const flow = await createFlow(apiClient, flowName);
const deployment = await createDeployment(apiClient, {
name: `${TEST_PREFIX}date-deployment-${Date.now()}`,
flowId: flow.id,
});
await createFlowRun(apiClient, {
flowId: flow.id,
deploymentId: deployment.id,
name: `${TEST_PREFIX}recent-run-${Date.now()}`,
state: { type: "COMPLETED", name: "Completed" },
});
// --- NAVIGATE to dashboard ---
await page.goto("/dashboard");
await waitForDashboardReady(page);
// --- VERIFY: Date range selector is visible ---
// The RichDateRangeSelector component shows "Past day" by default (86400 seconds)
await expect(
page.getByRole("button", { name: /past day|select a time span/i }),
).toBeVisible();
// --- ACTION: Open date range selector and change to a different range ---
await page
.getByRole("button", { name: /past day|select a time span/i })
.click();
// Select "Past 7 days" option (7 days = 604800 seconds)
const past7DaysOption = page.getByRole("button", {
name: /past 7 days/i,
});
if (await past7DaysOption.isVisible()) {
await past7DaysOption.click();
// --- VERIFY: URL updates with new time span ---
await expect(page).toHaveURL(/seconds=-604800|rangeType=span/);
} else {
// If the exact option isn't available, just verify the selector works
// by checking that it opened (there's a popover/dropdown)
await expect(
page.getByRole("listbox").or(page.getByRole("dialog")),
).toBeVisible();
// Close by pressing Escape
await page.keyboard.press("Escape");
}
// --- VERIFY: Dashboard still displays correctly after filter change ---
await expect(page.getByText("Flow Runs")).toBeVisible();
});
});
test.describe("Empty State", () => {
test("should show empty state when no flow runs exist and hide filters", async ({
page,
apiClient,
}) => {
// First, clean up any existing flow runs with our prefix
await cleanupFlowRuns(apiClient, TEST_PREFIX);
// Navigate to dashboard
await page.goto("/dashboard");
// Check if empty state is shown (if there are no flow runs at all in the system)
// This test may be skipped if other flow runs exist from other tests
const emptyStateHeading = page.getByRole("heading", {
name: /run a task or flow to get started/i,
});
const flowRunsCard = page.getByText("Flow Runs");
// Wait for page to load
await expect(emptyStateHeading.or(flowRunsCard)).toBeVisible({
timeout: 10000,
});
// If empty state is shown, verify the expected content
if (await emptyStateHeading.isVisible()) {
// --- VERIFY: Empty state content ---
await expect(
page.getByText(/runs store the state history/i),
).toBeVisible();
await expect(
page.getByRole("link", { name: /view docs/i }),
).toBeVisible();
// --- VERIFY: Filters are hidden in empty state ---
await expect(page.getByLabel("Hide subflows")).not.toBeVisible();
await expect(page.getByText("All tags")).not.toBeVisible();
} else {
// Other flow runs exist - skip this test
test.skip(
true,
"Skipping empty state test because flow runs already exist",
);
}
});
});
});