import type { Locator, Page } from '@playwright/test';
import { expect } from '@playwright/test';
import { timeouts } from '../constants/common';
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
export async function verifyClusterInstalling(page: Page, clusterName: string) {
await expect(page.getByText('集群信息')).toBeVisible();
await page.waitForFunction(() => {
const el = document.querySelector('.clusterLogContent');
return el && el.textContent && el.textContent.trim().length > 0;
});
await page.getByRole('button', { name: '退出日志' }).click();
await page.waitForURL(/\/container_platform\/cluster\/cluster$/);
const status = clusterName + ' loading 安装中';
await expect(page.getByRole('row', { name: status })).toBeVisible({ timeout: timeouts.validateTimeout });
}
export async function verifyClusterHealthy(page: Page, clusterName: string) {
const successStatus = page.getByRole('row', { name: clusterName + ' check-circle 健康' });
const failedStatus = page.getByRole('row', { name: clusterName + ' exclamation-circle 安装失败' });
await expect(successStatus.or(failedStatus)).toBeVisible({ timeout: timeouts.installTimeout });
if (await failedStatus.isVisible()) {
throw new Error(`Cluster ${clusterName} installation failed`);
}
await expect(successStatus).toBeVisible();
}
export async function verifyClusterHealthy2(page: Page, clusterName: string) {
const escapedClusterName = escapeRegExp(clusterName);
const successStatus = page.getByRole('row', { name: new RegExp(`${escapedClusterName}.*check-circle\\s*健康`) });
const failedStatus = page.getByRole('row', { name: new RegExp(`${escapedClusterName}.*exclamation-circle\\s*安装失败`) });
await expect(successStatus.or(failedStatus)).toBeVisible({ timeout: timeouts.installTimeout });
if (await failedStatus.isVisible()) {
throw new Error(`Cluster ${clusterName} installation failed`);
}
await expect(successStatus).toBeVisible();
}
export async function verifyClusterDeleted(page: Page, clusterName: string) {
const escapedClusterName = escapeRegExp(clusterName);
const status = new RegExp(`${escapedClusterName}.*loading\\s*删除中`);
await expect(page.getByRole('row', { name: status })).toBeVisible();
await expect(page.getByRole('row', { name: clusterName })).not.toBeVisible({ timeout: timeouts.uninstallTimeout * 3 });
}
export async function verifyClusterUpgrading(page: Page, clusterName: string) {
await expect(page.getByText('升级中,时间较长请耐心等待')).toBeVisible();
const status = clusterName + ' loading 升级中';
await expect(page.getByRole('row', { name: status })).toBeVisible({ timeout: timeouts.upgradeVisibleTimeout });
}
async function getClusterRowText(page: Page, clusterName: string): Promise<string> {
const row = page.getByRole('row', { name: new RegExp(escapeRegExp(clusterName)) }).first();
await expect(row).toBeVisible({ timeout: timeouts.validateTimeout });
return (await row.textContent()) ?? '';
}
export async function waitForClusterUpgradeStarted(
page: Page,
clusterName: string,
options?: {
timeoutMs?: number;
intervalMs?: number;
requiredOccurrences?: number;
fallbackMs?: number;
},
) {
const start = Date.now();
const timeoutMs = options?.timeoutMs ?? timeouts.upgradeVisibleTimeout;
const intervalMs = options?.intervalMs ?? 1000;
const requiredOccurrences = options?.requiredOccurrences ?? 1;
const fallbackMs = options?.fallbackMs ?? 2 * 60 * 1000;
let seenUpgrading = false;
let occurrenceCount = 0;
while (Date.now() - start < timeoutMs) {
const elapsedMs = Date.now() - start;
const rowText = await getClusterRowText(page, clusterName);
if (rowText.includes('安装失败') || rowText.includes('升级失败')) {
throw new Error(`Cluster ${clusterName} upgrade failed: ${rowText}`);
}
const isUpgrading = rowText.includes('升级中');
if (isUpgrading && !seenUpgrading) {
occurrenceCount += 1;
if (occurrenceCount >= requiredOccurrences) {
return;
}
}
if (occurrenceCount > 0 && elapsedMs >= fallbackMs) {
return;
}
seenUpgrading = isUpgrading;
await page.waitForTimeout(intervalMs);
}
throw new Error(
`Cluster ${clusterName} did not enter upgrading status ${requiredOccurrences} time(s) within ${timeoutMs}ms`,
);
}
export async function waitForClusterUpgradeFinishedAfterStarted(
page: Page,
clusterName: string,
targetVersion: string,
options?: {
timeoutMs?: number;
intervalMs?: number;
stableRounds?: number;
},
) {
const timeoutMs = options?.timeoutMs ?? timeouts.installTimeout;
const intervalMs = options?.intervalMs ?? 5000;
const stableRounds = options?.stableRounds ?? 3;
const start = Date.now();
let stableCount = 0;
while (Date.now() - start < timeoutMs) {
const rowText = await getClusterRowText(page, clusterName);
if (rowText.includes('安装失败') || rowText.includes('升级失败')) {
throw new Error(`Cluster ${clusterName} upgrade failed: ${rowText}`);
}
const isReady =
rowText.includes('健康') &&
rowText.includes(targetVersion) &&
!rowText.includes('升级中');
if (isReady) {
stableCount += 1;
if (stableCount >= stableRounds) {
return;
}
} else {
stableCount = 0;
}
await page.waitForTimeout(intervalMs);
}
throw new Error(
`Cluster ${clusterName} upgrade did not finish within ${timeoutMs}ms (targetVersion=${targetVersion}, stableRounds=${stableRounds})`,
);
}
async function refreshClusterListSoftly(page: Page) {
const overviewLink = page.getByRole('link', { name: '总览' });
const lifecycleLink = page.getByRole('link', { name: '集群生命周期管理' });
if (await overviewLink.isVisible().catch(() => false)) {
await overviewLink.click();
}
if (await lifecycleLink.isVisible().catch(() => false)) {
await lifecycleLink.click();
}
}
export async function verifyClusterUpgradeCompletedStable(
page: Page,
clusterName: string,
targetVersion: string,
options?: {
timeoutMs?: number;
intervalMs?: number;
stableRounds?: number;
},
) {
const timeoutMs = options?.timeoutMs ?? timeouts.installTimeout;
const intervalMs = options?.intervalMs ?? 10_000;
const stableRounds = options?.stableRounds ?? 10;
const start = Date.now();
let stableCount = 0;
while (Date.now() - start < timeoutMs) {
const failedStatus = page.getByRole('row', { name: clusterName + ' exclamation-circle 安装失败' });
if (await failedStatus.isVisible().catch(() => false)) {
throw new Error(`Cluster ${clusterName} upgrade failed`);
}
const healthyStatus = page.getByRole('row', { name: clusterName + ' check-circle 健康' });
const versionCell = page.getByRole('cell', { name: targetVersion }).first();
const bothReady =
(await healthyStatus.isVisible().catch(() => false)) &&
(await versionCell.isVisible().catch(() => false));
if (bothReady) {
stableCount += 1;
if (stableCount >= stableRounds) {
return;
}
} else {
stableCount = 0;
}
await page.waitForTimeout(intervalMs);
await refreshClusterListSoftly(page);
}
throw new Error(
`Cluster ${clusterName} upgrade did not become stable within ${timeoutMs}ms (targetVersion=${targetVersion}, stableRounds=${stableRounds})`,
);
}
export async function verifyClusterScaledUp(page: Page, clusterName: string, nodeNames: string[]) {
await expect(page.getByText(`${clusterName}安装中`)).toBeVisible({ timeout: timeouts.validateTimeout });
await expect(page.getByText(`${clusterName}健康`)).toBeVisible({ timeout: timeouts.installTimeout });
for (const nodeName of nodeNames) {
const status = nodeName + ' check-circle 就绪 ';
await expect(page.getByRole('row', { name: status }).getByRole('paragraph')).toBeVisible({
timeout: timeouts.installTimeout,
});
}
}
export async function verifyClusterScaledDown(page: Page, clusterName: string, nodeNames: string[]) {
await expect(page.getByText(`${clusterName}缩容中`)).toBeVisible({ timeout: timeouts.validateTimeout });
await expect(page.getByText(`${clusterName}健康`)).toBeVisible({ timeout: timeouts.installTimeout });
for (const nodeName of nodeNames) {
await expect(page.getByRole('cell', { name: nodeName })).not.toBeVisible({ timeout: timeouts.installTimeout });
}
}
export async function verifyClusterHealthOrInstallFailed(page: Page, clusterName: string) {
const pattern = new RegExp(`${clusterName} check-circle 健康|${clusterName} exclamation-circle 安装失败`);
await expect(page.getByRole('row', { name: pattern })).toBeVisible({ timeout: timeouts.installTimeout });
}
export async function checkFieldsAndValidateErrorMessages(rows: Locator) {
const count = await rows.count();
const fieldSelectors = [
'input[placeholder="请输入节点名称"]',
'input[placeholder="请输入IP地址"]',
'input[placeholder="请输入端口号"]',
'input[placeholder="请输入用户名"]',
'input[type="password"]',
];
const errorMessages = ['节点名称不为空', 'ip地址不为空', '端口号不为空', '用户名不为空', '密码不为空'];
for (let i = 0; i < count; i++) {
const row = rows.nth(i);
for (let j = 0; j < fieldSelectors.length; j++) {
const fieldSelector = fieldSelectors[j];
const errorMessage = errorMessages[j];
await checkEmptyFieldAndValidateErrorMessage(
row,
fieldSelector,
'div.cluster-form-item-explain-error',
errorMessage,
);
}
}
}
async function checkEmptyFieldAndValidateErrorMessage(
row: Locator,
fieldName: string,
errorMessageLocator: string,
errorMessage: string,
) {
const fieldInput = row.locator(fieldName);
const fieldValue = await fieldInput.inputValue();
if (fieldValue.trim() === '') {
console.log(`Field is empty, error message displayed: ${errorMessage}`);
const errorMessageElement = row.locator(errorMessageLocator);
console.log(errorMessageElement);
try {
await errorMessageElement.waitFor({ state: 'visible', timeout: timeouts.validateTimeout });
await expect(errorMessageElement).toHaveText(errorMessage);
} catch (error) {
console.error('Error message not found:', error);
}
}
}
管理集群 & 业务集群
*/
export async function verifyDeploymentStatus(page: Page, deploymentName: string, expectedStatus: string) {
await page.getByRole('menu').getByText('工作负载').click();
await page.getByRole('link', { name: 'Deployment' }).click();
await expect(page.getByRole('link', { name: deploymentName })).toBeVisible({ timeout: 30000 });
const row = page.getByRole('row', { name: deploymentName });
await expect(row.getByText(expectedStatus)).toBeVisible();
}
export async function verifyDeploymentImage(page: Page, deploymentName: string, expectedImageVersion: string) {
await page.getByRole('link', { name: deploymentName }).click();
const imagePattern = new RegExp(`cluster-api-provider-bke:${expectedImageVersion}`);
try {
await expect(page.getByText(imagePattern)).toBeVisible({ timeout: 5000 });
} catch {
await page.getByRole('tab', { name: 'YAML' }).click();
await expect(page.getByText(imagePattern)).toBeVisible({ timeout: 10000 });
}
}