import { expect, test, type Page } from '@playwright/test';
import { FUYAO_BASE_URL } from '@/utils/constants';
import {
clickDetailTab,
clickRefresh,
clickResourceNameInList,
ExpectAddLabelAnnotationSuccess,
deleteLabelAnnotationAndExpectSuccess,
expectEmptyLabelAnnotationError,
expectNoChangeLabelAnnotationMessage,
openModifyLabelAnnotationEditor,
searchResource,
} from './utils/common';
const NODE_LIST_PATH = '/container_platform/nodeManage';
const WAIT_AFTER_CLICK = 300;
test.describe('节点管理', () => {
test.beforeEach(async ({ page }) => {
await page.goto(FUYAO_BASE_URL + NODE_LIST_PATH);
await page.waitForLoadState('networkidle');
});
test('【资源管理-563】节点,集群的节点信息展示正确', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
for (const header of ['节点名称', '类型', '状态', '是否繁忙']) {
await expect(page.getByRole('columnheader', { name: header })).toBeVisible();
}
const dataRow = page.locator('table tbody tr').filter({ has: page.locator('td a') }).first();
await expect(dataRow).toBeVisible({ timeout: 10000 });
const cells = dataRow.locator('td');
const cellCount = await cells.count();
for (let i = 0; i < cellCount; i++) {
expect((await cells.nth(i).innerText()).trim()).not.toBe('');
}
});
test('【资源管理-564】节点,刷新按钮功能正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
await clickRefresh(page);
await expect(page.getByRole('columnheader', { name: '节点名称' })).toBeVisible();
await expect(page.locator('table tbody tr').filter({ has: page.locator('td a') }).first()).toBeVisible();
});
test('【资源管理-565】节点,搜索框输入正确的节点名称,可正确查询出节点信息', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
const nodeName = await getFirstNodeName(page);
await searchResource(page, nodeName!);
await expect(page.getByRole('row', { name: new RegExp(nodeName!) }).first()).toBeVisible({ timeout: 10000 });
});
test('【资源管理-566】节点,搜索框异常场景验证', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
const nodeName = await getFirstNodeName(page);
await searchResource(page, nodeName!.toUpperCase());
await expect(page.getByRole('row', { name: new RegExp(nodeName!, 'i') }).first()).toBeVisible({ timeout: 10000 });
await searchResource(page, 'a'.repeat(64));
await expect(page.getByText(/暂无数据/)).toBeVisible({ timeout: 5000 });
});
test('【资源管理-567】节点,列表按节点名称排序正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
await assertColumnSorted(page, /节点名称/, 0);
});
test('【资源管理-568】节点,列表按类型排序正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
await assertColumnSorted(page, /类型/, 2);
});
test('【资源管理-569】节点,列表按类型筛选正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
await assertColumnFiltered(page, /类型/, 2, '管理节点');
});
test('【资源管理-570】节点,列表按状态排序正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
await assertColumnSorted(page, /状态/, 3);
});
test('【资源管理-571】节点,列表按状态筛选正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
const statusHeader = page.getByRole('columnheader', { name: /状态/ });
const hasStatusFilter = await statusHeader.getByRole('button', { name: /filter/i }).count() > 0;
if (hasStatusFilter) {
await assertColumnFiltered(page, /状态/, 3, '正常');
return;
}
await assertColumnFiltered(page, /类型/, 2, '工作节点');
});
test('【资源管理-572】节点,列表按是否繁忙排序正确', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
await assertColumnSorted(page, /是否繁忙/, 4);
});
test('【资源管理-573】节点,列表按是否繁忙排序正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
await assertColumnFiltered(page, /是否繁忙/, 4, '否');
});
test('【资源管理-574】节点,列表分页功能正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
const totalText = await page.getByText(/共\d+条/).textContent().catch(() => null);
const totalMatch = totalText?.match(/共(\d+)条/);
const total = totalMatch?.[1] !== undefined ? parseInt(totalMatch[1], 10) : 0;
if (total <= 10) {
const rowCount = await countDataRows(page);
expect(rowCount).toBeLessThanOrEqual(10);
} else {
await page.getByText('10 条/页', { exact: true }).click();
await page.waitForTimeout(WAIT_AFTER_CLICK);
await page.getByText('20 条/页', { exact: true }).click();
await page.waitForTimeout(WAIT_AFTER_CLICK);
expect(await countDataRows(page)).toBeLessThanOrEqual(20);
}
const nodeName = await getFirstNodeName(page);
await clickResourceNameInList(page, nodeName!);
await page.getByRole('tab', { name: 'Pod' }).click();
await page.waitForLoadState('networkidle');
const podTotalText = await page.getByText(/共\d+条/).textContent().catch(() => null);
const podTotalMatch = podTotalText?.match(/共(\d+)条/);
const podTotal = podTotalMatch?.[1] !== undefined ? parseInt(podTotalMatch[1], 10) : 0;
if (podTotal <= 10) {
expect(await countDataRows(page)).toBeLessThanOrEqual(10);
}
});
test('【资源管理-575】节点,点击节点名称跳转到节点详情正确', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
const nodeName = await getFirstNodeName(page);
await clickResourceNameInList(page, nodeName!);
await expect(page).toHaveURL(new RegExp(`${NODE_LIST_PATH}/${nodeName}$`));
});
test('【资源管理-576】节点,节点详情中的详情Tab页面信息展示正确', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
const nodeName = await getFirstNodeName(page);
await page.goto(`${FUYAO_BASE_URL}${NODE_LIST_PATH}/${nodeName}`);
await page.waitForLoadState('networkidle');
await clickDetailTab(page);
for (const val of await page.locator('.base_value').all()) {
expect(await val.textContent()).not.toMatch(/^-?$/);
}
});
test('【资源管理-581】节点,节点详情中的Pod Tab页面信息展示正确', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
const nodeName = await getFirstNodeName(page);
await page.goto(`${FUYAO_BASE_URL}${NODE_LIST_PATH}/${nodeName}`);
await page.waitForLoadState('networkidle');
await page.getByRole('tab', { name: 'Pod' }).click();
await page.waitForLoadState('networkidle');
await expect(page.getByPlaceholder('搜索Pod名称')).toBeVisible({ timeout: 10000 });
await expect(page.getByText(/共\d+条/)).toBeVisible({ timeout: 10000 });
await expect(page.locator('a[href*="/workload/pod/"]').first()).toBeVisible({ timeout: 10000 });
});
test('【资源管理-582】节点,节点详情中的pod Tab页面搜索功能正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
const nodeName = await getFirstNodeName(page);
await page.goto(`${FUYAO_BASE_URL}${NODE_LIST_PATH}/${nodeName}`);
await page.waitForLoadState('networkidle');
await page.getByRole('tab', { name: 'Pod' }).click();
await page.waitForSelector('.node_title,.flexBox', { timeout: 10000 });
const testPodName = (await page.locator('.node_title a').first().textContent())?.trim().slice(0, 5);
await page.getByPlaceholder('搜索Pod名称').fill('nonexistent-pod-e2e');
await page.getByRole('button', { name: 'search' }).click();
await expect(page.locator('.node_title a')).toHaveCount(0, { timeout: 10000 });
if (testPodName) {
await page.getByPlaceholder('搜索Pod名称').fill(testPodName);
await page.getByRole('button', { name: 'search' }).click();
for (const podLink of await page.locator('.node_title a').all()) {
const resPodName = (await podLink.textContent())?.trim();
expect(resPodName).toContain(testPodName);
}
}
});
});
test.describe('节点管理-标签注解变更', () => {
test.describe.configure({ mode: 'serial' });
test.beforeEach(async ({ page }) => {
await page.goto(FUYAO_BASE_URL + NODE_LIST_PATH);
await page.waitForLoadState('networkidle');
});
test('【资源管理-579】节点,节点详情中的详情Tab-基本信息中修改标签功能正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
const nodeName = await getFirstNodeName(page);
await page.goto(`${FUYAO_BASE_URL}${NODE_LIST_PATH}/${nodeName}`);
await page.waitForLoadState('networkidle');
const key = `e2e-label-${Date.now()}`;
const value = 'test-value';
await test.step('修改成功', async () => {
await openModifyLabelAnnotationEditor(page, 'label', 'icon');
await ExpectAddLabelAnnotationSuccess(page, 'label', key, value);
});
await test.step('内容为空', async () => {
await openModifyLabelAnnotationEditor(page, 'label', 'icon');
await expectEmptyLabelAnnotationError(page, 'label');
});
await test.step('无变化提交', async () => {
await openModifyLabelAnnotationEditor(page, 'label', 'icon');
await expectNoChangeLabelAnnotationMessage(page, 'label');
});
await test.step('清理测试数据', async () => {
await openModifyLabelAnnotationEditor(page, 'label', 'icon');
await deleteLabelAnnotationAndExpectSuccess(page, 'label', key, value);
});
});
test('【资源管理-580】节点,节点详情中的详情Tab-基本信息中修改注解功能正常', {
tag: ['@v24.06', '@k8s'],
}, async ({ page }) => {
const nodeName = await getFirstNodeName(page);
await page.goto(`${FUYAO_BASE_URL}${NODE_LIST_PATH}/${nodeName}`);
await page.waitForLoadState('networkidle');
const key = `e2e-annotation-${Date.now()}`;
const value = 'test-value';
await test.step('修改成功', async () => {
await openModifyLabelAnnotationEditor(page, 'annotation', 'icon');
await ExpectAddLabelAnnotationSuccess(page, 'annotation', key, value);
});
await test.step('内容为空', async () => {
await openModifyLabelAnnotationEditor(page, 'annotation', 'icon');
await expectEmptyLabelAnnotationError(page, 'annotation');
});
await test.step('无变化提交', async () => {
await openModifyLabelAnnotationEditor(page, 'annotation', 'icon');
await expectNoChangeLabelAnnotationMessage(page, 'annotation');
});
await test.step('清理测试数据', async () => {
await openModifyLabelAnnotationEditor(page, 'annotation', 'icon');
await deleteLabelAnnotationAndExpectSuccess(page, 'annotation', key, value);
});
});
});
async function getFirstNodeName(page: Page): Promise<string | null> {
const link = page.locator('table tbody tr td a').first();
if (!(await link.isVisible({ timeout: 10000 }).catch(() => false))) {
return null;
}
return (await link.textContent())?.trim() ?? null;
}
async function countDataRows(page: Page): Promise<number> {
return page.locator('table tbody tr').evaluateAll((rows) =>
rows.filter((row) => {
const cells = row.querySelectorAll('td');
return cells.length > 0 && (row.textContent?.trim().length ?? 0) > 0;
}).length,
);
}
async function getColumnValues(page: Page, columnIndex: number): Promise<string[]> {
const rowLocator = page.locator('table tbody tr').filter({ has: page.locator('td a') });
const values: string[] = [];
const count = await rowLocator.count();
for (let i = 0; i < count; i++) {
const text = (await rowLocator.nth(i).locator('td').nth(columnIndex).innerText()).trim();
if (text) {
values.push(text);
}
}
return values;
}
async function assertColumnSorted(
page: Page,
columnHeader: RegExp | string,
columnIndex: number,
): Promise<void> {
const header = page.getByRole('columnheader', { name: columnHeader });
await expect(header).toBeVisible();
await header.click();
await page.waitForTimeout(WAIT_AFTER_CLICK);
let values = await getColumnValues(page, columnIndex);
for (let i = 1; i < values.length; i++) {
expect(values[i - 1].localeCompare(values[i], 'zh-Hans-CN') <= 0).toBeTruthy();
}
await header.click();
await page.waitForTimeout(WAIT_AFTER_CLICK);
values = await getColumnValues(page, columnIndex);
for (let i = 1; i < values.length; i++) {
expect(values[i - 1].localeCompare(values[i], 'zh-Hans-CN') >= 0).toBeTruthy();
}
}
async function assertColumnFiltered(
page: Page,
columnHeader: RegExp | string,
columnIndex: number,
filterValue: string,
): Promise<void> {
const header = page.getByRole('columnheader', { name: columnHeader });
await expect(header).toBeVisible();
const filterButton = header.getByRole('button', { name: /filter/i });
await expect(filterButton).toBeVisible();
await filterButton.click();
await page.waitForTimeout(WAIT_AFTER_CLICK);
await page.getByRole('menuitem', { name: filterValue, exact: true }).click();
await page.getByRole('button', { name: /确\s*定/ }).click();
await page.waitForTimeout(WAIT_AFTER_CLICK);
const rowCount = await countDataRows(page);
const hasNoData = await page.getByText(/暂无数据/).isVisible().catch(() => false);
if (rowCount === 0 || hasNoData) {
await expect(page.getByText(/暂无数据/)).toBeVisible({ timeout: 5000 });
return;
}
const values = await getColumnValues(page, columnIndex);
for (const value of values) {
expect(value).toContain(filterValue);
}
}