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,145 @@
|
||||
import { expect, test } from '../../../../fixtures/base';
|
||||
import type { n8nPage } from '../../../../pages/n8nPage';
|
||||
import {
|
||||
buildRepoUrl,
|
||||
generateUniqueRepoName,
|
||||
initSourceControl,
|
||||
} from '../../../../utils/source-control-helper';
|
||||
|
||||
test.use({ capability: 'source-control' });
|
||||
|
||||
async function saveSettings(n8n: n8nPage) {
|
||||
await n8n.settingsEnvironment.getSaveButton().click();
|
||||
await n8n.page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/rest/source-control/preferences') &&
|
||||
response.request().method() === 'PATCH',
|
||||
);
|
||||
}
|
||||
|
||||
// Skipped: These tests are flaky. Re-enable when PAY-4365 is resolved.
|
||||
// https://linear.app/n8n/issue/PAY-4365/bug-source-control-operations-fail-in-multi-main-deployment
|
||||
test.describe(
|
||||
'Source Control Settings @capability:source-control',
|
||||
{
|
||||
annotation: [{ type: 'owner', description: 'Lifecycle & Governance' }],
|
||||
},
|
||||
() => {
|
||||
test.fixme();
|
||||
|
||||
let repoUrl: string;
|
||||
let repoName: string;
|
||||
|
||||
test.beforeEach(async ({ n8n, services }) => {
|
||||
await n8n.api.enableFeature('sourceControl');
|
||||
const gitea = services.gitea;
|
||||
await initSourceControl({ n8n, gitea });
|
||||
|
||||
// Create unique repo with branches via API (not UI)
|
||||
repoName = generateUniqueRepoName();
|
||||
await gitea.createRepo(repoName);
|
||||
|
||||
repoUrl = buildRepoUrl(repoName);
|
||||
});
|
||||
|
||||
test('should connect to Git repository using SSH', async ({ n8n }) => {
|
||||
// Test UI connection flow with unique repo
|
||||
await n8n.navigate.toEnvironments();
|
||||
|
||||
await n8n.settingsEnvironment.fillRepoUrl(repoUrl);
|
||||
await expect(n8n.settingsEnvironment.getConnectButton()).toBeEnabled();
|
||||
await n8n.settingsEnvironment.getConnectButton().click();
|
||||
|
||||
await expect(n8n.settingsEnvironment.getDisconnectButton()).toBeVisible();
|
||||
await expect(n8n.settingsEnvironment.getBranchSelect()).toBeVisible();
|
||||
|
||||
await n8n.settingsEnvironment.getBranchSelect().click();
|
||||
await expect(n8n.page.getByRole('option', { name: 'main' })).toBeVisible();
|
||||
|
||||
// Verify source control connected indicator is visible
|
||||
await n8n.navigate.toHome();
|
||||
await expect(n8n.sideBar.getSourceControlConnectedIndicator()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should switch between branches', async ({ n8n, services }) => {
|
||||
const gitea = services.gitea;
|
||||
await gitea.createBranch(repoName, 'development');
|
||||
await gitea.createBranch(repoName, 'staging');
|
||||
await gitea.createBranch(repoName, 'production');
|
||||
|
||||
await n8n.api.sourceControl.connect({ repositoryUrl: repoUrl });
|
||||
|
||||
await n8n.navigate.toEnvironments();
|
||||
|
||||
// Switch to 'development' branch
|
||||
await n8n.settingsEnvironment.getBranchSelect().click();
|
||||
await expect(n8n.page.getByRole('option', { name: 'main' })).toBeVisible();
|
||||
await expect(n8n.page.getByRole('option', { name: 'development' })).toBeVisible();
|
||||
await expect(n8n.page.getByRole('option', { name: 'staging' })).toBeVisible();
|
||||
await expect(n8n.page.getByRole('option', { name: 'production' })).toBeVisible();
|
||||
await n8n.page.getByRole('option', { name: 'development' }).click();
|
||||
await saveSettings(n8n);
|
||||
|
||||
// Verify branch switched by checking preferences
|
||||
let preferencesResponse = await n8n.page.request.get('/rest/source-control/preferences');
|
||||
let preferences = await preferencesResponse.json();
|
||||
expect(preferences.data.branchName).toBe('development');
|
||||
|
||||
// Switch back to 'main'
|
||||
await n8n.settingsEnvironment.selectBranch('main');
|
||||
await saveSettings(n8n);
|
||||
|
||||
// Verify switched back
|
||||
preferencesResponse = await n8n.page.request.get('/rest/source-control/preferences');
|
||||
preferences = await preferencesResponse.json();
|
||||
expect(preferences.data.branchName).toBe('main');
|
||||
});
|
||||
|
||||
test('should enable read-only mode and restrict operations', async ({ n8n }) => {
|
||||
await n8n.api.sourceControl.connect({ repositoryUrl: repoUrl });
|
||||
|
||||
await n8n.navigate.toEnvironments();
|
||||
|
||||
await n8n.settingsEnvironment.enableReadOnlyMode();
|
||||
await saveSettings(n8n);
|
||||
|
||||
// Verify push button is disabled in read-only mode
|
||||
await n8n.navigate.toHome();
|
||||
await expect(n8n.sideBar.getSourceControlPushButton()).toBeDisabled();
|
||||
await expect(n8n.sideBar.getSourceControlPullButton()).toBeEnabled();
|
||||
|
||||
await n8n.navigate.toEnvironments();
|
||||
await n8n.settingsEnvironment.disableReadOnlyMode();
|
||||
await saveSettings(n8n);
|
||||
|
||||
// Verify push button is enabled again
|
||||
await n8n.navigate.toHome();
|
||||
await expect(n8n.sideBar.getSourceControlPushButton()).toBeEnabled();
|
||||
await expect(n8n.sideBar.getSourceControlPullButton()).toBeEnabled();
|
||||
});
|
||||
|
||||
test('should disconnect and reconnect with existing keys', async ({ n8n }) => {
|
||||
await n8n.api.sourceControl.connect({ repositoryUrl: repoUrl });
|
||||
|
||||
await n8n.navigate.toEnvironments();
|
||||
await n8n.settingsEnvironment.disconnect();
|
||||
|
||||
// check that source control is disconnected
|
||||
await n8n.navigate.toHome();
|
||||
await expect(n8n.sideBar.getSourceControlConnectedIndicator()).toBeHidden();
|
||||
|
||||
// Reconnect
|
||||
await n8n.navigate.toEnvironments();
|
||||
await n8n.settingsEnvironment.fillRepoUrl(repoUrl);
|
||||
await expect(n8n.settingsEnvironment.getConnectButton()).toBeEnabled();
|
||||
await n8n.settingsEnvironment.getConnectButton().click();
|
||||
|
||||
await expect(n8n.settingsEnvironment.getDisconnectButton()).toBeVisible();
|
||||
await expect(n8n.settingsEnvironment.getBranchSelect()).toBeVisible();
|
||||
|
||||
// check that source control is connected
|
||||
await n8n.navigate.toHome();
|
||||
await expect(n8n.sideBar.getSourceControlConnectedIndicator()).toBeVisible();
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,157 @@
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
import { test, expect } from '../../../../fixtures/base';
|
||||
|
||||
const generateValidId = customAlphabet(
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_',
|
||||
8,
|
||||
);
|
||||
|
||||
test.describe('Variables', {
|
||||
annotation: [
|
||||
{ type: 'owner', description: 'Lifecycle & Governance' },
|
||||
],
|
||||
}, () => {
|
||||
// These tests are serial since it's at an instance level and they interact with the same variables
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
test.describe('unlicensed', () => {
|
||||
test('should show the unlicensed action box when the feature is disabled', async ({ n8n }) => {
|
||||
await n8n.api.disableFeature('variables');
|
||||
await n8n.navigate.toVariables();
|
||||
await expect(n8n.variables.getUnavailableResourcesList()).toBeVisible();
|
||||
await expect(n8n.variables.getResourcesList()).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('licensed', () => {
|
||||
test.beforeEach(async ({ n8n }) => {
|
||||
await n8n.api.enableFeature('variables');
|
||||
await n8n.api.variables.deleteAllVariables();
|
||||
await n8n.navigate.toVariables();
|
||||
});
|
||||
|
||||
test('should create a new variable using empty state', async ({ n8n }) => {
|
||||
const key = `ENV_VAR_${generateValidId()}`;
|
||||
const value = 'test_value';
|
||||
|
||||
await n8n.variables.createVariableFromEmptyState(key, value);
|
||||
|
||||
const variableRow = n8n.variables.getVariableRow(key);
|
||||
await expect(variableRow).toContainText(value);
|
||||
await expect(variableRow).toBeVisible();
|
||||
await expect(n8n.variables.getVariablesRows()).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('should create multiple variables', async ({ n8n }) => {
|
||||
const key1 = `ENV_VAR_NEW_${generateValidId()}`;
|
||||
const value1 = 'test_value_1';
|
||||
await n8n.variables.createVariableFromEmptyState(key1, value1);
|
||||
await expect(n8n.variables.getVariablesRows()).toHaveCount(1);
|
||||
|
||||
const key2 = `ENV_EXAMPLE_${generateValidId()}`;
|
||||
const value2 = 'test_value_2';
|
||||
await n8n.variables.createVariable(key2, value2);
|
||||
|
||||
await expect(n8n.variables.getVariablesRows()).toHaveCount(2);
|
||||
|
||||
const variableRow1 = n8n.variables.getVariableRow(key1);
|
||||
await expect(variableRow1).toContainText(value1);
|
||||
await expect(variableRow1).toBeVisible();
|
||||
|
||||
const variableRow2 = n8n.variables.getVariableRow(key2);
|
||||
await expect(variableRow2).toContainText(value2);
|
||||
await expect(variableRow2).toBeVisible();
|
||||
});
|
||||
|
||||
test('should get validation errors and cancel variable creation', async ({ n8n }) => {
|
||||
await n8n.variables.createVariableFromEmptyState(
|
||||
`ENV_BASE_${generateValidId()}`,
|
||||
'base_value',
|
||||
);
|
||||
await expect(n8n.variables.getVariablesRows()).toHaveCount(1);
|
||||
const initialCount = await n8n.variables.getVariablesRows().count();
|
||||
|
||||
const key = `ENV_VAR_INVALID_${generateValidId()}$`; // Invalid key with special character
|
||||
const value = 'test_value';
|
||||
|
||||
await n8n.variables.createVariable(key, value, { shouldSave: false });
|
||||
const saveButton = n8n.variables.variableModal.getSaveButton();
|
||||
await expect(saveButton).toBeDisabled();
|
||||
await n8n.variables.variableModal.close();
|
||||
|
||||
await expect(n8n.variables.getVariablesRows()).toHaveCount(initialCount);
|
||||
});
|
||||
|
||||
test('should edit a variable', async ({ n8n }) => {
|
||||
const key = `ENV_VAR_EDIT_${generateValidId()}`;
|
||||
const initialValue = 'initial_value';
|
||||
await n8n.variables.createVariableFromEmptyState(key, initialValue);
|
||||
|
||||
const newValue = 'updated_value';
|
||||
|
||||
await n8n.variables.editVariable(key, newValue, { shouldSave: true });
|
||||
|
||||
const variableRow = n8n.variables.getVariableRow(key);
|
||||
await expect(variableRow).toContainText(newValue);
|
||||
await expect(variableRow).toBeVisible();
|
||||
});
|
||||
|
||||
test('should delete a variable', async ({ n8n }) => {
|
||||
const key = `TO_DELETE_${generateValidId()}`;
|
||||
const value = 'delete_test_value';
|
||||
|
||||
await n8n.variables.createVariableFromEmptyState(key, value);
|
||||
await expect(n8n.variables.getVariablesRows()).toHaveCount(1);
|
||||
const initialCount = await n8n.variables.getVariablesRows().count();
|
||||
|
||||
await n8n.variables.deleteVariable(key);
|
||||
|
||||
await expect(n8n.variables.getVariablesRows()).toHaveCount(initialCount - 1);
|
||||
|
||||
await expect(n8n.variables.getVariableRow(key)).toBeHidden();
|
||||
});
|
||||
|
||||
test('should search for a variable', async ({ n8n }) => {
|
||||
const uniqueId = generateValidId();
|
||||
|
||||
const key1 = `SEARCH_VAR_${uniqueId}`;
|
||||
const key2 = `SEARCH_VAR_NEW_${uniqueId}`;
|
||||
const key3 = `SEARCH_EXAMPLE_${uniqueId}`;
|
||||
|
||||
await n8n.variables.createVariableFromEmptyState(key1, 'search_value_1');
|
||||
await n8n.variables.createVariable(key2, 'search_value_2');
|
||||
await n8n.variables.createVariable(key3, 'search_value_3');
|
||||
|
||||
await n8n.variables.getSearchBar().fill('NEW_');
|
||||
await n8n.variables.getSearchBar().press('Enter');
|
||||
await expect(n8n.variables.getVariablesRows()).toHaveCount(1);
|
||||
await expect(n8n.variables.getVariableRow(key2)).toBeVisible();
|
||||
await expect(n8n.page).toHaveURL(new RegExp('search=NEW_'));
|
||||
|
||||
await n8n.variables.getSearchBar().clear();
|
||||
await n8n.variables.getSearchBar().fill('SEARCH_VAR_');
|
||||
await n8n.variables.getSearchBar().press('Enter');
|
||||
await expect(n8n.variables.getVariablesRows()).toHaveCount(2);
|
||||
await expect(n8n.variables.getVariableRow(key1)).toBeVisible();
|
||||
await expect(n8n.variables.getVariableRow(key2)).toBeVisible();
|
||||
await expect(n8n.page).toHaveURL(new RegExp('search=SEARCH_VAR_'));
|
||||
|
||||
await n8n.variables.getSearchBar().clear();
|
||||
await n8n.variables.getSearchBar().fill('SEARCH_');
|
||||
await n8n.variables.getSearchBar().press('Enter');
|
||||
await expect(n8n.variables.getVariablesRows()).toHaveCount(3);
|
||||
await expect(n8n.variables.getVariableRow(key1)).toBeVisible();
|
||||
await expect(n8n.variables.getVariableRow(key2)).toBeVisible();
|
||||
await expect(n8n.variables.getVariableRow(key3)).toBeVisible();
|
||||
await expect(n8n.page).toHaveURL(new RegExp('search=SEARCH_'));
|
||||
|
||||
await n8n.variables.getSearchBar().clear();
|
||||
await n8n.variables.getSearchBar().fill(`NonExistent_${generateValidId()}`);
|
||||
await n8n.variables.getSearchBar().press('Enter');
|
||||
await expect(n8n.variables.getVariablesRows()).toBeHidden();
|
||||
await expect(n8n.page).toHaveURL(/search=NonExistent_/);
|
||||
|
||||
await expect(n8n.variables.getNoVariablesFoundMessage()).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user