test: Migrate workflow executions tests from cypress to playwright (#20363)

This commit is contained in:
Artem Sorokin
2025-10-03 19:19:51 +02:00
committed by GitHub
parent 21abe2d557
commit bc7bd9a325
8 changed files with 459 additions and 315 deletions
@@ -1,315 +0,0 @@
import type { RouteHandler } from 'cypress/types/net-stubbing';
import executionOutOfMemoryServerResponse from '../../fixtures/responses/execution-out-of-memory-server-response.json';
import { WorkflowPage } from '../../pages';
import { WorkflowExecutionsTab } from '../../pages/workflow-executions-tab';
import { getVisibleSelect } from '../../utils';
const workflowPage = new WorkflowPage();
const executionsTab = new WorkflowExecutionsTab();
const executionsRefreshInterval = 4000;
// Test suite for executions tab
describe('Workflow Executions', () => {
describe('when workflow is saved', () => {
beforeEach(() => {
workflowPage.actions.visit();
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json');
});
it('should render executions tab correctly', () => {
createMockExecutions();
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
executionsTab.actions.switchToExecutionsTab();
cy.wait(['@getExecutions']);
executionsTab.getters.executionsList().scrollTo(0, 500).wait(0);
executionsTab.getters.executionListItems().should('have.length', 30);
executionsTab.getters.successfulExecutionListItems().should('have.length', 28);
executionsTab.getters.failedExecutionListItems().should('have.length', 2);
executionsTab.getters
.executionListItems()
.first()
.invoke('attr', 'class')
.should('match', /_active_/);
});
it('should not redirect back to execution tab when request is not done before leaving the page', () => {
cy.intercept('GET', '/rest/executions?filter=*');
cy.intercept('GET', '/rest/executions/active?filter=*');
executionsTab.actions.switchToExecutionsTab();
executionsTab.actions.switchToEditorTab();
cy.wait(executionsRefreshInterval);
cy.url().should('not.include', '/executions');
executionsTab.actions.switchToExecutionsTab();
executionsTab.actions.switchToEditorTab();
executionsTab.actions.switchToExecutionsTab();
executionsTab.actions.switchToEditorTab();
executionsTab.actions.switchToExecutionsTab();
executionsTab.actions.switchToEditorTab();
cy.wait(executionsRefreshInterval);
cy.url().should('not.include', '/executions');
executionsTab.actions.switchToExecutionsTab();
cy.wait(1000);
executionsTab.actions.switchToEditorTab();
cy.wait(executionsRefreshInterval);
cy.url().should('not.include', '/executions');
});
it('should not redirect back to execution tab when slow request is not done before leaving the page', () => {
const throttleResponse: RouteHandler = async (req) => {
return await new Promise((resolve) => {
setTimeout(() => resolve(req.continue()), 2000);
});
};
cy.intercept('GET', '/rest/executions?filter=*', throttleResponse);
cy.intercept('GET', '/rest/executions/active?filter=*', throttleResponse);
executionsTab.actions.switchToExecutionsTab();
executionsTab.actions.switchToEditorTab();
cy.wait(executionsRefreshInterval);
cy.url().should('not.include', '/executions');
});
it('should error toast when server error message returned without stack trace', () => {
executionsTab.actions.createManualExecutions(1);
const message = 'Workflow did not finish, possible out-of-memory issue';
cy.intercept('GET', '/rest/executions/*', {
statusCode: 200,
body: executionOutOfMemoryServerResponse,
}).as('getExecution');
executionsTab.actions.switchToExecutionsTab();
cy.wait(['@getExecution']);
executionsTab.getters
.workflowExecutionPreviewIframe()
.should('be.visible')
.its('0.contentDocument.body') // Access the body of the iframe document
.should('not.be.empty') // Ensure the body is not empty
.then(cy.wrap)
.find('.el-notification:has(.el-notification--error)')
.should('be.visible')
.filter(`:contains("${message}")`)
.should('be.visible');
});
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
it.skip('should show workflow data in executions tab after hard reload and modify name and tags', () => {
executionsTab.actions.switchToExecutionsTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 2);
workflowPage.getters.workflowTags().click();
getVisibleSelect().find('li:contains("Manage tags")').click();
cy.get('button:contains("Add new")').click();
cy.getByTestId('tags-table').find('input').type('nutag').type('{enter}');
cy.get('button:contains("Done")').click();
cy.reload();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.workflowTags().click();
workflowPage.getters.tagsInDropdown().first().should('have.text', 'nutag').click();
workflowPage.getters.tagPills().should('have.length', 3);
let newWorkflowName = 'Renamed workflow';
workflowPage.actions.renameWorkflow(newWorkflowName);
workflowPage.getters.isWorkflowSaved();
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
executionsTab.actions.switchToEditorTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 3);
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
executionsTab.actions.switchToExecutionsTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 3);
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
executionsTab.actions.switchToEditorTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 3);
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
newWorkflowName = 'New workflow';
workflowPage.actions.renameWorkflow(newWorkflowName);
workflowPage.getters.isWorkflowSaved();
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
workflowPage.getters.workflowTags().click();
workflowPage.getters.tagsDropdown().find('.el-tag__close').first().click();
cy.get('body').click(0, 0);
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 2);
executionsTab.actions.switchToExecutionsTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 2);
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
executionsTab.actions.switchToEditorTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 2);
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
});
// This should be a component test. Abstracting this away into to ensure our lists work.
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
it.skip('should load items and auto scroll after filter change', () => {
createMockExecutions();
createMockExecutions();
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
executionsTab.actions.switchToExecutionsTab();
cy.wait(['@getExecutions']);
executionsTab.getters.executionsList().scrollTo(0, 500).wait(0);
executionsTab.getters.executionListItems().eq(10).click();
cy.getByTestId('executions-filter-button').click();
cy.getByTestId('executions-filter-status-select').should('be.visible').click();
getVisibleSelect().find('li:contains("Error")').click();
executionsTab.getters.executionListItems().should('have.length', 5);
executionsTab.getters.successfulExecutionListItems().should('have.length', 1);
executionsTab.getters.failedExecutionListItems().should('have.length', 4);
cy.getByTestId('executions-filter-button').click();
cy.getByTestId('executions-filter-status-select').should('be.visible').click();
getVisibleSelect().find('li:contains("Success")').click();
// check if the list is scrolled
executionsTab.getters.executionListItems().eq(10).should('be.visible');
executionsTab.getters.executionsList().then(($el) => {
const { scrollTop, scrollHeight, clientHeight } = $el[0];
expect(scrollTop).to.be.greaterThan(0);
expect(scrollTop + clientHeight).to.be.lessThan(scrollHeight);
// scroll to the bottom
$el[0].scrollTo(0, scrollHeight);
executionsTab.getters.executionListItems().should('have.length', 18);
executionsTab.getters.successfulExecutionListItems().should('have.length', 18);
executionsTab.getters.failedExecutionListItems().should('have.length', 0);
});
cy.getByTestId('executions-filter-button').click();
cy.getByTestId('executions-filter-reset-button').should('be.visible').click();
executionsTab.getters.executionListItems().eq(11).should('be.visible');
});
it('should redirect back to editor after seeing a couple of execution using browser back button', () => {
createMockExecutions();
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
executionsTab.actions.switchToExecutionsTab();
cy.wait(['@getExecutions']);
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
executionsTab.getters.executionListItems().eq(2).click();
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
executionsTab.getters.executionListItems().eq(4).click();
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
executionsTab.getters.executionListItems().eq(6).click();
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
cy.go('back');
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
cy.go('back');
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
cy.go('back');
executionsTab.getters.workflowExecutionPreviewIframe().should('exist');
cy.go('back');
cy.url().should('not.include', '/executions');
cy.url().should('include', '/workflow/');
workflowPage.getters.nodeViewRoot().should('be.visible');
});
});
describe('when new workflow is not saved', () => {
beforeEach(() => {
workflowPage.actions.visit();
});
it('should open executions tab', () => {
executionsTab.actions.switchToExecutionsTab();
executionsTab.getters.executionsSidebar().should('be.visible');
executionsTab.getters.executionsEmptyList().should('be.visible');
cy.getByTestId('workflow-execution-no-trigger-content').should('be.visible');
cy.get('button:contains("Add first step")').should('be.visible').click();
cy.getByTestId('node-creator-item-name')
.should('be.visible')
.filter(':contains("Trigger manually")')
.click();
executionsTab.actions.switchToExecutionsTab();
executionsTab.getters.executionsSidebar().should('be.visible');
executionsTab.getters.executionsEmptyList().should('be.visible');
cy.getByTestId('workflow-execution-no-content').should('be.visible');
workflowPage.getters.saveButton().find('button').should('be.enabled').click();
workflowPage.getters.isWorkflowSaved();
workflowPage.getters.nodeViewRoot().should('be.visible');
});
});
});
const createMockExecutions = () => {
executionsTab.actions.createManualExecutions(15);
// This wait is added to allow time for the notifications to expire
cy.wait(2000);
// Make some failed executions by enabling Code node with syntax error
executionsTab.actions.toggleNodeEnabled('Error');
workflowPage.getters.disabledNodes().should('have.length', 0);
executionsTab.actions.createManualExecutions(2);
// This wait is added to allow time for the notifications to expire
cy.wait(2000);
// Then add some more successful ones
executionsTab.actions.toggleNodeEnabled('Error');
workflowPage.getters.disabledNodes().should('have.length', 1);
executionsTab.actions.createManualExecutions(15);
};
const checkMainHeaderELements = () => {
workflowPage.getters.workflowNameInputContainer().should('be.visible');
workflowPage.getters.workflowTagsContainer().should('be.visible');
workflowPage.getters.workflowMenu().should('be.visible');
workflowPage.getters.saveButton().should('be.visible');
};
@@ -0,0 +1,32 @@
import type { n8nPage } from '../pages/n8nPage';
/**
* A class for user interactions with workflow executions that go across multiple pages.
*/
export class ExecutionsComposer {
constructor(private readonly n8n: n8nPage) {}
/**
* Creates workflow executions by executing the workflow multiple times.
* Waits for each execution to complete (by waiting for the POST /rest/workflows/:id/run response)
* before starting the next one.
*
* @param count - Number of executions to create
* @example
* // Create 10 executions
* await n8n.executionsComposer.createExecutions(10);
*/
async createExecutions(count: number): Promise<void> {
for (let i = 0; i < count; i++) {
const responsePromise = this.n8n.page.waitForResponse(
(response) =>
response.url().includes('/rest/workflows/') &&
response.url().includes('/run') &&
response.request().method() === 'POST',
);
await this.n8n.canvas.clickExecuteWorkflowButton();
await responsePromise;
}
}
}
File diff suppressed because one or more lines are too long
@@ -660,6 +660,15 @@ export class CanvasPage extends BasePage {
.click();
}
/**
* Toggle node enabled/disabled state using keyboard shortcut
* @param nodeName - The name of the node to toggle
*/
async toggleNodeEnabled(nodeName: string): Promise<void> {
await this.nodeByName(nodeName).click();
await this.page.keyboard.press('d');
}
// Chat open/close buttons (manual chat)
async clickManualChatButton(): Promise<void> {
await this.page.getByTestId('workflow-chat-button').click();
@@ -43,4 +43,42 @@ export class ExecutionsPage extends BasePage {
async handlePinnedNodesConfirmation(action: 'Unpin' | 'Cancel'): Promise<void> {
await this.page.getByRole('button', { name: action }).click();
}
getExecutionsList(): Locator {
return this.page.getByTestId('current-executions-list');
}
getExecutionsSidebar(): Locator {
return this.page.getByTestId('executions-sidebar');
}
getExecutionsEmptyList(): Locator {
return this.page.getByTestId('execution-list-empty');
}
getSuccessfulExecutionItems(): Locator {
return this.page.locator('[data-test-execution-status="success"]');
}
getFailedExecutionItems(): Locator {
return this.page.locator('[data-test-execution-status="error"]');
}
/**
* Scroll the executions list to the bottom to trigger lazy loading
*/
async scrollExecutionsListToBottom(): Promise<void> {
await this.getExecutionsList().evaluate((el) => el.scrollTo(0, el.scrollHeight));
}
/**
* Get error notifications in the preview iframe
*/
getErrorNotificationsInPreview(): Locator {
return this.getPreviewIframe().locator('.el-notification:has(.el-notification--error)');
}
getFirstExecutionItem(): Locator {
return this.getExecutionItems().first();
}
}
@@ -31,6 +31,7 @@ import { WorkflowSharingModal } from './WorkflowSharingModal';
import { WorkflowsPage } from './WorkflowsPage';
import { CanvasComposer } from '../composables/CanvasComposer';
import { CredentialsComposer } from '../composables/CredentialsComposer';
import { ExecutionsComposer } from '../composables/ExecutionsComposer';
import { MfaComposer } from '../composables/MfaComposer';
import { NodeDetailsViewComposer } from '../composables/NodeDetailsViewComposer';
import { PartialExecutionComposer } from '../composables/PartialExecutionComposer';
@@ -88,6 +89,7 @@ export class n8nPage {
readonly projectComposer: ProjectComposer;
readonly canvasComposer: CanvasComposer;
readonly credentialsComposer: CredentialsComposer;
readonly executionsComposer: ExecutionsComposer;
readonly mfaComposer: MfaComposer;
readonly partialExecutionComposer: PartialExecutionComposer;
readonly ndvComposer: NodeDetailsViewComposer;
@@ -141,6 +143,7 @@ export class n8nPage {
this.projectComposer = new ProjectComposer(this);
this.canvasComposer = new CanvasComposer(this);
this.credentialsComposer = new CredentialsComposer(this);
this.executionsComposer = new ExecutionsComposer(this);
this.mfaComposer = new MfaComposer(this);
this.partialExecutionComposer = new PartialExecutionComposer(this);
this.ndvComposer = new NodeDetailsViewComposer(this);
@@ -0,0 +1,228 @@
import { test, expect } from '../../fixtures/base';
import executionOutOfMemoryResponse from '../../fixtures/execution-out-of-memory-server-response.json';
const ERROR_MESSAGES = {
OUT_OF_MEMORY: 'Workflow did not finish, possible out-of-memory issue',
} as const;
const TIMEOUTS = {
EXECUTIONS_REFRESH_INTERVAL: 4000,
} as const;
test.describe('Workflow Executions', () => {
test.describe('when workflow is saved', () => {
test.beforeEach(async ({ n8n }) => {
await n8n.start.fromImportedWorkflow('Test_workflow_4_executions_view.json');
});
test('should render executions tab correctly', async ({ n8n }) => {
await n8n.executionsComposer.createExecutions(15);
await n8n.canvas.toggleNodeEnabled('Error');
await expect(n8n.canvas.disabledNodes()).toHaveCount(0);
await n8n.executionsComposer.createExecutions(2);
await n8n.canvas.toggleNodeEnabled('Error');
await expect(n8n.canvas.disabledNodes()).toHaveCount(1);
await n8n.executionsComposer.createExecutions(15);
const executionsResponsePromise = n8n.page.waitForResponse((response) =>
response.url().includes('/rest/executions?filter='),
);
await n8n.canvas.clickExecutionsTab();
await executionsResponsePromise;
await expect(n8n.executions.getExecutionItems().first()).toBeVisible();
const loadMoreResponsePromise = n8n.page.waitForResponse((response) =>
response.url().includes('/rest/executions?filter='),
);
await n8n.executions.scrollExecutionsListToBottom();
await loadMoreResponsePromise;
await expect(n8n.executions.getExecutionItems()).toHaveCount(30, { timeout: 15000 });
await expect(n8n.executions.getSuccessfulExecutionItems()).toHaveCount(28);
await expect(n8n.executions.getFailedExecutionItems()).toHaveCount(2);
await expect(n8n.executions.getFirstExecutionItem()).toHaveClass(/_active_/);
});
test('should not redirect back to execution tab when request is not done before leaving the page', async ({
n8n,
}) => {
await n8n.page.route('**/rest/executions?filter=*', (route) => route.continue());
await n8n.page.route('**/rest/executions/active?filter=*', (route) => route.continue());
await n8n.canvas.clickExecutionsTab();
await n8n.canvas.clickEditorTab();
await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
});
for (let i = 0; i < 3; i++) {
await n8n.canvas.clickExecutionsTab();
await n8n.canvas.clickEditorTab();
}
await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
});
await n8n.canvas.clickExecutionsTab();
// eslint-disable-next-line playwright/no-wait-for-timeout
await n8n.page.waitForTimeout(1000);
await n8n.canvas.clickEditorTab();
await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
});
});
test('should not redirect back to execution tab when slow request is not done before leaving the page', async ({
n8n,
}) => {
await n8n.page.route('**/rest/executions?filter=*', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 2000));
await route.continue();
});
await n8n.page.route('**/rest/executions/active?filter=*', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 2000));
await route.continue();
});
await n8n.canvas.clickExecutionsTab();
await n8n.page.waitForURL(/\/executions/);
await n8n.canvas.clickEditorTab();
await n8n.page.waitForURL(/\/workflow\/[^/]+$/);
await expect(n8n.page).toHaveURL(/\/workflow\/[^/]+$/, {
timeout: TIMEOUTS.EXECUTIONS_REFRESH_INTERVAL,
});
});
test('should error toast when server error message returned without stack trace', async ({
n8n,
}) => {
const responsePromise = n8n.page.waitForResponse(
(response) =>
response.url().includes('/rest/workflows/') &&
response.url().includes('/run') &&
response.request().method() === 'POST',
);
await n8n.canvas.clickExecuteWorkflowButton();
await responsePromise;
await n8n.page.route('**/rest/executions/*', async (route) => {
if (route.request().url().includes('?filter=')) {
await route.continue();
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(executionOutOfMemoryResponse),
});
});
const executionDetailPromise = n8n.page.waitForResponse(
(response) =>
response.url().includes('/rest/executions/') && !response.url().includes('?filter='),
);
await n8n.canvas.clickExecutionsTab();
await executionDetailPromise;
const iframe = n8n.executions.getPreviewIframe();
await expect(iframe.locator('body')).not.toBeEmpty();
await n8n.executions.getErrorNotificationsInPreview().first().waitFor({ timeout: 5000 });
const errorNotification = n8n.executions
.getErrorNotificationsInPreview()
.filter({ hasText: ERROR_MESSAGES.OUT_OF_MEMORY });
await expect(errorNotification).toBeVisible();
});
// eslint-disable-next-line playwright/no-skipped-test
test.skip('should show workflow data in executions tab after hard reload and modify name and tags', async () => {
// TODO: Migrate from Cypress
});
// eslint-disable-next-line playwright/no-skipped-test
test.skip('should load items and auto scroll after filter change', async () => {
// TODO: This should be a component test
});
test('should redirect back to editor after seeing a couple of execution using browser back button', async ({
n8n,
}) => {
await n8n.executionsComposer.createExecutions(15);
await n8n.canvas.toggleNodeEnabled('Error');
await expect(n8n.canvas.disabledNodes()).toHaveCount(0);
await n8n.executionsComposer.createExecutions(2);
await n8n.canvas.toggleNodeEnabled('Error');
await expect(n8n.canvas.disabledNodes()).toHaveCount(1);
await n8n.executionsComposer.createExecutions(15);
const executionsResponsePromise = n8n.page.waitForResponse((response) =>
response.url().includes('/rest/executions?filter='),
);
await n8n.canvas.clickExecutionsTab();
await executionsResponsePromise;
const iframe = n8n.executions.getPreviewIframe();
await expect(iframe.locator('body')).toBeAttached();
await n8n.executions.getExecutionItems().nth(2).click();
await expect(iframe.locator('body')).toBeAttached();
await n8n.executions.getExecutionItems().nth(4).click();
await expect(iframe.locator('body')).toBeAttached();
await n8n.executions.getExecutionItems().nth(6).click();
await expect(iframe.locator('body')).toBeAttached();
await n8n.page.goBack();
await expect(iframe.locator('body')).toBeAttached();
await n8n.page.goBack();
await expect(iframe.locator('body')).toBeAttached();
await n8n.page.goBack();
await expect(iframe.locator('body')).toBeAttached();
await n8n.page.goBack();
await expect(n8n.page).not.toHaveURL(/\/executions/);
await expect(n8n.page).toHaveURL(/\/workflow\//);
await expect(n8n.canvas.canvasPane()).toBeVisible();
});
});
test.describe('when new workflow is not saved', () => {
test.beforeEach(async ({ n8n }) => {
await n8n.start.fromBlankCanvas();
});
test('should open executions tab', async ({ n8n }) => {
await n8n.canvas.clickExecutionsTab();
await expect(n8n.executions.getExecutionsSidebar()).toBeVisible();
await expect(n8n.executions.getExecutionsEmptyList()).toBeVisible();
await expect(n8n.page.getByTestId('workflow-execution-no-trigger-content')).toBeVisible();
await n8n.page.getByRole('button', { name: 'Add first step' }).click();
await n8n.canvas.nodeCreatorItemByName('Trigger manually').click();
await n8n.canvas.clickExecutionsTab();
await expect(n8n.executions.getExecutionsSidebar()).toBeVisible();
await expect(n8n.executions.getExecutionsEmptyList()).toBeVisible();
await expect(n8n.page.getByTestId('workflow-execution-no-content')).toBeVisible();
await expect(n8n.canvas.saveWorkflowButton()).toBeEnabled();
await n8n.canvas.saveWorkflowButton().click();
await n8n.page.waitForURL(/\/workflow\/[^/]+$/);
await expect(n8n.canvas.canvasPane()).toBeVisible();
});
});
});
@@ -0,0 +1,74 @@
{
"meta": {
"instanceId": "6b85439d79c07750ea49eced4bc2a12b283cfcba0ab2917cd4f3fee36080e869"
},
"nodes": [
{
"parameters": {
"jsCode": "// Loop over input items and add a new field\n// called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n error\n}\n\nreturn $input.all();"
},
"id": "d0ab7e12-0e1b-4c08-8081-83107794f37d",
"name": "Error",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [680, 460],
"disabled": true
},
{
"parameters": {},
"id": "f5026145-66c1-463c-8ac8-46a1309a6632",
"name": "On clicking 'execute'",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [460, 460]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field\n// 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": "9926f884-348a-4af0-872e-dd7c8b3da811",
"name": "Code",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [900, 460]
}
],
"connections": {
"Error": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"On clicking 'execute'": {
"main": [
[
{
"node": "Error",
"type": "main",
"index": 0
}
]
]
}
},
"tags": [
{
"name": "some-tag-1",
"createdAt": "2022-11-10T13:43:34.001Z",
"updatedAt": "2022-11-10T13:43:34.001Z",
"id": "6"
},
{
"name": "some-tag-2",
"createdAt": "2022-11-10T13:43:39.778Z",
"updatedAt": "2022-11-10T13:43:39.778Z",
"id": "7"
}
]
}