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,153 @@
|
||||
import { MANUAL_TRIGGER_NODE_NAME } from '../../../config/constants';
|
||||
import { expect, test } from '../../../fixtures/base';
|
||||
import type { n8nPage } from '../../../pages/n8nPage';
|
||||
import { type GitRepoHelper, setupGitRepo } from '../../../utils/source-control-helper';
|
||||
|
||||
test.use({ capability: 'source-control' });
|
||||
|
||||
async function expectPullSuccess(n8n: n8nPage) {
|
||||
expect(
|
||||
await n8n.notifications.waitForNotificationAndClose('Pulled successfully', { timeout: 30000 }),
|
||||
).toBe(true);
|
||||
}
|
||||
|
||||
// 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(
|
||||
'Pull resources from Git @capability:source-control',
|
||||
{
|
||||
annotation: [{ type: 'owner', description: 'Lifecycle & Governance' }],
|
||||
},
|
||||
() => {
|
||||
test.fixme();
|
||||
|
||||
let gitRepo: GitRepoHelper;
|
||||
|
||||
test.beforeEach(async ({ n8n, services }) => {
|
||||
await n8n.api.enableFeature('sourceControl');
|
||||
await n8n.api.enableFeature('variables');
|
||||
gitRepo = await setupGitRepo(n8n, services.gitea);
|
||||
});
|
||||
|
||||
test('should pull new resources from remote', async ({ n8n }) => {
|
||||
// create project
|
||||
const project = await n8n.api.projects.createProject('Test Project');
|
||||
const folder = await n8n.api.projects.createFolder(project.id, 'Test Folder');
|
||||
// create folder
|
||||
const workflow = await n8n.api.workflows.createInProject(project.id, {
|
||||
name: 'Pull Test Workflow',
|
||||
folder: folder.id,
|
||||
});
|
||||
const credential = await n8n.api.credentials.createCredential({
|
||||
name: 'Test Credential',
|
||||
type: 'notionApi',
|
||||
data: { apiKey: '1234567890' },
|
||||
projectId: project.id,
|
||||
});
|
||||
const variable = await n8n.api.variables.createVariable({
|
||||
key: 'PULL_TEST_VARIABLE',
|
||||
value: 'test-value',
|
||||
projectId: project.id,
|
||||
});
|
||||
|
||||
const tag = await n8n.api.tags.create('pull-test-tag');
|
||||
|
||||
// push all resources
|
||||
await n8n.api.sourceControl.pushWorkFolder({
|
||||
commitMessage: 'Initial push',
|
||||
});
|
||||
|
||||
// disconnect
|
||||
await n8n.api.sourceControl.disconnect();
|
||||
|
||||
// delete created resources
|
||||
await n8n.api.projects.deleteProject(project.id); // This also deletes all related resources
|
||||
await n8n.api.tags.delete(tag.id);
|
||||
|
||||
// re-connect to source control
|
||||
await n8n.api.sourceControl.connect({ repositoryUrl: gitRepo.repoUrl });
|
||||
|
||||
// pull all resources
|
||||
await n8n.navigate.toHome();
|
||||
await n8n.sideBar.getSourceControlPullButton().click();
|
||||
await expectPullSuccess(n8n);
|
||||
|
||||
// check that all new resources are pulled
|
||||
await n8n.navigate.toProjectSettings(project.id);
|
||||
await expect(n8n.projectSettings.getTitle()).toHaveText(project.name);
|
||||
|
||||
await n8n.projectTabs.clickCredentialsTab();
|
||||
await expect(n8n.credentials.cards.getCredential(credential.name)).toBeVisible();
|
||||
|
||||
await n8n.projectTabs.clickVariablesTab();
|
||||
await expect(n8n.variables.getVariableRow(variable.key)).toBeVisible();
|
||||
|
||||
await n8n.projectTabs.clickWorkflowsTab();
|
||||
await expect(n8n.workflows.cards.getFolder(folder.name)).toBeVisible();
|
||||
|
||||
await n8n.workflows.cards.openFolder(folder.name);
|
||||
await expect(n8n.workflows.cards.getWorkflow(workflow.name)).toBeVisible();
|
||||
|
||||
await n8n.workflows.cards.clickWorkflowCard(workflow.name);
|
||||
await n8n.canvas.openTagManagerModal();
|
||||
await expect(n8n.canvas.tagsManagerModal.getModal()).toBeVisible();
|
||||
await expect(n8n.canvas.tagsManagerModal.getTable()).toBeVisible();
|
||||
await expect(n8n.canvas.tagsManagerModal.getTable().getByText('pull-test-tag')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should pull modified and deleted resources from remote', async ({ n8n }) => {
|
||||
const project = await n8n.api.projects.createProject('Pull Test Project');
|
||||
const workflow = await n8n.api.workflows.createInProject(project.id, {
|
||||
name: 'Pull Test Workflow',
|
||||
});
|
||||
|
||||
// push
|
||||
await n8n.api.sourceControl.pushWorkFolder({
|
||||
commitMessage: 'Initial push',
|
||||
});
|
||||
|
||||
// disconnect
|
||||
await n8n.api.sourceControl.disconnect();
|
||||
|
||||
// modify workflow
|
||||
await n8n.navigate.toWorkflow(workflow.id);
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.waitForSaveWorkflowCompleted();
|
||||
|
||||
// add new workflow
|
||||
await n8n.api.workflows.createInProject(project.id, {
|
||||
name: 'New Workflow',
|
||||
});
|
||||
|
||||
// re-connect to source control
|
||||
await n8n.api.sourceControl.connect({ repositoryUrl: gitRepo.repoUrl });
|
||||
|
||||
// pull
|
||||
await n8n.navigate.toHome();
|
||||
await n8n.sideBar.getSourceControlPullButton().click();
|
||||
await expect(n8n.sourceControlPullModal.getModal()).toBeVisible();
|
||||
|
||||
// check that conflicts are detected
|
||||
await n8n.sourceControlPullModal.selectWorkflowsTab();
|
||||
await expect(n8n.sourceControlPullModal.getFileInModal('New Workflow')).toBeVisible();
|
||||
await expect(
|
||||
n8n.sourceControlPullModal.getStatusBadge('New Workflow', 'Deleted'),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(n8n.sourceControlPullModal.getFileInModal('Pull Test Workflow')).toBeVisible();
|
||||
await expect(
|
||||
n8n.sourceControlPullModal.getStatusBadge('Pull Test Workflow', 'Modified'),
|
||||
).toBeVisible();
|
||||
|
||||
// click on pull & override button
|
||||
await n8n.sourceControlPullModal.getPullAndOverrideButton().click();
|
||||
await expectPullSuccess(n8n);
|
||||
|
||||
// check pulled resources
|
||||
await n8n.navigate.toWorkflow(workflow.id);
|
||||
await expect(n8n.canvas.getWorkflowName()).toHaveAttribute('title', 'Pull Test Workflow');
|
||||
await n8n.navigate.toHome();
|
||||
await expect(n8n.workflows.cards.getWorkflow('New Workflow')).toBeHidden();
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,267 @@
|
||||
import { MANUAL_TRIGGER_NODE_NAME } from '../../../config/constants';
|
||||
import { expect, test } from '../../../fixtures/base';
|
||||
import type { n8nPage } from '../../../pages/n8nPage';
|
||||
import { type GitRepoHelper, setupGitRepo } from '../../../utils/source-control-helper';
|
||||
|
||||
test.use({ capability: 'source-control' });
|
||||
|
||||
async function expectPushSuccess(n8n: n8nPage) {
|
||||
expect(
|
||||
await n8n.notifications.waitForNotificationAndClose('Pushed successfully', { timeout: 30000 }),
|
||||
).toBe(true);
|
||||
}
|
||||
|
||||
async function expectNoChangesToCommit(n8n: n8nPage) {
|
||||
expect(
|
||||
await n8n.notifications.waitForNotificationAndClose('No changes to commit', { timeout: 10000 }),
|
||||
).toBe(true);
|
||||
}
|
||||
|
||||
// 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(
|
||||
'Push resources to Git @capability:source-control',
|
||||
{
|
||||
annotation: [{ type: 'owner', description: 'Lifecycle & Governance' }],
|
||||
},
|
||||
() => {
|
||||
test.fixme();
|
||||
|
||||
let gitRepo: GitRepoHelper;
|
||||
|
||||
test.beforeEach(async ({ n8n, services }) => {
|
||||
await n8n.api.enableFeature('sourceControl');
|
||||
await n8n.api.enableFeature('variables');
|
||||
|
||||
gitRepo = await setupGitRepo(n8n, services.gitea);
|
||||
});
|
||||
|
||||
test('should push a new workflow', async ({ n8n }) => {
|
||||
// create workflow
|
||||
const workflow = await n8n.api.workflows.createWorkflow({
|
||||
name: 'Test Workflow',
|
||||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
});
|
||||
await n8n.navigate.toWorkflow(workflow.id);
|
||||
|
||||
// check modal
|
||||
await n8n.sideBar.getSourceControlPushButton().click();
|
||||
await expect(n8n.sourceControlPushModal.getModal()).toBeVisible();
|
||||
const isWorkflowsTabSelected = await n8n.sourceControlPushModal.isWorkflowsTabSelected();
|
||||
expect(isWorkflowsTabSelected).toBe(true);
|
||||
|
||||
// check selected workflow
|
||||
await expect(n8n.sourceControlPushModal.getFileInModal('Test Workflow')).toBeVisible();
|
||||
const workflowCheckbox = n8n.sourceControlPushModal.getFileCheckboxByName('Test Workflow');
|
||||
await expect(workflowCheckbox).toBeChecked();
|
||||
|
||||
// push and wait for commit to be indexed
|
||||
await gitRepo.pushAndWait(n8n, 'Add test workflow with manual trigger');
|
||||
await expectPushSuccess(n8n);
|
||||
|
||||
// check no changes to commit
|
||||
await n8n.sideBar.getSourceControlPushButton().click();
|
||||
await expectNoChangesToCommit(n8n);
|
||||
});
|
||||
|
||||
test('should push all resource types together', async ({ n8n }) => {
|
||||
// variables and tags
|
||||
await n8n.api.variables.createTestVariable();
|
||||
await n8n.api.tags.create('test-tag');
|
||||
|
||||
// projects and folders
|
||||
const project = await n8n.api.projects.createProject('Test Project');
|
||||
const folderA = await n8n.api.projects.createFolder(project.id, 'Folder A');
|
||||
const folderB = await n8n.api.projects.createFolder(project.id, 'Folder B', folderA.id);
|
||||
|
||||
// workflows
|
||||
await n8n.api.workflows.createInProject(project.id, {
|
||||
name: 'Workflow in Folder A',
|
||||
folder: folderA.id,
|
||||
});
|
||||
await n8n.api.workflows.createInProject(project.id, {
|
||||
name: 'Workflow in Folder B',
|
||||
folder: folderB.id,
|
||||
});
|
||||
await n8n.api.workflows.createInProject(project.id, {
|
||||
name: 'Root Workflow',
|
||||
});
|
||||
|
||||
// credentials
|
||||
await n8n.api.credentials.createCredential({
|
||||
name: 'Credential in Project 1',
|
||||
type: 'notionApi',
|
||||
data: { apiKey: '1234567890' },
|
||||
projectId: project.id,
|
||||
});
|
||||
|
||||
// Open push modal
|
||||
await n8n.navigate.toHome();
|
||||
await expect(n8n.workflows.cards.getWorkflow('Workflow in Folder A')).toBeVisible();
|
||||
await expect(n8n.workflows.cards.getWorkflow('Workflow in Folder B')).toBeVisible();
|
||||
await expect(n8n.workflows.cards.getWorkflow('Root Workflow')).toBeVisible();
|
||||
|
||||
await n8n.sideBar.getSourceControlPushButton().click();
|
||||
await expect(n8n.sourceControlPushModal.getModal()).toBeVisible();
|
||||
|
||||
// check notice
|
||||
const notice = n8n.sourceControlPushModal.getNotice();
|
||||
await expect(notice).toBeVisible();
|
||||
expect(await notice.textContent()).toContain('Variables');
|
||||
expect(await notice.textContent()).toContain('Projects');
|
||||
expect(await notice.textContent()).toContain('Folders');
|
||||
expect(await notice.textContent()).toContain('Tags');
|
||||
|
||||
// check workflows
|
||||
await n8n.sourceControlPushModal.selectWorkflowsTab();
|
||||
await expect(n8n.sourceControlPushModal.getFileInModal('Workflow in Folder A')).toBeVisible();
|
||||
await expect(n8n.sourceControlPushModal.getFileInModal('Workflow in Folder B')).toBeVisible();
|
||||
await expect(n8n.sourceControlPushModal.getFileInModal('Root Workflow')).toBeVisible();
|
||||
await n8n.sourceControlPushModal.selectAllFilesInModal();
|
||||
|
||||
// check credentials
|
||||
await n8n.sourceControlPushModal.selectCredentialsTab();
|
||||
await expect(
|
||||
n8n.sourceControlPushModal.getFileInModal('Credential in Project 1'),
|
||||
).toBeVisible();
|
||||
await n8n.sourceControlPushModal.selectAllFilesInModal();
|
||||
|
||||
// Push all resources
|
||||
await n8n.sourceControlPushModal.push('Add all resource types');
|
||||
await expectPushSuccess(n8n);
|
||||
});
|
||||
|
||||
test('should push modifications and deletions', async ({ n8n }) => {
|
||||
// create resources
|
||||
const project = await n8n.api.projects.createProject('Test Project');
|
||||
const workflow = await n8n.api.workflows.createInProject(project.id, {
|
||||
name: 'Workflow to Modify',
|
||||
});
|
||||
const credential = await n8n.api.credentials.createCredential({
|
||||
name: 'Credential to Delete',
|
||||
type: 'notionApi',
|
||||
data: { apiKey: '1234567890' },
|
||||
projectId: project.id,
|
||||
});
|
||||
|
||||
// Push it to Git first
|
||||
await n8n.navigate.toHome();
|
||||
await expect(n8n.workflows.cards.getWorkflow('Workflow to Modify')).toBeVisible();
|
||||
await n8n.sideBar.getSourceControlPushButton().click();
|
||||
await expect(n8n.sourceControlPushModal.getModal()).toBeVisible();
|
||||
|
||||
await n8n.sourceControlPushModal.selectWorkflowsTab();
|
||||
await n8n.sourceControlPushModal.selectAllFilesInModal();
|
||||
|
||||
await gitRepo.pushAndWait(n8n, 'new resources');
|
||||
await expectPushSuccess(n8n);
|
||||
|
||||
// modify and delete resources
|
||||
await n8n.navigate.toWorkflow(workflow.id);
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.waitForSaveWorkflowCompleted();
|
||||
|
||||
await n8n.api.credentials.deleteCredential(credential.id);
|
||||
|
||||
await n8n.sideBar.getSourceControlPushButton().click();
|
||||
await expect(n8n.sourceControlPushModal.getModal()).toBeVisible();
|
||||
|
||||
await n8n.sourceControlPushModal.selectWorkflowsTab();
|
||||
await expect(n8n.sourceControlPushModal.getFileInModal('Workflow to Modify')).toBeVisible();
|
||||
await expect(
|
||||
n8n.sourceControlPushModal.getStatusBadge('Workflow to Modify', 'Modified'),
|
||||
).toBeVisible();
|
||||
|
||||
await n8n.sourceControlPushModal.selectCredentialsTab();
|
||||
await expect(n8n.sourceControlPushModal.getFileInModal('Credential to Delete')).toBeVisible();
|
||||
await expect(
|
||||
n8n.sourceControlPushModal.getStatusBadge('Credential to Delete', 'Deleted'),
|
||||
).toBeVisible();
|
||||
|
||||
// push and wait for commit
|
||||
await gitRepo.pushAndWait(n8n, 'Modify workflow and delete credential');
|
||||
await expectPushSuccess(n8n);
|
||||
|
||||
// check no changes to commit
|
||||
await n8n.sideBar.getSourceControlPushButton().click();
|
||||
await expectNoChangesToCommit(n8n);
|
||||
});
|
||||
|
||||
test('should push selected resources', async ({ n8n }) => {
|
||||
// Create multiple workflows
|
||||
const workflowA = await n8n.api.workflows.createWorkflow({
|
||||
name: 'Workflow A',
|
||||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
});
|
||||
|
||||
await n8n.api.workflows.createWorkflow({
|
||||
name: 'Workflow B',
|
||||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
});
|
||||
|
||||
await n8n.api.workflows.createWorkflow({
|
||||
name: 'Workflow C',
|
||||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
});
|
||||
|
||||
// check resources
|
||||
await n8n.navigate.toHome();
|
||||
await expect(n8n.workflows.cards.getWorkflow('Workflow A')).toBeVisible();
|
||||
await expect(n8n.workflows.cards.getWorkflow('Workflow B')).toBeVisible();
|
||||
await expect(n8n.workflows.cards.getWorkflow('Workflow C')).toBeVisible();
|
||||
|
||||
await n8n.sideBar.getSourceControlPushButton().click();
|
||||
await expect(n8n.sourceControlPushModal.getModal()).toBeVisible();
|
||||
await expect(n8n.sourceControlPushModal.getFileCheckboxByName('Workflow A')).toBeVisible();
|
||||
await expect(n8n.sourceControlPushModal.getFileCheckboxByName('Workflow B')).toBeVisible();
|
||||
await expect(n8n.sourceControlPushModal.getFileCheckboxByName('Workflow C')).toBeVisible();
|
||||
|
||||
// select
|
||||
await n8n.sourceControlPushModal.selectFile('Workflow A');
|
||||
await n8n.sourceControlPushModal.selectFile('Workflow C');
|
||||
|
||||
await expect(n8n.sourceControlPushModal.getFileCheckboxByName('Workflow A')).toBeChecked();
|
||||
await expect(
|
||||
n8n.sourceControlPushModal.getFileCheckboxByName('Workflow B'),
|
||||
).not.toBeChecked();
|
||||
await expect(n8n.sourceControlPushModal.getFileCheckboxByName('Workflow C')).toBeChecked();
|
||||
|
||||
// push and wait for commit
|
||||
await gitRepo.pushAndWait(n8n, 'Push workflows A and C');
|
||||
await expectPushSuccess(n8n);
|
||||
|
||||
// modify workflow A
|
||||
await n8n.navigate.toWorkflow(workflowA.id);
|
||||
await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME);
|
||||
await n8n.canvas.waitForSaveWorkflowCompleted();
|
||||
|
||||
// check resources
|
||||
await n8n.navigate.toHome();
|
||||
await n8n.sideBar.getSourceControlPushButton().click();
|
||||
await expect(n8n.sourceControlPushModal.getModal()).toBeVisible();
|
||||
|
||||
await expect(n8n.sourceControlPushModal.getFileCheckboxByName('Workflow A')).toBeVisible();
|
||||
await expect(n8n.sourceControlPushModal.getFileCheckboxByName('Workflow B')).toBeVisible();
|
||||
await expect(n8n.sourceControlPushModal.getFileCheckboxByName('Workflow C')).toBeHidden();
|
||||
|
||||
await n8n.sourceControlPushModal.selectFile('Workflow A');
|
||||
await n8n.sourceControlPushModal.selectFile('Workflow B');
|
||||
|
||||
// push and wait for commit
|
||||
await gitRepo.pushAndWait(n8n, 'Push workflow A and B');
|
||||
await expectPushSuccess(n8n);
|
||||
|
||||
// Verify no more changes
|
||||
await n8n.sideBar.getSourceControlPushButton().click();
|
||||
await expectNoChangesToCommit(n8n);
|
||||
});
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user