Skip to content

Commit b0e3850

Browse files
committed
Add E2E tests for Propagation Policy/Namespace level in the dashboard
Signed-off-by: SunsetB612 <[email protected]>
1 parent aa6df58 commit b0e3850

17 files changed

+840
-29
lines changed

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

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

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

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

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

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

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

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

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
/*
2-
Copyright 2024 The Karmada Authors.
3-
2+
Copyright 2025 The Karmada Authors.
43
Licensed under the Apache License, Version 2.0 (the "License");
54
you may not use this file except in compliance with the License.
65
You may obtain a copy of the License at
7-
86
http://www.apache.org/licenses/LICENSE-2.0
9-
107
Unless required by applicable law or agreed to in writing, software
118
distributed under the License is distributed on an "AS IS" BASIS,
129
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
Copyright 2025 The Karmada Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
import { test, expect } from '@playwright/test';
15+
import { setupDashboardAuthentication, generateTestPropagationPolicyYaml, deleteK8sPropagationPolicy, getPropagationPolicyNameFromYaml } from './test-utils';
16+
17+
test.beforeEach(async ({ page }) => {
18+
await setupDashboardAuthentication(page);
19+
});
20+
21+
test('should create a new propagationpolicy', async ({ page }) => {
22+
// Open Policies menu
23+
await page.click('text=Policies');
24+
25+
// Click Propagation Policy menu item
26+
const propagationPolicyMenuItem = page.locator('text=Propagation Policy');
27+
await propagationPolicyMenuItem.waitFor({ state: 'visible', timeout: 30000 });
28+
await propagationPolicyMenuItem.click();
29+
30+
// Click Namespace level tab
31+
const namespaceLevelTab = page.locator('role=option[name="Namespace level"]');
32+
await namespaceLevelTab.waitFor({ state: 'visible', timeout: 30000 });
33+
await namespaceLevelTab.click();
34+
35+
// Verify selected state
36+
await expect(namespaceLevelTab).toHaveAttribute('aria-selected', 'true');
37+
await expect(page.locator('table')).toBeVisible({ timeout: 30000 });
38+
await page.click('button:has-text("Add")');
39+
await page.waitForSelector('[role="dialog"]', { timeout: 10000 });
40+
41+
// Listen for API calls
42+
const apiRequestPromise = page.waitForResponse(response => {
43+
return response.url().includes('/api/v1/propagationpolicy') && response.status() === 200;
44+
}, { timeout: 15000 });
45+
46+
const testPropagationPolicyYaml = generateTestPropagationPolicyYaml();
47+
48+
// Set Monaco editor DOM content
49+
await page.evaluate((yaml) => {
50+
const textarea = document.querySelector('.monaco-editor textarea') as HTMLTextAreaElement;
51+
if (textarea) {
52+
textarea.value = yaml;
53+
textarea.focus();
54+
}
55+
}, testPropagationPolicyYaml);
56+
57+
/* eslint-disable */
58+
// Call React onChange callback to update component state
59+
await page.evaluate((yaml) => {
60+
61+
const findReactFiber = (element: any) => {
62+
const keys = Object.keys(element);
63+
return keys.find(key => key.startsWith('__reactFiber') || key.startsWith('__reactInternalInstance'));
64+
};
65+
66+
const monacoContainer = document.querySelector('.monaco-editor');
67+
if (monacoContainer) {
68+
const fiberKey = findReactFiber(monacoContainer);
69+
if (fiberKey) {
70+
let fiber = (monacoContainer as any)[fiberKey];
71+
72+
while (fiber) {
73+
if (fiber.memoizedProps && fiber.memoizedProps.onChange) {
74+
fiber.memoizedProps.onChange(yaml);
75+
return;
76+
}
77+
fiber = fiber.return;
78+
}
79+
}
80+
}
81+
82+
const dialog = document.querySelector('[role="dialog"]');
83+
if (dialog) {
84+
const fiberKey = findReactFiber(dialog);
85+
if (fiberKey) {
86+
let fiber = (dialog as any)[fiberKey];
87+
88+
const traverse = (node: any, depth = 0) => {
89+
if (!node || depth > 20) return false;
90+
91+
if (node.memoizedProps && node.memoizedProps.onChange) {
92+
node.memoizedProps.onChange(yaml);
93+
return true;
94+
}
95+
96+
if (node.child && traverse(node.child, depth + 1)) return true;
97+
if (node.sibling && traverse(node.sibling, depth + 1)) return true;
98+
99+
return false;
100+
};
101+
102+
traverse(fiber);
103+
}
104+
}
105+
}, testPropagationPolicyYaml);
106+
/* eslint-enable */
107+
108+
// Wait for submit button to become enabled
109+
await expect(page.locator('[role="dialog"] button:has-text("Submit")')).toBeEnabled();
110+
await page.click('[role="dialog"] button:has-text("Submit")');
111+
112+
// Wait for API call to succeed
113+
await apiRequestPromise;
114+
115+
// Wait for dialog to close
116+
await page.waitForSelector('[role="dialog"]', { state: 'detached', timeout: 5000 }).catch(() => {
117+
// Dialog may already be closed
118+
});
119+
120+
// Verify new propagationpolicy appears in list
121+
const propagationPolicyName = getPropagationPolicyNameFromYaml(testPropagationPolicyYaml);
122+
123+
// Assert propagationpolicy name exists
124+
expect(propagationPolicyName).toBeTruthy();
125+
expect(propagationPolicyName).toBeDefined();
126+
127+
try {
128+
await expect(page.locator('table').locator(`text=${propagationPolicyName}`)).toBeVisible({
129+
timeout: 15000
130+
});
131+
} catch {
132+
// If not shown immediately in list, may be due to cache or refresh delay
133+
// But API success indicates propagationpolicy was created
134+
}
135+
136+
// Cleanup: Delete the created propagationpolicy
137+
try {
138+
await deleteK8sPropagationPolicy(propagationPolicyName, 'default');
139+
} catch (error) {
140+
console.warn(`Failed to cleanup propagationpolicy ${propagationPolicyName}:`, error);
141+
}
142+
143+
// Debug
144+
if(process.env.DEBUG === 'true'){
145+
await page.screenshot({ path: 'debug-propagationpolicy-create.png', fullPage: true });
146+
}
147+
148+
});
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
Copyright 2025 The Karmada Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
import { test, expect } from '@playwright/test';
15+
import { setupDashboardAuthentication, generateTestPropagationPolicyYaml, createK8sPropagationPolicy, getPropagationPolicyNameFromYaml} from './test-utils';
16+
17+
test.beforeEach(async ({ page }) => {
18+
await setupDashboardAuthentication(page);
19+
});
20+
21+
test('should delete propagationpolicy successfully', async ({ page }) => {
22+
// Create a test propagationpolicy directly via kubectl to set up test data
23+
const testPropagationPolicyYaml = generateTestPropagationPolicyYaml();
24+
const propagationPolicyName = getPropagationPolicyNameFromYaml(testPropagationPolicyYaml);
25+
26+
// Setup: Create propagationpolicy using kubectl
27+
await createK8sPropagationPolicy(testPropagationPolicyYaml);
28+
29+
// Open Policies menu
30+
await page.click('text=Policies');
31+
32+
// Click Propagation Policy menu item
33+
const propagationPolicyMenuItem = page.locator('text=Propagation Policy');
34+
await propagationPolicyMenuItem.waitFor({ state: 'visible', timeout: 30000 });
35+
await propagationPolicyMenuItem.click();
36+
37+
// Click Namespace level tab
38+
const namespaceLevelTab = page.locator('role=option[name="Namespace level"]');
39+
await namespaceLevelTab.waitFor({ state: 'visible', timeout: 30000 });
40+
await namespaceLevelTab.click();
41+
42+
// Verify selected state
43+
await expect(namespaceLevelTab).toHaveAttribute('aria-selected', 'true');
44+
await expect(page.locator('table')).toBeVisible({ timeout: 30000 });
45+
46+
// Wait for propagationpolicy to appear in list
47+
const table = page.locator('table');
48+
await expect(table.locator(`text=${propagationPolicyName}`)).toBeVisible({ timeout: 30000 });
49+
50+
// Find row containing test propagationpolicy name
51+
const targetRow = page.locator(`table tbody tr:has-text("${propagationPolicyName}")`);
52+
await expect(targetRow).toBeVisible({ timeout: 15000 });
53+
54+
// Find Delete button in that row and click
55+
const deleteButton = targetRow.locator('button[type="button"]').filter({ hasText: /^(Delete)$/ });
56+
await expect(deleteButton).toBeVisible({ timeout: 10000 });
57+
58+
// Listen for delete API call
59+
const deleteApiPromise = page.waitForResponse(response => {
60+
return response.url().includes('/propagationpolicy') &&
61+
response.request().method() === 'DELETE' &&
62+
response.status() === 200;
63+
}, { timeout: 15000 });
64+
65+
await deleteButton.click();
66+
67+
// Wait for delete confirmation tooltip to appear
68+
await page.waitForSelector('[role="tooltip"]', { timeout: 10000 });
69+
70+
// Click Confirm button to confirm deletion
71+
const confirmButton = page.locator('[role="tooltip"] button').filter({ hasText: /^(Confirm)$/ });
72+
await expect(confirmButton).toBeVisible({ timeout: 5000 });
73+
await confirmButton.click();
74+
75+
// Wait for delete API call to succeed
76+
await deleteApiPromise;
77+
78+
// Wait for tooltip to close
79+
await page.waitForSelector('[role="tooltip"]', { state: 'detached', timeout: 10000 }).catch(() => {});
80+
81+
// Refresh page to ensure UI is updated after deletion
82+
await page.reload();
83+
await page.click('text=Policies');
84+
await expect(table).toBeVisible({ timeout: 30000 });
85+
86+
// Verify propagationpolicy no longer exists in table
87+
await expect(table.locator(`text=${propagationPolicyName}`)).toHaveCount(0, { timeout: 30000 });
88+
89+
// Debug
90+
if(process.env.DEBUG === 'true'){
91+
await page.screenshot({ path: 'debug-propagationpolicy-delete-kubectl.png', fullPage: true });
92+
}
93+
});

0 commit comments

Comments
 (0)