Skip to content

Commit 589494b

Browse files
committed
chore(FR-2622): update E2E coverage report - admin model store 26→28 features (bulk folder trash)
1 parent cadbaf4 commit 589494b

3 files changed

Lines changed: 206 additions & 26 deletions

File tree

e2e/E2E_COVERAGE_REPORT.md

Lines changed: 7 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-24
3+
> **Last Updated:** 2026-04-28
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): 265 / 412 features covered (64%)**
15+
**Overall (in-scope routes): 267 / 414 features covered (64%)**
1616

1717
| Page | Route | Features | Covered | Status |
1818
|------|-------|:--------:|:-------:|:------:|
@@ -27,7 +27,7 @@
2727
| Service Launcher | `/service/start` | 5 | 1 | 🔶 20% |
2828
| VFolder / Data | `/data` | 45 | 32 | 🔶 71% |
2929
| Model Store | `/model-store` | 6 | 6 | ✅ 100% |
30-
| Admin Model Store | `/admin-model-store` | 26 | 26 | ✅ 100% |
30+
| Admin Model Store | `/admin-model-store` | 28 | 28 | ✅ 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** | | **412** | **265** | **64%** |
50+
| **Total** | | **414** | **267** | **64%** |
5151

5252
---
5353

@@ -428,10 +428,12 @@
428428
| Cancel bulk delete || `admin-model-card-delete.spec.ts` |
429429
| Clear selection || `admin-model-card-delete.spec.ts` |
430430
| Select all via header checkbox || `admin-model-card-delete.spec.ts` |
431+
| Bulk delete + move folders to trash (checkbox) || `admin-model-card-delete.spec.ts` |
432+
| Bulk delete notification → Go to Trash (no folder filter) || `admin-model-card-delete.spec.ts` |
431433
| Non-admin access blocked || `admin-model-card-access-control.spec.ts` |
432434
| URL state persistence (filter/sort/pagination) || `admin-model-card-url-state.spec.ts` |
433435

434-
**Coverage: ✅ 26/26 features**
436+
**Coverage: ✅ 28/28 features**
435437

436438
---
437439

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

Lines changed: 193 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -170,44 +170,38 @@ test.describe(
170170
test('Superadmin can select multiple model cards and delete them in bulk', async ({
171171
page,
172172
}) => {
173-
test.setTimeout(180000);
173+
test.setTimeout(300000);
174174
const adminModelCardPage = new AdminModelCardPage(page);
175175
const timestamp = Date.now();
176176
const folderName = `e2e-test-bulk-delete-folder-${timestamp}`;
177177
const filterPrefix = `e2e-test-bulk-delete-${timestamp}`;
178-
const cardNames = [
179-
`${filterPrefix}-1`,
180-
`${filterPrefix}-2`,
181-
`${filterPrefix}-3`,
182-
];
178+
const cardNames = [`${filterPrefix}-1`, `${filterPrefix}-2`];
183179

184180
// Track folder for cleanup (cards will be deleted by test)
185181
createdResources.push({ folderName });
186182

187183
// Setup: create a shared folder via the "+" button for the first card,
188-
// then reuse it for the remaining cards
184+
// then reuse it for the second card
189185
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
190186
await adminModelCardPage.waitForTableLoad();
191187
await adminModelCardPage.createModelCard({
192188
name: cardNames[0],
193189
createNewFolderName: folderName,
194190
});
195191

196-
for (const name of cardNames.slice(1)) {
197-
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
198-
await adminModelCardPage.waitForTableLoad();
199-
await adminModelCardPage.createModelCard({
200-
name,
201-
vfolderTitle: folderName,
202-
});
203-
}
192+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
193+
await adminModelCardPage.waitForTableLoad();
194+
await adminModelCardPage.createModelCard({
195+
name: cardNames[1],
196+
vfolderTitle: folderName,
197+
});
204198

205199
// Filter to show only this run's test cards (timestamp ensures uniqueness)
206200
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
207201
await adminModelCardPage.waitForTableLoad();
208202
await adminModelCardPage.applyNameFilter(filterPrefix);
209203
await expect(adminModelCardPage.getDataRows().first()).toBeVisible({
210-
timeout: 10000,
204+
timeout: 30000,
211205
});
212206

213207
// Check the header checkbox to select all visible rows
@@ -241,8 +235,12 @@ test.describe(
241235
// Click Delete to confirm
242236
await bulkDialog.getByRole('button', { name: 'Delete' }).click();
243237

244-
// Wait for bulk delete to complete — dialog closes when all mutations finish
245-
await expect(bulkDialog).toBeHidden({ timeout: 90000 });
238+
// Wait for the success toast — under parallel-test load the bulk deletion
239+
// can be slow, so wait on the visible outcome rather than polling the dialog.
240+
await expect(
241+
page.getByText(/\d+ model card\(s\) have been deleted\./i),
242+
).toBeVisible({ timeout: 240000 });
243+
await expect(bulkDialog).toBeHidden();
246244

247245
// Verify the selection label disappears
248246
await expect(adminModelCardPage.getSelectionLabel()).toBeHidden();
@@ -284,7 +282,7 @@ test.describe(
284282
await adminModelCardPage.waitForTableLoad();
285283
await adminModelCardPage.applyNameFilter(filterPrefix);
286284
await expect(adminModelCardPage.getDataRows().first()).toBeVisible({
287-
timeout: 10000,
285+
timeout: 30000,
288286
});
289287

290288
// Select all visible rows
@@ -338,7 +336,7 @@ test.describe(
338336
await adminModelCardPage.waitForTableLoad();
339337
await adminModelCardPage.applyNameFilter(cardName);
340338
await expect(adminModelCardPage.getDataRows().first()).toBeVisible({
341-
timeout: 10000,
339+
timeout: 30000,
342340
});
343341

344342
// Check the checkbox for the first row
@@ -489,7 +487,7 @@ test.describe(
489487
const rawFilter = url.searchParams.get('filter') ?? '';
490488
return rawFilter === `name == "${folderName}"`;
491489
},
492-
{ timeout: 10000 },
490+
{ timeout: 30000 },
493491
);
494492

495493
// Verify the folder row is visible in the trash list and permanently delete it
@@ -546,5 +544,179 @@ test.describe(
546544

547545
// Card was deleted; afterEach will clean up the folder
548546
});
547+
548+
// 5.9 Superadmin can bulk delete model cards and move their folders to trash
549+
test('Superadmin can bulk delete model cards and move their associated folders to trash', async ({
550+
page,
551+
}) => {
552+
test.setTimeout(300000);
553+
const adminModelCardPage = new AdminModelCardPage(page);
554+
const timestamp = Date.now();
555+
const folderName1 = `e2e-test-bulk-trash-f1-${timestamp}`;
556+
const folderName2 = `e2e-test-bulk-trash-f2-${timestamp}`;
557+
const filterPrefix = `e2e-test-bulk-trash-${timestamp}`;
558+
const cardNames = [`${filterPrefix}-1`, `${filterPrefix}-2`];
559+
560+
// Register for cleanup — afterEach handles mid-test failure leaks via try-catch.
561+
// Only push cardNames here; folders are permanently deleted at the end of the test
562+
// body via deleteForeverAndVerifyFromTrash. If afterEach also tried to
563+
// moveToTrashAndVerify them it would hang looking for already-gone folders.
564+
createdResources.push({ cardName: cardNames[0] });
565+
createdResources.push({ cardName: cardNames[1] });
566+
567+
// Setup: create card 1 with its own folder
568+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
569+
await adminModelCardPage.waitForTableLoad();
570+
await adminModelCardPage.createModelCard({
571+
name: cardNames[0],
572+
createNewFolderName: folderName1,
573+
});
574+
575+
// Setup: create card 2 with its own folder
576+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
577+
await adminModelCardPage.waitForTableLoad();
578+
await adminModelCardPage.createModelCard({
579+
name: cardNames[1],
580+
createNewFolderName: folderName2,
581+
});
582+
583+
// Filter to show only this run's test cards
584+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
585+
await adminModelCardPage.waitForTableLoad();
586+
await adminModelCardPage.applyNameFilter(filterPrefix);
587+
await expect(adminModelCardPage.getDataRows().first()).toBeVisible({
588+
timeout: 30000,
589+
});
590+
591+
// Select all visible rows
592+
await adminModelCardPage.getHeaderCheckbox().click();
593+
await expect(adminModelCardPage.getSelectionLabel()).toBeVisible();
594+
595+
// Open bulk delete dialog
596+
await adminModelCardPage.getBulkDeleteButton().click();
597+
const bulkDialog = adminModelCardPage.getBulkDeleteConfirmDialog();
598+
await expect(bulkDialog).toBeVisible();
599+
600+
// Verify the "Also delete the associated model folders" checkbox is visible and unchecked by default
601+
const alsoDeleteCheckbox =
602+
adminModelCardPage.getAlsoDeleteFoldersBulkCheckbox();
603+
await expect(alsoDeleteCheckbox).toBeVisible();
604+
await expect(alsoDeleteCheckbox).not.toBeChecked();
605+
606+
// Check the checkbox to also move folders to trash
607+
await alsoDeleteCheckbox.check();
608+
await expect(alsoDeleteCheckbox).toBeChecked();
609+
610+
// Type "Delete" to confirm
611+
await bulkDialog.getByLabel(/Type.*to confirm/i).fill('Delete');
612+
613+
// Confirm deletion
614+
await bulkDialog.getByRole('button', { name: 'Delete' }).click();
615+
616+
// Wait for the success notification — card delete + folder soft-delete run
617+
// sequentially; under parallel-test load the combined mutation can be slow,
618+
// so we wait directly on the visible outcome rather than polling the dialog.
619+
await expect(
620+
page.getByText(
621+
/model card\(s\) and their folders have been moved to trash/i,
622+
),
623+
).toBeVisible({ timeout: 240000 });
624+
625+
// Dialog must be hidden once the notification is shown
626+
await expect(bulkDialog).toBeHidden();
627+
628+
// Verify "Go to Data > Trash" link is visible
629+
const goToTrashLink = page.getByText('Go to Data > Trash');
630+
await expect(goToTrashLink).toBeVisible();
631+
632+
// Click the link and verify navigation to trash tab without folder filter
633+
await goToTrashLink.click();
634+
await page.waitForURL(
635+
(url) => {
636+
if (url.pathname !== '/admin-data') return false;
637+
return url.searchParams.get('statusCategory') === 'deleted';
638+
},
639+
{ timeout: 30000 },
640+
);
641+
642+
// Permanently delete both folders from trash for cleanup
643+
await deleteForeverAndVerifyFromTrash(page, folderName1, 'admin-data');
644+
await deleteForeverAndVerifyFromTrash(page, folderName2, 'admin-data');
645+
});
646+
647+
// 5.10 Superadmin can bulk delete model cards without moving their folders to trash
648+
test('Superadmin can bulk delete model cards without moving their folders to trash', async ({
649+
page,
650+
}) => {
651+
test.setTimeout(300000);
652+
const adminModelCardPage = new AdminModelCardPage(page);
653+
const timestamp = Date.now();
654+
const folderName = `e2e-test-bulk-notrash-folder-${timestamp}`;
655+
const filterPrefix = `e2e-test-bulk-notrash-${timestamp}`;
656+
const cardNames = [`${filterPrefix}-1`, `${filterPrefix}-2`];
657+
658+
// Track folder for cleanup (cards deleted by test; folder stays active)
659+
createdResources.push({ folderName });
660+
661+
// Setup: create card 1 with a new folder
662+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
663+
await adminModelCardPage.waitForTableLoad();
664+
await adminModelCardPage.createModelCard({
665+
name: cardNames[0],
666+
createNewFolderName: folderName,
667+
});
668+
669+
// Setup: create card 2 sharing the same folder
670+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
671+
await adminModelCardPage.waitForTableLoad();
672+
await adminModelCardPage.createModelCard({
673+
name: cardNames[1],
674+
vfolderTitle: folderName,
675+
});
676+
677+
// Filter to show only this run's test cards
678+
await page.goto(`${webuiEndpoint}/admin-serving?tab=model-store`);
679+
await adminModelCardPage.waitForTableLoad();
680+
await adminModelCardPage.applyNameFilter(filterPrefix);
681+
await expect(adminModelCardPage.getDataRows().first()).toBeVisible({
682+
timeout: 30000,
683+
});
684+
685+
// Select all visible rows
686+
await adminModelCardPage.getHeaderCheckbox().click();
687+
await expect(adminModelCardPage.getSelectionLabel()).toBeVisible();
688+
689+
// Open bulk delete dialog
690+
await adminModelCardPage.getBulkDeleteButton().click();
691+
const bulkDialog = adminModelCardPage.getBulkDeleteConfirmDialog();
692+
await expect(bulkDialog).toBeVisible();
693+
694+
// Verify the checkbox is visible but leave it unchecked (default)
695+
const alsoDeleteCheckbox =
696+
adminModelCardPage.getAlsoDeleteFoldersBulkCheckbox();
697+
await expect(alsoDeleteCheckbox).toBeVisible();
698+
await expect(alsoDeleteCheckbox).not.toBeChecked();
699+
700+
// Type "Delete" to confirm (checkbox unchecked)
701+
await bulkDialog.getByLabel(/Type.*to confirm/i).fill('Delete');
702+
703+
// Confirm deletion
704+
await bulkDialog.getByRole('button', { name: 'Delete' }).click();
705+
706+
// Wait for the success toast — under parallel-test load the bulk deletion
707+
// can be slow, so wait on the visible outcome rather than polling the dialog.
708+
await expect(
709+
page.getByText(/\d+ model card\(s\) have been deleted\./i),
710+
).toBeVisible({ timeout: 240000 });
711+
await expect(bulkDialog).toBeHidden();
712+
713+
// Verify no "Go to Data > Trash" link appears
714+
await expect(page.getByText('Go to Data > Trash')).not.toBeVisible();
715+
716+
// Verify selection label has cleared (cards were deleted)
717+
await expect(adminModelCardPage.getSelectionLabel()).toBeHidden();
718+
719+
// afterEach handles folder cleanup (folder remains in active state)
720+
});
549721
},
550722
);

e2e/utils/classes/AdminModelCardPage.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,12 @@ export class AdminModelCardPage {
413413
return this.getDeleteConfirmDialog().getByRole('link').first();
414414
}
415415

416+
getAlsoDeleteFoldersBulkCheckbox(): Locator {
417+
return this.getBulkDeleteConfirmDialog().getByRole('checkbox', {
418+
name: /Also delete.*model folders/i,
419+
});
420+
}
421+
416422
// ── Helper: create via UI and return ─────────────────────────────────────
417423

418424
async createModelCard(fields: {

0 commit comments

Comments
 (0)