diff --git a/cypress/e2e/10-undo-redo.cy.ts b/cypress/e2e/10-undo-redo.cy.ts
index 89d1bad696..3227f68323 100644
--- a/cypress/e2e/10-undo-redo.cy.ts
+++ b/cypress/e2e/10-undo-redo.cy.ts
@@ -47,28 +47,28 @@ describe('Undo/Redo', () => {
SET_NODE_NAME,
);
WorkflowPage.actions.zoomToFit();
- WorkflowPage.getters
- .canvasNodeByName('Code')
- .should('have.css', 'left', '860px')
- .should('have.css', 'top', '220px');
+ WorkflowPage.getters.canvasNodeByName('Code').then(($codeNode) => {
+ const cssLeft = parseInt($codeNode.css('left'));
+ const cssTop = parseInt($codeNode.css('top'));
- WorkflowPage.actions.hitUndo();
- WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
- WorkflowPage.getters.nodeConnections().should('have.length', 1);
- WorkflowPage.actions.hitUndo();
- WorkflowPage.getters.canvasNodes().should('have.have.length', 1);
- WorkflowPage.getters.nodeConnections().should('have.length', 0);
- WorkflowPage.actions.hitRedo();
- WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
- WorkflowPage.getters.nodeConnections().should('have.length', 1);
- WorkflowPage.actions.hitRedo();
- WorkflowPage.getters.canvasNodes().should('have.have.length', 3);
- WorkflowPage.getters.nodeConnections().should('have.length', 2);
- // Last node should be added back to original position
- WorkflowPage.getters
- .canvasNodeByName('Code')
- .should('have.css', 'left', '860px')
- .should('have.css', 'top', '220px');
+ WorkflowPage.actions.hitUndo();
+ WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
+ WorkflowPage.getters.nodeConnections().should('have.length', 1);
+ WorkflowPage.actions.hitUndo();
+ WorkflowPage.getters.canvasNodes().should('have.have.length', 1);
+ WorkflowPage.getters.nodeConnections().should('have.length', 0);
+ WorkflowPage.actions.hitRedo();
+ WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
+ WorkflowPage.getters.nodeConnections().should('have.length', 1);
+ WorkflowPage.actions.hitRedo();
+ WorkflowPage.getters.canvasNodes().should('have.have.length', 3);
+ WorkflowPage.getters.nodeConnections().should('have.length', 2);
+ // Last node should be added back to original position
+ WorkflowPage.getters
+ .canvasNodeByName('Code')
+ .should('have.css', 'left', cssLeft + 'px')
+ .should('have.css', 'top', cssTop + 'px');
+ });
});
it('should undo/redo deleting node using context menu', () => {
@@ -135,22 +135,30 @@ describe('Undo/Redo', () => {
it('should undo/redo moving nodes', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
- cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], { clickToFinish: true });
- WorkflowPage.getters
- .canvasNodeByName('Code')
- .should('have.css', 'left', '740px')
- .should('have.css', 'top', '320px');
+ WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).then(($node) => {
+ const initialPosition = $node.position();
+ cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], { clickToFinish: true });
- WorkflowPage.actions.hitUndo();
- WorkflowPage.getters
- .canvasNodeByName('Code')
- .should('have.css', 'left', '640px')
- .should('have.css', 'top', '220px');
- WorkflowPage.actions.hitRedo();
- WorkflowPage.getters
- .canvasNodeByName('Code')
- .should('have.css', 'left', '740px')
- .should('have.css', 'top', '320px');
+ WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).then(($node) => {
+ const cssLeft = parseInt($node.css('left'));
+ const cssTop = parseInt($node.css('top'));
+ expect(cssLeft).to.be.greaterThan(initialPosition.left);
+ expect(cssTop).to.be.greaterThan(initialPosition.top);
+ });
+
+ WorkflowPage.actions.hitUndo();
+ WorkflowPage.getters
+ .canvasNodeByName(CODE_NODE_NAME)
+ .should('have.css', 'left', `${initialPosition.left}px`)
+ .should('have.css', 'top', `${initialPosition.top}px`);
+ WorkflowPage.actions.hitRedo();
+ WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).then(($node) => {
+ const cssLeft = parseInt($node.css('left'));
+ const cssTop = parseInt($node.css('top'));
+ expect(cssLeft).to.be.greaterThan(initialPosition.left);
+ expect(cssTop).to.be.greaterThan(initialPosition.top);
+ });
+ });
});
it('should undo/redo deleting a connection using context menu', () => {
@@ -292,8 +300,12 @@ describe('Undo/Redo', () => {
WorkflowPage.getters
.canvasNodes()
.first()
- .should('have.css', 'left', `${initialPosition.left + 120}px`)
- .should('have.css', 'top', `${initialPosition.top + 140}px`);
+ .then(($node) => {
+ const cssLeft = parseInt($node.css('left'));
+ const cssTop = parseInt($node.css('top'));
+ expect(cssLeft).to.be.greaterThan(initialPosition.left);
+ expect(cssTop).to.be.greaterThan(initialPosition.top);
+ });
// Delete the set node
WorkflowPage.getters.canvasNodeByName(EDIT_FIELDS_SET_NODE_NAME).click().click();
@@ -322,8 +334,12 @@ describe('Undo/Redo', () => {
WorkflowPage.getters
.canvasNodes()
.first()
- .should('have.css', 'left', `${initialPosition.left + 120}px`)
- .should('have.css', 'top', `${initialPosition.top + 140}px`);
+ .then(($node) => {
+ const cssLeft = parseInt($node.css('left'));
+ const cssTop = parseInt($node.css('top'));
+ expect(cssLeft).to.be.greaterThan(initialPosition.left);
+ expect(cssTop).to.be.greaterThan(initialPosition.top);
+ });
// Third redo: Should delete the Set node
WorkflowPage.actions.hitRedo();
WorkflowPage.getters.canvasNodes().should('have.length', 3);
@@ -340,9 +356,6 @@ describe('Undo/Redo', () => {
WorkflowPage.getters.canvasNodes().should('have.length', 2);
WorkflowPage.getters.nodeConnections().should('have.length', 1);
cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')).should('have.length', 1);
- cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch'))
- .should('have.css', 'left', '637px')
- .should('have.css', 'top', '501px');
cy.fixture('Test_workflow_form_switch.json').then((data) => {
cy.get('body').paste(JSON.stringify(data));
@@ -355,9 +368,6 @@ describe('Undo/Redo', () => {
WorkflowPage.getters.canvasNodes().should('have.length', 2);
WorkflowPage.getters.nodeConnections().should('have.length', 1);
cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')).should('have.length', 1);
- cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch'))
- .should('have.css', 'left', '637px')
- .should('have.css', 'top', '501px');
});
it('should not undo/redo when NDV or a modal is open', () => {
diff --git a/cypress/e2e/11-inline-expression-editor.cy.ts b/cypress/e2e/11-inline-expression-editor.cy.ts
index a7fadd196f..143648ce1b 100644
--- a/cypress/e2e/11-inline-expression-editor.cy.ts
+++ b/cypress/e2e/11-inline-expression-editor.cy.ts
@@ -14,6 +14,7 @@ describe('Inline expression editor', () => {
describe('Static data', () => {
beforeEach(() => {
WorkflowPage.actions.addNodeToCanvas('Hacker News');
+ WorkflowPage.actions.zoomToFit();
WorkflowPage.actions.openNode('Hacker News');
WorkflowPage.actions.openInlineExpressionEditor();
});
@@ -75,6 +76,7 @@ describe('Inline expression editor', () => {
ndv.actions.close();
WorkflowPage.actions.addNodeToCanvas('No Operation');
WorkflowPage.actions.addNodeToCanvas('Hacker News');
+ WorkflowPage.actions.zoomToFit();
WorkflowPage.actions.openNode('Hacker News');
WorkflowPage.actions.openInlineExpressionEditor();
});
diff --git a/cypress/e2e/12-canvas-actions.cy.ts b/cypress/e2e/12-canvas-actions.cy.ts
index fafb1d9d79..9b05cb84d4 100644
--- a/cypress/e2e/12-canvas-actions.cy.ts
+++ b/cypress/e2e/12-canvas-actions.cy.ts
@@ -125,6 +125,8 @@ describe('Canvas Actions', () => {
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
WorkflowPage.actions.zoomToFit();
+ WorkflowPage.getters.canvasNodes().should('have.length', 3);
+ WorkflowPage.getters.nodeConnections().should('have.length', 2);
WorkflowPage.actions.addNodeBetweenNodes(
CODE_NODE_NAME,
EDIT_FIELDS_SET_NODE_NAME,
@@ -132,12 +134,15 @@ describe('Canvas Actions', () => {
);
WorkflowPage.getters.canvasNodes().should('have.length', 4);
WorkflowPage.getters.nodeConnections().should('have.length', 3);
- // And last node should be pushed to the right
- WorkflowPage.getters
- .canvasNodes()
- .last()
- .should('have.css', 'left', '860px')
- .should('have.css', 'top', '220px');
+
+ WorkflowPage.getters.canvasNodeByName(EDIT_FIELDS_SET_NODE_NAME).then(($editFieldsNode) => {
+ const editFieldsNodeLeft = parseFloat($editFieldsNode.css('left'));
+
+ WorkflowPage.getters.canvasNodeByName(HTTP_REQUEST_NODE_NAME).then(($httpNode) => {
+ const httpNodeLeft = parseFloat($httpNode.css('left'));
+ expect(httpNodeLeft).to.be.lessThan(editFieldsNodeLeft);
+ });
+ });
});
it('should delete connections by pressing the delete button', () => {
diff --git a/cypress/e2e/12-canvas.cy.ts b/cypress/e2e/12-canvas.cy.ts
index 79b6c200e1..325e509e79 100644
--- a/cypress/e2e/12-canvas.cy.ts
+++ b/cypress/e2e/12-canvas.cy.ts
@@ -69,7 +69,9 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
for (let i = 0; i < 2; i++) {
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true);
- WorkflowPage.getters.nodeViewBackground().click(600 + i * 100, 200, { force: true });
+ WorkflowPage.getters
+ .nodeViewBackground()
+ .click((i + 1) * 200, (i + 1) * 200, { force: true });
}
WorkflowPage.actions.zoomToFit();
@@ -197,13 +199,23 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.zoomToFit();
-
- cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], { clickToFinish: true });
WorkflowPage.getters
.canvasNodes()
.last()
- .should('have.css', 'left', '740px')
- .should('have.css', 'top', '320px');
+ .then(($node) => {
+ const { left, top } = $node.position();
+ cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], {
+ clickToFinish: true,
+ });
+ WorkflowPage.getters
+ .canvasNodes()
+ .last()
+ .then(($node) => {
+ const { left: newLeft, top: newTop } = $node.position();
+ expect(newLeft).to.be.greaterThan(left);
+ expect(newTop).to.be.greaterThan(top);
+ });
+ });
});
it('should zoom in', () => {
diff --git a/cypress/e2e/13-pinning.cy.ts b/cypress/e2e/13-pinning.cy.ts
index bbc3aa9cab..468e73276a 100644
--- a/cypress/e2e/13-pinning.cy.ts
+++ b/cypress/e2e/13-pinning.cy.ts
@@ -109,6 +109,8 @@ describe('Data pinning', () => {
.parent()
.should('have.class', 'is-disabled');
+ cy.get('body').type('{esc}');
+
// Unpin using context menu
workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME);
ndv.actions.setPinnedData([{ test: 1 }]);
diff --git a/cypress/e2e/14-mapping.cy.ts b/cypress/e2e/14-mapping.cy.ts
index 9845d61db9..0579bba24d 100644
--- a/cypress/e2e/14-mapping.cy.ts
+++ b/cypress/e2e/14-mapping.cy.ts
@@ -18,6 +18,8 @@ describe('Data mapping', () => {
cy.fixture('Test_workflow-actions_paste-data.json').then((data) => {
cy.get('body').paste(JSON.stringify(data));
});
+ workflowPage.actions.zoomToFit();
+
workflowPage.actions.openNode('Set');
ndv.actions.executePrevious();
ndv.actions.switchInputMode('Table');
@@ -49,6 +51,7 @@ describe('Data mapping', () => {
cy.fixture('Test_workflow_3.json').then((data) => {
cy.get('body').paste(JSON.stringify(data));
});
+ workflowPage.actions.zoomToFit();
workflowPage.actions.openNode('Set');
ndv.actions.switchInputMode('Table');
@@ -111,6 +114,7 @@ describe('Data mapping', () => {
cy.fixture('Test_workflow_3.json').then((data) => {
cy.get('body').paste(JSON.stringify(data));
});
+ workflowPage.actions.zoomToFit();
workflowPage.actions.openNode('Set');
ndv.actions.switchInputMode('JSON');
@@ -149,6 +153,7 @@ describe('Data mapping', () => {
cy.fixture('Test_workflow_3.json').then((data) => {
cy.get('body').paste(JSON.stringify(data));
});
+ workflowPage.actions.zoomToFit();
workflowPage.actions.openNode('Set');
ndv.actions.clearParameterInput('value');
@@ -255,6 +260,7 @@ describe('Data mapping', () => {
cy.fixture('Test_workflow_3.json').then((data) => {
cy.get('body').paste(JSON.stringify(data));
});
+ workflowPage.actions.zoomToFit();
workflowPage.actions.openNode('Set');
@@ -286,6 +292,7 @@ describe('Data mapping', () => {
cy.fixture('Test_workflow_3.json').then((data) => {
cy.get('body').paste(JSON.stringify(data));
});
+ workflowPage.actions.zoomToFit();
workflowPage.actions.openNode('Set');
ndv.actions.typeIntoParameterInput('value', 'test_value');
@@ -307,6 +314,7 @@ describe('Data mapping', () => {
cy.fixture('Test_workflow_3.json').then((data) => {
cy.get('body').paste(JSON.stringify(data));
});
+ workflowPage.actions.zoomToFit();
workflowPage.actions.openNode('Set');
ndv.actions.clearParameterInput('value');
diff --git a/cypress/e2e/19-execution.cy.ts b/cypress/e2e/19-execution.cy.ts
index a09529968e..804e81d4e6 100644
--- a/cypress/e2e/19-execution.cy.ts
+++ b/cypress/e2e/19-execution.cy.ts
@@ -1,6 +1,6 @@
import { NDV, WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages';
import { SCHEDULE_TRIGGER_NODE_NAME, EDIT_FIELDS_SET_NODE_NAME } from '../constants';
-import { errorToast, successToast } from '../pages/notifications';
+import { clearNotifications, errorToast, successToast } from '../pages/notifications';
const workflowPage = new WorkflowPageClass();
const executionsTab = new WorkflowExecutionsTab();
@@ -62,13 +62,13 @@ describe('Execution', () => {
.within(() => cy.get('.fa-check'))
.should('exist');
+ successToast().should('be.visible');
+ clearNotifications();
+
// Clear execution data
workflowPage.getters.clearExecutionDataButton().should('be.visible');
workflowPage.getters.clearExecutionDataButton().click();
workflowPage.getters.clearExecutionDataButton().should('not.exist');
-
- // Check success toast (works because Cypress waits enough for the element to show after the http request node has finished)
- successToast().should('be.visible');
});
it('should test manual workflow stop', () => {
@@ -106,6 +106,9 @@ describe('Execution', () => {
.canvasNodeByName('Set')
.within(() => cy.get('.fa-check').should('not.exist'));
+ successToast().should('be.visible');
+ clearNotifications();
+
workflowPage.getters.stopExecutionButton().should('exist');
workflowPage.getters.stopExecutionButton().click();
@@ -121,13 +124,13 @@ describe('Execution', () => {
.canvasNodeByName('Set')
.within(() => cy.get('.fa-check').should('not.exist'));
+ successToast().should('be.visible');
+ clearNotifications();
+
// Clear execution data
workflowPage.getters.clearExecutionDataButton().should('be.visible');
workflowPage.getters.clearExecutionDataButton().click();
workflowPage.getters.clearExecutionDataButton().should('not.exist');
-
- // Check success toast (works because Cypress waits enough for the element to show after the http request node has finished)
- successToast().should('be.visible');
});
it('should test webhook workflow', () => {
@@ -194,13 +197,13 @@ describe('Execution', () => {
.within(() => cy.get('.fa-check'))
.should('exist');
+ successToast().should('be.visible');
+ clearNotifications();
+
// Clear execution data
workflowPage.getters.clearExecutionDataButton().should('be.visible');
workflowPage.getters.clearExecutionDataButton().click();
workflowPage.getters.clearExecutionDataButton().should('not.exist');
-
- // Check success toast (works because Cypress waits enough for the element to show after the http request node has finished)
- successToast().should('be.visible');
});
it('should test webhook workflow stop', () => {
@@ -239,6 +242,9 @@ describe('Execution', () => {
});
});
+ successToast().should('be.visible');
+ clearNotifications();
+
workflowPage.getters.stopExecutionButton().click();
// Check canvas nodes after 1st step (workflow passed the manual trigger node
workflowPage.getters
@@ -268,13 +274,13 @@ describe('Execution', () => {
.canvasNodeByName('Set')
.within(() => cy.get('.fa-check').should('not.exist'));
+ successToast().should('be.visible');
+ clearNotifications();
+
// Clear execution data
workflowPage.getters.clearExecutionDataButton().should('be.visible');
workflowPage.getters.clearExecutionDataButton().click();
workflowPage.getters.clearExecutionDataButton().should('not.exist');
-
- // Check success toast (works because Cypress waits enough for the element to show after the http request node has finished)
- successToast().should('be.visible');
});
describe('execution preview', () => {
diff --git a/cypress/e2e/25-stickies.cy.ts b/cypress/e2e/25-stickies.cy.ts
index 2b83f9abf9..597050c9f2 100644
--- a/cypress/e2e/25-stickies.cy.ts
+++ b/cypress/e2e/25-stickies.cy.ts
@@ -26,6 +26,9 @@ function checkStickiesStyle(
describe('Canvas Actions', () => {
beforeEach(() => {
workflowPage.actions.visit();
+ cy.get('#collapse-change-button').should('be.visible').click();
+ cy.get('#side-menu[class*=collapsed i]').should('be.visible');
+ workflowPage.actions.zoomToFit();
});
it('adds sticky to canvas with default text and position', () => {
diff --git a/cypress/e2e/9-expression-editor-modal.cy.ts b/cypress/e2e/9-expression-editor-modal.cy.ts
index 30b6a84957..840e3d4fc6 100644
--- a/cypress/e2e/9-expression-editor-modal.cy.ts
+++ b/cypress/e2e/9-expression-editor-modal.cy.ts
@@ -14,6 +14,7 @@ describe('Expression editor modal', () => {
describe('Static data', () => {
beforeEach(() => {
WorkflowPage.actions.addNodeToCanvas('Hacker News');
+ WorkflowPage.actions.zoomToFit();
WorkflowPage.actions.openNode('Hacker News');
WorkflowPage.actions.openExpressionEditorModal();
});
@@ -69,6 +70,7 @@ describe('Expression editor modal', () => {
ndv.actions.close();
WorkflowPage.actions.addNodeToCanvas('No Operation');
WorkflowPage.actions.addNodeToCanvas('Hacker News');
+ WorkflowPage.actions.zoomToFit();
WorkflowPage.actions.openNode('Hacker News');
WorkflowPage.actions.openExpressionEditorModal();
});
diff --git a/cypress/pages/sidebar/main-sidebar.ts b/cypress/pages/sidebar/main-sidebar.ts
index 1ce7eb54d7..4266b93688 100644
--- a/cypress/pages/sidebar/main-sidebar.ts
+++ b/cypress/pages/sidebar/main-sidebar.ts
@@ -11,16 +11,14 @@ export class MainSidebar extends BasePage {
credentials: () => this.getters.menuItem('credentials'),
executions: () => this.getters.menuItem('executions'),
adminPanel: () => this.getters.menuItem('cloud-admin'),
- userMenu: () => cy.get('div[class="action-dropdown-container"]'),
+ userMenu: () => cy.getByTestId('user-menu'),
logo: () => cy.getByTestId('n8n-logo'),
};
actions = {
goToSettings: () => {
- this.getters.settings().should('be.visible');
- // We must wait before ElementUI menu is done with its animations
- cy.get('[data-old-overflow]').should('not.exist');
- this.getters.settings().click();
+ this.getters.userMenu().click();
+ cy.getByTestId('user-menu-item-settings').should('be.visible').click();
},
goToCredentials: () => {
this.getters.credentials().should('be.visible');
diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue
index 59c4fb7cf2..c66e0a89b5 100644
--- a/packages/editor-ui/src/components/MainSidebar.vue
+++ b/packages/editor-ui/src/components/MainSidebar.vue
@@ -28,10 +28,7 @@
-
+