test: Update e2e tests for canvas specific actions (no-changelog) (#12614)

This commit is contained in:
Alex Grozav 2025-01-21 10:30:27 +02:00 committed by GitHub
parent ac2f6476c1
commit 2d3b643f6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 281 additions and 93 deletions

View file

@ -1,6 +1,7 @@
import { getManualChatModal } from './modals/chat-modal'; import { getManualChatModal } from './modals/chat-modal';
import { clickGetBackToCanvas, getParameterInputByName } from './ndv'; import { clickGetBackToCanvas, getParameterInputByName } from './ndv';
import { ROUTES } from '../constants'; import { ROUTES } from '../constants';
import type { OpenContextMenuOptions } from '../types';
/** /**
* Types * Types
@ -24,7 +25,36 @@ export type EndpointType =
* Getters * Getters
*/ */
export function getAddInputEndpointByType(nodeName: string, endpointType: EndpointType) { export function getCanvas() {
return cy.getByTestId('canvas');
}
export function getCanvasPane() {
return cy.ifCanvasVersion(
() => cy.getByTestId('node-view-background'),
() => getCanvas().find('.vue-flow__pane'),
);
}
export function getContextMenu() {
return cy.getByTestId('context-menu').find('.el-dropdown-menu');
}
export function getContextMenuAction(action: string) {
return cy.getByTestId(`context-menu-item-${action}`);
}
export function getInputPlusHandle(nodeName: string) {
return cy.ifCanvasVersion(
() => cy.get(`.add-input-endpoint[data-endpoint-name="${nodeName}"]`),
() =>
cy.get(
`[data-test-id="canvas-node-input-handle"][data-node-name="${nodeName}"] [data-test-id="canvas-handle-plus"]`,
),
);
}
export function getInputPlusHandleByType(nodeName: string, endpointType: EndpointType) {
return cy.ifCanvasVersion( return cy.ifCanvasVersion(
() => () =>
cy.get( cy.get(
@ -37,6 +67,29 @@ export function getAddInputEndpointByType(nodeName: string, endpointType: Endpoi
); );
} }
export function getOutputPlusHandle(nodeName: string) {
return cy.ifCanvasVersion(
() => cy.get(`.add-output-endpoint[data-endpoint-name="${nodeName}"]`),
() =>
cy.get(
`[data-test-id="canvas-node-output-handle"][data-node-name="${nodeName}"] [data-test-id="canvas-handle-plus"]`,
),
);
}
export function getOutputPlusHandleByType(nodeName: string, endpointType: EndpointType) {
return cy.ifCanvasVersion(
() =>
cy.get(
`.add-output-endpoint[data-jtk-scope-${endpointType}][data-endpoint-name="${nodeName}"]`,
),
() =>
cy.get(
`[data-test-id="canvas-node-output-handle"][data-connection-type="${endpointType}"][data-node-name="${nodeName}"] [data-test-id="canvas-handle-plus"]`,
),
);
}
export function getNodeCreatorItems() { export function getNodeCreatorItems() {
return cy.getByTestId('item-iterator-item'); return cy.getByTestId('item-iterator-item');
} }
@ -60,6 +113,13 @@ export function getNodeByName(name: string) {
); );
} }
export function getNodeRenderedTypeByName(name: string) {
return cy.ifCanvasVersion(
() => getNodeByName(name),
() => getNodeByName(name).find('[data-canvas-node-render-type]'),
);
}
export function getWorkflowHistoryCloseButton() { export function getWorkflowHistoryCloseButton() {
return cy.getByTestId('workflow-history-close-button'); return cy.getByTestId('workflow-history-close-button');
} }
@ -85,6 +145,12 @@ export function getConnectionBySourceAndTarget(source: string, target: string) {
); );
} }
export function getConnectionLabelBySourceAndTarget(source: string, target: string) {
return cy
.getByTestId('edge-label')
.filter(`[data-source-node-name="${source}"][data-target-node-name="${target}"]`);
}
export function getNodeCreatorSearchBar() { export function getNodeCreatorSearchBar() {
return cy.getByTestId('node-creator-search-bar'); return cy.getByTestId('node-creator-search-bar');
} }
@ -94,10 +160,7 @@ export function getNodeCreatorPlusButton() {
} }
export function getCanvasNodes() { export function getCanvasNodes() {
return cy.ifCanvasVersion( return cy.getByTestId('canvas-node');
() => cy.getByTestId('canvas-node'),
() => cy.getByTestId('canvas-node').not('[data-node-type="n8n-nodes-internal.addNodes"]'),
);
} }
export function getCanvasNodeByName(nodeName: string) { export function getCanvasNodeByName(nodeName: string) {
@ -157,7 +220,7 @@ function connectNodeToParent(
parentNodeName: string, parentNodeName: string,
exactMatch = false, exactMatch = false,
) { ) {
getAddInputEndpointByType(parentNodeName, endpointType).click({ force: true }); getInputPlusHandleByType(parentNodeName, endpointType).click({ force: true });
if (exactMatch) { if (exactMatch) {
getNodeCreatorItems() getNodeCreatorItems()
.contains(new RegExp('^' + nodeName + '$', 'g')) .contains(new RegExp('^' + nodeName + '$', 'g'))
@ -257,3 +320,34 @@ export function deleteNode(name: string) {
getCanvasNodeByName(name).first().click(); getCanvasNodeByName(name).first().click();
cy.get('body').type('{del}'); cy.get('body').type('{del}');
} }
export function openContextMenu(
nodeName?: string,
{ method = 'right-click', anchor = 'center' }: OpenContextMenuOptions = {},
) {
let target;
if (nodeName) {
target =
method === 'right-click' ? getNodeRenderedTypeByName(nodeName) : getNodeByName(nodeName);
} else {
target = getCanvasPane();
}
if (method === 'right-click') {
target.rightclick(nodeName ? anchor : 'topLeft', { force: true });
} else {
target.realHover();
target.find('[data-test-id="overflow-node-button"]').click({ force: true });
}
cy.ifCanvasVersion(
() => {},
() => {
getContextMenu().should('be.visible');
},
);
}
export function clickContextMenuAction(action: string) {
getContextMenuAction(action).click();
}

View file

@ -184,7 +184,11 @@ describe('Canvas Actions', () => {
.last() .last()
.findChildByTestId('execute-node-button') .findChildByTestId('execute-node-button')
.click({ force: true }); .click({ force: true });
successToast().should('have.length', 1);
WorkflowPage.actions.executeNode(CODE_NODE_NAME); WorkflowPage.actions.executeNode(CODE_NODE_NAME);
successToast().should('have.length', 2); successToast().should('have.length', 2);
successToast().should('contain.text', 'Node executed successfully'); successToast().should('contain.text', 'Node executed successfully');
}); });

View file

@ -7,9 +7,17 @@ import {
SWITCH_NODE_NAME, SWITCH_NODE_NAME,
MERGE_NODE_NAME, MERGE_NODE_NAME,
} from './../constants'; } from './../constants';
import {
clickContextMenuAction,
getCanvasNodeByName,
getCanvasNodes,
getConnectionBySourceAndTarget,
getConnectionLabelBySourceAndTarget,
getOutputPlusHandle,
openContextMenu,
} from '../composables/workflow';
import { NDV, WorkflowExecutionsTab } from '../pages'; import { NDV, WorkflowExecutionsTab } from '../pages';
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
import { isCanvasV2 } from '../utils/workflowUtils';
const WorkflowPage = new WorkflowPageClass(); const WorkflowPage = new WorkflowPageClass();
const ExecutionsTab = new WorkflowExecutionsTab(); const ExecutionsTab = new WorkflowExecutionsTab();
@ -41,27 +49,52 @@ describe('Canvas Node Manipulation and Navigation', () => {
NDVDialog.actions.close(); NDVDialog.actions.close();
for (let i = 0; i < desiredOutputs; i++) { for (let i = 0; i < desiredOutputs; i++) {
WorkflowPage.getters.canvasNodePlusEndpointByName(SWITCH_NODE_NAME, i).click({ force: true }); cy.ifCanvasVersion(
() => {
WorkflowPage.getters
.canvasNodePlusEndpointByName(SWITCH_NODE_NAME, i)
.click({ force: true });
},
() => {
getOutputPlusHandle(SWITCH_NODE_NAME).eq(0).click();
},
);
WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible'); WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible');
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, false); WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, false);
WorkflowPage.actions.zoomToFit(); WorkflowPage.actions.zoomToFit();
} }
WorkflowPage.getters.nodeViewBackground().click({ force: true }); WorkflowPage.getters.nodeViewBackground().click({ force: true });
WorkflowPage.getters.canvasNodePlusEndpointByName(`${EDIT_FIELDS_SET_NODE_NAME}3`).click(); cy.ifCanvasVersion(
() => {
WorkflowPage.getters.canvasNodePlusEndpointByName(`${EDIT_FIELDS_SET_NODE_NAME}3`).click();
},
() => {
getOutputPlusHandle(`${EDIT_FIELDS_SET_NODE_NAME}3`).click();
},
);
WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, false); WorkflowPage.actions.addNodeToCanvas(SWITCH_NODE_NAME, false);
WorkflowPage.actions.saveWorkflowOnButtonClick(); WorkflowPage.actions.saveWorkflowOnButtonClick();
cy.reload(); cy.reload();
cy.waitForLoad(); cy.waitForLoad();
// Make sure outputless switch was connected correctly // Make sure outputless switch was connected correctly
WorkflowPage.getters cy.ifCanvasVersion(
.getConnectionBetweenNodes(`${EDIT_FIELDS_SET_NODE_NAME}3`, `${SWITCH_NODE_NAME}1`) () => {
.should('exist'); WorkflowPage.getters
.getConnectionBetweenNodes(`${EDIT_FIELDS_SET_NODE_NAME}3`, `${SWITCH_NODE_NAME}1`)
.should('exist');
},
() => {
getConnectionBySourceAndTarget(
`${EDIT_FIELDS_SET_NODE_NAME}3`,
`${SWITCH_NODE_NAME}1`,
).should('exist');
},
);
// Make sure all connections are there after reload // Make sure all connections are there after reload
for (let i = 0; i < desiredOutputs; i++) { for (let i = 0; i < desiredOutputs; i++) {
const setName = `${EDIT_FIELDS_SET_NODE_NAME}${i > 0 ? i : ''}`; const setName = `${EDIT_FIELDS_SET_NODE_NAME}${i > 0 ? i : ''}`;
WorkflowPage.getters
.getConnectionBetweenNodes(`${SWITCH_NODE_NAME}`, setName) getConnectionBySourceAndTarget(`${SWITCH_NODE_NAME}`, setName).should('exist');
.should('exist');
} }
}); });
@ -84,14 +117,29 @@ describe('Canvas Node Manipulation and Navigation', () => {
); );
// Connect Set1 and Set2 to merge // Connect Set1 and Set2 to merge
cy.draganddrop( cy.ifCanvasVersion(
WorkflowPage.getters.getEndpointSelector('plus', EDIT_FIELDS_SET_NODE_NAME), () => {
WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 0), cy.draganddrop(
); WorkflowPage.getters.getEndpointSelector('plus', EDIT_FIELDS_SET_NODE_NAME),
cy.draganddrop( WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 0),
WorkflowPage.getters.getEndpointSelector('plus', `${EDIT_FIELDS_SET_NODE_NAME}1`), );
WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 1), cy.draganddrop(
WorkflowPage.getters.getEndpointSelector('plus', `${EDIT_FIELDS_SET_NODE_NAME}1`),
WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 1),
);
},
() => {
cy.draganddrop(
WorkflowPage.getters.getEndpointSelector('output', EDIT_FIELDS_SET_NODE_NAME),
WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 0),
);
cy.draganddrop(
WorkflowPage.getters.getEndpointSelector('output', `${EDIT_FIELDS_SET_NODE_NAME}1`),
WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 1),
);
},
); );
const checkConnections = () => { const checkConnections = () => {
WorkflowPage.getters WorkflowPage.getters
.getConnectionBetweenNodes( .getConnectionBetweenNodes(
@ -117,10 +165,22 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.executeWorkflow(); WorkflowPage.actions.executeWorkflow();
WorkflowPage.getters.stopExecutionButton().should('not.exist'); WorkflowPage.getters.stopExecutionButton().should('not.exist');
// Make sure all connections are there after save & reload
WorkflowPage.actions.saveWorkflowOnButtonClick();
cy.reload();
cy.waitForLoad();
checkConnections();
WorkflowPage.actions.executeWorkflow();
WorkflowPage.getters.stopExecutionButton().should('not.exist');
// If the merged set nodes are connected and executed correctly, there should be 2 items in the output of merge node // If the merged set nodes are connected and executed correctly, there should be 2 items in the output of merge node
cy.ifCanvasVersion( cy.ifCanvasVersion(
() => cy.get('[data-label="2 items"]').should('be.visible'), () => cy.get('[data-label="2 items"]').should('be.visible'),
() => cy.getByTestId('canvas-node-output-handle').contains('2 items').should('be.visible'), () =>
getConnectionLabelBySourceAndTarget(`${EDIT_FIELDS_SET_NODE_NAME}1`, MERGE_NODE_NAME)
.contains('2 items')
.should('be.visible'),
); );
}); });
@ -144,7 +204,10 @@ describe('Canvas Node Manipulation and Navigation', () => {
cy.ifCanvasVersion( cy.ifCanvasVersion(
() => cy.get('.plus-draggable-endpoint').should('have.class', 'ep-success'), () => cy.get('.plus-draggable-endpoint').should('have.class', 'ep-success'),
() => cy.getByTestId('canvas-handle-plus').should('have.attr', 'data-plus-type', 'success'), () =>
cy
.getByTestId('canvas-handle-plus-wrapper')
.should('have.attr', 'data-plus-type', 'success'),
); );
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
@ -212,8 +275,8 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.wait(500); cy.wait(500);
WorkflowPage.actions.selectAllFromContextMenu(); WorkflowPage.actions.selectAllFromContextMenu();
WorkflowPage.actions.openContextMenu(); openContextMenu();
WorkflowPage.actions.contextMenuAction('delete'); clickContextMenuAction('delete');
WorkflowPage.getters.canvasNodes().should('have.length', 0); WorkflowPage.getters.canvasNodes().should('have.length', 0);
}); });
@ -228,41 +291,43 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.wait(500); cy.wait(500);
WorkflowPage.actions.selectAllFromContextMenu(); WorkflowPage.actions.selectAllFromContextMenu();
WorkflowPage.actions.openContextMenu(); openContextMenu();
WorkflowPage.actions.contextMenuAction('delete'); clickContextMenuAction('delete');
WorkflowPage.getters.canvasNodes().should('have.length', 0); WorkflowPage.getters.canvasNodes().should('have.length', 0);
}); });
// FIXME: Canvas V2: Figure out how to test moving of the node
it('should move node', () => { it('should move node', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.zoomToFit(); WorkflowPage.actions.zoomToFit();
WorkflowPage.getters
.canvasNodes() getCanvasNodes()
.last() .last()
.then(($node) => { .then(($node) => {
const { left, top } = $node.position(); const { x: x1, y: y1 } = $node[0].getBoundingClientRect();
if (isCanvasV2()) { cy.ifCanvasVersion(
cy.drag('.vue-flow__node', [300, 300], { () => {
realMouse: true, cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], {
}); clickToFinish: true,
} else { });
cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], { },
clickToFinish: true, () => {
}); cy.drag(getCanvasNodes().last(), [50, 150], {
} realMouse: true,
abs: true,
});
},
);
WorkflowPage.getters getCanvasNodes()
.canvasNodes()
.last() .last()
.then(($node) => { .then(($node) => {
const { left: newLeft, top: newTop } = $node.position(); const { x: x2, y: y2 } = $node[0].getBoundingClientRect();
expect(newLeft).to.be.greaterThan(left); expect(x2).to.be.greaterThan(x1);
expect(newTop).to.be.greaterThan(top); expect(y2).to.be.greaterThan(y1);
}); });
}); });
}); });
@ -369,7 +434,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.hitDisableNodeShortcut(); WorkflowPage.actions.hitDisableNodeShortcut();
WorkflowPage.getters.disabledNodes().should('have.length', 0); WorkflowPage.getters.disabledNodes().should('have.length', 0);
WorkflowPage.actions.deselectAll(); WorkflowPage.actions.deselectAll();
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); getCanvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.hitDisableNodeShortcut(); WorkflowPage.actions.hitDisableNodeShortcut();
WorkflowPage.getters.disabledNodes().should('have.length', 1); WorkflowPage.getters.disabledNodes().should('have.length', 1);
WorkflowPage.actions.hitSelectAll(); WorkflowPage.actions.hitSelectAll();
@ -378,19 +443,19 @@ describe('Canvas Node Manipulation and Navigation', () => {
// Context menu // Context menu
WorkflowPage.actions.hitSelectAll(); WorkflowPage.actions.hitSelectAll();
WorkflowPage.actions.openContextMenu(); openContextMenu();
WorkflowPage.actions.contextMenuAction('toggle_activation'); WorkflowPage.actions.contextMenuAction('toggle_activation');
WorkflowPage.getters.disabledNodes().should('have.length', 0); WorkflowPage.getters.disabledNodes().should('have.length', 0);
WorkflowPage.actions.openContextMenu(); openContextMenu();
WorkflowPage.actions.contextMenuAction('toggle_activation'); WorkflowPage.actions.contextMenuAction('toggle_activation');
WorkflowPage.getters.disabledNodes().should('have.length', 2); WorkflowPage.getters.disabledNodes().should('have.length', 2);
WorkflowPage.actions.deselectAll(); WorkflowPage.actions.deselectAll();
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.openContextMenu(); openContextMenu();
WorkflowPage.actions.contextMenuAction('toggle_activation'); WorkflowPage.actions.contextMenuAction('toggle_activation');
WorkflowPage.getters.disabledNodes().should('have.length', 1); WorkflowPage.getters.disabledNodes().should('have.length', 1);
WorkflowPage.actions.hitSelectAll(); WorkflowPage.actions.hitSelectAll();
WorkflowPage.actions.openContextMenu(); openContextMenu();
WorkflowPage.actions.contextMenuAction('toggle_activation'); WorkflowPage.actions.contextMenuAction('toggle_activation');
WorkflowPage.getters.disabledNodes().should('have.length', 2); WorkflowPage.getters.disabledNodes().should('have.length', 2);
}); });
@ -466,7 +531,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.getters.canvasNodes().should('have.length', 2); WorkflowPage.getters.canvasNodes().should('have.length', 2);
WorkflowPage.getters.nodeConnections().should('have.length', 1); WorkflowPage.getters.nodeConnections().should('have.length', 1);
}); });
// FIXME: Canvas V2: Credentials should show issue on the first open
it('should remove unknown credentials on pasting workflow', () => { it('should remove unknown credentials on pasting workflow', () => {
cy.fixture('workflow-with-unknown-credentials.json').then((data) => { cy.fixture('workflow-with-unknown-credentials.json').then((data) => {
cy.get('body').paste(JSON.stringify(data)); cy.get('body').paste(JSON.stringify(data));

View file

@ -57,7 +57,7 @@ const editWorkflowMoreAndActivate = () => {
position.left = $element.position().left; position.left = $element.position().left;
}); });
cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 200], { clickToFinish: true }); cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 200]);
workflowPage.getters workflowPage.getters
.canvasNodes() .canvasNodes()
.last() .last()

View file

@ -1,5 +1,6 @@
import { BasePage } from './base'; import { BasePage } from './base';
import { NodeCreator } from './features/node-creator'; import { NodeCreator } from './features/node-creator';
import { clickContextMenuAction, getCanvasPane, openContextMenu } from '../composables/workflow';
import { META_KEY } from '../constants'; import { META_KEY } from '../constants';
import type { OpenContextMenuOptions } from '../types'; import type { OpenContextMenuOptions } from '../types';
import { getVisibleSelect } from '../utils'; import { getVisibleSelect } from '../utils';
@ -38,15 +39,7 @@ export class WorkflowPage extends BasePage {
nodeCreatorSearchBar: () => cy.getByTestId('node-creator-search-bar'), nodeCreatorSearchBar: () => cy.getByTestId('node-creator-search-bar'),
nodeCreatorPlusButton: () => cy.getByTestId('node-creator-plus-button'), nodeCreatorPlusButton: () => cy.getByTestId('node-creator-plus-button'),
canvasPlusButton: () => cy.getByTestId('canvas-plus-button'), canvasPlusButton: () => cy.getByTestId('canvas-plus-button'),
canvasNodes: () => canvasNodes: () => cy.getByTestId('canvas-node'),
cy.ifCanvasVersion(
() => cy.getByTestId('canvas-node'),
() =>
cy
.getByTestId('canvas-node')
.not('[data-node-type="n8n-nodes-internal.addNodes"]')
.not('[data-node-type="n8n-nodes-base.stickyNote"]'),
),
canvasNodeByName: (nodeName: string) => canvasNodeByName: (nodeName: string) =>
this.getters.canvasNodes().filter(`:contains(${nodeName})`), this.getters.canvasNodes().filter(`:contains(${nodeName})`),
nodeIssuesByName: (nodeName: string) => nodeIssuesByName: (nodeName: string) =>
@ -110,7 +103,7 @@ export class WorkflowPage extends BasePage {
disabledNodes: () => disabledNodes: () =>
cy.ifCanvasVersion( cy.ifCanvasVersion(
() => cy.get('.node-box.disabled'), () => cy.get('.node-box.disabled'),
() => cy.get('[data-test-id*="node"][class*="disabled"]'), () => cy.get('[data-canvas-node-render-type][class*="disabled"]'),
), ),
selectedNodes: () => selectedNodes: () =>
cy.ifCanvasVersion( cy.ifCanvasVersion(
@ -288,71 +281,77 @@ export class WorkflowPage extends BasePage {
nodeTypeName?: string, nodeTypeName?: string,
{ method = 'right-click', anchor = 'center' }: OpenContextMenuOptions = {}, { method = 'right-click', anchor = 'center' }: OpenContextMenuOptions = {},
) => { ) => {
const target = nodeTypeName cy.ifCanvasVersion(
? this.getters.canvasNodeByName(nodeTypeName) () => {
: this.getters.nodeViewBackground(); const target = nodeTypeName
? this.getters.canvasNodeByName(nodeTypeName)
: this.getters.nodeViewBackground();
if (method === 'right-click') { if (method === 'right-click') {
target.rightclick(nodeTypeName ? anchor : 'topLeft', { force: true }); target.rightclick(nodeTypeName ? anchor : 'topLeft', { force: true });
} else { } else {
target.realHover(); target.realHover();
target.find('[data-test-id="overflow-node-button"]').click({ force: true }); target.find('[data-test-id="overflow-node-button"]').click({ force: true });
} }
},
() => {
openContextMenu(nodeTypeName, { method, anchor });
},
);
}, },
openNode: (nodeTypeName: string) => { openNode: (nodeTypeName: string) => {
this.getters.canvasNodeByName(nodeTypeName).first().dblclick(); this.getters.canvasNodeByName(nodeTypeName).first().dblclick();
}, },
duplicateNode: (nodeTypeName: string) => { duplicateNode: (nodeTypeName: string) => {
this.actions.openContextMenu(nodeTypeName); this.actions.openContextMenu(nodeTypeName);
this.actions.contextMenuAction('duplicate'); clickContextMenuAction('duplicate');
}, },
deleteNodeFromContextMenu: (nodeTypeName: string) => { deleteNodeFromContextMenu: (nodeTypeName: string) => {
this.actions.openContextMenu(nodeTypeName); this.actions.openContextMenu(nodeTypeName);
this.actions.contextMenuAction('delete'); clickContextMenuAction('delete');
}, },
executeNode: (nodeTypeName: string, options?: OpenContextMenuOptions) => { executeNode: (nodeTypeName: string, options?: OpenContextMenuOptions) => {
this.actions.openContextMenu(nodeTypeName, options); this.actions.openContextMenu(nodeTypeName, options);
this.actions.contextMenuAction('execute'); clickContextMenuAction('execute');
}, },
addStickyFromContextMenu: () => { addStickyFromContextMenu: () => {
this.actions.openContextMenu(); this.actions.openContextMenu();
this.actions.contextMenuAction('add_sticky'); clickContextMenuAction('add_sticky');
}, },
renameNode: (nodeTypeName: string) => { renameNode: (nodeTypeName: string) => {
this.actions.openContextMenu(nodeTypeName); this.actions.openContextMenu(nodeTypeName);
this.actions.contextMenuAction('rename'); clickContextMenuAction('rename');
}, },
copyNode: (nodeTypeName: string) => { copyNode: (nodeTypeName: string) => {
this.actions.openContextMenu(nodeTypeName); this.actions.openContextMenu(nodeTypeName);
this.actions.contextMenuAction('copy'); clickContextMenuAction('copy');
}, },
contextMenuAction: (action: string) => { contextMenuAction: (action: string) => {
this.getters.contextMenuAction(action).click(); this.getters.contextMenuAction(action).click();
}, },
disableNode: (nodeTypeName: string) => { disableNode: (nodeTypeName: string) => {
this.actions.openContextMenu(nodeTypeName); this.actions.openContextMenu(nodeTypeName);
this.actions.contextMenuAction('toggle_activation'); clickContextMenuAction('toggle_activation');
}, },
pinNode: (nodeTypeName: string) => { pinNode: (nodeTypeName: string) => {
this.actions.openContextMenu(nodeTypeName); this.actions.openContextMenu(nodeTypeName);
this.actions.contextMenuAction('toggle_pin'); clickContextMenuAction('toggle_pin');
}, },
openNodeFromContextMenu: (nodeTypeName: string) => { openNodeFromContextMenu: (nodeTypeName: string) => {
this.actions.openContextMenu(nodeTypeName, { method: 'overflow-button' }); this.actions.openContextMenu(nodeTypeName, { method: 'overflow-button' });
this.actions.contextMenuAction('open'); clickContextMenuAction('open');
}, },
selectAllFromContextMenu: () => { selectAllFromContextMenu: () => {
this.actions.openContextMenu(); this.actions.openContextMenu();
this.actions.contextMenuAction('select_all'); clickContextMenuAction('select_all');
}, },
deselectAll: () => { deselectAll: () => {
cy.ifCanvasVersion( cy.ifCanvasVersion(
() => { () => {
this.actions.openContextMenu(); this.actions.openContextMenu();
this.actions.contextMenuAction('deselect_all'); clickContextMenuAction('deselect_all');
}, },
// rightclick doesn't work with vueFlow canvas () => getCanvasPane().click('topLeft'),
() => this.getters.nodeViewBackground().click('topLeft'),
); );
}, },
openExpressionEditorModal: () => { openExpressionEditorModal: () => {
@ -431,7 +430,7 @@ export class WorkflowPage extends BasePage {
pinchToZoom: (steps: number, mode: 'zoomIn' | 'zoomOut' = 'zoomIn') => { pinchToZoom: (steps: number, mode: 'zoomIn' | 'zoomOut' = 'zoomIn') => {
cy.window().then((win) => { cy.window().then((win) => {
// Pinch-to-zoom simulates a 'wheel' event with ctrlKey: true (same as zooming by scrolling) // Pinch-to-zoom simulates a 'wheel' event with ctrlKey: true (same as zooming by scrolling)
this.getters.nodeView().trigger('wheel', { this.getters.canvasViewport().trigger('wheel', {
force: true, force: true,
bubbles: true, bubbles: true,
ctrlKey: true, ctrlKey: true,

View file

@ -172,6 +172,7 @@ Cypress.Commands.add('drag', (selector, pos, options) => {
}; };
if (options?.realMouse) { if (options?.realMouse) {
element.realMouseDown(); element.realMouseDown();
element.realMouseMove(0, 0);
element.realMouseMove(newPosition.x, newPosition.y); element.realMouseMove(newPosition.x, newPosition.y);
element.realMouseUp(); element.realMouseUp();
} else { } else {
@ -218,8 +219,15 @@ Cypress.Commands.add('draganddrop', (draggableSelector, droppableSelector, optio
const pageY = coords.top + coords.height / 2; const pageY = coords.top + coords.height / 2;
if (draggableSelector) { if (draggableSelector) {
// We can't use realMouseDown here because it hangs headless run cy.ifCanvasVersion(
cy.get(draggableSelector).trigger('mousedown'); () => {
// We can't use realMouseDown here because it hangs headless run
cy.get(draggableSelector).trigger('mousedown');
},
() => {
cy.get(draggableSelector).realMouseDown();
},
);
} }
// We don't chain these commands to make sure cy.get is re-trying correctly // We don't chain these commands to make sure cy.get is re-trying correctly
cy.get(droppableSelector).realMouseMove(0, 0); cy.get(droppableSelector).realMouseMove(0, 0);

View file

@ -29,7 +29,9 @@ function onActionSelect(item: string) {
} }
function closeMenu(event: MouseEvent) { function closeMenu(event: MouseEvent) {
event.preventDefault(); if (event.cancelable) {
event.preventDefault();
}
event.stopPropagation(); event.stopPropagation();
contextMenu.close(); contextMenu.close();
} }

View file

@ -16,7 +16,7 @@ import type {
CanvasNodeEventBusEvents, CanvasNodeEventBusEvents,
CanvasEventBusEvents, CanvasEventBusEvents,
} from '@/types'; } from '@/types';
import { CanvasConnectionMode } from '@/types'; import { CanvasNodeRenderType, CanvasConnectionMode } from '@/types';
import NodeIcon from '@/components/NodeIcon.vue'; import NodeIcon from '@/components/NodeIcon.vue';
import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import CanvasNodeToolbar from '@/components/canvas/elements/nodes/CanvasNodeToolbar.vue'; import CanvasNodeToolbar from '@/components/canvas/elements/nodes/CanvasNodeToolbar.vue';
@ -107,6 +107,14 @@ const classes = computed(() => ({
...Object.fromEntries([...nodeClasses.value].map((c) => [c, true])), ...Object.fromEntries([...nodeClasses.value].map((c) => [c, true])),
})); }));
const renderType = computed<CanvasNodeRenderType>(() => props.data.render.type);
const dataTestId = computed(() =>
[CanvasNodeRenderType.StickyNote, CanvasNodeRenderType.AddNodes].includes(renderType.value)
? undefined
: 'canvas-node',
);
/** /**
* Event bus * Event bus
*/ */
@ -330,7 +338,7 @@ onBeforeUnmount(() => {
<template> <template>
<div <div
:class="classes" :class="classes"
data-test-id="canvas-node" :data-test-id="dataTestId"
:data-node-name="data.name" :data-node-name="data.name"
:data-node-type="data.type" :data-node-type="data.type"
> >

View file

@ -13,8 +13,10 @@ const slots = defineSlots<{
}>(); }>();
const Render = () => { const Render = () => {
const renderType = node?.data.value.render.type ?? CanvasNodeRenderType.Default;
let Component; let Component;
switch (node?.data.value.render.type) {
switch (renderType) {
case CanvasNodeRenderType.StickyNote: case CanvasNodeRenderType.StickyNote:
Component = CanvasNodeStickyNote; Component = CanvasNodeStickyNote;
break; break;
@ -25,7 +27,13 @@ const Render = () => {
Component = CanvasNodeDefault; Component = CanvasNodeDefault;
} }
return h(Component, slots.default); return h(
Component,
{
'data-canvas-node-render-type': renderType,
},
slots.default,
);
}; };
</script> </script>