mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
test(editor): Add canvas dragging tests (no-changelog) (#5406)
* WIP * Fix undo/redo tests and add canvas tests * Cleanup * Cleanup * Use drag and draganddrop commands * Fix hanging CI run * Undo redo spec
This commit is contained in:
parent
389e4af4d9
commit
14d1743595
|
@ -4,9 +4,13 @@ export const DEFAULT_USER_EMAIL = 'nathan@n8n.io';
|
|||
export const DEFAULT_USER_PASSWORD = 'CypressTest123';
|
||||
|
||||
export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
|
||||
export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking "Execute Workflow"';
|
||||
export const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
|
||||
export const CODE_NODE_NAME = 'Code';
|
||||
export const SET_NODE_NAME = 'Set';
|
||||
export const IF_NODE_NAME = 'IF';
|
||||
export const MERGE_NODE_NAME = 'Merge';
|
||||
export const SWITCH_NODE_NAME = 'Switch';
|
||||
export const GMAIL_NODE_NAME = 'Gmail';
|
||||
export const TRELLO_NODE_NAME = 'Trello';
|
||||
export const NOTION_NODE_NAME = 'Notion';
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import {
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
CODE_NODE_NAME,
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
SET_NODE_NAME,
|
||||
SWITCH_NODE_NAME,
|
||||
IF_NODE_NAME,
|
||||
MERGE_NODE_NAME,
|
||||
HTTP_REQUEST_NODE_NAME,
|
||||
} from './../constants';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
@ -33,12 +37,125 @@ describe('Canvas Actions', () => {
|
|||
WorkflowPage.getters.executeWorkflowButton().should('be.visible');
|
||||
});
|
||||
|
||||
it('should connect and disconnect a simple node', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME);
|
||||
WorkflowPage.getters.nodeViewBackground().click(600, 200, { force: true });
|
||||
cy.get('.jtk-connector').should('have.length', 1);
|
||||
WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME);
|
||||
|
||||
// Change connection from Set to Set1
|
||||
cy.draganddrop(
|
||||
WorkflowPage.getters.getEndpointSelector('input', SET_NODE_NAME),
|
||||
WorkflowPage.getters.getEndpointSelector('input', `${SET_NODE_NAME}1`)
|
||||
)
|
||||
|
||||
WorkflowPage.getters.canvasNodeInputEndpointByName(`${SET_NODE_NAME}1`).should('have.class', 'jtk-endpoint-connected');
|
||||
|
||||
cy.get('.jtk-connector').should('have.length', 1);
|
||||
// Disconnect Set1
|
||||
cy.drag(WorkflowPage.getters.getEndpointSelector('input', `${SET_NODE_NAME}1`), [-200, 100])
|
||||
cy.get('.jtk-connector').should('have.length', 0);
|
||||
});
|
||||
|
||||
it('should add first step', () => {
|
||||
WorkflowPage.getters.canvasPlusButton().should('be.visible');
|
||||
WorkflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 1);
|
||||
});
|
||||
|
||||
it('should add a node via plus endpoint drag', () => {
|
||||
WorkflowPage.getters.canvasPlusButton().should('be.visible');
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME, true);
|
||||
|
||||
cy.drag(WorkflowPage.getters.getEndpointSelector('plus', SCHEDULE_TRIGGER_NODE_NAME), [100, 100])
|
||||
|
||||
WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible');
|
||||
WorkflowPage.actions.addNodeToCanvas(IF_NODE_NAME, false);
|
||||
WorkflowPage.getters.nodeViewBackground().click({ force: true });
|
||||
});
|
||||
|
||||
it('should add switch node and test connections', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, true);
|
||||
|
||||
// Switch has 4 output endpoints
|
||||
for (let i = 0; i < 4; i++) {
|
||||
WorkflowPage.getters.canvasNodePlusEndpointByName(SWITCH_NODE_NAME, i).click({ force: true })
|
||||
WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible');
|
||||
WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME, false);
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
}
|
||||
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||
cy.reload()
|
||||
cy.waitForLoad();
|
||||
// Make sure all connections are there after reload
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const setName = `${SET_NODE_NAME}${i > 0 ? i : ''}`;
|
||||
WorkflowPage.getters.canvasNodeInputEndpointByName(setName).should('have.class', 'jtk-endpoint-connected');
|
||||
}
|
||||
});
|
||||
|
||||
it('should add merge node and test connections', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
for (let i = 0; i < 2; i++) {
|
||||
WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME, true);
|
||||
WorkflowPage.getters.nodeViewBackground().click(600 + i * 100, 200, { force: true });
|
||||
}
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
|
||||
WorkflowPage.actions.addNodeToCanvas(MERGE_NODE_NAME);
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
|
||||
// Connect manual to Set1
|
||||
cy.draganddrop(
|
||||
WorkflowPage.getters.getEndpointSelector('output', MANUAL_TRIGGER_NODE_DISPLAY_NAME),
|
||||
WorkflowPage.getters.getEndpointSelector('input', `${SET_NODE_NAME}1`)
|
||||
)
|
||||
|
||||
cy.get('.rect-input-endpoint.jtk-endpoint-connected').should('have.length', 2);
|
||||
|
||||
// Connect Set1 and Set2 to merge
|
||||
cy.draganddrop(
|
||||
WorkflowPage.getters.getEndpointSelector('plus', SET_NODE_NAME),
|
||||
WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 0)
|
||||
)
|
||||
cy.draganddrop(
|
||||
WorkflowPage.getters.getEndpointSelector('plus', `${SET_NODE_NAME}1`),
|
||||
WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 1)
|
||||
)
|
||||
|
||||
cy.get('.rect-input-endpoint.jtk-endpoint-connected').should('have.length', 4);
|
||||
|
||||
// Make sure all connections are there after save & reload
|
||||
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||
cy.reload()
|
||||
cy.waitForLoad();
|
||||
|
||||
cy.get('.rect-input-endpoint.jtk-endpoint-connected').should('have.length', 4);
|
||||
WorkflowPage.actions.executeWorkflow();
|
||||
// If the merged set nodes are connected and executed correctly, there should be 2 items in the output of merge node
|
||||
cy.get('[data-label="2 items"]').should('be.visible');
|
||||
});
|
||||
|
||||
it('should add nodes and check execution success', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME, true);
|
||||
}
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
WorkflowPage.actions.executeWorkflow();
|
||||
|
||||
cy.get('.jtk-connector.success').should('have.length', 3);
|
||||
cy.get('.data-count').should('have.length', 4);
|
||||
cy.get('.plus-draggable-endpoint').should('have.class', 'ep-success');
|
||||
|
||||
WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME);
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
|
||||
cy.get('.plus-draggable-endpoint').filter(':visible').should('not.have.class', 'ep-success');
|
||||
cy.get('.jtk-connector.success').should('have.length', 3);
|
||||
cy.get('.jtk-connector').should('have.length', 4);
|
||||
})
|
||||
|
||||
it('should add a connected node using plus endpoint', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
cy.get('.plus-endpoint').should('be.visible').click();
|
||||
|
@ -226,7 +343,7 @@ describe('Canvas Actions', () => {
|
|||
it('should delete a connection by moving it away from endpoint', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
cy.drag('.rect-input-endpoint.jtk-endpoint-connected', [0, -100]);
|
||||
cy.drag(WorkflowPage.getters.getEndpointSelector('input', CODE_NODE_NAME), [0, -100]);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||
});
|
||||
|
||||
|
|
|
@ -24,6 +24,18 @@ export class WorkflowPage extends BasePage {
|
|||
canvasNodes: () => cy.getByTestId('canvas-node'),
|
||||
canvasNodeByName: (nodeName: string) =>
|
||||
this.getters.canvasNodes().filter(`:contains("${nodeName}")`),
|
||||
getEndpointSelector: (type: 'input' | 'output' | 'plus', nodeName: string, index = 0) => {
|
||||
return `[data-endpoint-name='${nodeName}'][data-endpoint-type='${type}'][data-input-index='${index}']`
|
||||
},
|
||||
canvasNodeInputEndpointByName: (nodeName: string, index = 0) => {
|
||||
return cy.get(this.getters.getEndpointSelector('input', nodeName, index));
|
||||
},
|
||||
canvasNodeOutputEndpointByName: (nodeName: string, index = 0) => {
|
||||
return cy.get(this.getters.getEndpointSelector('output', nodeName, index));
|
||||
},
|
||||
canvasNodePlusEndpointByName: (nodeName: string, index = 0) => {
|
||||
return cy.get(this.getters.getEndpointSelector('plus', nodeName, index));
|
||||
},
|
||||
successToast: () => cy.get('.el-notification .el-icon-success').parent(),
|
||||
errorToast: () => cy.get('.el-notification .el-icon-error'),
|
||||
activatorSwitch: () => cy.getByTestId('workflow-activate-switch'),
|
||||
|
@ -122,7 +134,9 @@ export class WorkflowPage extends BasePage {
|
|||
this.getters.workflowMenu().click();
|
||||
},
|
||||
saveWorkflowOnButtonClick: () => {
|
||||
this.getters.saveButton().should('contain', 'Save');
|
||||
this.getters.saveButton().click();
|
||||
this.getters.saveButton().should('contain', 'Saved')
|
||||
},
|
||||
saveWorkflowUsingKeyboardShortcut: () => {
|
||||
cy.get('body').type('{meta}', { release: false }).type('s');
|
||||
|
|
|
@ -187,23 +187,27 @@ Cypress.Commands.add('drag', (selector, pos) => {
|
|||
pageY: originalLocation.top + yDiff,
|
||||
force: true,
|
||||
});
|
||||
element.trigger('mouseup');
|
||||
element.trigger('mouseup', { force: true });
|
||||
});
|
||||
|
||||
Cypress.Commands.add('draganddrop', (draggableSelector, droppableSelector) => {
|
||||
cy.get(draggableSelector).should('exist');
|
||||
cy.get(droppableSelector).should('exist');
|
||||
|
||||
const droppableEl = Cypress.$(droppableSelector)[0];
|
||||
const coords = droppableEl.getBoundingClientRect();
|
||||
cy.get(droppableSelector).first().then(([$el]) => {
|
||||
const coords = $el.getBoundingClientRect();
|
||||
|
||||
const pageX = coords.left + coords.width / 2;
|
||||
const pageY = coords.top + coords.height / 2;
|
||||
const pageX = coords.left + coords.width / 2;
|
||||
const pageY = coords.top + coords.height / 2;
|
||||
|
||||
cy.get(draggableSelector).realMouseDown();
|
||||
cy.get(droppableSelector).realMouseMove(pageX, pageY)
|
||||
.realHover()
|
||||
.realMouseUp();
|
||||
// We can't use realMouseDown here because it hangs headless run
|
||||
cy.get(draggableSelector).trigger('mousedown');
|
||||
// We don't chain these commands to make sure cy.get is re-trying correctly
|
||||
cy.get(droppableSelector).realMouseMove(pageX, pageY)
|
||||
cy.get(droppableSelector).realHover()
|
||||
cy.get(droppableSelector).realMouseUp();
|
||||
cy.get(draggableSelector).realMouseUp();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { useUIStore } from '@/stores/ui';
|
|||
import { useWorkflowsStore } from '@/stores/workflows';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes';
|
||||
import { BrowserJsPlumbInstance } from '@jsplumb/browser-ui';
|
||||
import { EndpointOptions } from '@jsplumb/core';
|
||||
import { Endpoint, EndpointOptions } from '@jsplumb/core';
|
||||
import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
||||
import { useHistoryStore } from '@/stores/history';
|
||||
import { useCanvasStore } from '@/stores/canvas';
|
||||
|
@ -60,6 +60,14 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
__addEndpointTestingData(endpoint: Endpoint, type: string, inputIndex: number) {
|
||||
if (window?.Cypress && 'canvas' in endpoint.endpoint) {
|
||||
const canvas = endpoint.endpoint.canvas;
|
||||
this.instance.setAttribute(canvas, 'data-endpoint-name', this.data.name);
|
||||
this.instance.setAttribute(canvas, 'data-input-index', inputIndex.toString());
|
||||
this.instance.setAttribute(canvas, 'data-endpoint-type', type);
|
||||
}
|
||||
},
|
||||
__addInputEndpoints(node: INodeUi, nodeTypeData: INodeTypeDescription) {
|
||||
// Add Inputs
|
||||
let index;
|
||||
|
@ -104,6 +112,7 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({
|
|||
this.$refs[this.data.name] as Element,
|
||||
newEndpointData,
|
||||
);
|
||||
this.__addEndpointTestingData(endpoint, 'input', index);
|
||||
if (nodeTypeData.inputNames) {
|
||||
// Apply input names if they got set
|
||||
endpoint.addOverlay(NodeViewUtils.getInputNameOverlay(nodeTypeData.inputNames[index]));
|
||||
|
@ -155,7 +164,6 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({
|
|||
uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, index),
|
||||
anchor: anchorPosition,
|
||||
maxConnections: -1,
|
||||
|
||||
endpoint: {
|
||||
type: 'Dot',
|
||||
options: {
|
||||
|
@ -185,10 +193,11 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({
|
|||
this.$refs[this.data.name] as Element,
|
||||
newEndpointData,
|
||||
);
|
||||
this.__addEndpointTestingData(endpoint, 'output', index);
|
||||
if (nodeTypeData.outputNames) {
|
||||
// Apply output names if they got set
|
||||
const overlaySpec = NodeViewUtils.getOutputNameOverlay(nodeTypeData.outputNames[index]);
|
||||
const overlay = endpoint.addOverlay(overlaySpec);
|
||||
endpoint.addOverlay(overlaySpec);
|
||||
}
|
||||
|
||||
if (!Array.isArray(endpoint)) {
|
||||
|
@ -236,6 +245,7 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({
|
|||
this.$refs[this.data.name] as Element,
|
||||
plusEndpointData,
|
||||
);
|
||||
this.__addEndpointTestingData(plusEndpoint, 'plus', index);
|
||||
|
||||
if (!Array.isArray(plusEndpoint)) {
|
||||
plusEndpoint.__meta = {
|
||||
|
|
Loading…
Reference in a new issue