Skip to content

Commit e39e4b7

Browse files
committed
test: Add E2E covering the dashboards listing page
1 parent 3e0a78d commit e39e4b7

4 files changed

Lines changed: 363 additions & 31 deletions

File tree

packages/app/tests/e2e/features/dashboard.spec.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,6 @@ test.describe('Dashboard', { tag: ['@dashboard'] }, () => {
1616
await dashboardPage.goto();
1717
});
1818

19-
test(
20-
'should display the "temporary dashboard" banner until the dashboard is created',
21-
{ tag: '@full-stack' },
22-
async () => {
23-
await test.step('Verify that banner is initially displayed', async () => {
24-
await expect(dashboardPage.temporaryDashboardBanner).toBeVisible();
25-
});
26-
27-
await test.step('Add a tile, verify that banner is still displayed', async () => {
28-
await dashboardPage.addTileWithConfig('Test tile');
29-
await expect(dashboardPage.temporaryDashboardBanner).toBeVisible();
30-
});
31-
32-
await test.step('Create the dashboard, verify the banner is no longer displayed', async () => {
33-
await dashboardPage.createNewDashboard();
34-
await expect(dashboardPage.temporaryDashboardBanner).toBeHidden();
35-
});
36-
},
37-
);
38-
3919
test('should persist dashboard across page reloads', {}, async () => {
4020
const uniqueDashboardName = `Test Dashboard ${Date.now()}`;
4121

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import { DashboardPage } from '../page-objects/DashboardPage';
2+
import { DashboardsListPage } from '../page-objects/DashboardsListPage';
3+
import { getApiUrl } from '../utils/api-helpers';
4+
import { expect, test } from '../utils/base-test';
5+
6+
test.describe('Dashboards Listing Page', { tag: ['@dashboard'] }, () => {
7+
let dashboardsListPage: DashboardsListPage;
8+
let dashboardPage: DashboardPage;
9+
10+
test.beforeEach(async ({ page }) => {
11+
dashboardsListPage = new DashboardsListPage(page);
12+
dashboardPage = new DashboardPage(page);
13+
});
14+
15+
test(
16+
'should display the dashboards listing page with preset dashboards',
17+
{ tag: '@full-stack' },
18+
async () => {
19+
await dashboardsListPage.goto();
20+
21+
await test.step('Verify the page container is visible', async () => {
22+
await expect(dashboardsListPage.pageContainer).toBeVisible();
23+
});
24+
25+
await test.step('Verify preset dashboard cards are visible', async () => {
26+
await expect(
27+
dashboardsListPage.getPresetDashboardCard('Services'),
28+
).toBeVisible();
29+
await expect(
30+
dashboardsListPage.getPresetDashboardCard('ClickHouse'),
31+
).toBeVisible();
32+
await expect(
33+
dashboardsListPage.getPresetDashboardCard('Kubernetes'),
34+
).toBeVisible();
35+
});
36+
},
37+
);
38+
39+
test(
40+
'should create a new dashboard from the listing page',
41+
{ tag: '@full-stack' },
42+
async ({ page }) => {
43+
await dashboardsListPage.goto();
44+
45+
await test.step('Click the New Dashboard button', async () => {
46+
await dashboardsListPage.createNewDashboard();
47+
});
48+
49+
await test.step('Verify navigation to the individual dashboard page', async () => {
50+
await expect(page).toHaveURL(/\/dashboards\/.+/);
51+
});
52+
53+
await test.step('Verify the dashboard name heading "My Dashboard" is visible', async () => {
54+
await expect(
55+
page.getByRole('heading', { name: 'My Dashboard', level: 3 }),
56+
).toBeVisible();
57+
});
58+
},
59+
);
60+
61+
test('should search dashboards by name', { tag: '@full-stack' }, async () => {
62+
const ts = Date.now();
63+
const uniqueName = `E2E Search Dashboard ${ts}`;
64+
65+
await test.step('Create a dashboard with a unique name', async () => {
66+
await dashboardPage.goto();
67+
await dashboardPage.createNewDashboard();
68+
await dashboardPage.editDashboardName(uniqueName);
69+
});
70+
71+
await test.step('Navigate to the dashboards listing page', async () => {
72+
await dashboardsListPage.goto();
73+
});
74+
75+
await test.step('Search for the unique dashboard name', async () => {
76+
await dashboardsListPage.searchDashboards(uniqueName);
77+
});
78+
79+
await test.step('Verify the dashboard appears in results', async () => {
80+
await expect(
81+
dashboardsListPage.getDashboardCard(uniqueName),
82+
).toBeVisible();
83+
});
84+
85+
await test.step('Search for a non-existent name', async () => {
86+
await dashboardsListPage.searchDashboards(
87+
`nonexistent-dashboard-xyz-${ts}`,
88+
);
89+
});
90+
91+
await test.step('Verify no matches state is shown', async () => {
92+
await expect(dashboardsListPage.getNoMatchesState()).toBeVisible();
93+
});
94+
});
95+
96+
test(
97+
'should switch between grid and list views',
98+
{ tag: '@full-stack' },
99+
async () => {
100+
const ts = Date.now();
101+
const uniqueName = `E2E View Toggle Dashboard ${ts}`;
102+
103+
await test.step('Create a dashboard with a unique name', async () => {
104+
await dashboardPage.goto();
105+
await dashboardPage.createNewDashboard();
106+
await dashboardPage.editDashboardName(uniqueName);
107+
});
108+
109+
await test.step('Navigate to the dashboards listing page', async () => {
110+
await dashboardsListPage.goto();
111+
});
112+
113+
await test.step('Verify grid view is the default and dashboard card is visible', async () => {
114+
await expect(
115+
dashboardsListPage.getDashboardCard(uniqueName),
116+
).toBeVisible();
117+
});
118+
119+
await test.step('Switch to list view', async () => {
120+
await dashboardsListPage.switchToListView();
121+
});
122+
123+
await test.step('Verify the dashboard appears in a table row', async () => {
124+
await expect(
125+
dashboardsListPage.getDashboardRow(uniqueName),
126+
).toBeVisible();
127+
});
128+
129+
await test.step('Switch back to grid view', async () => {
130+
await dashboardsListPage.switchToGridView();
131+
});
132+
133+
await test.step('Verify the dashboard card reappears', async () => {
134+
await expect(
135+
dashboardsListPage.getDashboardCard(uniqueName),
136+
).toBeVisible();
137+
});
138+
},
139+
);
140+
141+
test(
142+
'should delete a dashboard from the listing page',
143+
{ tag: '@full-stack' },
144+
async ({ page }) => {
145+
const ts = Date.now();
146+
const uniqueName = `E2E Delete Dashboard ${ts}`;
147+
148+
await test.step('Create a dashboard with a unique name', async () => {
149+
await dashboardPage.goto();
150+
await dashboardPage.createNewDashboard();
151+
await dashboardPage.editDashboardName(uniqueName);
152+
});
153+
154+
await test.step('Navigate to the dashboards listing page', async () => {
155+
await dashboardsListPage.goto();
156+
});
157+
158+
await test.step('Delete the dashboard via the card menu', async () => {
159+
await dashboardsListPage.deleteDashboardFromCard(uniqueName);
160+
});
161+
162+
await test.step('Verify the dashboard is no longer visible', async () => {
163+
await expect(
164+
dashboardsListPage.getDashboardCard(uniqueName),
165+
).toBeHidden();
166+
});
167+
168+
await test.step('Verify the "Dashboard deleted" notification appears', async () => {
169+
await expect(page.getByText('Dashboard deleted')).toBeVisible();
170+
});
171+
},
172+
);
173+
174+
test(
175+
'should filter dashboards by tag',
176+
{ tag: '@full-stack' },
177+
async ({ page }) => {
178+
const ts = Date.now();
179+
const taggedName = `E2E Tagged Dashboard ${ts}`;
180+
const untaggedName = `E2E Untagged Dashboard ${ts}`;
181+
const tag = `e2e-tag-${ts}`;
182+
const API_URL = getApiUrl();
183+
184+
await test.step('Create a dashboard with a tag', async () => {
185+
// Create dashboard
186+
await dashboardPage.goto();
187+
await dashboardPage.createNewDashboard();
188+
await dashboardPage.editDashboardName(taggedName);
189+
190+
// Extract dashboard ID from the URL and PATCH tags via API
191+
const dashboardId = page.url().split('/dashboards/')[1]?.split('?')[0];
192+
await page.request.patch(`${API_URL}/dashboards/${dashboardId}`, {
193+
data: { tags: [tag] },
194+
});
195+
});
196+
197+
await test.step('Create a dashboard without the tag', async () => {
198+
await dashboardsListPage.goto();
199+
await dashboardPage.createNewDashboard();
200+
await dashboardPage.editDashboardName(untaggedName);
201+
});
202+
203+
await test.step('Navigate to the listing page and verify both are visible', async () => {
204+
await dashboardsListPage.goto();
205+
await expect(
206+
dashboardsListPage.getDashboardCard(taggedName),
207+
).toBeVisible();
208+
await expect(
209+
dashboardsListPage.getDashboardCard(untaggedName),
210+
).toBeVisible();
211+
});
212+
213+
await test.step('Select the tag filter', async () => {
214+
await dashboardsListPage.selectTagFilter(tag);
215+
});
216+
217+
await test.step('Verify only the tagged dashboard is shown', async () => {
218+
await expect(
219+
dashboardsListPage.getDashboardCard(taggedName),
220+
).toBeVisible();
221+
await expect(
222+
dashboardsListPage.getDashboardCard(untaggedName),
223+
).toBeHidden();
224+
});
225+
226+
await test.step('Clear the tag filter', async () => {
227+
await dashboardsListPage.clearTagFilter();
228+
});
229+
230+
await test.step('Verify both dashboards are visible again', async () => {
231+
await expect(
232+
dashboardsListPage.getDashboardCard(taggedName),
233+
).toBeVisible();
234+
await expect(
235+
dashboardsListPage.getDashboardCard(untaggedName),
236+
).toBeVisible();
237+
});
238+
},
239+
);
240+
});

packages/app/tests/e2e/page-objects/DashboardPage.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export class DashboardPage {
6868
private readonly dashboardNameHeading: Locator;
6969
private readonly searchSubmitButton: Locator;
7070
private readonly liveButton: Locator;
71-
private readonly tempDashboardBanner: Locator;
7271
private readonly editFiltersButton: Locator;
7372
private readonly filtersListModal: Locator;
7473
private readonly emptyFiltersListModal: Locator;
@@ -111,9 +110,6 @@ export class DashboardPage {
111110
this.liveButton = page.locator('button:has-text("Live")');
112111
this.dashboardNameHeading = page.getByRole('heading', { level: 3 });
113112
this.granularityPicker = page.getByTestId('granularity-picker');
114-
this.tempDashboardBanner = page.locator(
115-
'[data-testid="temporary-dashboard-banner"]',
116-
);
117113
this.editFiltersButton = page.getByTestId('edit-filters-button');
118114
this.filtersListModal = page.getByTestId('dashboard-filters-list');
119115
this.emptyFiltersListModal = page.getByTestId(
@@ -156,11 +152,12 @@ export class DashboardPage {
156152
}
157153

158154
/**
159-
* Create a new dashboard
155+
* Create a new dashboard from the listing page.
156+
* Clicks the create button and waits for navigation to the new dashboard page.
160157
*/
161158
async createNewDashboard() {
162159
await this.createDashboardButton.click();
163-
await this.page.waitForURL('**/dashboards**');
160+
await this.page.waitForURL(/\/dashboards\/.+/);
164161
}
165162

166163
async changeGranularity(granularity: string) {
@@ -228,7 +225,7 @@ export class DashboardPage {
228225
*/
229226
async openNewTileEditor() {
230227
await this.createDashboardButton.click();
231-
await this.page.waitForURL('**/dashboards**');
228+
await this.page.waitForURL(/\/dashboards\/.+/);
232229
await this.addDropdownButton.click();
233230
await this.addTileMenuItem.click();
234231
await expect(this.chartEditor.nameInput).toBeVisible();
@@ -613,10 +610,6 @@ export class DashboardPage {
613610
return this.searchSubmitButton;
614611
}
615612

616-
get temporaryDashboardBanner() {
617-
return this.tempDashboardBanner;
618-
}
619-
620613
get filtersList() {
621614
return this.filtersListModal;
622615
}

0 commit comments

Comments
 (0)