Skip to content

Commit c4c949d

Browse files
committed
test(FR-2652): add E2E tests for admin model card deletion with folder option
- Add tests 5.7 and 5.8 to admin-model-card-delete.spec.ts - 5.7: delete model card + folder together, verify notification and Go to Trash URL with folder filter - 5.8: delete model card only, verify notification and Go to Trash URL without filter - Add getAlsoDeleteFolderCheckbox() and getFolderNameLinkInDeleteDialog() to AdminModelCardPage POM - Update E2E_COVERAGE_REPORT.md: Admin Model Store 22→26 features covered
1 parent 444c84b commit c4c949d

4 files changed

Lines changed: 324 additions & 61 deletions

File tree

e2e/E2E_COVERAGE_REPORT.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# E2E Test Coverage Report
22

3-
> **Last Updated:** 2026-04-13
3+
> **Last Updated:** 2026-04-22
44
> **Router Source:** [`react/src/routes.tsx`](../react/src/routes.tsx)
55
> **E2E Root:** [`e2e/`](.)
66
>
@@ -12,7 +12,7 @@
1212

1313
**Scope:** Coverage metrics apply only to the routes listed below and do **not** include all entries from `react/src/routes.tsx`. Routes such as `/admin-dashboard` (not yet exposed in menu) and `/ai-agent` (experimental) are currently out of scope.
1414

15-
**Overall (in-scope routes): 261 / 408 features covered (64%)**
15+
**Overall (in-scope routes): 265 / 412 features covered (64%)**
1616

1717
| Page | Route | Features | Covered | Status |
1818
|------|-------|:--------:|:-------:|:------:|
@@ -27,7 +27,7 @@
2727
| Service Launcher | `/service/start` | 5 | 0 | ❌ 0% |
2828
| VFolder / Data | `/data` | 45 | 32 | 🔶 71% |
2929
| Model Store | `/model-store` | 6 | 6 | ✅ 100% |
30-
| Admin Model Store | `/admin-model-store` | 22 | 22 | ✅ 100% |
30+
| Admin Model Store | `/admin-model-store` | 26 | 26 | ✅ 100% |
3131
| Storage Host | `/storage-settings/:hostname` | 3 | 0 | ❌ 0% |
3232
| My Environment | `/my-environment` | 2 | 2 | ✅ 100% |
3333
| Environment | `/environment` | 27 | 21 | 🔶 78% |
@@ -47,7 +47,7 @@
4747
| Chat | `/chat/:id?` | 6 | 6 | ✅ 100% |
4848
| Plugin System | (config-based) | 12 | 12 | ✅ 100% |
4949
| RBAC Management | `/rbac` | 22 | 21 | 🔶 95% |
50-
| **Total** | | **408** | **261** | **64%** |
50+
| **Total** | | **412** | **265** | **64%** |
5151

5252
---
5353

@@ -420,14 +420,18 @@
420420
| Cancel edit modal || `admin-model-card-edit.spec.ts` |
421421
| Single delete with confirmation || `admin-model-card-delete.spec.ts` |
422422
| Cancel single delete || `admin-model-card-delete.spec.ts` |
423+
| Delete card + folder together (checkbox) || `admin-model-card-delete.spec.ts` |
424+
| Notification + Go to Trash with folder filter || `admin-model-card-delete.spec.ts` |
425+
| Delete card only, folder kept notification || `admin-model-card-delete.spec.ts` |
426+
| Go to Trash without folder filter || `admin-model-card-delete.spec.ts` |
423427
| Bulk select and delete || `admin-model-card-delete.spec.ts` |
424428
| Cancel bulk delete || `admin-model-card-delete.spec.ts` |
425429
| Clear selection || `admin-model-card-delete.spec.ts` |
426430
| Select all via header checkbox || `admin-model-card-delete.spec.ts` |
427431
| Non-admin access blocked || `admin-model-card-access-control.spec.ts` |
428432
| URL state persistence (filter/sort/pagination) || `admin-model-card-url-state.spec.ts` |
429433

430-
**Coverage: ✅ 22/22 features**
434+
**Coverage: ✅ 26/26 features**
431435

432436
---
433437

e2e/admin-model-card/admin-model-card-delete.spec.ts

Lines changed: 207 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// spec: e2e/.agent-output/test-plan-admin-model-card.md
22
// section: 5. Delete Model Card
33
import { AdminModelCardPage } from '../utils/classes/AdminModelCardPage';
4-
import { loginAsAdmin, webuiEndpoint } from '../utils/test-util';
4+
import {
5+
deleteForeverAndVerifyFromTrash,
6+
loginAsAdmin,
7+
moveToTrashAndVerify,
8+
webuiEndpoint,
9+
} from '../utils/test-util';
510
import { test, expect } from '@playwright/test';
611

712
test.describe(
@@ -18,13 +23,19 @@ test.describe(
1823
test('Superadmin can delete a model card via the trash icon with confirmation', async ({
1924
page,
2025
}) => {
26+
test.setTimeout(90000);
2127
const adminModelCardPage = new AdminModelCardPage(page);
22-
const cardName = `e2e-test-delete-single-${Date.now()}`;
28+
const timestamp = Date.now();
29+
const folderName = `e2e-test-delete-single-folder-${timestamp}`;
30+
const cardName = `e2e-test-delete-single-${timestamp}`;
2331

24-
// Setup: create a model card to delete
32+
// Setup: create a dedicated folder and model card
2533
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
2634
await adminModelCardPage.waitForTableLoad();
27-
await adminModelCardPage.createModelCard({ name: cardName });
35+
await adminModelCardPage.createModelCard({
36+
name: cardName,
37+
createNewFolderName: folderName,
38+
});
2839

2940
// Navigate back and find the row
3041
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
@@ -51,31 +62,46 @@ test.describe(
5162
await expect(adminModelCardPage.getDeleteConfirmButton()).toBeVisible();
5263
await expect(adminModelCardPage.getDeleteCancelButton()).toBeVisible();
5364

54-
// Click Delete to confirm
65+
// The "Also delete folder" checkbox should be visible (card has an associated folder)
66+
await expect(
67+
adminModelCardPage.getAlsoDeleteFolderCheckbox(),
68+
).toBeVisible();
69+
70+
// Click Delete to confirm (leave folder checkbox unchecked — folder cleanup handled separately)
5571
await adminModelCardPage.getDeleteConfirmButton().click();
5672

5773
// Verify success message
58-
await expect(page.getByText('Model card has been deleted.')).toBeVisible({
74+
await expect(page.getByText(/Model card has been deleted/)).toBeVisible({
5975
timeout: 15000,
6076
});
6177

6278
// Verify the row is no longer in the table
6379
await expect(adminModelCardPage.getPaginationInfo()).toContainText(
6480
'0 items',
6581
);
82+
83+
// Cleanup: move folder to trash then permanently delete
84+
await moveToTrashAndVerify(page, folderName, 'admin-data');
85+
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
6686
});
6787

6888
// 5.2 Superadmin can cancel a single-delete confirmation without deleting
6989
test('Superadmin can cancel a single-delete confirmation without deleting', async ({
7090
page,
7191
}) => {
92+
test.setTimeout(90000);
7293
const adminModelCardPage = new AdminModelCardPage(page);
73-
const cardName = `e2e-test-no-delete-${Date.now()}`;
94+
const timestamp = Date.now();
95+
const folderName = `e2e-test-no-delete-folder-${timestamp}`;
96+
const cardName = `e2e-test-no-delete-${timestamp}`;
7497

75-
// Setup: create a model card to keep
98+
// Setup: create a dedicated folder and model card
7699
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
77100
await adminModelCardPage.waitForTableLoad();
78-
await adminModelCardPage.createModelCard({ name: cardName });
101+
await adminModelCardPage.createModelCard({
102+
name: cardName,
103+
createNewFolderName: folderName,
104+
});
79105

80106
// Navigate back and filter
81107
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
@@ -94,34 +120,48 @@ test.describe(
94120
// Verify the model card is still in the table
95121
await expect(adminModelCardPage.getRowByName(cardName)).toBeVisible();
96122

97-
// Cleanup: delete the test model card
123+
// Cleanup: delete card only, then move folder to trash and permanently delete
98124
await adminModelCardPage.deleteModelCardByName(cardName);
125+
await moveToTrashAndVerify(page, folderName, 'admin-data');
126+
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
99127
});
100128

101129
// 5.3 Superadmin can select multiple model cards and delete them in bulk
102130
test('Superadmin can select multiple model cards and delete them in bulk', async ({
103131
page,
104132
}) => {
105-
test.setTimeout(90000);
133+
test.setTimeout(120000);
106134
const adminModelCardPage = new AdminModelCardPage(page);
107135
const timestamp = Date.now();
136+
const folderName = `e2e-test-bulk-delete-folder-${timestamp}`;
108137
const filterPrefix = `e2e-test-bulk-delete-${timestamp}`;
109138
const cardNames = [
110139
`${filterPrefix}-1`,
111140
`${filterPrefix}-2`,
112141
`${filterPrefix}-3`,
113142
];
114143

115-
// Setup: create three model cards
144+
// Setup: create a shared folder via the "+" button for the first card,
145+
// then reuse it for the remaining cards
116146
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
117147
await adminModelCardPage.waitForTableLoad();
118-
for (const name of cardNames) {
119-
await adminModelCardPage.createModelCard({ name });
148+
await adminModelCardPage.createModelCard({
149+
name: cardNames[0],
150+
createNewFolderName: folderName,
151+
});
152+
153+
for (const name of cardNames.slice(1)) {
120154
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
121155
await adminModelCardPage.waitForTableLoad();
156+
await adminModelCardPage.createModelCard({
157+
name,
158+
vfolderTitle: folderName,
159+
});
122160
}
123161

124162
// Filter to show only this run's test cards (timestamp ensures uniqueness)
163+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
164+
await adminModelCardPage.waitForTableLoad();
125165
await adminModelCardPage.applyNameFilter(filterPrefix);
126166
await expect(adminModelCardPage.getDataRows().first()).toBeVisible({
127167
timeout: 10000,
@@ -161,26 +201,41 @@ test.describe(
161201

162202
// Verify the selection label disappears
163203
await expect(adminModelCardPage.getSelectionLabel()).toBeHidden();
204+
205+
// Cleanup: model cards were deleted but the shared folder remains;
206+
// move it to trash and permanently delete
207+
await moveToTrashAndVerify(page, folderName, 'admin-data');
208+
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
164209
});
165210

166211
// 5.4 Superadmin can cancel bulk deletion
167212
test('Superadmin can cancel bulk deletion', async ({ page }) => {
168-
test.setTimeout(90000);
213+
test.setTimeout(120000);
169214
const adminModelCardPage = new AdminModelCardPage(page);
170215
const timestamp = Date.now();
216+
const folderName = `e2e-test-bulk-cancel-folder-${timestamp}`;
171217
const filterPrefix = `e2e-test-bulk-cancel-${timestamp}`;
172218
const cardNames = [`${filterPrefix}-1`, `${filterPrefix}-2`];
173219

174-
// Setup: create two model cards
220+
// Setup: create a shared folder via the "+" button for the first card,
221+
// then reuse it for the second card
175222
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
176223
await adminModelCardPage.waitForTableLoad();
177-
for (const name of cardNames) {
178-
await adminModelCardPage.createModelCard({ name });
179-
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
180-
await adminModelCardPage.waitForTableLoad();
181-
}
224+
await adminModelCardPage.createModelCard({
225+
name: cardNames[0],
226+
createNewFolderName: folderName,
227+
});
228+
229+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
230+
await adminModelCardPage.waitForTableLoad();
231+
await adminModelCardPage.createModelCard({
232+
name: cardNames[1],
233+
vfolderTitle: folderName,
234+
});
182235

183236
// Filter to show only this run's test cards (timestamp ensures uniqueness)
237+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
238+
await adminModelCardPage.waitForTableLoad();
184239
await adminModelCardPage.applyNameFilter(filterPrefix);
185240
await expect(adminModelCardPage.getDataRows().first()).toBeVisible({
186241
timeout: 10000,
@@ -210,10 +265,12 @@ test.describe(
210265
await expect(adminModelCardPage.getRowByName(name)).toBeVisible();
211266
}
212267

213-
// Cleanup: delete the test model cards
268+
// Cleanup: delete each model card (card only), then clean up the shared folder
214269
for (const name of cardNames) {
215270
await adminModelCardPage.deleteModelCardByName(name);
216271
}
272+
await moveToTrashAndVerify(page, folderName, 'admin-data');
273+
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
217274
});
218275

219276
// 5.5 Superadmin can clear selection using the BAISelectionLabel clear button
@@ -266,5 +323,133 @@ test.describe(
266323
`${rowCount} selected`,
267324
);
268325
});
326+
327+
// 5.7 Superadmin can delete a model card and its associated folder together
328+
test('Superadmin can delete a model card and its associated folder, and navigate to trash with folder filter', async ({
329+
page,
330+
}) => {
331+
test.setTimeout(90000);
332+
const adminModelCardPage = new AdminModelCardPage(page);
333+
const timestamp = Date.now();
334+
const folderName = `e2e-test-delete-folder-${timestamp}`;
335+
const cardName = `e2e-test-delete-with-folder-${timestamp}`;
336+
337+
// Create a model card with a new dedicated folder via the "+" button
338+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
339+
await adminModelCardPage.waitForTableLoad();
340+
await adminModelCardPage.createModelCard({
341+
name: cardName,
342+
createNewFolderName: folderName,
343+
});
344+
345+
// Navigate back and filter for the created card
346+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
347+
await adminModelCardPage.waitForTableLoad();
348+
await adminModelCardPage.applyNameFilter(cardName);
349+
350+
// Open the delete confirmation dialog
351+
await adminModelCardPage.clickDeleteForRow(cardName);
352+
353+
// Verify the "Also delete the associated model folder" checkbox is visible
354+
await expect(
355+
adminModelCardPage.getAlsoDeleteFolderCheckbox(),
356+
).toBeVisible();
357+
358+
// Verify the folder link shows the expected folder name
359+
const folderLink = adminModelCardPage.getFolderNameLinkInDeleteDialog();
360+
await expect(folderLink).toBeVisible();
361+
await expect(folderLink).toHaveText(folderName);
362+
363+
// Check the "Also delete the associated model folder" checkbox
364+
await adminModelCardPage.getAlsoDeleteFolderCheckbox().check();
365+
await expect(
366+
adminModelCardPage.getAlsoDeleteFolderCheckbox(),
367+
).toBeChecked();
368+
369+
// Confirm deletion
370+
await adminModelCardPage.getDeleteConfirmButton().click();
371+
372+
// Verify the success notification for card + folder deletion
373+
await expect(
374+
page.getByText('Model card and folder have been moved to trash.'),
375+
).toBeVisible({ timeout: 15000 });
376+
377+
// Verify "Go to Data > Trash" link is visible in the notification
378+
const goToTrashLink = page.getByText('Go to Data > Trash');
379+
await expect(goToTrashLink).toBeVisible();
380+
381+
// Click "Go to Data > Trash" and verify URL includes folder filter
382+
await goToTrashLink.click();
383+
await page.waitForURL(
384+
(url) =>
385+
url.pathname === '/data' &&
386+
url.searchParams.get('statusCategory') === 'deleted' &&
387+
url.searchParams.get('filter') === `name == "${folderName}"`,
388+
{ timeout: 10000 },
389+
);
390+
391+
// Verify the folder row is visible in the trash list and permanently delete it
392+
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
393+
});
394+
395+
// 5.8 Superadmin deletes card only: notification shows correct message and Go to Trash navigates correctly
396+
test('Superadmin can delete a model card only and navigate to trash without folder filter', async ({
397+
page,
398+
}) => {
399+
test.setTimeout(90000);
400+
const adminModelCardPage = new AdminModelCardPage(page);
401+
const timestamp = Date.now();
402+
const folderName = `e2e-test-keep-folder-${timestamp}`;
403+
const cardName = `e2e-test-delete-card-only-${timestamp}`;
404+
405+
// Create a model card with a new dedicated folder via the "+" button
406+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
407+
await adminModelCardPage.waitForTableLoad();
408+
await adminModelCardPage.createModelCard({
409+
name: cardName,
410+
createNewFolderName: folderName,
411+
});
412+
413+
// Navigate back and filter
414+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
415+
await adminModelCardPage.waitForTableLoad();
416+
await adminModelCardPage.applyNameFilter(cardName);
417+
418+
// Open delete dialog
419+
await adminModelCardPage.clickDeleteForRow(cardName);
420+
421+
// Leave "Also delete folder" checkbox unchecked (default)
422+
await expect(
423+
adminModelCardPage.getAlsoDeleteFolderCheckbox(),
424+
).not.toBeChecked();
425+
426+
// Confirm deletion
427+
await adminModelCardPage.getDeleteConfirmButton().click();
428+
429+
// Verify the notification message for card-only deletion
430+
await expect(
431+
page.getByText(
432+
'Model card has been deleted. The model folder was not deleted.',
433+
),
434+
).toBeVisible({ timeout: 15000 });
435+
436+
// Verify "Go to Data > Trash" link is visible
437+
const goToTrashLink = page.getByText('Go to Data > Trash');
438+
await expect(goToTrashLink).toBeVisible();
439+
440+
// Click "Go to Data > Trash" and verify URL (no folder filter)
441+
await goToTrashLink.click();
442+
await page.waitForURL(
443+
(url) =>
444+
url.pathname === '/data' &&
445+
url.searchParams.get('statusCategory') === 'deleted' &&
446+
!url.searchParams.has('filter'),
447+
{ timeout: 10000 },
448+
);
449+
450+
// Cleanup: move the kept test folder to trash then permanently delete it
451+
await moveToTrashAndVerify(page, folderName, 'admin-data');
452+
await deleteForeverAndVerifyFromTrash(page, folderName, 'admin-data');
453+
});
269454
},
270455
);

0 commit comments

Comments
 (0)