Skip to content

Commit 13affa1

Browse files
committed
Add E2E tests for Resources/Namespaces in the dashboard
Signed-off-by: SunsetB612 <[email protected]>
1 parent dee5c31 commit 13affa1

File tree

7 files changed

+369
-4
lines changed

7 files changed

+369
-4
lines changed

.github/workflows/ci.yml

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ jobs:
7676
# the same as the End of Life of the Kubernetes release: https://kubernetes.io/releases/
7777
# Please remember to update the CI Schedule Workflow when we add a new version.
7878
k8s: [ v1.31.0, v1.32.0, v1.33.0 ]
79+
# k8s: [ v1.31.0 ]
7980
steps:
8081
- name: Free Disk Space (Ubuntu)
8182
uses: jlumbroso/free-disk-space@main
@@ -89,20 +90,55 @@ jobs:
8990
large-packages: false
9091
docker-images: false
9192
swap-storage: false
93+
9294
- name: Checkout karmada repo
9395
uses: actions/checkout@v4
9496
with:
9597
repository: karmada-io/karmada
9698
path: karmada
99+
97100
- name: setup e2e test environment
98101
run: |
99102
cd karmada
100103
export CLUSTER_VERSION=kindest/node:${{ matrix.k8s }}
101104
hack/local-up-karmada.sh
105+
echo "KUBECONFIG=/home/runner/.kube/karmada.config" >> $GITHUB_ENV
106+
- name: Debug cluster status
107+
run: |
108+
echo "=== Debug Info ==="
109+
echo "check k8s ctx info"
110+
kubectl config get-contexts
111+
echo "check k8s ctx info finished"
112+
echo "Current directory: $(pwd)"
113+
echo "KUBECONFIG: $KUBECONFIG"
114+
kubectl config get-contexts || echo "No contexts available"
115+
kubectl cluster-info || echo "Cluster info failed"
116+
docker ps || echo "Docker ps failed"
117+
kind get clusters || echo "Kind get clusters failed"
118+
ls -la $HOME/.kube/ || echo "No .kube directory"
119+
cat $HOME/.kube/config || echo "No standard config"
120+
cat $HOME/.kube/karmada.config || echo "No karmada config"
121+
cat $HOME/.kube/members.config || echo "No members config"
122+
echo "=================="
102123
- name: Checkout dashboard repo
103124
uses: actions/checkout@v4
104125
with:
105126
path: karmada-dashboard
127+
128+
- name: Generate KARMADA_TOKEN for CI
129+
run: |
130+
# kubectl --context=karmada-apiserver create sa e2e-test -n kube-system
131+
# kubectl --context=karmada-apiserver create clusterrolebinding e2e-test-binding \
132+
# --clusterrole=cluster-admin \
133+
# --serviceaccount=kube-system:e2e-test
134+
135+
# 使用 kubectl create token 命令生成临时 token
136+
#TOKEN=$(kubectl --context=karmada-apiserver create token e2e-test -n kube-system --duration=3600s)
137+
# kubectl --context=karmada-apiserver apply -f karmada-dashboard/artifacts/dashboard
138+
kubectl --context=karmada-host apply -k karmada-dashboard/artifacts/overlays/nodeport-mode
139+
kubectl --context=karmada-apiserver apply -f karmada-dashboard/artifacts/dashboard/karmada-dashboard-sa.yaml
140+
TOKEN=$(kubectl --context=karmada-apiserver -n karmada-system get secret/karmada-dashboard-secret -o go-template="{{.data.token | base64decode}}")
141+
echo "KARMADA_TOKEN=$TOKEN" >> $GITHUB_ENV
106142
- name: Use Node.js 20
107143
uses: actions/setup-node@v4
108144
with:
@@ -111,25 +147,85 @@ jobs:
111147
with:
112148
# keep in sync with the packageManager version in `ui/package.json`
113149
version: 9.1.2
150+
114151
- name: Install dependencies
115152
run: |
116153
echo "Start build"
117154
pnpm --version
118155
cd karmada-dashboard/ui
119156
pnpm install
120157
pnpm turbo build
158+
- name: Build API binary
159+
run: |
160+
cd karmada-dashboard
161+
make build
162+
- name: Start API server
163+
run: |
164+
cd karmada-dashboard
165+
make run-api &
166+
echo $! > .api.pid
167+
sleep 5
168+
# ===== 添加API服务器健康检查 =====
169+
- name: Wait for API server to be ready
170+
run: |
171+
echo "Waiting for API server to be ready..."
172+
timeout 60s bash -c 'while ! nc -z localhost 8000; do echo "Waiting for API server port..."; sleep 5; done' || {
173+
echo "API server failed to start within 60 seconds"
174+
echo "=== API Server Debug ==="
175+
if [ -f karmada-dashboard/.api.pid ]; then
176+
API_PID=$(cat karmada-dashboard/.api.pid)
177+
ps aux | grep $API_PID || echo "API process not found"
178+
fi
179+
ss -tuln | grep 8000 || echo "Port 8000 not listening"
180+
exit 1
181+
}
182+
echo "API server is ready!"
121183
- name: Install Playwright Browsers
122184
run: |
123185
cd karmada-dashboard/ui/apps/dashboard
124186
pnpm exec playwright install --with-deps
187+
- name: Debug environment before starting dashboard
188+
run: |
189+
echo "=== Environment Debug ==="
190+
echo "KARMADA_TOKEN is set: ${{ env.KARMADA_TOKEN != '' }}"
191+
echo "KUBECONFIG: $KUBECONFIG"
192+
echo "Current directory: $(pwd)"
193+
echo "Dashboard directory contents:"
194+
ls -la karmada-dashboard/ || echo "No dashboard directory"
195+
echo "Check if Makefile exists:"
196+
ls -la karmada-dashboard/Makefile || echo "No Makefile found"
197+
echo "========================="
198+
- name: Build frontend (for e2e)
199+
working-directory: karmada-dashboard/ui/apps/dashboard
200+
run: |
201+
pnpm build
202+
- name: Test API connectivity before running tests
203+
run: |
204+
echo "Testing API connectivity..."
205+
# 测试几个关键的API端点
206+
curl -f http://localhost:8000/api/v1/cluster && echo "✓ Cluster API works" || echo "✗ Cluster API failed"
207+
curl -f http://localhost:8000/api/v1/namespace && echo "✓ Namespace API works" || echo "✗ Namespace API failed"
208+
echo "API tests completed"
209+
- name: Debug working dir
210+
run: |
211+
pwd
212+
ls -al
125213
- name: Run Playwright tests
214+
working-directory: karmada-dashboard/ui/apps/dashboard
126215
run: |
127-
cd karmada-dashboard/ui/apps/dashboard
128216
pnpm exec playwright test
217+
# ===== 添加进程清理 =====
218+
- name: Cleanup processes
219+
if: always()
220+
run: |
221+
echo "Cleaning up processes..."
222+
if [ -f karmada-dashboard/.api.pid ]; then
223+
API_PID=$(cat karmada-dashboard/.api.pid)
224+
kill $API_PID 2>/dev/null || echo "API process already stopped"
225+
fi
129226
- uses: actions/upload-artifact@v4
130227
if: ${{ !cancelled() }}
131228
with:
132229
name: playwright-report-${{ matrix.k8s }}
133230
path: karmada-dashboard/ui/apps/dashboard/playwright-report/
134-
retention-days: 30
135-
231+
retention-days: 30
File renamed without changes.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright 2024 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+
// apps/dashboard/e2e/namespace-create.spec.ts
18+
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 || '';
26+
27+
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 });
34+
});
35+
36+
test('should create a new namespace', async ({ page }) => {
37+
// 打开 Namespaces 页面
38+
await page.waitForSelector('text=Namespaces', { timeout: 60000 });
39+
await page.click('text=Namespaces');
40+
41+
// 点击 "Add" 创建新 namespace
42+
await page.waitForSelector('button:has-text("Add")', { timeout: 30000 });
43+
await page.click('button:has-text("Add")');
44+
45+
// 填写唯一 namespace 名称
46+
const namespaceName = `test-${Date.now()}`;
47+
await page.waitForSelector('#name', { timeout: 30000 });
48+
await page.fill('#name', namespaceName);
49+
50+
// 提交创建
51+
await page.click('label:has-text("No")');
52+
await page.click('button:has-text("Submit")');
53+
54+
// 搜索并验证 namespace 已创建
55+
const searchBox = page.getByPlaceholder('Search by Name');
56+
await searchBox.fill(namespaceName);
57+
await searchBox.press('Enter');
58+
await page.waitForTimeout(1000);
59+
60+
await page.waitForSelector(`tr:has-text("${namespaceName}")`, { timeout: 60000 });
61+
await expect(page.locator('table')).toContainText(namespaceName);
62+
63+
await page.screenshot({ path: 'debug-namespace-create.png', fullPage: true });
64+
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
Copyright 2024 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+
// apps/dashboard/e2e/namespace-delete.spec.ts
18+
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 || '';
26+
27+
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 });
34+
});
35+
36+
test('should delete a namespace', async ({ page }) => {
37+
await page.waitForSelector('text=Namespaces', { timeout: 60000 });
38+
await page.click('text=Namespaces');
39+
40+
// 创建临时 namespace
41+
const namespaceName = `test-to-delete-${Date.now()}`;
42+
await page.click('button:has-text("Add")');
43+
await page.fill('#name', namespaceName);
44+
await page.click('label:has-text("No")');
45+
await page.click('button:has-text("Submit")');
46+
47+
// 使用搜索框确认创建成功
48+
const searchBox = page.getByPlaceholder('Search by Name');
49+
await searchBox.fill(namespaceName);
50+
await searchBox.press('Enter');
51+
await page.waitForTimeout(1000);
52+
53+
await page.waitForSelector(`tr:has-text("${namespaceName}")`, { timeout: 30000 });
54+
55+
// 删除 namespace
56+
await page.click(`tr:has-text("${namespaceName}") button:has-text("Delete")`);
57+
await page.click('button:has-text("Confirm")');
58+
59+
// 刷新页面,确保表格拉取最新数据
60+
await page.reload({ waitUntil: 'networkidle' });
61+
62+
// 使用搜索框确认删除
63+
await searchBox.fill('');
64+
await searchBox.fill(namespaceName);
65+
await searchBox.press('Enter');
66+
67+
// // 确认 namespace 已删除
68+
// await expect(page.locator('table')).not.toContainText(namespaceName, { timeout: 60000 });
69+
70+
const table = page.locator('table');
71+
const start = Date.now();
72+
let gone = false;
73+
74+
while (Date.now() - start < 120000) { // 最多等 120 秒
75+
const content = await table.innerText();
76+
if (!content.includes(namespaceName)) {
77+
console.log(`Namespace ${namespaceName} 已彻底删除`);
78+
gone = true;
79+
break;
80+
} else if (content.includes('Terminating')) {
81+
console.log(`Namespace ${namespaceName} Terminating`);
82+
} else {
83+
console.log(`Namespace ${namespaceName} 仍然存在`);
84+
}
85+
await page.waitForTimeout(5000); // 每 5 秒检查一次
86+
await page.reload({ waitUntil: 'networkidle' }); // 强制刷新,拿最新数据
87+
await searchBox.fill(namespaceName);
88+
await searchBox.press('Enter');
89+
}
90+
91+
// 确认最终被删除(如果超时则失败)
92+
expect(gone).toBeTruthy();
93+
94+
// 清空搜索框
95+
await searchBox.clear();
96+
97+
await page.screenshot({ path: 'debug-namespace-delete.png', fullPage: true });
98+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Copyright 2024 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+
// apps/dashboard/e2e/namespace-list.spec.ts
18+
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 || '';
26+
27+
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 });
34+
});
35+
36+
test('should display namespace list', async ({ page }) => {
37+
// 打开 Namespaces 页面
38+
await page.waitForSelector('text=Namespaces', { timeout: 60000 });
39+
await page.click('text=Namespaces');
40+
41+
// 获取表格元素并验证可见
42+
const table = page.locator('table');
43+
await expect(table).toBeVisible({ timeout: 30000 });
44+
45+
// 验证表格中包含默认 namespace
46+
await expect(table).toContainText('default');
47+
48+
await page.screenshot({ path: 'debug-namespace-list.png', fullPage: true });
49+
});

0 commit comments

Comments
 (0)