mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
test: Enable Canvas V2 E2E Testing (#11321)
Co-authored-by: Alex Grozav <alex@grozav.com>
This commit is contained in:
parent
c0b5b92f62
commit
de04c93f2c
6
.github/workflows/e2e-reusable.yml
vendored
6
.github/workflows/e2e-reusable.yml
vendored
|
@ -41,6 +41,11 @@ on:
|
|||
description: 'PR number to run tests for.'
|
||||
required: false
|
||||
type: number
|
||||
node_view_version:
|
||||
description: 'Node View version to run tests with.'
|
||||
required: false
|
||||
default: '1'
|
||||
type: string
|
||||
secrets:
|
||||
CYPRESS_RECORD_KEY:
|
||||
description: 'Cypress record key.'
|
||||
|
@ -160,6 +165,7 @@ jobs:
|
|||
spec: '${{ inputs.spec }}'
|
||||
env:
|
||||
NODE_OPTIONS: --dns-result-order=ipv4first
|
||||
CYPRESS_NODE_VIEW_VERSION: ${{ inputs.node_view_version }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
E2E_TESTS: true
|
||||
|
|
6
.github/workflows/e2e-tests.yml
vendored
6
.github/workflows/e2e-tests.yml
vendored
|
@ -27,6 +27,11 @@ on:
|
|||
description: 'URL to call after workflow is done.'
|
||||
required: false
|
||||
default: ''
|
||||
node_view_version:
|
||||
description: 'Node View version to run tests with.'
|
||||
required: false
|
||||
default: '1'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
calls-start-url:
|
||||
|
@ -46,6 +51,7 @@ jobs:
|
|||
branch: ${{ github.event.inputs.branch || 'master' }}
|
||||
user: ${{ github.event.inputs.user || 'PR User' }}
|
||||
spec: ${{ github.event.inputs.spec || 'e2e/*' }}
|
||||
node_view_version: ${{ github.event.inputs.node_view_version || '1' }}
|
||||
secrets:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ describe('Undo/Redo', () => {
|
|||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Fix redo connections
|
||||
it('should undo/redo adding node in the middle', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
@ -114,6 +115,7 @@ describe('Undo/Redo', () => {
|
|||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Fix moving of nodes via e2e tests
|
||||
it('should undo/redo moving nodes', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
@ -146,18 +148,14 @@ describe('Undo/Redo', () => {
|
|||
it('should undo/redo deleting a connection using context menu', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.getters.nodeConnections().realHover();
|
||||
cy.get('.connection-actions .delete')
|
||||
.filter(':visible')
|
||||
.should('be.visible')
|
||||
.click({ force: true });
|
||||
WorkflowPage.actions.deleteNodeBetweenNodes(SCHEDULE_TRIGGER_NODE_NAME, CODE_NODE_NAME);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||
WorkflowPage.actions.hitUndo();
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
||||
WorkflowPage.actions.hitRedo();
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Fix disconnecting by moving
|
||||
it('should undo/redo deleting a connection by moving it away', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
@ -206,6 +204,7 @@ describe('Undo/Redo', () => {
|
|||
WorkflowPage.getters.disabledNodes().should('have.length', 2);
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Fix undo renaming node
|
||||
it('should undo/redo renaming node using keyboard shortcut', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
@ -244,6 +243,7 @@ describe('Undo/Redo', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Figure out why moving doesn't work from e2e
|
||||
it('should undo/redo multiple steps', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
|
|
@ -16,6 +16,7 @@ describe('Canvas Actions', () => {
|
|||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Missing execute button if no nodes
|
||||
it('should render canvas', () => {
|
||||
WorkflowPage.getters.nodeViewRoot().should('be.visible');
|
||||
WorkflowPage.getters.canvasPlusButton().should('be.visible');
|
||||
|
@ -25,10 +26,11 @@ describe('Canvas Actions', () => {
|
|||
WorkflowPage.getters.executeWorkflowButton().should('be.visible');
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Fix changing of connection
|
||||
it('should connect and disconnect a simple node', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
||||
WorkflowPage.getters.nodeViewBackground().click(600, 200, { force: true });
|
||||
cy.get('.jtk-connector').should('have.length', 1);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
||||
|
||||
WorkflowPage.getters.nodeViewBackground().click(600, 400, { force: true });
|
||||
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
||||
|
@ -40,16 +42,16 @@ describe('Canvas Actions', () => {
|
|||
);
|
||||
|
||||
WorkflowPage.getters
|
||||
.canvasNodeInputEndpointByName(`${EDIT_FIELDS_SET_NODE_NAME}1`)
|
||||
.should('have.class', 'jtk-endpoint-connected');
|
||||
.getConnectionBetweenNodes(MANUAL_TRIGGER_NODE_DISPLAY_NAME, `${EDIT_FIELDS_SET_NODE_NAME}1`)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('.jtk-connector').should('have.length', 1);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
||||
// Disconnect Set1
|
||||
cy.drag(
|
||||
WorkflowPage.getters.getEndpointSelector('input', `${EDIT_FIELDS_SET_NODE_NAME}1`),
|
||||
[-200, 100],
|
||||
);
|
||||
cy.get('.jtk-connector').should('have.length', 0);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||
});
|
||||
|
||||
it('should add first step', () => {
|
||||
|
@ -74,7 +76,7 @@ describe('Canvas Actions', () => {
|
|||
|
||||
it('should add a connected node using plus endpoint', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
cy.get('.plus-endpoint').should('be.visible').click();
|
||||
WorkflowPage.getters.canvasNodePlusEndpointByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible');
|
||||
WorkflowPage.getters.nodeCreatorSearchBar().type(CODE_NODE_NAME);
|
||||
WorkflowPage.getters.nodeCreatorSearchBar().type('{enter}');
|
||||
|
@ -85,7 +87,7 @@ describe('Canvas Actions', () => {
|
|||
|
||||
it('should add a connected node dragging from node creator', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
cy.get('.plus-endpoint').should('be.visible').click();
|
||||
WorkflowPage.getters.canvasNodePlusEndpointByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible');
|
||||
WorkflowPage.getters.nodeCreatorSearchBar().type(CODE_NODE_NAME);
|
||||
cy.drag(WorkflowPage.getters.nodeCreatorNodeItems().first(), [100, 100], {
|
||||
|
@ -99,7 +101,7 @@ describe('Canvas Actions', () => {
|
|||
|
||||
it('should open a category when trying to drag and drop it on the canvas', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
cy.get('.plus-endpoint').should('be.visible').click();
|
||||
WorkflowPage.getters.canvasNodePlusEndpointByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
WorkflowPage.getters.nodeCreatorSearchBar().should('be.visible');
|
||||
WorkflowPage.getters.nodeCreatorSearchBar().type(CODE_NODE_NAME);
|
||||
cy.drag(WorkflowPage.getters.nodeCreatorActionItems().first(), [100, 100], {
|
||||
|
@ -114,7 +116,7 @@ describe('Canvas Actions', () => {
|
|||
it('should add disconnected node if nothing is selected', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
// Deselect nodes
|
||||
WorkflowPage.getters.nodeViewBackground().click({ force: true });
|
||||
WorkflowPage.getters.nodeView().click({ force: true });
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 2);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||
|
@ -136,10 +138,10 @@ describe('Canvas Actions', () => {
|
|||
WorkflowPage.getters.nodeConnections().should('have.length', 3);
|
||||
|
||||
WorkflowPage.getters.canvasNodeByName(EDIT_FIELDS_SET_NODE_NAME).then(($editFieldsNode) => {
|
||||
const editFieldsNodeLeft = parseFloat($editFieldsNode.css('left'));
|
||||
const editFieldsNodeLeft = WorkflowPage.getters.getNodeLeftPosition($editFieldsNode);
|
||||
|
||||
WorkflowPage.getters.canvasNodeByName(HTTP_REQUEST_NODE_NAME).then(($httpNode) => {
|
||||
const httpNodeLeft = parseFloat($httpNode.css('left'));
|
||||
const httpNodeLeft = WorkflowPage.getters.getNodeLeftPosition($httpNode);
|
||||
expect(httpNodeLeft).to.be.lessThan(editFieldsNodeLeft);
|
||||
});
|
||||
});
|
||||
|
@ -159,10 +161,12 @@ describe('Canvas Actions', () => {
|
|||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.getters.nodeConnections().first().realHover();
|
||||
cy.get('.connection-actions .delete').first().click({ force: true });
|
||||
WorkflowPage.actions.deleteNodeBetweenNodes(MANUAL_TRIGGER_NODE_DISPLAY_NAME, CODE_NODE_NAME);
|
||||
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Fix disconnecting of connection by dragging it
|
||||
it('should delete a connection by moving it away from endpoint', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
|
@ -216,10 +220,10 @@ describe('Canvas Actions', () => {
|
|||
WorkflowPage.actions.hitSelectAll();
|
||||
|
||||
WorkflowPage.actions.hitCopy();
|
||||
successToast().should('contain', 'Copied!');
|
||||
successToast().should('contain', 'Copied to clipboard');
|
||||
|
||||
WorkflowPage.actions.copyNode(CODE_NODE_NAME);
|
||||
successToast().should('contain', 'Copied!');
|
||||
successToast().should('contain', 'Copied to clipboard');
|
||||
});
|
||||
|
||||
it('should select/deselect all nodes', () => {
|
||||
|
@ -231,17 +235,31 @@ describe('Canvas Actions', () => {
|
|||
WorkflowPage.getters.selectedNodes().should('have.length', 0);
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Selection via arrow keys is broken
|
||||
it('should select nodes using arrow keys', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
cy.wait(500);
|
||||
cy.get('body').type('{leftArrow}');
|
||||
WorkflowPage.getters.canvasNodes().first().should('have.class', 'jtk-drag-selected');
|
||||
const selectedCanvasNodes = () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => WorkflowPage.getters.canvasNodes(),
|
||||
() => WorkflowPage.getters.canvasNodes().parent(),
|
||||
);
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => selectedCanvasNodes().first().should('have.class', 'jtk-drag-selected'),
|
||||
() => selectedCanvasNodes().first().should('have.class', 'selected'),
|
||||
);
|
||||
cy.get('body').type('{rightArrow}');
|
||||
WorkflowPage.getters.canvasNodes().last().should('have.class', 'jtk-drag-selected');
|
||||
cy.ifCanvasVersion(
|
||||
() => selectedCanvasNodes().last().should('have.class', 'jtk-drag-selected'),
|
||||
() => selectedCanvasNodes().last().should('have.class', 'selected'),
|
||||
);
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Selection via shift and arrow keys is broken
|
||||
it('should select nodes using shift and arrow keys', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
|
@ -251,6 +269,7 @@ describe('Canvas Actions', () => {
|
|||
WorkflowPage.getters.selectedNodes().should('have.length', 2);
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Fix select & deselect
|
||||
it('should not break lasso selection when dragging node action buttons', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.getters
|
||||
|
@ -262,6 +281,7 @@ describe('Canvas Actions', () => {
|
|||
WorkflowPage.actions.testLassoSelection([100, 100], [200, 200]);
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Fix select & deselect
|
||||
it('should not break lasso selection with multiple clicks on node action buttons', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.testLassoSelection([100, 100], [200, 200]);
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from './../constants';
|
||||
import { NDV, WorkflowExecutionsTab } from '../pages';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { isCanvasV2 } from '../utils/workflowUtils';
|
||||
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
const ExecutionsTab = new WorkflowExecutionsTab();
|
||||
|
@ -52,15 +53,15 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
cy.reload();
|
||||
cy.waitForLoad();
|
||||
// Make sure outputless switch was connected correctly
|
||||
cy.get(
|
||||
`[data-target-node="${SWITCH_NODE_NAME}1"][data-source-node="${EDIT_FIELDS_SET_NODE_NAME}3"]`,
|
||||
).should('be.visible');
|
||||
WorkflowPage.getters
|
||||
.getConnectionBetweenNodes(`${EDIT_FIELDS_SET_NODE_NAME}3`, `${SWITCH_NODE_NAME}1`)
|
||||
.should('exist');
|
||||
// Make sure all connections are there after reload
|
||||
for (let i = 0; i < desiredOutputs; i++) {
|
||||
const setName = `${EDIT_FIELDS_SET_NODE_NAME}${i > 0 ? i : ''}`;
|
||||
WorkflowPage.getters
|
||||
.canvasNodeInputEndpointByName(setName)
|
||||
.should('have.class', 'jtk-endpoint-connected');
|
||||
.getConnectionBetweenNodes(`${SWITCH_NODE_NAME}`, setName)
|
||||
.should('exist');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -69,9 +70,7 @@ 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((i + 1) * 200, (i + 1) * 200, { force: true });
|
||||
WorkflowPage.getters.nodeView().click((i + 1) * 200, (i + 1) * 200, { force: true });
|
||||
}
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
|
||||
|
@ -84,8 +83,6 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
WorkflowPage.getters.getEndpointSelector('input', `${EDIT_FIELDS_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', EDIT_FIELDS_SET_NODE_NAME),
|
||||
|
@ -95,20 +92,36 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
WorkflowPage.getters.getEndpointSelector('plus', `${EDIT_FIELDS_SET_NODE_NAME}1`),
|
||||
WorkflowPage.getters.getEndpointSelector('input', MERGE_NODE_NAME, 1),
|
||||
);
|
||||
|
||||
cy.get('.rect-input-endpoint.jtk-endpoint-connected').should('have.length', 4);
|
||||
const checkConnections = () => {
|
||||
WorkflowPage.getters
|
||||
.getConnectionBetweenNodes(
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
`${EDIT_FIELDS_SET_NODE_NAME}1`,
|
||||
)
|
||||
.should('exist');
|
||||
WorkflowPage.getters
|
||||
.getConnectionBetweenNodes(EDIT_FIELDS_SET_NODE_NAME, MERGE_NODE_NAME)
|
||||
.should('exist');
|
||||
WorkflowPage.getters
|
||||
.getConnectionBetweenNodes(`${EDIT_FIELDS_SET_NODE_NAME}1`, MERGE_NODE_NAME)
|
||||
.should('exist');
|
||||
};
|
||||
checkConnections();
|
||||
|
||||
// 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);
|
||||
checkConnections();
|
||||
// cy.get('.rect-input-endpoint.jtk-endpoint-connected').should('have.length', 4);
|
||||
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
|
||||
cy.get('[data-label="2 items"]').should('be.visible');
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.get('[data-label="2 items"]').should('be.visible'),
|
||||
() => cy.getByTestId('canvas-node-output-handle').contains('2 items').should('be.visible'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should add nodes and check execution success', () => {
|
||||
|
@ -120,16 +133,42 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
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');
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.get('.jtk-connector.success').should('have.length', 3),
|
||||
() => cy.get('[data-edge-status=success]').should('have.length', 3),
|
||||
);
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.get('.data-count').should('have.length', 4),
|
||||
() => cy.getByTestId('canvas-node-status-success').should('have.length', 4),
|
||||
);
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.get('.plus-draggable-endpoint').should('have.class', 'ep-success'),
|
||||
() => cy.getByTestId('canvas-handle-plus').should('have.attr', 'data-plus-type', 'success'),
|
||||
);
|
||||
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_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);
|
||||
cy.ifCanvasVersion(
|
||||
() =>
|
||||
cy
|
||||
.get('.plus-draggable-endpoint')
|
||||
.filter(':visible')
|
||||
.should('not.have.class', 'ep-success'),
|
||||
() =>
|
||||
cy.getByTestId('canvas-handle-plus').should('not.have.attr', 'data-plus-type', 'success'),
|
||||
);
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.get('.jtk-connector.success').should('have.length', 3),
|
||||
// The new version of the canvas correctly shows executed data being passed to the input of the next node
|
||||
() => cy.get('[data-edge-status=success]').should('have.length', 4),
|
||||
);
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.get('.data-count').should('have.length', 4),
|
||||
() => cy.getByTestId('canvas-node-status-success').should('have.length', 4),
|
||||
);
|
||||
});
|
||||
|
||||
it('should delete node using context menu', () => {
|
||||
|
@ -194,19 +233,29 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
WorkflowPage.getters.canvasNodes().should('have.length', 0);
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Figure out how to test moving of the node
|
||||
it('should move node', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.last()
|
||||
.then(($node) => {
|
||||
const { left, top } = $node.position();
|
||||
cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], {
|
||||
clickToFinish: true,
|
||||
});
|
||||
|
||||
if (isCanvasV2()) {
|
||||
cy.drag('.vue-flow__node', [300, 300], {
|
||||
realMouse: true,
|
||||
});
|
||||
} else {
|
||||
cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], {
|
||||
clickToFinish: true,
|
||||
});
|
||||
}
|
||||
|
||||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.last()
|
||||
|
@ -218,91 +267,80 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should zoom in', () => {
|
||||
WorkflowPage.getters.zoomInButton().should('be.visible').click();
|
||||
WorkflowPage.getters
|
||||
.nodeView()
|
||||
.should(
|
||||
'have.css',
|
||||
'transform',
|
||||
`matrix(${ZOOM_IN_X1_FACTOR}, 0, 0, ${ZOOM_IN_X1_FACTOR}, 0, 0)`,
|
||||
describe('Canvas Zoom Functionality', () => {
|
||||
const getContainer = () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => WorkflowPage.getters.nodeView(),
|
||||
() => WorkflowPage.getters.canvasViewport(),
|
||||
);
|
||||
WorkflowPage.getters.zoomInButton().click();
|
||||
WorkflowPage.getters
|
||||
.nodeView()
|
||||
.should(
|
||||
'have.css',
|
||||
'transform',
|
||||
`matrix(${ZOOM_IN_X2_FACTOR}, 0, 0, ${ZOOM_IN_X2_FACTOR}, 0, 0)`,
|
||||
);
|
||||
});
|
||||
const checkZoomLevel = (expectedFactor: number) => {
|
||||
return getContainer().should(($nodeView) => {
|
||||
const newTransform = $nodeView.css('transform');
|
||||
const newScale = parseFloat(newTransform.split(',')[0].slice(7));
|
||||
|
||||
it('should zoom out', () => {
|
||||
WorkflowPage.getters.zoomOutButton().should('be.visible').click();
|
||||
WorkflowPage.getters
|
||||
.nodeView()
|
||||
.should(
|
||||
'have.css',
|
||||
'transform',
|
||||
`matrix(${ZOOM_OUT_X1_FACTOR}, 0, 0, ${ZOOM_OUT_X1_FACTOR}, 0, 0)`,
|
||||
);
|
||||
WorkflowPage.getters.zoomOutButton().click();
|
||||
WorkflowPage.getters
|
||||
.nodeView()
|
||||
.should(
|
||||
'have.css',
|
||||
'transform',
|
||||
`matrix(${ZOOM_OUT_X2_FACTOR}, 0, 0, ${ZOOM_OUT_X2_FACTOR}, 0, 0)`,
|
||||
);
|
||||
});
|
||||
expect(newScale).to.be.closeTo(expectedFactor, 0.2);
|
||||
});
|
||||
};
|
||||
|
||||
it('should zoom using scroll or pinch gesture', () => {
|
||||
WorkflowPage.actions.pinchToZoom(1, 'zoomIn');
|
||||
WorkflowPage.getters
|
||||
.nodeView()
|
||||
.should(
|
||||
'have.css',
|
||||
'transform',
|
||||
`matrix(${PINCH_ZOOM_IN_FACTOR}, 0, 0, ${PINCH_ZOOM_IN_FACTOR}, 0, 0)`,
|
||||
const zoomAndCheck = (action: 'zoomIn' | 'zoomOut', expectedFactor: number) => {
|
||||
WorkflowPage.getters[`${action}Button`]().click();
|
||||
checkZoomLevel(expectedFactor);
|
||||
};
|
||||
|
||||
it('should zoom in', () => {
|
||||
WorkflowPage.getters.zoomInButton().should('be.visible');
|
||||
getContainer().then(($nodeView) => {
|
||||
const initialTransform = $nodeView.css('transform');
|
||||
const initialScale =
|
||||
initialTransform === 'none' ? 1 : parseFloat(initialTransform.split(',')[0].slice(7));
|
||||
|
||||
zoomAndCheck('zoomIn', initialScale * ZOOM_IN_X1_FACTOR);
|
||||
zoomAndCheck('zoomIn', initialScale * ZOOM_IN_X2_FACTOR);
|
||||
});
|
||||
});
|
||||
|
||||
it('should zoom out', () => {
|
||||
zoomAndCheck('zoomOut', ZOOM_OUT_X1_FACTOR);
|
||||
zoomAndCheck('zoomOut', ZOOM_OUT_X2_FACTOR);
|
||||
});
|
||||
|
||||
it('should zoom using scroll or pinch gesture', () => {
|
||||
WorkflowPage.actions.pinchToZoom(1, 'zoomIn');
|
||||
|
||||
// V2 Canvas is using the same zoom factor for both pinch and scroll
|
||||
cy.ifCanvasVersion(
|
||||
() => checkZoomLevel(PINCH_ZOOM_IN_FACTOR),
|
||||
() => checkZoomLevel(ZOOM_IN_X1_FACTOR),
|
||||
);
|
||||
|
||||
WorkflowPage.actions.pinchToZoom(1, 'zoomOut');
|
||||
// Zoom in 1x + Zoom out 1x should reset to default (=1)
|
||||
WorkflowPage.getters.nodeView().should('have.css', 'transform', 'matrix(1, 0, 0, 1, 0, 0)');
|
||||
WorkflowPage.actions.pinchToZoom(1, 'zoomOut');
|
||||
checkZoomLevel(1); // Zoom in 1x + Zoom out 1x should reset to default (=1)
|
||||
|
||||
WorkflowPage.actions.pinchToZoom(1, 'zoomOut');
|
||||
WorkflowPage.getters
|
||||
.nodeView()
|
||||
.should(
|
||||
'have.css',
|
||||
'transform',
|
||||
`matrix(${PINCH_ZOOM_OUT_FACTOR}, 0, 0, ${PINCH_ZOOM_OUT_FACTOR}, 0, 0)`,
|
||||
WorkflowPage.actions.pinchToZoom(1, 'zoomOut');
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => checkZoomLevel(PINCH_ZOOM_OUT_FACTOR),
|
||||
() => checkZoomLevel(ZOOM_OUT_X1_FACTOR),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset zoom', () => {
|
||||
// Reset zoom should not appear until zoom level changed
|
||||
WorkflowPage.getters.resetZoomButton().should('not.exist');
|
||||
WorkflowPage.getters.zoomInButton().click();
|
||||
WorkflowPage.getters.resetZoomButton().should('be.visible').click();
|
||||
WorkflowPage.getters
|
||||
.nodeView()
|
||||
.should(
|
||||
'have.css',
|
||||
'transform',
|
||||
`matrix(${DEFAULT_ZOOM_FACTOR}, 0, 0, ${DEFAULT_ZOOM_FACTOR}, 0, 0)`,
|
||||
);
|
||||
});
|
||||
it('should reset zoom', () => {
|
||||
WorkflowPage.getters.resetZoomButton().should('not.exist');
|
||||
WorkflowPage.getters.zoomInButton().click();
|
||||
WorkflowPage.getters.resetZoomButton().should('be.visible').click();
|
||||
checkZoomLevel(DEFAULT_ZOOM_FACTOR);
|
||||
});
|
||||
|
||||
it('should zoom to fit', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
// At this point last added node should be off-screen
|
||||
WorkflowPage.getters.canvasNodes().last().should('not.be.visible');
|
||||
WorkflowPage.getters.zoomToFitButton().click();
|
||||
WorkflowPage.getters.canvasNodes().last().should('be.visible');
|
||||
it('should zoom to fit', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
// At this point last added node should be off-screen
|
||||
WorkflowPage.getters.canvasNodes().last().should('not.be.visible');
|
||||
WorkflowPage.getters.zoomToFitButton().click();
|
||||
WorkflowPage.getters.canvasNodes().last().should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable node (context menu or shortcut)', () => {
|
||||
|
@ -426,9 +464,9 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
cy.reload();
|
||||
cy.waitForLoad();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 2);
|
||||
cy.get('.rect-input-endpoint.jtk-endpoint-connected').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', () => {
|
||||
cy.fixture('workflow-with-unknown-credentials.json').then((data) => {
|
||||
cy.get('body').paste(JSON.stringify(data));
|
||||
|
@ -441,6 +479,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Unknown nodes should still render connection endpoints
|
||||
it('should render connections correctly if unkown nodes are present', () => {
|
||||
const unknownNodeName = 'Unknown node';
|
||||
cy.createFixtureWorkflow('workflow-with-unknown-nodes.json', 'Unknown nodes');
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { SCHEDULE_TRIGGER_NODE_NAME, EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
||||
import { NDV, WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages';
|
||||
import { clearNotifications, errorToast, successToast } from '../pages/notifications';
|
||||
import { isCanvasV2 } from '../utils/workflowUtils';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
const executionsTab = new WorkflowExecutionsTab();
|
||||
|
@ -117,15 +118,22 @@ describe('Execution', () => {
|
|||
.canvasNodeByName('Manual')
|
||||
.within(() => cy.get('.fa-check'))
|
||||
.should('exist');
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Wait')
|
||||
.within(() => cy.get('.fa-sync-alt').should('not.visible'));
|
||||
|
||||
if (isCanvasV2()) {
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Wait')
|
||||
.within(() => cy.get('.fa-sync-alt').should('not.exist'));
|
||||
} else {
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Wait')
|
||||
.within(() => cy.get('.fa-sync-alt').should('not.be.visible'));
|
||||
}
|
||||
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Set')
|
||||
.within(() => cy.get('.fa-check').should('not.exist'));
|
||||
|
||||
successToast().should('be.visible');
|
||||
clearNotifications();
|
||||
|
||||
// Clear execution data
|
||||
workflowPage.getters.clearExecutionDataButton().should('be.visible');
|
||||
|
@ -206,6 +214,7 @@ describe('Execution', () => {
|
|||
workflowPage.getters.clearExecutionDataButton().should('not.exist');
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Webhook should show waiting state but it doesn't
|
||||
it('should test webhook workflow stop', () => {
|
||||
cy.createFixtureWorkflow('Webhook_wait_set.json');
|
||||
|
||||
|
@ -267,9 +276,17 @@ describe('Execution', () => {
|
|||
.canvasNodeByName('Webhook')
|
||||
.within(() => cy.get('.fa-check'))
|
||||
.should('exist');
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Wait')
|
||||
.within(() => cy.get('.fa-sync-alt').should('not.visible'));
|
||||
|
||||
if (isCanvasV2()) {
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Wait')
|
||||
.within(() => cy.get('.fa-sync-alt').should('not.exist'));
|
||||
} else {
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Wait')
|
||||
.within(() => cy.get('.fa-sync-alt').should('not.be.visible'));
|
||||
}
|
||||
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Set')
|
||||
.within(() => cy.get('.fa-check').should('not.exist'));
|
||||
|
@ -295,6 +312,7 @@ describe('Execution', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Missing pinned states for `edge-label-wrapper`
|
||||
describe('connections should be colored differently for pinned data', () => {
|
||||
beforeEach(() => {
|
||||
cy.createFixtureWorkflow('Schedule_pinned.json');
|
||||
|
|
|
@ -2,7 +2,7 @@ import { BasePage } from './base';
|
|||
import { NodeCreator } from './features/node-creator';
|
||||
import { META_KEY } from '../constants';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import { getUniqueWorkflowName } from '../utils/workflowUtils';
|
||||
import { getUniqueWorkflowName, isCanvasV2 } from '../utils/workflowUtils';
|
||||
|
||||
const nodeCreator = new NodeCreator();
|
||||
export class WorkflowPage extends BasePage {
|
||||
|
@ -27,7 +27,11 @@ export class WorkflowPage extends BasePage {
|
|||
nodeCreatorSearchBar: () => cy.getByTestId('node-creator-search-bar'),
|
||||
nodeCreatorPlusButton: () => cy.getByTestId('node-creator-plus-button'),
|
||||
canvasPlusButton: () => cy.getByTestId('canvas-plus-button'),
|
||||
canvasNodes: () => cy.getByTestId('canvas-node'),
|
||||
canvasNodes: () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.getByTestId('canvas-node'),
|
||||
() => cy.getByTestId('canvas-node').not('[data-node-type="n8n-nodes-internal.addNodes"]'),
|
||||
),
|
||||
canvasNodeByName: (nodeName: string) =>
|
||||
this.getters.canvasNodes().filter(`:contains(${nodeName})`),
|
||||
nodeIssuesByName: (nodeName: string) =>
|
||||
|
@ -37,6 +41,17 @@ export class WorkflowPage extends BasePage {
|
|||
.should('have.length.greaterThan', 0)
|
||||
.findChildByTestId('node-issues'),
|
||||
getEndpointSelector: (type: 'input' | 'output' | 'plus', nodeName: string, index = 0) => {
|
||||
if (isCanvasV2()) {
|
||||
if (type === 'input') {
|
||||
return `[data-test-id="canvas-node-input-handle"][data-node-name="${nodeName}"][data-handle-index="${index}"]`;
|
||||
}
|
||||
if (type === 'output') {
|
||||
return `[data-test-id="canvas-node-output-handle"][data-node-name="${nodeName}"][data-handle-index="${index}"]`;
|
||||
}
|
||||
if (type === 'plus') {
|
||||
return `[data-test-id="canvas-node-output-handle"][data-node-name="${nodeName}"][data-handle-index="${index}"] [data-test-id="canvas-handle-plus"] .clickable`;
|
||||
}
|
||||
}
|
||||
return `[data-endpoint-name='${nodeName}'][data-endpoint-type='${type}'][data-input-index='${index}']`;
|
||||
},
|
||||
canvasNodeInputEndpointByName: (nodeName: string, index = 0) => {
|
||||
|
@ -46,7 +61,15 @@ export class WorkflowPage extends BasePage {
|
|||
return cy.get(this.getters.getEndpointSelector('output', nodeName, index));
|
||||
},
|
||||
canvasNodePlusEndpointByName: (nodeName: string, index = 0) => {
|
||||
return cy.get(this.getters.getEndpointSelector('plus', nodeName, index));
|
||||
return cy.ifCanvasVersion(
|
||||
() => cy.get(this.getters.getEndpointSelector('plus', nodeName, index)),
|
||||
() =>
|
||||
cy
|
||||
.get(
|
||||
`[data-test-id="canvas-node-output-handle"][data-node-name="${nodeName}"] [data-test-id="canvas-handle-plus"] .clickable`,
|
||||
)
|
||||
.eq(index),
|
||||
);
|
||||
},
|
||||
activatorSwitch: () => cy.getByTestId('workflow-activate-switch'),
|
||||
workflowMenu: () => cy.getByTestId('workflow-menu'),
|
||||
|
@ -56,13 +79,29 @@ export class WorkflowPage extends BasePage {
|
|||
expressionModalInput: () => cy.getByTestId('expression-modal-input').find('[role=textbox]'),
|
||||
expressionModalOutput: () => cy.getByTestId('expression-modal-output'),
|
||||
|
||||
nodeViewRoot: () => cy.getByTestId('node-view-root'),
|
||||
nodeViewRoot: () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.getByTestId('node-view-root'),
|
||||
() => this.getters.nodeView(),
|
||||
),
|
||||
copyPasteInput: () => cy.getByTestId('hidden-copy-paste'),
|
||||
nodeConnections: () => cy.get('.jtk-connector'),
|
||||
nodeConnections: () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.get('.jtk-connector'),
|
||||
() => cy.getByTestId('edge-label-wrapper'),
|
||||
),
|
||||
zoomToFitButton: () => cy.getByTestId('zoom-to-fit'),
|
||||
nodeEndpoints: () => cy.get('.jtk-endpoint-connected'),
|
||||
disabledNodes: () => cy.get('.node-box.disabled'),
|
||||
selectedNodes: () => this.getters.canvasNodes().filter('.jtk-drag-selected'),
|
||||
disabledNodes: () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.get('.node-box.disabled'),
|
||||
() => cy.get('[data-test-id="canvas-trigger-node"][class*="disabled"]'),
|
||||
),
|
||||
selectedNodes: () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => this.getters.canvasNodes().filter('.jtk-drag-selected'),
|
||||
() => this.getters.canvasNodes().parent().filter('.selected'),
|
||||
),
|
||||
// Workflow menu items
|
||||
workflowMenuItemDuplicate: () => cy.getByTestId('workflow-menu-item-duplicate'),
|
||||
workflowMenuItemDownload: () => cy.getByTestId('workflow-menu-item-download'),
|
||||
|
@ -92,8 +131,21 @@ export class WorkflowPage extends BasePage {
|
|||
shareButton: () => cy.getByTestId('workflow-share-button'),
|
||||
|
||||
duplicateWorkflowModal: () => cy.getByTestId('duplicate-modal'),
|
||||
nodeViewBackground: () => cy.getByTestId('node-view-background'),
|
||||
nodeView: () => cy.getByTestId('node-view'),
|
||||
nodeViewBackground: () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.getByTestId('node-view-background'),
|
||||
() => cy.getByTestId('canvas'),
|
||||
),
|
||||
nodeView: () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.getByTestId('node-view'),
|
||||
() => cy.get('[data-test-id="canvas-wrapper"]'),
|
||||
),
|
||||
canvasViewport: () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.getByTestId('node-view'),
|
||||
() => cy.get('.vue-flow__transformationpane.vue-flow__container'),
|
||||
),
|
||||
inlineExpressionEditorInput: () =>
|
||||
cy.getByTestId('inline-expression-editor-input').find('[role=textbox]'),
|
||||
inlineExpressionEditorOutput: () => cy.getByTestId('inline-expression-editor-output'),
|
||||
|
@ -115,12 +167,26 @@ export class WorkflowPage extends BasePage {
|
|||
ndvParameters: () => cy.getByTestId('parameter-item'),
|
||||
nodeCredentialsLabel: () => cy.getByTestId('credentials-label'),
|
||||
getConnectionBetweenNodes: (sourceNodeName: string, targetNodeName: string) =>
|
||||
cy.get(
|
||||
`.jtk-connector[data-source-node="${sourceNodeName}"][data-target-node="${targetNodeName}"]`,
|
||||
cy.ifCanvasVersion(
|
||||
() =>
|
||||
cy.get(
|
||||
`.jtk-connector[data-source-node="${sourceNodeName}"][data-target-node="${targetNodeName}"]`,
|
||||
),
|
||||
() =>
|
||||
cy.get(
|
||||
`[data-test-id="edge-label-wrapper"][data-source-node-name="${sourceNodeName}"][data-target-node-name="${targetNodeName}"]`,
|
||||
),
|
||||
),
|
||||
getConnectionActionsBetweenNodes: (sourceNodeName: string, targetNodeName: string) =>
|
||||
cy.get(
|
||||
`.connection-actions[data-source-node="${sourceNodeName}"][data-target-node="${targetNodeName}"]`,
|
||||
cy.ifCanvasVersion(
|
||||
() =>
|
||||
cy.get(
|
||||
`.connection-actions[data-source-node="${sourceNodeName}"][data-target-node="${targetNodeName}"]`,
|
||||
),
|
||||
() =>
|
||||
cy.get(
|
||||
`[data-test-id="edge-label-wrapper"][data-source-node-name="${sourceNodeName}"][data-target-node-name="${targetNodeName}"] [data-test-id="canvas-edge-toolbar"]`,
|
||||
),
|
||||
),
|
||||
addStickyButton: () => cy.getByTestId('add-sticky-button'),
|
||||
stickies: () => cy.getByTestId('sticky'),
|
||||
|
@ -128,6 +194,18 @@ export class WorkflowPage extends BasePage {
|
|||
workflowHistoryButton: () => cy.getByTestId('workflow-history-button'),
|
||||
colors: () => cy.getByTestId('color'),
|
||||
contextMenuAction: (action: string) => cy.getByTestId(`context-menu-item-${action}`),
|
||||
getNodeLeftPosition: (element: JQuery<HTMLElement>) => {
|
||||
if (isCanvasV2()) {
|
||||
return parseFloat(element.parent().css('transform').split(',')[4]);
|
||||
}
|
||||
return parseFloat(element.css('left'));
|
||||
},
|
||||
getNodeTopPosition: (element: JQuery<HTMLElement>) => {
|
||||
if (isCanvasV2()) {
|
||||
return parseFloat(element.parent().css('transform').split(',')[5]);
|
||||
}
|
||||
return parseFloat(element.css('top'));
|
||||
},
|
||||
};
|
||||
|
||||
actions = {
|
||||
|
@ -332,7 +410,7 @@ export class WorkflowPage extends BasePage {
|
|||
pinchToZoom: (steps: number, mode: 'zoomIn' | 'zoomOut' = 'zoomIn') => {
|
||||
cy.window().then((win) => {
|
||||
// Pinch-to-zoom simulates a 'wheel' event with ctrlKey: true (same as zooming by scrolling)
|
||||
this.getters.nodeViewBackground().trigger('wheel', {
|
||||
this.getters.nodeView().trigger('wheel', {
|
||||
force: true,
|
||||
bubbles: true,
|
||||
ctrlKey: true,
|
||||
|
@ -391,9 +469,12 @@ export class WorkflowPage extends BasePage {
|
|||
action?: string,
|
||||
) => {
|
||||
this.getters.getConnectionBetweenNodes(sourceNodeName, targetNodeName).first().realHover();
|
||||
this.getters
|
||||
.getConnectionActionsBetweenNodes(sourceNodeName, targetNodeName)
|
||||
.find('.add')
|
||||
const connectionsBetweenNodes = () =>
|
||||
this.getters.getConnectionActionsBetweenNodes(sourceNodeName, targetNodeName);
|
||||
cy.ifCanvasVersion(
|
||||
() => connectionsBetweenNodes().find('.add'),
|
||||
() => connectionsBetweenNodes().get('[data-test-id="add-connection-button"]'),
|
||||
)
|
||||
.first()
|
||||
.click({ force: true });
|
||||
|
||||
|
@ -401,9 +482,12 @@ export class WorkflowPage extends BasePage {
|
|||
},
|
||||
deleteNodeBetweenNodes: (sourceNodeName: string, targetNodeName: string) => {
|
||||
this.getters.getConnectionBetweenNodes(sourceNodeName, targetNodeName).first().realHover();
|
||||
this.getters
|
||||
.getConnectionActionsBetweenNodes(sourceNodeName, targetNodeName)
|
||||
.find('.delete')
|
||||
const connectionsBetweenNodes = () =>
|
||||
this.getters.getConnectionActionsBetweenNodes(sourceNodeName, targetNodeName);
|
||||
cy.ifCanvasVersion(
|
||||
() => connectionsBetweenNodes().find('.delete'),
|
||||
() => connectionsBetweenNodes().get('[data-test-id="delete-connection-button"]'),
|
||||
)
|
||||
.first()
|
||||
.click({ force: true });
|
||||
},
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
N8N_AUTH_COOKIE,
|
||||
} from '../constants';
|
||||
import { WorkflowPage } from '../pages';
|
||||
import { getUniqueWorkflowName } from '../utils/workflowUtils';
|
||||
import { getUniqueWorkflowName, isCanvasV2 } from '../utils/workflowUtils';
|
||||
|
||||
Cypress.Commands.add('setAppDate', (targetDate: number | Date) => {
|
||||
cy.window().then((win) => {
|
||||
|
@ -26,6 +26,10 @@ Cypress.Commands.add('getByTestId', (selector, ...args) => {
|
|||
return cy.get(`[data-test-id="${selector}"]`, ...args);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('ifCanvasVersion', (getterV1, getterV2) => {
|
||||
return isCanvasV2() ? getterV2() : getterV1();
|
||||
});
|
||||
|
||||
Cypress.Commands.add(
|
||||
'createFixtureWorkflow',
|
||||
(fixtureKey: string, workflowName = getUniqueWorkflowName()) => {
|
||||
|
|
|
@ -20,6 +20,11 @@ beforeEach(() => {
|
|||
win.localStorage.setItem('N8N_THEME', 'light');
|
||||
win.localStorage.setItem('N8N_AUTOCOMPLETE_ONBOARDED', 'true');
|
||||
win.localStorage.setItem('N8N_MAPPING_ONBOARDED', 'true');
|
||||
|
||||
const nodeViewVersion = Cypress.env('NODE_VIEW_VERSION');
|
||||
if (nodeViewVersion) {
|
||||
win.localStorage.setItem('NodeView.version', nodeViewVersion);
|
||||
}
|
||||
});
|
||||
|
||||
cy.intercept('GET', '/rest/settings', (req) => {
|
||||
|
|
|
@ -28,6 +28,7 @@ declare global {
|
|||
selector: string,
|
||||
...args: Array<Partial<Loggable & Timeoutable & Withinable & Shadow> | undefined>
|
||||
): Chainable<JQuery<HTMLElement>>;
|
||||
ifCanvasVersion<T1, T2>(getterV1: () => T1, getterV2: () => T2): T1 | T2;
|
||||
findChildByTestId(childTestId: string): Chainable<JQuery<HTMLElement>>;
|
||||
/**
|
||||
* Creates a workflow from the given fixture and optionally renames it.
|
||||
|
|
|
@ -3,3 +3,7 @@ import { nanoid } from 'nanoid';
|
|||
export function getUniqueWorkflowName(workflowNamePrefix?: string) {
|
||||
return workflowNamePrefix ? `${workflowNamePrefix} ${nanoid(12)}` : nanoid(12);
|
||||
}
|
||||
|
||||
export function isCanvasV2() {
|
||||
return Cypress.env('NODE_VIEW_VERSION') === 2;
|
||||
}
|
||||
|
|
|
@ -78,14 +78,14 @@ watch(
|
|||
void externalHooks.run('expressionEdit.dialogVisibleChanged', {
|
||||
dialogVisible: newValue,
|
||||
parameter: props.parameter,
|
||||
value: props.modelValue,
|
||||
value: props.modelValue.toString(),
|
||||
resolvedExpressionValue,
|
||||
});
|
||||
|
||||
if (!newValue) {
|
||||
const telemetryPayload = createExpressionTelemetryPayload(
|
||||
segments.value,
|
||||
props.modelValue,
|
||||
props.modelValue.toString(),
|
||||
workflowsStore.workflowId,
|
||||
ndvStore.pushRef,
|
||||
ndvStore.activeNode?.type ?? '',
|
||||
|
|
|
@ -668,7 +668,7 @@ onBeforeUnmount(() => {
|
|||
width="auto"
|
||||
:append-to="`#${APP_MODALS_ELEMENT_ID}`"
|
||||
data-test-id="ndv"
|
||||
z-index="1800"
|
||||
:z-index="1800"
|
||||
:data-has-output-connection="hasOutputConnection"
|
||||
>
|
||||
<n8n-tooltip
|
||||
|
|
|
@ -713,7 +713,8 @@ const populateSettings = () => {
|
|||
},
|
||||
],
|
||||
default: 'stopWorkflow',
|
||||
noDataExpression: i18n.baseText('nodeSettings.onError.description'),
|
||||
description: i18n.baseText('nodeSettings.onError.description'),
|
||||
noDataExpression: true,
|
||||
},
|
||||
] as INodeProperties[]),
|
||||
);
|
||||
|
|
|
@ -52,7 +52,7 @@ const { nodes: mappedNodes, connections: mappedConnections } = useCanvasMapping(
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.wrapper">
|
||||
<div :class="$style.wrapper" data-test-id="canvas-wrapper">
|
||||
<div :class="$style.canvas">
|
||||
<Canvas
|
||||
v-if="workflow"
|
||||
|
|
|
@ -117,6 +117,9 @@ function onDelete() {
|
|||
<EdgeLabelRenderer>
|
||||
<div
|
||||
data-test-id="edge-label-wrapper"
|
||||
:data-source-node-name="sourceNode?.label"
|
||||
:data-target-node-name="targetNode?.label"
|
||||
:data-edge-status="status"
|
||||
:style="edgeToolbarStyle"
|
||||
:class="$style.edgeLabelWrapper"
|
||||
@mouseenter="isHovered = true"
|
||||
|
|
|
@ -73,6 +73,7 @@ function onClickAdd() {
|
|||
v-if="!isConnected && !isReadOnly"
|
||||
v-show="isHandlePlusVisible"
|
||||
data-test-id="canvas-handle-plus"
|
||||
:data-plus-type="plusType"
|
||||
:line-size="plusLineSize"
|
||||
:handle-classes="handleClasses"
|
||||
:type="plusType"
|
||||
|
|
|
@ -259,6 +259,7 @@ onBeforeUnmount(() => {
|
|||
<div
|
||||
:class="[$style.canvasNode, { [$style.showToolbar]: showToolbar }]"
|
||||
data-test-id="canvas-node"
|
||||
:data-node-type="data.type"
|
||||
>
|
||||
<template
|
||||
v-for="source in mappedOutputs"
|
||||
|
@ -269,7 +270,9 @@ onBeforeUnmount(() => {
|
|||
:mode="CanvasConnectionMode.Output"
|
||||
:is-read-only="readOnly"
|
||||
:is-valid-connection="isValidConnection"
|
||||
:data-node-name="label"
|
||||
data-test-id="canvas-node-output-handle"
|
||||
:data-handle-index="source.index"
|
||||
@add="onAdd"
|
||||
/>
|
||||
</template>
|
||||
|
@ -284,6 +287,8 @@ onBeforeUnmount(() => {
|
|||
:is-read-only="readOnly"
|
||||
:is-valid-connection="isValidConnection"
|
||||
data-test-id="canvas-node-input-handle"
|
||||
:data-handle-index="target.index"
|
||||
:data-node-name="label"
|
||||
@add="onAdd"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1784,9 +1784,6 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
}, []);
|
||||
|
||||
workflowsStore.addWorkflowTagIds(tagIds);
|
||||
setTimeout(() => {
|
||||
nodeHelpers.addPinDataConnections(workflowsStore.pinnedWorkflowData);
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchWorkflowDataFromUrl(url: string): Promise<IWorkflowDataUpdate | undefined> {
|
||||
|
|
|
@ -1443,6 +1443,7 @@ function selectNodes(ids: string[]) {
|
|||
function onClickPane(position: CanvasNode['position']) {
|
||||
lastClickPosition.value = [position.x, position.y];
|
||||
uiStore.isCreateNodeActive = false;
|
||||
setNodeSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1704,7 +1704,7 @@ export default defineComponent({
|
|||
if (data.nodes.length > 0) {
|
||||
if (!isCut) {
|
||||
this.showMessage({
|
||||
title: 'Copied!',
|
||||
title: this.$locale.baseText('generic.copiedToClipboard'),
|
||||
message: '',
|
||||
type: 'success',
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue