first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { test, expect } from '../../../fixtures/base';
|
||||
|
||||
test.describe('Project Settings - Member Management', {
|
||||
annotation: [
|
||||
{ type: 'owner', description: 'Identity & Access' },
|
||||
],
|
||||
}, () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.goHome();
|
||||
});
|
||||
|
||||
test('should display project settings page with correct layout @auth:owner', async ({ n8n }) => {
|
||||
// Create a new project
|
||||
const projectName = `UI Test ${nanoid(8)}`;
|
||||
const { projectId } = await n8n.projectComposer.createProject(projectName);
|
||||
|
||||
// Navigate to project settings
|
||||
await n8n.navigate.toProjectSettings(projectId);
|
||||
await expect(n8n.projectSettings.getTitle()).toHaveText(projectName);
|
||||
|
||||
// Verify basic project settings form elements are visible (inner controls)
|
||||
await expect(n8n.projectSettings.getNameInput()).toBeVisible();
|
||||
await expect(n8n.projectSettings.getDescriptionTextarea()).toBeVisible();
|
||||
await n8n.projectSettings.expectMembersSelectIsVisible();
|
||||
|
||||
// Verify members table is visible when there are members
|
||||
await n8n.projectSettings.expectTableIsVisible();
|
||||
|
||||
// Initially should have only the owner (current user)
|
||||
await n8n.projectSettings.expectTableHasMemberCount(1);
|
||||
|
||||
// Verify save/cancel buttons are disabled initially (no changes)
|
||||
await expect(n8n.projectSettings.getSaveButton()).toBeDisabled();
|
||||
await expect(n8n.projectSettings.getCancelButton()).toBeDisabled();
|
||||
|
||||
// Delete button should always be visible
|
||||
await expect(n8n.projectSettings.getDeleteButton()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow editing project name and description @auth:owner', async ({ n8n }) => {
|
||||
// Create a new project
|
||||
const projectName = `Edit Test ${nanoid(8)}`;
|
||||
const { projectId } = await n8n.projectComposer.createProject(projectName);
|
||||
|
||||
// Navigate to project settings
|
||||
await n8n.navigate.toProjectSettings(projectId);
|
||||
await expect(n8n.projectSettings.getTitle()).toHaveText(projectName);
|
||||
|
||||
// Update project name
|
||||
const newName = 'Updated Project Name';
|
||||
await n8n.projectSettings.fillProjectName(newName);
|
||||
|
||||
// Update project description
|
||||
const newDescription = 'This is an updated project description.';
|
||||
await n8n.projectSettings.fillProjectDescription(newDescription);
|
||||
|
||||
// Save changes
|
||||
await n8n.projectSettings.clickSaveButton();
|
||||
|
||||
// Wait for success notification
|
||||
await expect(
|
||||
n8n.page.getByText('Project Updated Project Name saved successfully', { exact: false }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify the form shows the updated values
|
||||
await n8n.projectSettings.expectProjectNameValue(newName);
|
||||
await n8n.projectSettings.expectProjectDescriptionValue(newDescription);
|
||||
});
|
||||
|
||||
test('should display members table with correct structure @auth:owner', async ({ n8n }) => {
|
||||
// Create a new project
|
||||
const projectName = `Table Structure ${nanoid(8)}`;
|
||||
const { projectId } = await n8n.projectComposer.createProject(projectName);
|
||||
|
||||
// Navigate to project settings
|
||||
await n8n.navigate.toProjectSettings(projectId);
|
||||
await expect(n8n.projectSettings.getTitle()).toHaveText(projectName);
|
||||
|
||||
const table = n8n.projectSettings.getMembersTable();
|
||||
|
||||
// Verify table headers are present
|
||||
await expect(table.getByText('User')).toBeVisible();
|
||||
await expect(table.getByText('Role')).toBeVisible();
|
||||
|
||||
// Verify the owner is displayed in the table
|
||||
const memberRows = table.locator('tbody tr');
|
||||
await expect(memberRows).toHaveCount(1);
|
||||
|
||||
// Verify owner cannot change their own role
|
||||
const ownerRow = memberRows.first();
|
||||
const roleDropdown = ownerRow.getByTestId('project-member-role-dropdown');
|
||||
await expect(roleDropdown).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('should display role dropdown for members but not for current user @auth:owner', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
// Create a new project
|
||||
const projectName = `Role Dropdown ${nanoid(8)}`;
|
||||
const { projectId } = await n8n.projectComposer.createProject(projectName);
|
||||
|
||||
// Navigate to project settings
|
||||
await n8n.navigate.toProjectSettings(projectId);
|
||||
await expect(n8n.projectSettings.getTitle()).toHaveText(projectName);
|
||||
|
||||
// Current user (owner) should not have a role dropdown
|
||||
const currentUserRow = n8n.page.locator('tbody tr').first();
|
||||
await expect(currentUserRow.getByTestId('project-member-role-dropdown')).toHaveCount(0);
|
||||
|
||||
// The role should be displayed as static text for the current user
|
||||
await expect(currentUserRow.getByText('Admin')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show project settings form validation @auth:owner', async ({ n8n }) => {
|
||||
// Create a new project
|
||||
const projectName = `Validation ${nanoid(8)}`;
|
||||
const { projectId } = await n8n.projectComposer.createProject(projectName);
|
||||
|
||||
// Navigate to project settings
|
||||
await n8n.navigate.toProjectSettings(projectId);
|
||||
await expect(n8n.projectSettings.getTitle()).toHaveText(projectName);
|
||||
|
||||
// Clear the project name (required field)
|
||||
await n8n.projectSettings.fillProjectName('');
|
||||
|
||||
// Save button should be disabled when required field is empty
|
||||
await expect(n8n.projectSettings.getSaveButton()).toBeDisabled();
|
||||
|
||||
// Fill in a valid name
|
||||
await n8n.projectSettings.fillProjectName('Valid Project Name');
|
||||
|
||||
// Save button should now be enabled
|
||||
await expect(n8n.projectSettings.getSaveButton()).toBeEnabled();
|
||||
});
|
||||
|
||||
test('should handle unsaved changes state @auth:owner', async ({ n8n }) => {
|
||||
// Create a new project
|
||||
const projectName = `Unsaved Changes ${nanoid(8)}`;
|
||||
const { projectId } = await n8n.projectComposer.createProject(projectName);
|
||||
|
||||
// Navigate to project settings
|
||||
await n8n.navigate.toProjectSettings(projectId);
|
||||
await expect(n8n.projectSettings.getTitle()).toHaveText(projectName);
|
||||
|
||||
// Initially, save and cancel buttons should be disabled (no changes)
|
||||
await expect(n8n.projectSettings.getSaveButton()).toBeDisabled();
|
||||
await expect(n8n.projectSettings.getCancelButton()).toBeDisabled();
|
||||
|
||||
// Make a change to the project name
|
||||
await n8n.projectSettings.fillProjectName('Modified Name');
|
||||
|
||||
// Save and cancel buttons should now be enabled
|
||||
await expect(n8n.projectSettings.getSaveButton()).toBeEnabled();
|
||||
await expect(n8n.projectSettings.getCancelButton()).toBeEnabled();
|
||||
|
||||
// Cancel changes
|
||||
await n8n.projectSettings.clickCancelButton();
|
||||
|
||||
// Buttons should be disabled again (no changes)
|
||||
await expect(n8n.projectSettings.getSaveButton()).toBeDisabled();
|
||||
await expect(n8n.projectSettings.getCancelButton()).toBeDisabled();
|
||||
});
|
||||
|
||||
test('should display delete project section with warning @auth:owner', async ({ n8n }) => {
|
||||
// Create a new project
|
||||
const projectName = `Delete Test ${nanoid(8)}`;
|
||||
const { projectId } = await n8n.projectComposer.createProject(projectName);
|
||||
|
||||
// Navigate to project settings
|
||||
await n8n.navigate.toProjectSettings(projectId);
|
||||
await expect(n8n.projectSettings.getTitle()).toHaveText(projectName);
|
||||
|
||||
// Scroll to bottom to see delete section
|
||||
await n8n.projectSettings.getDeleteButton().scrollIntoViewIfNeeded();
|
||||
|
||||
// Verify danger section is visible with warning
|
||||
// Copy was updated in UI to use sentence case and expanded description
|
||||
await expect(n8n.page.getByText('Danger zone')).toBeVisible();
|
||||
await expect(
|
||||
n8n.page.getByText(
|
||||
'When deleting a project, you can also choose to move all workflows and credentials to another project.',
|
||||
),
|
||||
).toBeVisible();
|
||||
await expect(n8n.projectSettings.getDeleteButton()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should persist settings after page reload @auth:owner', async ({ n8n }) => {
|
||||
// Create a new project
|
||||
const projectName = `Persistence ${nanoid(8)}`;
|
||||
const { projectId } = await n8n.projectComposer.createProject(projectName);
|
||||
|
||||
// Navigate to project settings
|
||||
await n8n.navigate.toProjectSettings(projectId);
|
||||
await expect(n8n.projectSettings.getTitle()).toHaveText(projectName);
|
||||
|
||||
// Update project details
|
||||
const newProjectName = 'Persisted Project Name';
|
||||
const projectDescription = 'This description should persist after reload';
|
||||
|
||||
await n8n.projectSettings.fillProjectName(newProjectName);
|
||||
await n8n.projectSettings.fillProjectDescription(projectDescription);
|
||||
await n8n.projectSettings.clickSaveButton();
|
||||
|
||||
// Wait for save confirmation (partial match to include project name)
|
||||
await expect(
|
||||
n8n.page.getByText('Project Persisted Project Name saved successfully', { exact: false }),
|
||||
).toBeVisible();
|
||||
|
||||
// Reload the page
|
||||
await n8n.page.reload();
|
||||
await expect(n8n.projectSettings.getTitle()).toHaveText('Persisted Project Name');
|
||||
|
||||
// Verify data persisted
|
||||
await n8n.projectSettings.expectProjectNameValue(newProjectName);
|
||||
await n8n.projectSettings.expectProjectDescriptionValue(projectDescription);
|
||||
|
||||
// Verify table still shows the owner
|
||||
await n8n.projectSettings.expectTableHasMemberCount(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user