Skip to content

Commit a3f2e0f

Browse files
committed
e2e(FR-2472): align route table test with current BAIRouteNodes columns
- Replace 'Traffic Ratio' column header expectations with 'Created At' (TrafficRatio column is currently commented out in BAIRouteNodes.tsx) - Mark test 4.6 / 7.2 as test.fixme with TODO(needs-backend) - Skip accelerator spinbutton edit when it is already disabled at 0
1 parent 19bdc71 commit a3f2e0f

5 files changed

Lines changed: 104 additions & 25 deletions

File tree

e2e/E2E_COVERAGE_REPORT.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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): 253 / 410 features covered (62%)**
15+
**Overall (in-scope routes): 228 / 372 features covered (61%)**
1616

1717
| Page | Route | Features | Covered | Status |
1818
|------|-------|:--------:|:-------:|:------:|
@@ -22,9 +22,9 @@
2222
| Dashboard | `/dashboard` | 9 | 7 | 🔶 78% |
2323
| Session List | `/session` | 22 | 14 | 🔶 64% |
2424
| Session Launcher | `/session/start` | 14 | 3 | 🔶 21% |
25-
| Serving | `/serving` | 7 | 0 | ❌ 0% |
25+
| Serving | `/serving` | 7 | 2 | 🔶 29% |
2626
| Endpoint Detail | `/serving/:serviceId` | 20 | 9 | 🔶 45% |
27-
| Service Launcher | `/service/start` | 5 | 0 | ❌ 0% |
27+
| Service Launcher | `/service/start` | 5 | 1 | 🔶 20% |
2828
| VFolder / Data | `/data` | 45 | 32 | 🔶 71% |
2929
| Model Store | `/model-store` | 6 | 6 | ✅ 100% |
3030
| Admin Model Store | `/admin-model-store` | 22 | 22 | ✅ 100% |
@@ -46,7 +46,7 @@
4646
| App Launcher | (modal) | 18 | 10 | 🔶 56% |
4747
| Chat | `/chat/:id?` | 6 | 6 | ✅ 100% |
4848
| Plugin System | (config-based) | 12 | 12 | ✅ 100% |
49-
| **Total** | | **357** | **203** | **57%** |
49+
| **Total** | | **372** | **228** | **61%** |
5050

5151
---
5252

@@ -223,7 +223,7 @@
223223

224224
### 6. Serving / Model Service (`/serving`)
225225

226-
**Test files:** None (visual regression only: [`e2e/visual_regression/serving/serving_page.test.ts`](visual_regression/serving/serving_page.test.ts))
226+
**Test files:** [`e2e/serving/serving-deploy-lifecycle.spec.ts`](serving/serving-deploy-lifecycle.spec.ts) (integration, `@integration @serving`)
227227

228228
**Filter:** Active | Destroyed (radio)
229229
**Primary action:** "Start Service" → navigates to `/service/start`
@@ -232,15 +232,15 @@
232232

233233
| Feature | Status | Test |
234234
| --------------------------------------------------------- | ------ | ---- |
235-
| Endpoint list rendering | | - |
235+
| Endpoint list rendering | | `Admin can deploy a model service via ServiceLauncher UI` (verifies row visible in serving list) |
236236
| "Start Service" → navigate to `/service/start` || - |
237237
| Endpoint name click → EndpointDetailPage || - |
238238
| Status filtering (Active/Destroyed) || - |
239239
| Property filtering || - |
240240
| Edit endpoint → navigate to `/service/update/:endpointId` || - |
241-
| Delete endpoint → confirm dialog | | - |
241+
| Delete endpoint → confirm dialog | | `Admin can terminate a deployed service` |
242242

243-
**Coverage: ❌ 0/7 features**
243+
**Coverage: 🔶 2/7 features**
244244

245245
---
246246

@@ -281,17 +281,17 @@
281281

282282
### 8. Service Launcher (`/service/start`, `/service/update/:endpointId`)
283283

284-
**Test files:** None
284+
**Test files:** [`e2e/serving/serving-deploy-lifecycle.spec.ts`](serving/serving-deploy-lifecycle.spec.ts) (integration, `@integration @serving`)
285285

286286
| Feature | Status | Test |
287287
| ----------------------- | ------ | ---- |
288-
| Create model service | | - |
288+
| Create model service | | `Admin can deploy a model service via ServiceLauncher UI` |
289289
| Update existing service || - |
290290
| Resource configuration || - |
291291
| Model folder selection || - |
292292
| Form validation || - |
293293

294-
**Coverage: ❌ 0/5 features**
294+
**Coverage: 🔶 1/5 features**
295295

296296
---
297297

e2e/serving/endpoint-route-table.spec.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ test.describe(
141141
card.getByRole('columnheader', { name: 'Traffic Status' }),
142142
).toBeVisible();
143143
await expect(
144-
card.getByRole('columnheader', { name: 'Traffic Ratio' }),
144+
card.getByRole('columnheader', { name: 'Created At' }),
145145
).toBeVisible();
146146
});
147147

@@ -455,7 +455,7 @@ test.describe(
455455
card.getByRole('columnheader', { name: 'Traffic Status' }),
456456
).toBeVisible();
457457
await expect(
458-
card.getByRole('columnheader', { name: 'Traffic Ratio' }),
458+
card.getByRole('columnheader', { name: 'Created At' }),
459459
).toBeVisible();
460460
});
461461

@@ -522,7 +522,10 @@ test.describe(
522522
await expect(inactiveTags.first()).toBeVisible();
523523
});
524524

525-
test('4.6 Admin sees the traffic ratio value in the Traffic Ratio column', async ({
525+
// TODO(needs-backend): Re-enable when BAIRouteNodes exposes the Traffic Ratio
526+
// column. It is currently commented out in BAIRouteNodes.tsx pending backend
527+
// support for per-route traffic ratio.
528+
test.fixme('4.6 Admin sees the traffic ratio value in the Traffic Ratio column', async ({
526529
page,
527530
request,
528531
}) => {
@@ -720,21 +723,22 @@ test.describe(
720723
).toBeVisible();
721724
});
722725

723-
test('7.2 Admin can sort routes by Traffic Ratio column', async ({
726+
// TODO(needs-backend): Re-enable when BAIRouteNodes exposes the Traffic Ratio
727+
// column. It is currently commented out in BAIRouteNodes.tsx pending backend
728+
// support for per-route traffic ratio.
729+
test.fixme('7.2 Admin can sort routes by Traffic Ratio column', async ({
724730
page,
725731
request,
726732
}) => {
727733
await setupAndNavigateToDetail(page, request);
728734

729735
const card = getRoutesInfoCard(page);
730736

731-
// Click the "Traffic Ratio" column header to sort
732737
const trafficRatioHeader = card.getByRole('columnheader', {
733738
name: 'Traffic Ratio',
734739
});
735740
await trafficRatioHeader.click();
736741

737-
// Verify a sort indicator is shown
738742
await expect(
739743
trafficRatioHeader.locator('.ant-table-column-sorter'),
740744
).toBeVisible();

e2e/serving/fixtures/model-definition.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ models:
88
port: 8000
99
health_check:
1010
path: /health
11-
initial_delay: 5.0
1211
max_retries: 10

e2e/serving/model-card-drawer.spec.ts

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,38 @@ import { test, expect } from '@playwright/test';
2525
// Shared helpers
2626
// ─────────────────────────────────────────────────────────────────────────────
2727

28+
/**
29+
* Intercepts the REST endpoints used by `useProjectResourceGroups` so tests
30+
* can control which resource groups appear in the Deploy modal selector.
31+
* The hook calls `/scaling-groups?group=...` and `/folders/_/hosts` via the
32+
* Backend.AI client and is shared by `BAIProjectResourceGroupSelect`.
33+
*/
34+
async function setupResourceGroupsRestMock(
35+
page: any,
36+
resourceGroupNames: ReadonlyArray<string>,
37+
) {
38+
await page.route('**/func/scaling-groups**', async (route: any) => {
39+
await route.fulfill({
40+
status: 200,
41+
contentType: 'application/json',
42+
body: JSON.stringify({
43+
scaling_groups: resourceGroupNames.map((name) => ({ name })),
44+
}),
45+
});
46+
});
47+
await page.route('**/func/folders/_/hosts', async (route: any) => {
48+
await route.fulfill({
49+
status: 200,
50+
contentType: 'application/json',
51+
body: JSON.stringify({
52+
allowed: [],
53+
default: '',
54+
volume_info: {},
55+
}),
56+
});
57+
});
58+
}
59+
2860
/**
2961
* Shared setup: login, navigate to serving (establishes backendaiclient),
3062
* inject the model-card-v2 feature flag, then set up GraphQL mocks before
@@ -34,9 +66,15 @@ async function setupModelStorePage(
3466
page: any,
3567
request: any,
3668
mocks: Record<string, (vars: Record<string, any>) => Record<string, any>>,
69+
resourceGroupNames: ReadonlyArray<string> = ['default'],
3770
) {
3871
await loginAsAdmin(page, request);
3972

73+
// Mock the REST endpoints that feed `useProjectResourceGroups` before
74+
// anything navigates — the Deploy modal reads resource groups from REST,
75+
// not GraphQL, so the GraphQL `scaling_groups` field is not enough.
76+
await setupResourceGroupsRestMock(page, resourceGroupNames);
77+
4078
// Set up GraphQL mocks BEFORE any navigation that triggers GQL queries.
4179
// Mock ServingPageQuery as well to prevent real API calls on the serving page.
4280
await setupGraphQLMocks(page, {
@@ -288,11 +326,16 @@ test.describe(
288326
test.describe.configure({ mode: 'serial' });
289327

290328
test.beforeEach(async ({ page, request }) => {
291-
await setupModelStorePage(page, request, {
292-
ModelStoreListPageV2Query: modelStoreListWithMultiPresetsMock(),
293-
ModelCardDeployModalQuery: modelCardDeployModalQueryMock(),
294-
ModelCardDeployModalMutation: modelCardDeployModalMutationMock(),
295-
});
329+
await setupModelStorePage(
330+
page,
331+
request,
332+
{
333+
ModelStoreListPageV2Query: modelStoreListWithMultiPresetsMock(),
334+
ModelCardDeployModalQuery: modelCardDeployModalQueryMock(),
335+
ModelCardDeployModalMutation: modelCardDeployModalMutationMock(),
336+
},
337+
['default', 'gpu-cluster'],
338+
);
296339
});
297340

298341
test('admin can open the Deploy Model modal by clicking Deploy in the drawer', async ({

e2e/serving/serving-deploy-lifecycle.spec.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,37 @@ async function createServiceViaUI(
191191
.first()
192192
.click({ timeout: 10000 });
193193

194+
// Set AI Accelerator to 0 to avoid GPU-based allocation presets.
195+
// When the resource group has a GPU preset selected by default (e.g. cuda01-small),
196+
// service creation would fail if no GPU agents are available.
197+
// Setting the accelerator count to 0 ensures CPU-only resource allocation.
198+
//
199+
// Strategy: Find the AI Accelerator form item by its label text, then target
200+
// the spinbutton inside it. Ant Design Form.Item uses a `label` element that
201+
// may not have a `for` attribute in all versions, so we use a compound selector.
202+
const acceleratorFormItem = page
203+
.locator('.ant-form-item')
204+
.filter({ hasText: 'AI Accelerator' })
205+
.first();
206+
const acceleratorSpinbutton = acceleratorFormItem.getByRole('spinbutton');
207+
if (
208+
await acceleratorSpinbutton.isVisible({ timeout: 5000 }).catch(() => false)
209+
) {
210+
// If the spinbutton is already disabled (e.g., no GPU presets available in
211+
// the selected resource group), it is already at 0 — skip editing it.
212+
const isEditable = await acceleratorSpinbutton
213+
.isEditable({ timeout: 1000 })
214+
.catch(() => false);
215+
if (isEditable) {
216+
await acceleratorSpinbutton.scrollIntoViewIfNeeded();
217+
await acceleratorSpinbutton.click({ clickCount: 3 });
218+
await acceleratorSpinbutton.fill('0');
219+
await acceleratorSpinbutton.press('Tab');
220+
// Wait for the field to reflect the updated CPU-only allocation value
221+
await expect(acceleratorSpinbutton).toHaveValue('0', { timeout: 5000 });
222+
}
223+
}
224+
194225
// Check "Open To Public"
195226
const openToPublicCheckbox = page.getByLabel('Open To Public');
196227
await openToPublicCheckbox.scrollIntoViewIfNeeded();
@@ -205,8 +236,10 @@ async function createServiceViaUI(
205236
await expect(createButton).toBeEnabled({ timeout: 5000 });
206237
await createButton.click();
207238

208-
// Wait for redirect to serving page and verify the service appears
209-
await page.waitForURL('**/serving', { timeout: 15000 });
239+
// Wait for the service creation to complete.
240+
// The form navigates to /serving on success or stays on /service/start on error.
241+
await page.waitForURL('**/serving', { timeout: 30000 });
242+
210243
await expect(
211244
page.getByRole('row').filter({ hasText: serviceName }).first(),
212245
).toBeVisible({ timeout: 15000 });

0 commit comments

Comments
 (0)