test: Migrate cypress tests batch 1 to playwright (#19569)

This commit is contained in:
Artem Sorokin
2025-09-16 16:11:51 +02:00
committed by GitHub
parent b480f495d9
commit a4fc24371d
19 changed files with 2573 additions and 239 deletions

View File

@@ -1,3 +1,5 @@
import { expect } from '@playwright/test';
import type { n8nPage } from '../pages/n8nPage';
export class CanvasComposer {
@@ -37,4 +39,62 @@ export class CanvasComposer {
await this.n8n.canvas.selectAll();
await this.copySelectedNodesWithToast();
}
/**
* Switch between editor and workflow history and back
*/
async switchBetweenEditorAndHistory(): Promise<void> {
await this.n8n.page.getByTestId('workflow-history-button').click();
await this.n8n.page.getByTestId('workflow-history-close-button').click();
await this.n8n.page.waitForLoadState();
await expect(this.n8n.canvas.getCanvasNodes().first()).toBeVisible();
await expect(this.n8n.canvas.getCanvasNodes().last()).toBeVisible();
}
/**
* Switch between editor and workflow list and back
*/
async switchBetweenEditorAndWorkflowList(): Promise<void> {
await this.n8n.page.getByTestId('menu-item').first().click();
await this.n8n.page.getByTestId('resources-list-item-workflow').first().click();
await expect(this.n8n.canvas.getCanvasNodes().first()).toBeVisible();
await expect(this.n8n.canvas.getCanvasNodes().last()).toBeVisible();
}
/**
* Zoom in and validate that zoom functionality works
*/
async zoomInAndCheckNodes(): Promise<void> {
await this.n8n.canvas.getCanvasNodes().first().waitFor();
const initialNodeSize = await this.n8n.page.evaluate(() => {
const firstNode = document.querySelector('[data-test-id="canvas-node"]');
if (!firstNode) {
throw new Error('Canvas node not found during initial measurement');
}
return firstNode.getBoundingClientRect().width;
});
for (let i = 0; i < 4; i++) {
await this.n8n.canvas.clickZoomInButton();
}
const finalNodeSize = await this.n8n.page.evaluate(() => {
const firstNode = document.querySelector('[data-test-id="canvas-node"]');
if (!firstNode) {
throw new Error('Canvas node not found during final measurement');
}
return firstNode.getBoundingClientRect().width;
});
// Validate zoom increased node sizes by at least 50%
const zoomWorking = finalNodeSize > initialNodeSize * 1.5;
if (!zoomWorking) {
throw new Error(
"Zoom functionality not working: nodes didn't scale properly. " +
`Initial: ${initialNodeSize.toFixed(1)}px, Final: ${finalNodeSize.toFixed(1)}px`,
);
}
}
}

View File

@@ -0,0 +1,112 @@
import { expect } from '@playwright/test';
import type { n8nPage } from '../pages/n8nPage';
/**
* A class for partial execution testing workflows that involve
* complex multi-step scenarios across pages.
*/
export class PartialExecutionComposer {
constructor(private readonly n8n: n8nPage) {}
/**
* Sets up partial execution version 2 in localStorage
* This enables the v2 partial execution feature
*/
async enablePartialExecutionV2(): Promise<void> {
await this.n8n.page.evaluate(() => {
window.localStorage.setItem('PartialExecution.version', '2');
});
}
/**
* Executes a full workflow and verifies all nodes show success status
* @param nodeNames - Array of node names to verify
*/
async executeFullWorkflowAndVerifySuccess(nodeNames: string[]): Promise<void> {
await this.n8n.canvas.clickExecuteWorkflowButton();
// Verify all nodes show success status
for (const nodeName of nodeNames) {
await expect(this.n8n.canvas.getNodeSuccessStatusIndicator(nodeName)).toBeVisible();
}
}
/**
* Captures output data from a node for later comparison
* @param nodeName - The node to capture data from
* @returns The captured text content
*/
async captureNodeOutputData(nodeName: string): Promise<string> {
await this.n8n.canvas.openNode(nodeName);
await this.n8n.ndv.outputPanel.getTable().waitFor();
// Note: Using row 0 for tbody (equivalent to row 1 in Cypress which includes header)
const cell = this.n8n.ndv.outputPanel.getTbodyCell(0, 0);
await expect(cell).toHaveText(/.+/);
const beforeText = await cell.textContent();
await this.n8n.ndv.close();
return beforeText!;
}
/**
* Modifies a node parameter to trigger stale state
* @param nodeName - The node to modify
*/
async modifyNodeToTriggerStaleState(nodeName: string): Promise<void> {
await this.n8n.canvas.openNode(nodeName);
await this.n8n.ndv.clickAssignmentCollectionDropArea();
// Verify stale node indicator appears after parameter change
await expect(this.n8n.ndv.getStaleNodeIndicator()).toBeVisible();
await this.n8n.ndv.close();
}
/**
* Verifies node states after parameter change for partial execution v2
* @param unchangedNodes - Nodes that should still show success
* @param modifiedNodes - Nodes that should show warning (need re-execution)
*/
async verifyNodeStatesAfterChange(
unchangedNodes: string[],
modifiedNodes: string[],
): Promise<void> {
// Verify unchanged nodes still show success
for (const nodeName of unchangedNodes) {
await expect(this.n8n.canvas.getNodeSuccessStatusIndicator(nodeName)).toBeVisible();
}
// Verify modified nodes show warning status
for (const nodeName of modifiedNodes) {
await expect(this.n8n.canvas.getNodeWarningStatusIndicator(nodeName)).toBeVisible();
}
}
/**
* Performs partial execution on a node and verifies all nodes return to success
* @param targetNodeName - The node to execute from
* @param allNodeNames - All nodes that should show success after partial execution
*/
async performPartialExecutionAndVerifySuccess(
targetNodeName: string,
allNodeNames: string[],
): Promise<void> {
// Perform partial execution by clicking execute button on target node
await this.n8n.canvas.executeNode(targetNodeName);
// Verify all nodes show success status after partial execution
for (const nodeName of allNodeNames) {
await expect(this.n8n.canvas.getNodeSuccessStatusIndicator(nodeName)).toBeVisible();
}
}
/**
* Opens a node for data verification (test should handle the assertion)
* @param nodeName - The node to open for verification
* @returns Promise that resolves when node is open and ready for verification
*/
async openNodeForDataVerification(nodeName: string): Promise<void> {
await this.n8n.canvas.openNode(nodeName);
await this.n8n.ndv.outputPanel.getTable().waitFor();
}
}

View File

@@ -62,4 +62,37 @@ export class WorkflowComposer {
await this.n8n.canvas.importWorkflow(fileName, workflowName);
return { workflowName };
}
/**
* Creates a new workflow by importing from a URL
* @param url - The URL to import the workflow from
* @returns Promise that resolves when the import is complete
*/
async importWorkflowFromURL(url: string): Promise<void> {
await this.n8n.workflows.clickAddWorkflowButton();
await this.n8n.canvas.clickWorkflowMenu();
await this.n8n.canvas.clickImportFromURL();
await this.n8n.canvas.fillImportURLInput(url);
await this.n8n.canvas.clickConfirmImportURL();
}
/**
* Opens the import from URL dialog and then dismisses it by clicking outside
*/
async openAndDismissImportFromURLDialog(): Promise<void> {
await this.n8n.workflows.clickAddWorkflowButton();
await this.n8n.canvas.clickWorkflowMenu();
await this.n8n.canvas.clickImportFromURL();
await this.n8n.canvas.clickOutsideModal();
}
/**
* Opens the import from URL dialog and then cancels it
*/
async openAndCancelImportFromURLDialog(): Promise<void> {
await this.n8n.workflows.clickAddWorkflowButton();
await this.n8n.canvas.clickWorkflowMenu();
await this.n8n.canvas.clickImportFromURL();
await this.n8n.canvas.clickCancelImportURL();
}
}

View File

@@ -228,7 +228,7 @@ export class CanvasPage extends BasePage {
const [fileChooser] = await Promise.all([
this.page.waitForEvent('filechooser'),
this.clickByText('Import from File...'),
this.clickByTestId('workflow-menu-item-import-from-file'),
]);
await fileChooser.setFiles(resolveFromRoot('workflows', fixtureKey));
@@ -237,6 +237,40 @@ export class CanvasPage extends BasePage {
await this.page.getByTestId('inline-edit-input').press('Enter');
}
// Import workflow locators
getImportURLInput(): Locator {
return this.page.getByTestId('workflow-url-import-input');
}
// Import workflow actions
async clickWorkflowMenu(): Promise<void> {
await this.clickByTestId('workflow-menu');
}
async clickImportFromURL(): Promise<void> {
await this.clickByTestId('workflow-menu-item-import-from-url');
}
async clickImportFromFile(): Promise<void> {
await this.clickByTestId('workflow-menu-item-import-from-file');
}
async fillImportURLInput(url: string): Promise<void> {
await this.getImportURLInput().fill(url);
}
async clickConfirmImportURL(): Promise<void> {
await this.clickByTestId('confirm-workflow-import-url-button');
}
async clickCancelImportURL(): Promise<void> {
await this.clickByTestId('cancel-workflow-import-url-button');
}
async clickOutsideModal(): Promise<void> {
await this.page.locator('body').click({ position: { x: 0, y: 0 } });
}
getWorkflowTags() {
return this.page.getByTestId('workflow-tags').locator('.el-tag');
}
@@ -660,7 +694,46 @@ export class CanvasPage extends BasePage {
await this.page.getByTestId('radio-button-executions').click();
}
async clickZoomInButton(): Promise<void> {
await this.clickByTestId('zoom-in-button');
}
async clickZoomOutButton(): Promise<void> {
await this.clickByTestId('zoom-out-button');
}
/**
* Get the current zoom level of the canvas
* @returns The current zoom/scale factor as a number
*/
async getCanvasZoomLevel(): Promise<number> {
return await this.page.evaluate(() => {
const canvasViewport = document.querySelector('.vue-flow__viewport');
if (canvasViewport) {
const transform = window.getComputedStyle(canvasViewport).transform;
if (transform && transform !== 'none') {
const matrix = transform.match(/matrix\(([^)]+)\)/);
if (matrix) {
const values = matrix[1].split(',').map((v) => v.trim());
return parseFloat(values[0]); // First value is scaleX
}
}
}
// Fallback: return default zoom level
return 1.0;
});
}
waitingForTriggerEvent() {
return this.getExecuteWorkflowButton().getByText('Waiting for trigger event');
}
getNodeSuccessStatusIndicator(nodeName: string): Locator {
return this.nodeByName(nodeName).getByTestId('canvas-node-status-success');
}
getNodeWarningStatusIndicator(nodeName: string): Locator {
return this.nodeByName(nodeName).getByTestId('canvas-node-status-warning');
}
}

View File

@@ -166,6 +166,14 @@ export class NodeDetailsViewPage extends BasePage {
.getByTestId('assignment-collection-drop-area');
}
getAssignmentCollectionDropArea() {
return this.page.getByTestId('assignment-collection-drop-area');
}
async clickAssignmentCollectionDropArea() {
await this.getAssignmentCollectionDropArea().click();
}
getAssignmentValue(paramName: string) {
return this.page
.getByTestId(`assignment-collection-${paramName}`)
@@ -723,6 +731,18 @@ export class NodeDetailsViewPage extends BasePage {
return this.page.getByTestId('node-run-info');
}
getStaleNodeIndicator() {
return this.page.getByTestId('node-run-info-stale');
}
getExecuteStepButton() {
return this.page.getByRole('button').filter({ hasText: 'Execute step' });
}
async clickExecuteStep() {
await this.getExecuteStepButton().click();
}
async openSettings() {
await this.page.getByTestId('tab-settings').click();
}

View File

@@ -265,4 +265,20 @@ export class NotificationsPage {
// Silent fail
}
}
getErrorNotifications(): Locator {
return this.page.locator('.el-notification:has(.el-notification--error)');
}
getSuccessNotifications(): Locator {
return this.page.locator('.el-notification:has(.el-notification--success)');
}
getWarningNotifications(): Locator {
return this.page.locator('.el-notification:has(.el-notification--warning)');
}
getInfoNotifications(): Locator {
return this.page.locator('.el-notification:has(.el-notification--info)');
}
}

View File

@@ -25,6 +25,7 @@ import { WorkflowSharingModal } from './WorkflowSharingModal';
import { WorkflowsPage } from './WorkflowsPage';
import { CanvasComposer } from '../composables/CanvasComposer';
import { CredentialsComposer } from '../composables/CredentialsComposer';
import { PartialExecutionComposer } from '../composables/PartialExecutionComposer';
import { ProjectComposer } from '../composables/ProjectComposer';
import { TestEntryComposer } from '../composables/TestEntryComposer';
import { WorkflowComposer } from '../composables/WorkflowComposer';
@@ -68,6 +69,7 @@ export class n8nPage {
readonly projectComposer: ProjectComposer;
readonly canvasComposer: CanvasComposer;
readonly credentialsComposer: CredentialsComposer;
readonly partialExecutionComposer: PartialExecutionComposer;
readonly start: TestEntryComposer;
// Helpers
@@ -109,6 +111,7 @@ export class n8nPage {
this.projectComposer = new ProjectComposer(this);
this.canvasComposer = new CanvasComposer(this);
this.credentialsComposer = new CredentialsComposer(this);
this.partialExecutionComposer = new PartialExecutionComposer(this);
this.start = new TestEntryComposer(this);
// Helpers

View File

@@ -351,7 +351,7 @@ test.describe('Credentials', () => {
const saveBtn = n8n.canvas.credentialModal.getSaveButton();
await saveBtn.click();
const errorNotification = page.locator('.el-notification:has(.el-notification--error)');
const errorNotification = n8n.notifications.getErrorNotifications();
await expect(errorNotification).toBeVisible();
await expect(n8n.canvas.credentialModal.getModal()).toBeVisible();

View File

@@ -0,0 +1,42 @@
import { test, expect } from '../../fixtures/base';
const NOTIFICATIONS = {
WORKFLOW_CREATED: 'Workflow successfully created',
};
test.describe('Editor zoom should work after route changes', () => {
test.beforeEach(async ({ n8n, api }) => {
await api.enableFeature('debugInEditor');
await api.enableFeature('workflowHistory');
await n8n.workflowComposer.createWorkflowFromJsonFile(
'Lots_of_nodes.json',
'Lots of nodes test',
);
await n8n.notifications.waitForNotificationAndClose(NOTIFICATIONS.WORKFLOW_CREATED);
});
test('should maintain zoom functionality after switching between Editor and Workflow history and Workflow list', async ({
n8n,
}) => {
const initialNodeCount = await n8n.canvas.getCanvasNodes().count();
expect(initialNodeCount).toBeGreaterThan(0);
await n8n.canvasComposer.switchBetweenEditorAndHistory();
await n8n.canvasComposer.zoomInAndCheckNodes();
await n8n.canvasComposer.switchBetweenEditorAndHistory();
await n8n.canvasComposer.switchBetweenEditorAndHistory();
await n8n.canvasComposer.zoomInAndCheckNodes();
await n8n.canvasComposer.switchBetweenEditorAndWorkflowList();
await n8n.canvasComposer.zoomInAndCheckNodes();
await n8n.canvasComposer.switchBetweenEditorAndWorkflowList();
await n8n.canvasComposer.switchBetweenEditorAndWorkflowList();
await n8n.canvasComposer.zoomInAndCheckNodes();
await n8n.canvasComposer.switchBetweenEditorAndHistory();
await n8n.canvasComposer.switchBetweenEditorAndWorkflowList();
});
});

View File

@@ -0,0 +1,81 @@
import { test, expect } from '../../fixtures/base';
import onboardingWorkflow from '../../workflows/Onboarding_workflow.json';
test.describe('Import workflow', () => {
test.describe('From URL', () => {
test.beforeEach(async ({ page }) => {
await page.route('**/rest/workflows/from-url*', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ data: onboardingWorkflow }),
});
});
});
test('should import workflow', async ({ n8n }) => {
await n8n.navigate.toWorkflow('new');
await n8n.page.waitForLoadState('load');
await n8n.canvas.clickWorkflowMenu();
await n8n.canvas.clickImportFromURL();
await expect(n8n.canvas.getImportURLInput()).toBeVisible();
await n8n.canvas.fillImportURLInput('https://fakepage.com/workflow.json');
await n8n.canvas.clickConfirmImportURL();
await n8n.canvas.clickZoomToFitButton();
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(4);
await expect(n8n.notifications.getErrorNotifications()).toHaveCount(0);
await expect(n8n.notifications.getSuccessNotifications()).toHaveCount(0);
});
test('clicking outside modal should not show error toast', async ({ n8n }) => {
await n8n.navigate.toWorkflow('new');
await n8n.page.waitForLoadState('load');
await n8n.canvas.clickWorkflowMenu();
await n8n.canvas.clickImportFromURL();
await n8n.canvas.clickOutsideModal();
await expect(n8n.notifications.getErrorNotifications()).toHaveCount(0);
});
test('canceling modal should not show error toast', async ({ n8n }) => {
await n8n.navigate.toWorkflow('new');
await n8n.page.waitForLoadState('load');
await n8n.canvas.clickWorkflowMenu();
await n8n.canvas.clickImportFromURL();
await n8n.canvas.clickCancelImportURL();
await expect(n8n.notifications.getErrorNotifications()).toHaveCount(0);
});
});
test.describe('From File', () => {
test('should import workflow', async ({ n8n }) => {
await n8n.navigate.toWorkflow('new');
await n8n.page.waitForLoadState('load');
await n8n.canvas.importWorkflow(
'Test_workflow-actions_paste-data.json',
'Import Test Workflow',
);
await n8n.page.waitForLoadState('load');
await n8n.canvas.clickZoomToFitButton();
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(5);
const connections = n8n.page.getByTestId('edge');
await expect(connections).toHaveCount(5);
});
});
});

View File

@@ -0,0 +1,45 @@
import { test, expect } from '../../fixtures/base';
test.describe('Manual partial execution', () => {
test('should not execute parent nodes with no run data', async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('manual-partial-execution.json');
await n8n.canvas.clickZoomToFitButton();
await n8n.canvas.openNode('Edit Fields');
await n8n.ndv.clickExecuteStep();
await n8n.ndv.close();
await n8n.canvas.openNode('Webhook1');
await expect(n8n.ndv.getNodeRunSuccessIndicator()).toBeHidden();
await expect(n8n.ndv.getNodeRunTooltipIndicator()).toBeHidden();
await expect(n8n.ndv.outputPanel.getRunSelector()).toBeHidden();
});
test.describe('partial execution v2', () => {
test('should execute from the first dirty node up to the current node', async ({ n8n }) => {
const nodeNames = ['A', 'B', 'C'];
await n8n.navigate.toWorkflow('new');
await n8n.partialExecutionComposer.enablePartialExecutionV2();
await n8n.start.fromImportedWorkflow('Test_workflow_partial_execution_v2.json');
await n8n.canvas.clickZoomToFitButton();
await n8n.partialExecutionComposer.executeFullWorkflowAndVerifySuccess(nodeNames);
const beforeText = await n8n.partialExecutionComposer.captureNodeOutputData('A');
await n8n.partialExecutionComposer.modifyNodeToTriggerStaleState('B');
await n8n.partialExecutionComposer.verifyNodeStatesAfterChange(['A', 'C'], ['B']);
await n8n.partialExecutionComposer.performPartialExecutionAndVerifySuccess('C', nodeNames);
await n8n.partialExecutionComposer.openNodeForDataVerification('A');
await expect(n8n.ndv.outputPanel.getTbodyCell(0, 0)).toHaveText(beforeText);
});
});
});

View File

@@ -135,7 +135,7 @@ test.describe('Security Notifications', () => {
await expect(notification).toContainText('More info');
// Verify warning styling
await expect(notification.locator('.el-notification--warning')).toBeVisible();
await expect(n8n.notifications.getWarningNotifications()).toBeVisible();
// Close the notification
await n8n.notifications.closeNotificationByText('Critical update available');

View File

@@ -0,0 +1,913 @@
{
"name": "Lots of nodes",
"nodes": [
{
"parameters": {},
"id": "369fe424-dd3b-4399-9de3-50bd4ce1f75b",
"name": "When clicking Execute workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [860, 740]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "dce967a7-8c5e-43cc-ba2b-e0fb0c9cf14c",
"name": "Code",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1080, 740]
},
{
"parameters": {
"options": {}
},
"id": "df7a719e-b25a-43e3-b941-7091a7d9a1a8",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [1300, 740]
},
{
"parameters": {},
"id": "32968b79-6a8b-43ed-b884-eb906b597661",
"name": "IF",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [1520, 740]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "e9a72745-6dbb-4be1-b286-aaa679b95e36",
"name": "Code1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1820, 80]
},
{
"parameters": {
"options": {}
},
"id": "f831d21b-c3a9-4bd8-9fc3-6daef12bd43f",
"name": "Edit Fields1",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [2040, 80]
},
{
"parameters": {},
"id": "6e6b2a4f-9e61-4245-8502-ca01e851fcbe",
"name": "IF1",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [2260, 80]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "535b9786-ead9-44f9-bff2-ef2e019a4cf9",
"name": "Code3",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [2560, -260]
},
{
"parameters": {
"options": {}
},
"id": "6a181d75-f2f2-4ad1-be3c-81ebe077ccc8",
"name": "Edit Fields3",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [2780, -260]
},
{
"parameters": {},
"id": "4b45828e-4e2b-4046-b9ae-24b373a81863",
"name": "IF7",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3000, -260]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "059534cb-820c-4fb7-933c-eeed2ae74f1c",
"name": "Code7",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [3260, -400]
},
{
"parameters": {
"options": {}
},
"id": "4f5c0d94-b69d-4ad3-aa8f-f1dd5824ec4a",
"name": "Edit Fields7",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [3480, -400]
},
{
"parameters": {},
"id": "cd74f840-7b0f-425d-8ecd-e247a7d8abf5",
"name": "IF8",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3700, -400]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "3c97fd14-9c23-45e2-a1ac-934d743e9a01",
"name": "Code8",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [3260, -80]
},
{
"parameters": {
"options": {}
},
"id": "9e7bd7e9-5142-4751-b132-735d27007d82",
"name": "Edit Fields8",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [3480, -80]
},
{
"parameters": {},
"id": "8d3968b6-16d4-4e03-9026-eeaf70b17805",
"name": "IF9",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3700, -80]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "141edef3-ea0f-4e90-9b6a-09f5d5551195",
"name": "Code4",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [2560, 440]
},
{
"parameters": {
"options": {}
},
"id": "b5b93cd7-9448-4290-91b7-c3c8429925fd",
"name": "Edit Fields4",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [2780, 440]
},
{
"parameters": {},
"id": "79d2c11c-0378-4ff5-b166-ae1bf773f53a",
"name": "IF14",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3000, 440]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "8483e962-24e7-4495-9c8e-481481ebe897",
"name": "Code13",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [3260, 300]
},
{
"parameters": {
"options": {}
},
"id": "74dfb8f9-6d14-493e-97d5-729e1f44856b",
"name": "Edit Fields13",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [3480, 300]
},
{
"parameters": {},
"id": "0c2e8e54-958d-4932-91b5-b23979460c97",
"name": "IF15",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3700, 300]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "bfed29c6-c453-4850-8acf-7aa11b1d0d8e",
"name": "Code14",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [3260, 620]
},
{
"parameters": {
"options": {}
},
"id": "d8415057-c597-40a9-95f6-bafbe3fafac0",
"name": "Edit Fields14",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [3480, 620]
},
{
"parameters": {},
"id": "51ed9040-bb6c-4f77-9740-74b54ac56a00",
"name": "IF16",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3700, 620]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "5864e701-eb16-4412-ae8b-be1f2a1f16a5",
"name": "Code2",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1820, 1480]
},
{
"parameters": {
"options": {}
},
"id": "4b7de291-f1c7-4ae8-a545-81aaa2ebd1fb",
"name": "Edit Fields2",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [2040, 1480]
},
{
"parameters": {},
"id": "328aa16f-82ed-465e-b548-9436f21eb519",
"name": "IF2",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [2260, 1480]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "90aaf0b0-57b6-4a08-b000-abb2956ba640",
"name": "Code5",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [2560, 1140]
},
{
"parameters": {
"options": {}
},
"id": "7d327c87-da3b-4f4b-9f9a-51c9c622990d",
"name": "Edit Fields5",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [2780, 1140]
},
{
"parameters": {},
"id": "fa2a3b1b-53de-454e-a16d-e2bf62cb05ec",
"name": "IF21",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3000, 1140]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "8efaa5a3-982e-41b4-af6e-28e35c64093d",
"name": "Code19",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [3260, 1000]
},
{
"parameters": {
"options": {}
},
"id": "987e27fd-778a-4562-85a9-369b1ec232de",
"name": "Edit Fields19",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [3480, 1000]
},
{
"parameters": {},
"id": "b3f4e9b3-9995-4019-9b0f-dadd64e036b4",
"name": "IF22",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3700, 1000]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "681c1b30-063d-4c1e-b550-942a9dd3eb9a",
"name": "Code20",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [3260, 1320]
},
{
"parameters": {
"options": {}
},
"id": "024770b6-7bf4-44f6-9675-d4f7dc73d6ac",
"name": "Edit Fields20",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [3480, 1320]
},
{
"parameters": {},
"id": "24699015-3ccf-4ffa-b52f-8ba4c4853963",
"name": "IF23",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3700, 1320]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "f4b2d116-2fda-4a3a-9509-0e8c64e7796e",
"name": "Code6",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [2560, 1840]
},
{
"parameters": {
"options": {}
},
"id": "535e5b12-6743-4c01-9fc5-e27b10421423",
"name": "Edit Fields6",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [2780, 1840]
},
{
"parameters": {},
"id": "3dcbecdf-686b-445f-9c77-2902d0dc1f56",
"name": "IF28",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3000, 1840]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "7223c6ef-664b-426a-8d08-eca1b34e6b23",
"name": "Code25",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [3260, 1700]
},
{
"parameters": {
"options": {}
},
"id": "496414a6-384a-4f94-97ec-d2e5ad646f82",
"name": "Edit Fields25",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [3480, 1700]
},
{
"parameters": {},
"id": "82f9562d-e4a8-49f3-924d-983effb4b6c6",
"name": "IF29",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3700, 1700]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "c91d4bc5-3c60-4c22-aa31-44e84e0816ec",
"name": "Code26",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [3260, 2020]
},
{
"parameters": {
"options": {}
},
"id": "49b61f23-bf3f-474d-8bba-a3b7de6f6441",
"name": "Edit Fields26",
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [3480, 2020]
},
{
"parameters": {},
"id": "1cad6ae3-1064-4f30-a9ec-502891868332",
"name": "IF30",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [3700, 2020]
}
],
"pinData": {},
"connections": {
"When clicking Execute workflow": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "IF",
"type": "main",
"index": 0
}
]
]
},
"IF": {
"main": [
[
{
"node": "Code1",
"type": "main",
"index": 0
}
],
[
{
"node": "Code2",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "Edit Fields1",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields1": {
"main": [
[
{
"node": "IF1",
"type": "main",
"index": 0
}
]
]
},
"Code3": {
"main": [
[
{
"node": "Edit Fields3",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields3": {
"main": [
[
{
"node": "IF7",
"type": "main",
"index": 0
}
]
]
},
"IF1": {
"main": [
[
{
"node": "Code3",
"type": "main",
"index": 0
}
],
[
{
"node": "Code4",
"type": "main",
"index": 0
}
]
]
},
"IF7": {
"main": [
[
{
"node": "Code7",
"type": "main",
"index": 0
}
],
[
{
"node": "Code8",
"type": "main",
"index": 0
}
]
]
},
"Code7": {
"main": [
[
{
"node": "Edit Fields7",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields7": {
"main": [
[
{
"node": "IF8",
"type": "main",
"index": 0
}
]
]
},
"Code8": {
"main": [
[
{
"node": "Edit Fields8",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields8": {
"main": [
[
{
"node": "IF9",
"type": "main",
"index": 0
}
]
]
},
"Code4": {
"main": [
[
{
"node": "Edit Fields4",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields4": {
"main": [
[
{
"node": "IF14",
"type": "main",
"index": 0
}
]
]
},
"IF14": {
"main": [
[
{
"node": "Code13",
"type": "main",
"index": 0
}
],
[
{
"node": "Code14",
"type": "main",
"index": 0
}
]
]
},
"Code13": {
"main": [
[
{
"node": "Edit Fields13",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields13": {
"main": [
[
{
"node": "IF15",
"type": "main",
"index": 0
}
]
]
},
"Code14": {
"main": [
[
{
"node": "Edit Fields14",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields14": {
"main": [
[
{
"node": "IF16",
"type": "main",
"index": 0
}
]
]
},
"Code2": {
"main": [
[
{
"node": "Edit Fields2",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields2": {
"main": [
[
{
"node": "IF2",
"type": "main",
"index": 0
}
]
]
},
"IF2": {
"main": [
[
{
"node": "Code5",
"type": "main",
"index": 0
}
],
[
{
"node": "Code6",
"type": "main",
"index": 0
}
]
]
},
"Code5": {
"main": [
[
{
"node": "Edit Fields5",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields5": {
"main": [
[
{
"node": "IF21",
"type": "main",
"index": 0
}
]
]
},
"IF21": {
"main": [
[
{
"node": "Code19",
"type": "main",
"index": 0
}
],
[
{
"node": "Code20",
"type": "main",
"index": 0
}
]
]
},
"Code19": {
"main": [
[
{
"node": "Edit Fields19",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields19": {
"main": [
[
{
"node": "IF22",
"type": "main",
"index": 0
}
]
]
},
"Code20": {
"main": [
[
{
"node": "Edit Fields20",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields20": {
"main": [
[
{
"node": "IF23",
"type": "main",
"index": 0
}
]
]
},
"Code6": {
"main": [
[
{
"node": "Edit Fields6",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields6": {
"main": [
[
{
"node": "IF28",
"type": "main",
"index": 0
}
]
]
},
"IF28": {
"main": [
[
{
"node": "Code25",
"type": "main",
"index": 0
}
],
[
{
"node": "Code26",
"type": "main",
"index": 0
}
]
]
},
"Code25": {
"main": [
[
{
"node": "Edit Fields25",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields25": {
"main": [
[
{
"node": "IF29",
"type": "main",
"index": 0
}
]
]
},
"Code26": {
"main": [
[
{
"node": "Edit Fields26",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields26": {
"main": [
[
{
"node": "IF30",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "d38289e0-49d3-4e1d-8e4b-46e4eb85a2c9",
"id": "iKlx4AGIjCNJSu9M",
"meta": {
"instanceId": "8a47b83b4479b11330fdf21ccc96d4a8117035a968612e452b4c87bfd09c16c7"
},
"tags": []
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
{
"nodes": [
{
"parameters": {
"rule": {
"interval": [{}]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [0, 0],
"id": "dcc1c5e1-c6c1-45f8-80d5-65c88d66d56e",
"name": "A"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "3d8f0810-84f0-41ce-a81b-0e7f04fd88cb",
"name": "",
"value": "",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [220, 0],
"id": "097ffa30-d37b-4de6-bd5c-ccd945f31df1",
"name": "B"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [440, 0],
"id": "dc44e635-916f-4f76-a745-1add5762f730",
"name": "C"
}
],
"connections": {
"A": {
"main": [
[
{
"node": "B",
"type": "main",
"index": 0
}
]
]
},
"B": {
"main": [
[
{
"node": "C",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"meta": {
"instanceId": "b0d9447cff9c96796e4ac4f00fcd899b03cfac3ab3d4f748ae686d34881eae0c"
}
}

View File

@@ -0,0 +1,95 @@
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"parameters": {
"options": {}
},
"id": "f4467143-fdb9-46fa-8020-6417cc5eea7d",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [1140, 260]
},
{
"parameters": {
"path": "30ff316d-405f-4288-a0ac-e713546c9d4e",
"options": {}
},
"id": "4760aafb-5d56-4633-99d3-7a97c576a216",
"name": "Webhook1",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [680, 340],
"webhookId": "30ff316d-405f-4288-a0ac-e713546c9d4e"
},
{
"parameters": {
"articleId": "123",
"additionalFields": {}
},
"id": "8c811eca-8978-44d9-b8f7-ef2c7725784c",
"name": "Hacker News",
"type": "n8n-nodes-base.hackerNews",
"typeVersion": 1,
"position": [920, 260]
},
{
"parameters": {
"path": "4a3398e4-1388-4e10-9d21-add90b804955",
"options": {}
},
"id": "1c2c2d06-45c9-4712-9fa0-c655bef8d0e5",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [680, 180],
"webhookId": "4a3398e4-1388-4e10-9d21-add90b804955"
}
],
"connections": {
"Webhook1": {
"main": [
[
{
"node": "Hacker News",
"type": "main",
"index": 0
}
]
]
},
"Hacker News": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Hacker News",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"Webhook": [
{
"name": "First item",
"code": 1
}
]
}
}