Skip to content

Commit 8d87479

Browse files
authored
Merge pull request #250 from SunsetB612/add-e2e-workload-deployment
Add E2E tests for Workloads/Deployment in the dashboard
2 parents b9159d3 + 9134631 commit 8d87479

17 files changed

+858
-165
lines changed

ui/apps/dashboard/e2e/login/login.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 The Karmada Authors.
2+
Copyright 2025 The Karmada Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.

ui/apps/dashboard/e2e/namespace/namespace-create.spec.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 The Karmada Authors.
2+
Copyright 2025 The Karmada Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -16,42 +16,31 @@ limitations under the License.
1616

1717
// apps/dashboard/e2e/namespace-create.spec.ts
1818
import { test, expect } from '@playwright/test';
19-
20-
// Set webServer.url and use.baseURL with the location of the WebServer
21-
const HOST = process.env.HOST || 'localhost';
22-
const PORT = process.env.PORT || 5173;
23-
const baseURL = `http://${HOST}:${PORT}`;
24-
const basePath = '/multicloud-resource-manage';
25-
const token = process.env.KARMADA_TOKEN || '';
19+
import { setupDashboardAuthentication } from '../test-utils';
2620

2721
test.beforeEach(async ({ page }) => {
28-
await page.goto(`${baseURL}/login`, { waitUntil: 'networkidle' });
29-
await page.evaluate((t) => localStorage.setItem('token', t), token);
30-
await page.goto(`${baseURL}${basePath}`, { waitUntil: 'networkidle' });
31-
await page.evaluate((t) => localStorage.setItem('token', t), token);
32-
await page.reload({ waitUntil: 'networkidle' });
33-
await page.waitForSelector('text=Dashboard', { timeout: 30000 });
22+
await setupDashboardAuthentication(page);
3423
});
3524

3625
test('should create a new namespace', async ({ page }) => {
37-
// 打开 Namespaces 页面
26+
// Open Namespaces page
3827
await page.waitForSelector('text=Namespaces', { timeout: 60000 });
3928
await page.click('text=Namespaces');
4029

41-
// 点击 "Add" 创建新 namespace
30+
// Click "Add" to create new namespace
4231
await page.waitForSelector('button:has-text("Add")', { timeout: 30000 });
4332
await page.click('button:has-text("Add")');
4433

45-
// 填写唯一 namespace 名称
34+
// Fill unique namespace name
4635
const namespaceName = `test-${Date.now()}`;
4736
await page.waitForSelector('#name', { timeout: 30000 });
4837
await page.fill('#name', namespaceName);
4938

50-
// 提交创建
39+
// Submit creation
5140
await page.click('label:has-text("No")');
5241
await page.click('button:has-text("Submit")');
5342

54-
// 搜索并验证 namespace 已创建
43+
// Search and verify namespace is created
5544
const searchBox = page.getByPlaceholder('Search by Name');
5645
await searchBox.fill(namespaceName);
5746
await searchBox.press('Enter');

ui/apps/dashboard/e2e/namespace/namespace-delete.spec.ts

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 The Karmada Authors.
2+
Copyright 2025 The Karmada Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -16,77 +16,61 @@ limitations under the License.
1616

1717
// apps/dashboard/e2e/namespace-delete.spec.ts
1818
import { test, expect } from '@playwright/test';
19-
20-
// Set webServer.url and use.baseURL with the location of the WebServer
21-
const HOST = process.env.HOST || 'localhost';
22-
const PORT = process.env.PORT || 5173;
23-
const baseURL = `http://${HOST}:${PORT}`;
24-
const basePath = '/multicloud-resource-manage';
25-
const token = process.env.KARMADA_TOKEN || '';
19+
import { setupDashboardAuthentication } from '../test-utils';
2620

2721
test.beforeEach(async ({ page }) => {
28-
await page.goto(`${baseURL}/login`, { waitUntil: 'networkidle' });
29-
await page.evaluate((t) => localStorage.setItem('token', t), token);
30-
await page.goto(`${baseURL}${basePath}`, { waitUntil: 'networkidle' });
31-
await page.evaluate((t) => localStorage.setItem('token', t), token);
32-
await page.reload({ waitUntil: 'networkidle' });
33-
await page.waitForSelector('text=Dashboard', { timeout: 30000 });
22+
await setupDashboardAuthentication(page);
3423
});
3524

3625
test('should delete a namespace', async ({ page }) => {
3726
await page.waitForSelector('text=Namespaces', { timeout: 60000 });
3827
await page.click('text=Namespaces');
3928

40-
// 创建临时 namespace
29+
// Create temporary namespace
4130
const namespaceName = `test-to-delete-${Date.now()}`;
4231
await page.click('button:has-text("Add")');
4332
await page.fill('#name', namespaceName);
4433
await page.click('label:has-text("No")');
4534
await page.click('button:has-text("Submit")');
4635

47-
// 使用搜索框确认创建成功
36+
// Use search box to confirm creation success
4837
const searchBox = page.getByPlaceholder('Search by Name');
4938
await searchBox.fill(namespaceName);
5039
await searchBox.press('Enter');
5140
await page.waitForSelector(`tr:has-text("${namespaceName}")`, { timeout: 30000 });
5241

53-
// 删除 namespace
42+
// Delete namespace
5443
await page.click(`tr:has-text("${namespaceName}") button:has-text("Delete")`);
5544
await page.click('button:has-text("Confirm")');
5645

57-
// 刷新页面,确保表格拉取最新数据
46+
// Refresh page to ensure table gets latest data
5847
await page.reload({ waitUntil: 'networkidle' });
5948

60-
// 使用搜索框确认删除
49+
// Use search box to confirm deletion
6150
await searchBox.fill(namespaceName);
6251
await searchBox.press('Enter');
6352

64-
//确认 namespace 已删除
53+
// Confirm namespace is deleted
6554
const table = page.locator('table');
6655
const start = Date.now();
6756
let gone = false;
6857

69-
while (Date.now() - start < 120000) { // 最多等 120
58+
while (Date.now() - start < 120000) { // Wait up to 120 seconds
7059
const content = await table.innerText();
7160
if (!content.includes(namespaceName)) {
72-
console.log(`Namespace ${namespaceName} 已彻底删除`);
7361
gone = true;
7462
break;
75-
} else if (content.includes('Terminating')) {
76-
console.log(`Namespace ${namespaceName} Terminating`);
77-
} else {
78-
console.log(`Namespace ${namespaceName} 仍然存在`);
7963
}
80-
await page.waitForTimeout(5000); // 每 5 秒检查一次
81-
await page.reload({ waitUntil: 'networkidle' }); // 强制刷新,拿最新数据
64+
await page.waitForTimeout(5000); // Check every 5 seconds
65+
await page.reload({ waitUntil: 'networkidle' }); // Force refresh to get latest data
8266
await searchBox.fill(namespaceName);
8367
await searchBox.press('Enter');
8468
}
8569

86-
// 确认最终被删除(如果超时则失败)
70+
// Confirm final deletion (fails if timeout)
8771
expect(gone).toBeTruthy();
8872

89-
// 清空搜索框
73+
// Clear search box
9074
await searchBox.clear();
9175

9276
// Debug

ui/apps/dashboard/e2e/namespace/namespace-list.spec.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 The Karmada Authors.
2+
Copyright 2025 The Karmada Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -16,33 +16,22 @@ limitations under the License.
1616

1717
// apps/dashboard/e2e/namespace-list.spec.ts
1818
import { test, expect } from '@playwright/test';
19-
20-
// Set webServer.url and use.baseURL with the location of the WebServer
21-
const HOST = process.env.HOST || 'localhost';
22-
const PORT = process.env.PORT || 5173;
23-
const baseURL = `http://${HOST}:${PORT}`;
24-
const basePath = '/multicloud-resource-manage';
25-
const token = process.env.KARMADA_TOKEN || '';
19+
import { setupDashboardAuthentication } from '../test-utils';
2620

2721
test.beforeEach(async ({ page }) => {
28-
await page.goto(`${baseURL}/login`, { waitUntil: 'networkidle' });
29-
await page.evaluate((t) => localStorage.setItem('token', t), token);
30-
await page.goto(`${baseURL}${basePath}`, { waitUntil: 'networkidle' });
31-
await page.evaluate((t) => localStorage.setItem('token', t), token);
32-
await page.reload({ waitUntil: 'networkidle' });
33-
await page.waitForSelector('text=Dashboard', { timeout: 30000 });
22+
await setupDashboardAuthentication(page);
3423
});
3524

3625
test('should display namespace list', async ({ page }) => {
37-
// 打开 Namespaces 页面
26+
// Open Namespaces page
3827
await page.waitForSelector('text=Namespaces', { timeout: 60000 });
3928
await page.click('text=Namespaces');
4029

41-
// 获取表格元素并验证可见
30+
// Get table element and verify visibility
4231
const table = page.locator('table');
4332
await expect(table).toBeVisible({ timeout: 30000 });
4433

45-
// 验证表格中包含默认 namespace
34+
// Verify table contains default namespace
4635
await expect(table).toContainText('default');
4736

4837
// Debug

ui/apps/dashboard/e2e/namespace/namespace-network-error.spec.ts

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 The Karmada Authors.
2+
Copyright 2025 The Karmada Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -16,34 +16,20 @@ limitations under the License.
1616

1717
// apps/dashboard/e2e/namespace/namespace-network-error.spec.ts
1818
import { test, expect } from '@playwright/test';
19-
20-
// Set webServer.url and use.baseURL with the location of the WebServer
21-
const HOST = process.env.HOST || 'localhost';
22-
const PORT = process.env.PORT || 5173;
23-
const baseURL = `http://${HOST}:${PORT}`;
24-
const basePath = '/multicloud-resource-manage';
25-
const token = process.env.KARMADA_TOKEN || '';
19+
import { setupDashboardAuthentication } from '../test-utils';
2620

2721
test('Namespace network failure with refresh', async ({ page }) => {
28-
// 阻塞 Namespace API 请求
22+
// Block Namespace API requests
2923
await page.route('**/api/v1/namespaces', route => route.abort());
3024

31-
await page.goto(`${baseURL}/login`, { waitUntil: 'networkidle' });
32-
await page.evaluate((t) => localStorage.setItem('token', t), token);
33-
34-
// 导航到页面
35-
await page.goto(`${baseURL}${basePath}`, { waitUntil: 'networkidle' });
36-
37-
// 设置 token 并刷新,保证登录状态
38-
await page.evaluate((t) => localStorage.setItem('token', t), token);
39-
await page.reload({ waitUntil: 'networkidle' });
25+
await setupDashboardAuthentication(page);
4026

41-
// 等待关键元素加载完成,宽松等待 Namespaces 文字
27+
// Wait for key elements to load
4228
await page.waitForSelector('text=Namespaces', { timeout: 15000 });
4329

44-
// 验证表格
30+
// Verify table
4531
const tableRows = page.locator('table tbody tr');
46-
// 最终断言:网络错误时表格应该为空
32+
// Final assertion: table should be empty when network error occurs
4733
await expect(tableRows).toHaveCount(0);
4834

4935
// Debug
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
Copyright 2025 The Karmada Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { Page } from '@playwright/test';
18+
import * as k8s from '@kubernetes/client-node';
19+
20+
// Set webServer.url and use.baseURL with the location of the WebServer
21+
const HOST = process.env.HOST || 'localhost';
22+
const PORT = process.env.PORT || 5173;
23+
const baseURL = `http://${HOST}:${PORT}`;
24+
const basePath = '/multicloud-resource-manage';
25+
const token = process.env.KARMADA_TOKEN || '';
26+
27+
// Karmada API server configuration - can be overridden by environment variables
28+
const KARMADA_HOST = process.env.KARMADA_HOST || 'localhost';
29+
const KARMADA_PORT = process.env.KARMADA_PORT || '5443'; // Standard Karmada API server port
30+
const KARMADA_API_SERVER = `https://${KARMADA_HOST}:${KARMADA_PORT}`;
31+
32+
/**
33+
* Creates a configured Kubernetes API client for Karmada
34+
* @returns Kubernetes AppsV1Api client
35+
*/
36+
export function createKarmadaApiClient(): k8s.AppsV1Api {
37+
const kc = new k8s.KubeConfig();
38+
39+
// Try to use existing kubeconfig first (for CI)
40+
if (process.env.KUBECONFIG) {
41+
try {
42+
kc.loadFromFile(process.env.KUBECONFIG);
43+
// Set context to karmada-apiserver if available
44+
const contexts = kc.getContexts();
45+
const karmadaContext = contexts.find(c => c.name === 'karmada-apiserver');
46+
if (karmadaContext) {
47+
kc.setCurrentContext('karmada-apiserver');
48+
}
49+
return kc.makeApiClient(k8s.AppsV1Api);
50+
} catch (error) {
51+
console.error('Failed to load kubeconfig:', error);
52+
}
53+
}
54+
55+
// Fallback to custom config for local development
56+
const kubeConfigYaml = `
57+
apiVersion: v1
58+
kind: Config
59+
clusters:
60+
- cluster:
61+
insecure-skip-tls-verify: true
62+
server: ${KARMADA_API_SERVER}
63+
name: karmada-apiserver
64+
contexts:
65+
- context:
66+
cluster: karmada-apiserver
67+
user: karmada-dashboard
68+
name: default
69+
current-context: default
70+
users:
71+
- name: karmada-dashboard
72+
user:
73+
token: ${token}
74+
`;
75+
76+
kc.loadFromString(kubeConfigYaml);
77+
return kc.makeApiClient(k8s.AppsV1Api);
78+
}
79+
80+
/**
81+
* Setup dashboard authentication and navigate to dashboard main page
82+
*/
83+
export async function setupDashboardAuthentication(page: Page) {
84+
await page.goto(`${baseURL}/login`, { waitUntil: 'networkidle' });
85+
await page.evaluate((t) => localStorage.setItem('token', t), token);
86+
await page.goto(`${baseURL}${basePath}`, { waitUntil: 'networkidle' });
87+
await page.evaluate((t) => localStorage.setItem('token', t), token);
88+
await page.reload({ waitUntil: 'networkidle' });
89+
await page.waitForSelector('text=Overview', { timeout: 30000 });
90+
}

ui/apps/dashboard/e2e/workload/daemonset/daemonset-create.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
/*
2-
Copyright 2024 The Karmada Authors.
2+
Copyright 2025 The Karmada Authors.
3+
34
Licensed under the Apache License, Version 2.0 (the "License");
45
you may not use this file except in compliance with the License.
56
You may obtain a copy of the License at
7+
68
http://www.apache.org/licenses/LICENSE-2.0
9+
710
Unless required by applicable law or agreed to in writing, software
811
distributed under the License is distributed on an "AS IS" BASIS,
912
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

ui/apps/dashboard/e2e/workload/daemonset/daemonset-delete.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
/*
2-
Copyright 2024 The Karmada Authors.
2+
Copyright 2025 The Karmada Authors.
3+
34
Licensed under the Apache License, Version 2.0 (the "License");
45
you may not use this file except in compliance with the License.
56
You may obtain a copy of the License at
7+
68
http://www.apache.org/licenses/LICENSE-2.0
9+
710
Unless required by applicable law or agreed to in writing, software
811
distributed under the License is distributed on an "AS IS" BASIS,
912
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

ui/apps/dashboard/e2e/workload/daemonset/daemonset-edit.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
/*
2-
Copyright 2024 The Karmada Authors.
2+
Copyright 2025 The Karmada Authors.
3+
34
Licensed under the Apache License, Version 2.0 (the "License");
45
you may not use this file except in compliance with the License.
56
You may obtain a copy of the License at
7+
68
http://www.apache.org/licenses/LICENSE-2.0
9+
710
Unless required by applicable law or agreed to in writing, software
811
distributed under the License is distributed on an "AS IS" BASIS,
912
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

0 commit comments

Comments
 (0)