Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright 2025 The Karmada Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { test } from '@playwright/test';
import {
setupDashboardAuthentication,
generateTestPropagationPolicyYaml,
deleteK8sPropagationPolicy,
getPropagationPolicyNameFromYaml,
createPropagationPolicyResourceTest
} from './test-utils';

test.beforeEach(async ({ page }) => {
await setupDashboardAuthentication(page);
});

test('should create a new propagationpolicy', async ({ page }) => {
const testPropagationPolicyYaml = generateTestPropagationPolicyYaml();

await createPropagationPolicyResourceTest(page, {
resourceType: 'propagationpolicy',
tabName: 'Namespace level',
apiEndpoint: '/propagationpolicy',
yamlContent: testPropagationPolicyYaml,
getResourceName: getPropagationPolicyNameFromYaml,
deleteResource: deleteK8sPropagationPolicy,
screenshotName: 'debug-propagationpolicy-create.png'
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright 2025 The Karmada Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { test } from '@playwright/test';
import {
setupDashboardAuthentication,
generateTestPropagationPolicyYaml,
createK8sPropagationPolicy,
getPropagationPolicyNameFromYaml,
deletePropagationPolicyResourceTest
} from './test-utils';

test.beforeEach(async ({ page }) => {
await setupDashboardAuthentication(page);
});

test('should delete propagationpolicy successfully', async ({ page }) => {
const testPropagationPolicyYaml = generateTestPropagationPolicyYaml();

await deletePropagationPolicyResourceTest(page, {
resourceType: 'propagationpolicy',
tabName: 'Namespace level',
apiEndpointPattern: '/propagationpolicy',
yamlContent: testPropagationPolicyYaml,
getResourceName: getPropagationPolicyNameFromYaml,
createResource: createK8sPropagationPolicy,
screenshotName: 'debug-propagationpolicy-delete-kubectl.png'
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
Copyright 2025 The Karmada Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { test, expect } from '@playwright/test';
import {
setupDashboardAuthentication,
generateTestPropagationPolicyYaml,
createK8sPropagationPolicy,
getPropagationPolicyNameFromYaml,
deleteK8sPropagationPolicy,
setMonacoEditorContent,
waitForResourceInList,
debugScreenshot,
DeepRequired
} from './test-utils';
import { IResponse } from '@/services/base.ts';

// PropagationPolicy type for K8s API response
interface PropagationPolicy {
apiVersion?: string;
kind?: string;
metadata?: {
name?: string;
namespace?: string;
[key: string]: any;
};
spec?: {
resourceSelectors?: Array<{
apiVersion?: string;
kind?: string;
name?: string;
[key: string]: any;
}>;
placement?: {
clusterAffinity?: {
clusterNames?: string[];
[key: string]: any;
};
[key: string]: any;
};
[key: string]: any;
};
[key: string]: any;
}

test.beforeEach(async ({ page }) => {
await setupDashboardAuthentication(page);
});

test('should edit propagationpolicy successfully', async ({ page }) => {
// Create a test propagationpolicy directly via API to set up test data
const testPropagationPolicyYaml = generateTestPropagationPolicyYaml();
const propagationPolicyName = getPropagationPolicyNameFromYaml(testPropagationPolicyYaml);

// Setup: Create propagationpolicy using kubectl
await createK8sPropagationPolicy(testPropagationPolicyYaml);

// Open Policies menu
await page.click('text=Policies');

// Click Propagation Policy menu item
const propagationPolicyMenuItem = page.locator('text=Propagation Policy');
await propagationPolicyMenuItem.waitFor({ state: 'visible', timeout: 30000 });
await propagationPolicyMenuItem.click();

// Click Namespace level tab
const namespaceLevelTab = page.locator('role=option[name="Namespace level"]');
await namespaceLevelTab.waitFor({ state: 'visible', timeout: 30000 });
await namespaceLevelTab.click();

// Verify selected state
await expect(namespaceLevelTab).toHaveAttribute('aria-selected', 'true');

await expect(page.locator('table')).toBeVisible({ timeout: 30000 });

// Wait for propagationpolicy to appear in list and get target row
const targetRow = await waitForResourceInList(page, propagationPolicyName);

// Find Edit button in that row and click
const editButton = targetRow.getByText('Edit');
await expect(editButton).toBeVisible({ timeout: 15000 });

// Listen for edit API call
const apiRequestPromise = page.waitForResponse(response => {
return response.url().includes('_raw/propagationpolicy') && response.status() === 200;
}, { timeout: 15000 });

await editButton.click();

// Wait for edit dialog to appear
await page.waitForSelector('[role="dialog"]', { timeout: 10000 });

// Wait for network request to complete and get response data
const apiResponse = await apiRequestPromise;
const responseData = (await apiResponse.json()) as IResponse<DeepRequired<PropagationPolicy>>;

// Verify Monaco editor is loaded
await expect(page.locator('.monaco-editor')).toBeVisible({ timeout: 10000 });

// Wait for editor content to load
let yamlContent = '';
let attempts = 0;
const maxAttempts = 30;

const expectedName = responseData?.data?.metadata?.name || '';
const expectedKind = responseData?.data?.kind || '';

while (attempts < maxAttempts) {
yamlContent = await page.evaluate(() => {
const textarea = document.querySelector('.monaco-editor textarea') as HTMLTextAreaElement;
return textarea ? textarea.value : '';
});

if (yamlContent && yamlContent.length > 0) {
const containsExpectedName = !expectedName || yamlContent.includes(expectedName);
const containsExpectedKind = !expectedKind || yamlContent.includes(expectedKind);

if (containsExpectedName && containsExpectedKind) {
break;
}
}

await page.waitForSelector('.monaco-editor textarea[value*="apiVersion"]', { timeout: 500 }).catch(() => {});
attempts++;
}

// If content is still empty, manually set content from API response
if (!yamlContent || yamlContent.length === 0) {
yamlContent = await page.evaluate((apiData) => {
const data = apiData.data;
const yaml = `apiVersion: ${data.apiVersion}
kind: ${data.kind}
metadata:
name: ${data.metadata?.name || 'test-propagationpolicy'}
namespace: ${data.metadata?.namespace || 'default'}
spec:
resourceSelectors:
- apiVersion: ${data.spec?.resourceSelectors?.[0]?.apiVersion || 'apps/v1'}
kind: ${data.spec?.resourceSelectors?.[0]?.kind || 'Deployment'}
name: ${data.spec?.resourceSelectors?.[0]?.name || 'nginx-deployment'}
placement:
clusterAffinity:
clusterNames:
- ${data.spec?.placement?.clusterAffinity?.clusterNames?.[0] || 'member1'}
- ${data.spec?.placement?.clusterAffinity?.clusterNames?.[1] || 'member2'}`;

const textarea = document.querySelector('.monaco-editor textarea') as HTMLTextAreaElement;
if (textarea) {
textarea.value = yaml;
textarea.focus();
textarea.dispatchEvent(new Event('input', { bubbles: true }));
return yaml;
}
return '';
}, responseData);
}

// If still unable to get content, report error
if (!yamlContent || yamlContent.length === 0) {
throw new Error(`Edit feature error: Monaco editor does not load propagationpolicy YAML content. Expected name: "${expectedName}", kind: "${expectedKind}"`);
}

// Modify YAML content (change cluster name)
let modifiedYaml = yamlContent.replace(/- member1/, '- member3');

// Verify modification took effect
if (modifiedYaml === yamlContent) {
// Try alternative modification - change deployment name
const alternativeModified = yamlContent.replace(/nginx-deployment/, 'httpd-deployment');
if (alternativeModified !== yamlContent) {
modifiedYaml = alternativeModified;
} else {
// If still can't modify, try changing resource selector kind
const kindModified = yamlContent.replace(/kind: Deployment/, 'kind: StatefulSet');
if (kindModified !== yamlContent) {
modifiedYaml = kindModified;
}
}
}

// Set modified YAML content and trigger React onChange callback
await setMonacoEditorContent(page, modifiedYaml);

// Wait for submit button to become enabled and click
await expect(page.locator('[role="dialog"] button:has-text("Submit")')).toBeEnabled();
await page.click('[role="dialog"] button:has-text("Submit")');

// Wait for edit success message or dialog to close
try {
// Try waiting for success message
await expect(page.locator('text=Updated')).toBeVisible({ timeout: 3000 });
} catch (e) {
try {
// If no success message, wait for dialog to close
await page.waitForSelector('[role="dialog"]', { state: 'detached', timeout: 3000 });
} catch (e2) {
// If dialog close also failed, check if page still exists
try {
const isPageActive = await page.evaluate(() => document.readyState);

if (isPageActive === 'complete') {
// Edit operation may have succeeded
}
} catch (e3) {
// Page appears to be closed or crashed
}
}
}

// Cleanup: Delete the created propagationpolicy
try {
await deleteK8sPropagationPolicy(propagationPolicyName, 'default');
} catch (error) {
console.warn(`Failed to cleanup propagationpolicy ${propagationPolicyName}:`, error);
}

// Debug
await debugScreenshot(page, 'debug-propagationpolicy-edit.png');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
Copyright 2025 The Karmada Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { test } from '@playwright/test';
import {
setupDashboardAuthentication,
displayPropagationPolicyResourceListTest
} from './test-utils';

test.beforeEach(async ({ page }) => {
await setupDashboardAuthentication(page);
});

test('should display propagationpolicy list', async ({ page }) => {
await displayPropagationPolicyResourceListTest(page, {
tabName: 'Namespace level',
screenshotName: 'debug-propagationpolicy-list.png'
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright 2025 The Karmada Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { test } from '@playwright/test';
import {
setupDashboardAuthentication,
generateTestPropagationPolicyYaml,
createK8sPropagationPolicy,
getPropagationPolicyNameFromYaml,
deleteK8sPropagationPolicy,
viewPropagationPolicyResourceTest
} from './test-utils';

test.beforeEach(async ({ page }) => {
await setupDashboardAuthentication(page);
});

test('should view propagationpolicy details', async ({ page }) => {
const testPropagationPolicyYaml = generateTestPropagationPolicyYaml();
await viewPropagationPolicyResourceTest(page, {
resourceType: 'propagationpolicy',
tabName: 'Namespace level',
yamlContent: testPropagationPolicyYaml,
getResourceName: getPropagationPolicyNameFromYaml,
createResource: createK8sPropagationPolicy,
deleteResource: deleteK8sPropagationPolicy,
screenshotName: 'debug-propagationpolicy-view.png'
});
});
Loading