Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 85 additions & 60 deletions e2e/auto-scaling-rule-preset/preset-integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// This test deploys a brand-new model service, verifies the preset appears in the
// auto-scaling rule editor dropdown, then terminates the service in afterAll.
import { FolderExplorerModal } from '../utils/classes/vfolder/FolderExplorerModal';
import { sweepServices } from '../utils/cleanup-util';
import { sweepServices, sweepVFolders } from '../utils/cleanup-util';
import {
loginAsAdmin,
navigateTo,
Expand Down Expand Up @@ -53,10 +53,9 @@ async function createPreset(
): Promise<void> {
await page.goto(`${webuiEndpoint}/admin-serving?tab=auto-scaling-rule`);
await page.getByRole('button', { name: /Add Preset/i }).click();
const modal = page
.locator('.ant-modal')
.filter({ has: page.getByRole('dialog') });
await expect(modal).toBeVisible();
const modal = page.getByRole('dialog');
await expect(modal).toBeVisible({ timeout: 10000 });
await expect(modal).not.toHaveClass(/ant-zoom-appear/, { timeout: 10000 });
await modal.getByRole('textbox', { name: 'Name', exact: true }).fill(name);
await modal.getByRole('textbox', { name: 'Metric Name' }).fill(metricName);
await modal
Expand All @@ -66,24 +65,27 @@ async function createPreset(
await expect(
page.getByText('Prometheus query preset has been successfully created.'),
).toBeVisible({ timeout: 10000 });
await expect(modal).toBeHidden();
await expect(modal).toBeHidden({ timeout: 30000 });
}

async function deletePreset(page: Page, presetName: string): Promise<void> {
await page.goto(`${webuiEndpoint}/admin-serving?tab=auto-scaling-rule`);
const row = page.getByRole('row').filter({ hasText: presetName });
if ((await row.count()) === 0) return;
await row.getByRole('button', { name: 'delete' }).click();
const confirmInput = page.locator('#confirmText');
await expect(confirmInput).toBeVisible({ timeout: 5000 });
await confirmInput.fill(presetName);
await page
await row.locator('button:has(.anticon-delete)').click();
const confirmModal = page.getByRole('dialog');
await expect(confirmModal).toBeVisible({ timeout: 30000 });
await expect(confirmModal).not.toHaveClass(/ant-zoom-appear/, {
timeout: 10000,
});
await confirmModal.locator('input').fill(presetName);
await confirmModal
.getByRole('button', { name: 'Delete', exact: true })
.last()
.click();
await expect(
page.getByText('Prometheus query preset has been successfully deleted.'),
).toBeVisible({ timeout: 10000 });
await expect(confirmModal).toBeHidden({ timeout: 30000 });
}

async function uploadFixturesToVFolder(
Expand Down Expand Up @@ -286,7 +288,16 @@ test.describe(
let presetName: string;
let presetNameTimeWindow: string;

test.afterAll(async ({ page }) => {
test.afterAll(async ({ browser, request }) => {
// Cleanup presets and services using a fresh context (page fixture not allowed in afterAll)
const context = await browser.newContext();
const page = await context.newPage();
try {
await loginAsAdmin(page, request);
} catch {
await context.close();
return;
}
// Cleanup presets
if (presetName) {
try {
Expand All @@ -308,6 +319,13 @@ test.describe(
} catch {
// ignore
}
// Delete the model vfolder
try {
await sweepVFolders(page, new RegExp(VFOLDER_NAME));
} catch {
// ignore
}
await context.close();
});

// 9.1 A newly created Prometheus Query Preset appears in the auto-scaling rule editor dropdown
Expand All @@ -326,10 +344,11 @@ test.describe(

await page.goto(`${webuiEndpoint}/admin-serving?tab=auto-scaling-rule`);
await page.getByRole('button', { name: /Add Preset/i }).click();
const createModal = page
.locator('.ant-modal')
.filter({ has: page.getByRole('dialog') });
await expect(createModal).toBeVisible();
const createModal = page.getByRole('dialog');
await expect(createModal).toBeVisible({ timeout: 10000 });
await expect(createModal).not.toHaveClass(/ant-zoom-appear/, {
timeout: 10000,
});
await createModal
.getByRole('textbox', { name: 'Name', exact: true })
.fill(presetName);
Expand All @@ -349,7 +368,7 @@ test.describe(
'Prometheus query preset has been successfully created.',
),
).toBeVisible({ timeout: 10000 });
await expect(createModal).toBeHidden();
await expect(createModal).toBeHidden({ timeout: 30000 });

// Verify the preset row appears in the table
await expect(
Expand All @@ -375,10 +394,10 @@ test.describe(
await expect(serviceRow).toBeVisible({ timeout: 60_000 });

// Step 8: Navigate to the endpoint detail page
const serviceNameLink = serviceRow
await serviceRow
.getByRole('link', { name: SERVICE_NAME })
.or(serviceRow.getByRole('cell', { name: SERVICE_NAME }));
await serviceNameLink.first().click();
.first()
.click();

// Step 9: Locate the "Auto Scaling Rules" section and click "Add Rules"
const addRulesButton = page
Expand All @@ -388,24 +407,25 @@ test.describe(
await addRulesButton.click();

// Step 10: Verify the Add Auto Scaling Rule modal opens
const ruleModal = page
.locator('.ant-modal')
.filter({ has: page.getByRole('dialog') });
await expect(ruleModal).toBeVisible();
await expect(ruleModal.getByRole('dialog')).toContainText(
/Auto Scaling Rule/i,
);
const ruleModal = page.getByRole('dialog');
await expect(ruleModal).toBeVisible({ timeout: 10000 });
await expect(ruleModal).not.toHaveClass(/ant-zoom-appear/, {
timeout: 10000,
});
await expect(ruleModal).toContainText(/Auto Scaling Rule/i);

// Step 11: Set Metric Source to "Prometheus"
const metricSourceSelect = ruleModal.getByRole('combobox', {
name: /Metric Source/i,
});
await expect(metricSourceSelect).toBeVisible({ timeout: 10000 });
await metricSourceSelect.click();
await page
const prometheusItem = page
.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden)')
.getByRole('option', { name: 'Prometheus' })
.click();
.last()
.locator('.ant-select-item-option', { hasText: 'Prometheus' });
await expect(prometheusItem).toBeVisible({ timeout: 10000 });
await prometheusItem.click();

// Step 12: Verify the Metric Name (Prometheus Preset) select field is visible
const prometheusPresetSelect = ruleModal.getByRole('combobox', {
Expand All @@ -415,27 +435,27 @@ test.describe(

// Step 13: Click the dropdown and verify the test preset is listed
await prometheusPresetSelect.click();
// Use .last() to pick the actively appearing dropdown (the previous one may still be in leave-animation)
const presetOption = page
.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden)')
.getByRole('option', { name: presetName });
.last()
.locator('.ant-select-item-option', { hasText: presetName });
await expect(presetOption).toBeVisible({ timeout: 10000 });

// Step 14: Select the test preset
await presetOption.click();

// Step 15: Verify the (hidden) Metric Name field is auto-filled
// The hidden metric name field is filled when the preset is selected
// (via form.setFieldsValue in the component)
const metricNameField = ruleModal.getByRole('textbox', {
name: /Metric Name$/i,
});
await expect(metricNameField).toHaveValue('e2e_integration_metric', {
// The Metric Name field is hidden (display:none) when Prometheus is selected,
// since the field is controlled via form.setFieldsValue. Check via DOM input value.
const metricNameInput = ruleModal.locator('input[id*="metricName"]');
await expect(metricNameInput).toHaveValue('e2e_integration_metric', {
timeout: 5000,
});

// Step 16: Cancel the modal
await ruleModal.getByRole('button', { name: 'Cancel' }).click();
await expect(ruleModal).toBeHidden();
await expect(ruleModal).toBeHidden({ timeout: 30000 });
});

// 9.2 Selecting a Prometheus preset auto-fills the Metric Name and Cool Down Seconds
Expand All @@ -461,10 +481,11 @@ test.describe(
.filter({ hasText: presetNameTimeWindow });
await expect(twRow).toBeVisible({ timeout: 10000 });
await twRow.getByRole('button', { name: 'edit' }).click();
const editModal = page
.locator('.ant-modal')
.filter({ has: page.getByRole('dialog') });
await expect(editModal).toBeVisible();
const editModal = page.getByRole('dialog');
await expect(editModal).toBeVisible({ timeout: 10000 });
await expect(editModal).not.toHaveClass(/ant-zoom-appear/, {
timeout: 10000,
});
await editModal
.getByRole('textbox', { name: 'Time Window (optional)' })
.fill('15m');
Expand All @@ -474,18 +495,18 @@ test.describe(
'Prometheus query preset has been successfully updated.',
),
).toBeVisible({ timeout: 10000 });
await expect(editModal).toBeHidden();
await expect(editModal).toBeHidden({ timeout: 30000 });

// Navigate to the endpoint detail page for the service from test 9.1
await navigateTo(page, 'serving');
const serviceRow = page
.getByRole('row')
.filter({ hasText: SERVICE_NAME });
await expect(serviceRow).toBeVisible({ timeout: 30_000 });
const serviceNameLink = serviceRow
await serviceRow
.getByRole('link', { name: SERVICE_NAME })
.or(serviceRow.getByRole('cell', { name: SERVICE_NAME }));
await serviceNameLink.first().click();
.first()
.click();

// Open the "Add Auto Scaling Rule" modal
const addRulesButton = page
Expand All @@ -494,20 +515,23 @@ test.describe(
await expect(addRulesButton).toBeVisible({ timeout: 30_000 });
await addRulesButton.click();

const ruleModal = page
.locator('.ant-modal')
.filter({ has: page.getByRole('dialog') });
await expect(ruleModal).toBeVisible();
const ruleModal = page.getByRole('dialog');
await expect(ruleModal).toBeVisible({ timeout: 10000 });
await expect(ruleModal).not.toHaveClass(/ant-zoom-appear/, {
timeout: 10000,
});

// Set Metric Source to "Prometheus"
const metricSourceSelect = ruleModal.getByRole('combobox', {
name: /Metric Source/i,
});
await metricSourceSelect.click();
await page
const prometheusItemTw = page
.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden)')
.getByRole('option', { name: 'Prometheus' })
.click();
.last()
.locator('.ant-select-item-option', { hasText: 'Prometheus' });
await expect(prometheusItemTw).toBeVisible({ timeout: 10000 });
await prometheusItemTw.click();

// Select the time-window preset
const prometheusPresetSelect = ruleModal.getByRole('combobox', {
Expand All @@ -517,21 +541,22 @@ test.describe(
await prometheusPresetSelect.click();
const twPresetOption = page
.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden)')
.getByRole('option', { name: presetNameTimeWindow });
.last()
.locator('.ant-select-item-option', { hasText: presetNameTimeWindow });
await expect(twPresetOption).toBeVisible({ timeout: 10000 });
await twPresetOption.click();

// Verify the hidden Metric Name field is auto-filled
const metricNameField = ruleModal.getByRole('textbox', {
name: /Metric Name$/i,
});
await expect(metricNameField).toHaveValue('e2e_tw_metric', {
// The Metric Name field is hidden (display:none) when Prometheus is selected,
// since the field is controlled via form.setFieldsValue. Check via DOM input value.
const metricNameInputTw = ruleModal.locator('input[id*="metricName"]');
await expect(metricNameInputTw).toHaveValue('e2e_tw_metric', {
timeout: 5000,
});

// Cancel the modal
await ruleModal.getByRole('button', { name: 'Cancel' }).click();
await expect(ruleModal).toBeHidden();
await expect(ruleModal).toBeHidden({ timeout: 30000 });
});
},
);
7 changes: 3 additions & 4 deletions e2e/auto-scaling-rule-preset/preset-table-settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,9 @@ test.describe(
await expect(row).toBeVisible({ timeout: 15000 });

// Find and click the copy icon in the Query Template cell
const queryTemplateCell = row
.getByRole('cell')
.filter({ hasText: queryTemplate });
await expect(queryTemplateCell).toBeVisible();
// The cell text is truncated in the DOM (CSS ellipsis), so we locate it by column index (3rd cell = Query Template)
const queryTemplateCell = row.locator('td').nth(2);
await expect(queryTemplateCell).toBeVisible({ timeout: 15000 });

const copyButton = queryTemplateCell.getByRole('button', {
name: /copy/i,
Expand Down
9 changes: 8 additions & 1 deletion e2e/utils/cleanup-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,17 @@ export async function sweepServices(
.first();
if ((await serviceRow.count()) === 0) break;

// Hover over the first cell to reveal BAINameActionCell action buttons
await serviceRow
.getByRole('cell')
.first()
.hover()
.catch(() => {});

const deleteBtn = serviceRow
.getByRole('button', { name: 'delete' })
.first();
if (!(await deleteBtn.isVisible({ timeout: 3000 }).catch(() => false))) {
if (!(await deleteBtn.isVisible({ timeout: 5000 }).catch(() => false))) {
console.log('Service row found but no delete button, skipping');
break;
}
Expand Down
2 changes: 1 addition & 1 deletion e2e/utils/test-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ export async function createVFolderAndVerify(
type: 'user' | 'project' = 'user',
permission: 'rw' | 'ro' = 'rw',
) {
await page.getByRole('link', { name: 'Data' }).click();
await navigateTo(page, 'data');
await page.getByRole('button', { name: 'Create Folder' }).nth(1).click();

const modal = new FolderCreationModal(page);
Expand Down
Loading