Skip to content

Commit c253a80

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

File tree

6 files changed

+840
-0
lines changed

6 files changed

+840
-0
lines changed
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, generateTestOverridePolicyYaml, deleteK8sOverridePolicy, getOverridePolicyNameFromYaml } from './test-utils';
16+
17+
test.beforeEach(async ({ page }) => {
18+
await setupDashboardAuthentication(page);
19+
});
20+
21+
test('should create a new overridepolicy', async ({ page }) => {
22+
// Open Policies menu
23+
await page.click('text=Policies');
24+
25+
// Click Override Policy menu item
26+
const overridePolicyMenuItem = page.locator('text=Override Policy');
27+
await overridePolicyMenuItem.waitFor({ state: 'visible', timeout: 30000 });
28+
await overridePolicyMenuItem.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/overridepolicy') && response.status() === 200;
44+
}, { timeout: 15000 });
45+
46+
const testOverridePolicyYaml = generateTestOverridePolicyYaml();
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+
}, testOverridePolicyYaml);
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+
}, testOverridePolicyYaml);
106+
/* eslint-enable */
107+
108+
// Wait for submit button to become enabled
109+
await expect(page.locator('[role="dialog"] button:has-text("确 定")')).toBeEnabled();
110+
await page.click('[role="dialog"] button:has-text("确 定")');
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 overridepolicy appears in list
121+
const overridePolicyName = getOverridePolicyNameFromYaml(testOverridePolicyYaml);
122+
123+
// Assert overridepolicy name exists
124+
expect(overridePolicyName).toBeTruthy();
125+
expect(overridePolicyName).toBeDefined();
126+
127+
try {
128+
await expect(page.locator('table').locator(`text=${overridePolicyName}`)).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 overridepolicy was created
134+
}
135+
136+
// Cleanup: Delete the created overridepolicy
137+
try {
138+
await deleteK8sOverridePolicy(overridePolicyName, 'default');
139+
} catch (error) {
140+
console.warn(`Failed to cleanup overridepolicy ${overridePolicyName}:`, error);
141+
}
142+
143+
// Debug
144+
if(process.env.DEBUG === 'true'){
145+
await page.screenshot({ path: 'debug-overridepolicy-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, generateTestOverridePolicyYaml, createK8sOverridePolicy, getOverridePolicyNameFromYaml} from './test-utils';
16+
17+
test.beforeEach(async ({ page }) => {
18+
await setupDashboardAuthentication(page);
19+
});
20+
21+
test('should delete overridepolicy successfully', async ({ page }) => {
22+
// Create a test overridepolicy directly via API to set up test data
23+
const testOverridePolicyYaml = generateTestOverridePolicyYaml();
24+
const overridePolicyName = getOverridePolicyNameFromYaml(testOverridePolicyYaml);
25+
26+
// Setup: Create overridepolicy using API
27+
await createK8sOverridePolicy(testOverridePolicyYaml);
28+
29+
// Open Policies menu
30+
await page.click('text=Policies');
31+
32+
// Click Override Policy menu item
33+
const overridePolicyMenuItem = page.locator('text=Override Policy');
34+
await overridePolicyMenuItem.waitFor({ state: 'visible', timeout: 30000 });
35+
await overridePolicyMenuItem.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 overridepolicy to appear in list
47+
const table = page.locator('table');
48+
await expect(table.locator(`text=${overridePolicyName}`)).toBeVisible({ timeout: 30000 });
49+
50+
// Find row containing test overridepolicy name
51+
const targetRow = page.locator(`table tbody tr:has-text("${overridePolicyName}")`);
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('/overridepolicy') &&
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: /^(\s*|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 overridepolicy no longer exists in table
87+
await expect(table.locator(`text=${overridePolicyName}`)).toHaveCount(0, { timeout: 30000 });
88+
89+
// Debug
90+
if(process.env.DEBUG === 'true'){
91+
await page.screenshot({ path: 'debug-overridepolicy-delete.png', fullPage: true });
92+
}
93+
});

0 commit comments

Comments
 (0)