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 @@ +