mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat: Update e2e tests to run on new canvas (no-changelog) (#12784)
This commit is contained in:
parent
2c58d47f8e
commit
afbbfa3a90
7
.github/workflows/e2e-reusable.yml
vendored
7
.github/workflows/e2e-reusable.yml
vendored
|
@ -41,11 +41,6 @@ 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.'
|
||||
|
@ -165,7 +160,7 @@ jobs:
|
|||
spec: '${{ inputs.spec }}'
|
||||
env:
|
||||
NODE_OPTIONS: --dns-result-order=ipv4first
|
||||
CYPRESS_NODE_VIEW_VERSION: ${{ inputs.node_view_version }}
|
||||
CYPRESS_NODE_VIEW_VERSION: 2
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
E2E_TESTS: true
|
||||
|
|
81
cypress/composables/webhooks.ts
Normal file
81
cypress/composables/webhooks.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { BACKEND_BASE_URL } from '../constants';
|
||||
import { NDV, WorkflowPage } from '../pages';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
export const waitForWebhook = 500;
|
||||
|
||||
export interface SimpleWebhookCallOptions {
|
||||
method: string;
|
||||
webhookPath: string;
|
||||
responseCode?: number;
|
||||
respondWith?: string;
|
||||
executeNow?: boolean;
|
||||
responseData?: string;
|
||||
authentication?: string;
|
||||
}
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
||||
export const simpleWebhookCall = (options: SimpleWebhookCallOptions) => {
|
||||
const {
|
||||
authentication,
|
||||
method,
|
||||
webhookPath,
|
||||
responseCode,
|
||||
respondWith,
|
||||
responseData,
|
||||
executeNow = true,
|
||||
} = options;
|
||||
|
||||
workflowPage.actions.addInitialNodeToCanvas('Webhook');
|
||||
workflowPage.actions.openNode('Webhook');
|
||||
|
||||
cy.getByTestId('parameter-input-httpMethod').click();
|
||||
getVisibleSelect().find('.option-headline').contains(method).click();
|
||||
cy.getByTestId('parameter-input-path')
|
||||
.find('.parameter-input')
|
||||
.find('input')
|
||||
.clear()
|
||||
.type(webhookPath);
|
||||
|
||||
if (authentication) {
|
||||
cy.getByTestId('parameter-input-authentication').click();
|
||||
getVisibleSelect().find('.option-headline').contains(authentication).click();
|
||||
}
|
||||
|
||||
if (responseCode) {
|
||||
cy.get('.param-options').click();
|
||||
getVisibleSelect().contains('Response Code').click();
|
||||
cy.get('.parameter-item-wrapper > .parameter-input-list-wrapper').children().click();
|
||||
getVisibleSelect().contains('201').click();
|
||||
}
|
||||
|
||||
if (respondWith) {
|
||||
cy.getByTestId('parameter-input-responseMode').click();
|
||||
getVisibleSelect().find('.option-headline').contains(respondWith).click();
|
||||
}
|
||||
|
||||
if (responseData) {
|
||||
cy.getByTestId('parameter-input-responseData').click();
|
||||
getVisibleSelect().find('.option-headline').contains(responseData).click();
|
||||
}
|
||||
|
||||
const callEndpoint = (fn: (response: Cypress.Response<unknown>) => void) => {
|
||||
cy.request(method, `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then(fn);
|
||||
};
|
||||
|
||||
if (executeNow) {
|
||||
ndv.actions.execute();
|
||||
cy.wait(waitForWebhook);
|
||||
|
||||
callEndpoint((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
ndv.getters.outputPanel().contains('headers');
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
callEndpoint,
|
||||
};
|
||||
};
|
|
@ -67,6 +67,13 @@ export function getInputPlusHandleByType(nodeName: string, endpointType: Endpoin
|
|||
);
|
||||
}
|
||||
|
||||
export function getOutputHandle(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}"]`),
|
||||
);
|
||||
}
|
||||
|
||||
export function getOutputPlusHandle(nodeName: string) {
|
||||
return cy.ifCanvasVersion(
|
||||
() => cy.get(`.add-output-endpoint[data-endpoint-name="${nodeName}"]`),
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import { getCanvasNodes } from '../composables/workflow';
|
||||
import {
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
CODE_NODE_NAME,
|
||||
SET_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
} from '../constants';
|
||||
import { MessageBox as MessageBoxClass } from '../pages/modals/message-box';
|
||||
import { NDV } from '../pages/ndv';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
||||
// Suite-specific constants
|
||||
const CODE_NODE_NEW_NAME = 'Something else';
|
||||
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
const messageBox = new MessageBoxClass();
|
||||
const ndv = new NDV();
|
||||
|
@ -20,40 +19,6 @@ 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);
|
||||
WorkflowPage.actions.addNodeBetweenNodes(
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
CODE_NODE_NAME,
|
||||
SET_NODE_NAME,
|
||||
);
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
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', cssLeft + 'px')
|
||||
.should('have.css', 'top', cssTop + 'px');
|
||||
});
|
||||
});
|
||||
|
||||
it('should undo/redo deleting node using context menu', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
@ -115,34 +80,60 @@ 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(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
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.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.zoomToFit();
|
||||
|
||||
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);
|
||||
getCanvasNodes()
|
||||
.last()
|
||||
.then(($node) => {
|
||||
const { x: x1, y: y1 } = $node[0].getBoundingClientRect();
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => {
|
||||
cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], {
|
||||
clickToFinish: true,
|
||||
});
|
||||
},
|
||||
() => {
|
||||
cy.drag(getCanvasNodes().last(), [50, 150], {
|
||||
realMouse: true,
|
||||
abs: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
getCanvasNodes()
|
||||
.last()
|
||||
.then(($node) => {
|
||||
const { x: x2, y: y2 } = $node[0].getBoundingClientRect();
|
||||
expect(x2).to.be.greaterThan(x1);
|
||||
expect(y2).to.be.greaterThan(y1);
|
||||
});
|
||||
|
||||
WorkflowPage.actions.hitUndo();
|
||||
|
||||
getCanvasNodes()
|
||||
.last()
|
||||
.then(($node) => {
|
||||
const { x: x3, y: y3 } = $node[0].getBoundingClientRect();
|
||||
expect(x3).to.equal(x1);
|
||||
expect(y3).to.equal(y1);
|
||||
});
|
||||
|
||||
WorkflowPage.actions.hitRedo();
|
||||
|
||||
getCanvasNodes()
|
||||
.last()
|
||||
.then(($node) => {
|
||||
const { x: x4, y: y4 } = $node[0].getBoundingClientRect();
|
||||
expect(x4).to.be.greaterThan(x1);
|
||||
expect(y4).to.be.greaterThan(y1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should undo/redo deleting a connection using context menu', () => {
|
||||
|
@ -155,17 +146,6 @@ describe('Undo/Redo', () => {
|
|||
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);
|
||||
cy.drag('.rect-input-endpoint.jtk-endpoint-connected', [0, -100]);
|
||||
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);
|
||||
});
|
||||
|
||||
it('should undo/redo disabling a node using context menu', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
|
@ -204,23 +184,6 @@ 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);
|
||||
WorkflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').trigger('keydown', { key: 'F2' });
|
||||
cy.get('.rename-prompt').should('be.visible');
|
||||
cy.get('body').type(CODE_NODE_NEW_NAME);
|
||||
cy.get('body').type('{enter}');
|
||||
WorkflowPage.actions.hitUndo();
|
||||
cy.get('body').type('{esc}');
|
||||
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).should('exist');
|
||||
WorkflowPage.actions.hitRedo();
|
||||
cy.get('body').type('{esc}');
|
||||
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NEW_NAME).should('exist');
|
||||
});
|
||||
|
||||
it('should undo/redo duplicating a node', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
|
@ -243,77 +206,6 @@ 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);
|
||||
// WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
|
||||
// Disable last node
|
||||
WorkflowPage.getters.canvasNodes().last().click();
|
||||
WorkflowPage.actions.hitDisableNodeShortcut();
|
||||
|
||||
// Move first one
|
||||
WorkflowPage.actions
|
||||
.getNodePosition(WorkflowPage.getters.canvasNodes().first())
|
||||
.then((initialPosition) => {
|
||||
WorkflowPage.getters.canvasNodes().first().click();
|
||||
cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150], {
|
||||
clickToFinish: true,
|
||||
});
|
||||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.first()
|
||||
.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();
|
||||
cy.get('body').type('{backspace}');
|
||||
|
||||
// First undo: Should return deleted node
|
||||
WorkflowPage.actions.hitUndo();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 3);
|
||||
// Second undo: Should move first node to it's original position
|
||||
WorkflowPage.actions.hitUndo();
|
||||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.first()
|
||||
.should('have.css', 'left', `${initialPosition.left}px`)
|
||||
.should('have.css', 'top', `${initialPosition.top}px`);
|
||||
// Third undo: Should enable last node
|
||||
WorkflowPage.actions.hitUndo();
|
||||
WorkflowPage.getters.disabledNodes().should('have.length', 0);
|
||||
|
||||
// First redo: Should disable last node
|
||||
WorkflowPage.actions.hitRedo();
|
||||
WorkflowPage.getters.disabledNodes().should('have.length', 1);
|
||||
// Second redo: Should move the first node
|
||||
WorkflowPage.actions.hitRedo();
|
||||
WorkflowPage.getters
|
||||
.canvasNodes()
|
||||
.first()
|
||||
.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);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to copy and paste pinned data nodes in workflows with dynamic Switch node', () => {
|
||||
cy.fixture('Test_workflow_form_switch.json').then((data) => {
|
||||
cy.get('body').paste(JSON.stringify(data));
|
||||
|
|
|
@ -129,7 +129,7 @@ describe('Inline expression editor', () => {
|
|||
|
||||
// Run workflow
|
||||
ndv.actions.close();
|
||||
WorkflowPage.actions.executeNode('No Operation', { anchor: 'topLeft' });
|
||||
WorkflowPage.actions.executeNode('No Operation, do nothing', { anchor: 'topLeft' });
|
||||
WorkflowPage.actions.openNode('Hacker News');
|
||||
WorkflowPage.actions.openInlineExpressionEditor();
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ import {
|
|||
CODE_NODE_NAME,
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
IF_NODE_NAME,
|
||||
HTTP_REQUEST_NODE_NAME,
|
||||
} from './../constants';
|
||||
import { getCanvasPane } from '../composables/workflow';
|
||||
import { successToast } from '../pages/notifications';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
||||
|
@ -16,64 +16,12 @@ 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');
|
||||
WorkflowPage.getters.zoomToFitButton().should('be.visible');
|
||||
WorkflowPage.getters.zoomInButton().should('be.visible');
|
||||
WorkflowPage.getters.zoomOutButton().should('be.visible');
|
||||
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 });
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
||||
|
||||
WorkflowPage.getters.nodeViewBackground().click(600, 400, { force: true });
|
||||
WorkflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
||||
|
||||
// Change connection from Set to Set1
|
||||
cy.draganddrop(
|
||||
WorkflowPage.getters.getEndpointSelector('input', EDIT_FIELDS_SET_NODE_NAME),
|
||||
WorkflowPage.getters.getEndpointSelector('input', `${EDIT_FIELDS_SET_NODE_NAME}1`),
|
||||
);
|
||||
|
||||
WorkflowPage.getters
|
||||
.getConnectionBetweenNodes(MANUAL_TRIGGER_NODE_DISPLAY_NAME, `${EDIT_FIELDS_SET_NODE_NAME}1`)
|
||||
.should('be.visible');
|
||||
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
||||
// Disconnect Set1
|
||||
cy.drag(
|
||||
WorkflowPage.getters.getEndpointSelector('input', `${EDIT_FIELDS_SET_NODE_NAME}1`),
|
||||
[-200, 100],
|
||||
);
|
||||
WorkflowPage.getters.nodeConnections().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 a connected node using plus endpoint', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodePlusEndpointByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
|
@ -116,7 +64,7 @@ describe('Canvas Actions', () => {
|
|||
it('should add disconnected node if nothing is selected', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
// Deselect nodes
|
||||
WorkflowPage.getters.nodeView().click({ force: true });
|
||||
getCanvasPane().click({ force: true });
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 2);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||
|
@ -166,15 +114,6 @@ describe('Canvas Actions', () => {
|
|||
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();
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
cy.drag(WorkflowPage.getters.getEndpointSelector('input', CODE_NODE_NAME), [0, -100]);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 0);
|
||||
});
|
||||
|
||||
describe('Node hover actions', () => {
|
||||
it('should execute node', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
|
@ -239,7 +178,6 @@ 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();
|
||||
|
@ -263,7 +201,6 @@ describe('Canvas Actions', () => {
|
|||
);
|
||||
});
|
||||
|
||||
// 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();
|
||||
|
@ -272,31 +209,4 @@ describe('Canvas Actions', () => {
|
|||
cy.get('body').type('{shift}', { release: false }).type('{leftArrow}');
|
||||
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
|
||||
.canvasNodes()
|
||||
.last()
|
||||
.findChildByTestId('execute-node-button')
|
||||
.as('executeNodeButton');
|
||||
cy.drag('@executeNodeButton', [200, 200]);
|
||||
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]);
|
||||
WorkflowPage.getters.canvasNodes().last().as('lastNode');
|
||||
cy.get('@lastNode').findChildByTestId('execute-node-button').as('executeNodeButton');
|
||||
for (let i = 0; i < 20; i++) {
|
||||
cy.get('@lastNode').realHover();
|
||||
cy.get('@executeNodeButton').should('be.visible');
|
||||
cy.get('@executeNodeButton').realTouch();
|
||||
cy.getByTestId('execute-workflow-button').realHover();
|
||||
WorkflowPage.actions.testLassoSelection([100, 100], [200, 200]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,8 +28,6 @@ const ZOOM_IN_X2_FACTOR = 1.5625; // Zoom in factor after two clicks
|
|||
const ZOOM_OUT_X1_FACTOR = 0.8;
|
||||
const ZOOM_OUT_X2_FACTOR = 0.64;
|
||||
|
||||
const PINCH_ZOOM_IN_FACTOR = 1.05702;
|
||||
const PINCH_ZOOM_OUT_FACTOR = 0.946058;
|
||||
const RENAME_NODE_NAME = 'Something else';
|
||||
const RENAME_NODE_NAME2 = 'Something different';
|
||||
|
||||
|
@ -369,26 +367,6 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
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');
|
||||
checkZoomLevel(1); // Zoom in 1x + Zoom out 1x should reset to default (=1)
|
||||
|
||||
WorkflowPage.actions.pinchToZoom(1, 'zoomOut');
|
||||
|
||||
cy.ifCanvasVersion(
|
||||
() => checkZoomLevel(PINCH_ZOOM_OUT_FACTOR),
|
||||
() => checkZoomLevel(ZOOM_OUT_X1_FACTOR),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reset zoom', () => {
|
||||
WorkflowPage.getters.resetZoomButton().should('not.exist');
|
||||
WorkflowPage.getters.zoomInButton().click();
|
||||
|
@ -543,35 +521,4 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
NDVDialog.actions.close();
|
||||
});
|
||||
});
|
||||
|
||||
// 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');
|
||||
|
||||
WorkflowPage.getters.canvasNodeByName(`${unknownNodeName} 1`).should('exist');
|
||||
WorkflowPage.getters.canvasNodeByName(`${unknownNodeName} 2`).should('exist');
|
||||
WorkflowPage.actions.zoomToFit();
|
||||
|
||||
cy.draganddrop(
|
||||
WorkflowPage.getters.getEndpointSelector('plus', `${unknownNodeName} 1`),
|
||||
WorkflowPage.getters.getEndpointSelector('input', EDIT_FIELDS_SET_NODE_NAME),
|
||||
);
|
||||
|
||||
cy.draganddrop(
|
||||
WorkflowPage.getters.getEndpointSelector('plus', `${unknownNodeName} 2`),
|
||||
WorkflowPage.getters.getEndpointSelector('input', `${EDIT_FIELDS_SET_NODE_NAME}1`),
|
||||
);
|
||||
|
||||
WorkflowPage.actions.executeWorkflow();
|
||||
cy.contains('Unrecognized node type').should('be.visible');
|
||||
|
||||
WorkflowPage.actions.deselectAll();
|
||||
WorkflowPage.actions.deleteNodeFromContextMenu(`${unknownNodeName} 1`);
|
||||
WorkflowPage.actions.deleteNodeFromContextMenu(`${unknownNodeName} 2`);
|
||||
|
||||
WorkflowPage.actions.executeWorkflow();
|
||||
|
||||
cy.contains('Unrecognized node type').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { simpleWebhookCall, waitForWebhook } from './16-webhook-node.cy';
|
||||
import {
|
||||
HTTP_REQUEST_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
|
@ -109,36 +106,6 @@ describe('Data pinning', () => {
|
|||
ndv.getters.outputTbodyCell(1, 0).should('include.text', 1);
|
||||
});
|
||||
|
||||
it('Should be able to pin data from canvas (context menu or shortcut)', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
|
||||
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME);
|
||||
workflowPage.actions.openContextMenu(EDIT_FIELDS_SET_NODE_NAME, { method: 'overflow-button' });
|
||||
workflowPage.getters
|
||||
.contextMenuAction('toggle_pin')
|
||||
.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 }]);
|
||||
ndv.actions.close();
|
||||
workflowPage.actions.pinNode(EDIT_FIELDS_SET_NODE_NAME);
|
||||
workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME);
|
||||
ndv.getters.nodeOutputHint().should('exist');
|
||||
ndv.actions.close();
|
||||
|
||||
// Unpin using shortcut
|
||||
workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME);
|
||||
ndv.actions.setPinnedData([{ test: 1 }]);
|
||||
ndv.actions.close();
|
||||
workflowPage.getters.canvasNodeByName(EDIT_FIELDS_SET_NODE_NAME).click();
|
||||
workflowPage.actions.hitPinNodeShortcut();
|
||||
workflowPage.actions.openNode(EDIT_FIELDS_SET_NODE_NAME);
|
||||
ndv.getters.nodeOutputHint().should('exist');
|
||||
});
|
||||
|
||||
it('Should show an error when maximum pin data size is exceeded', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
|
||||
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
||||
|
@ -217,32 +184,6 @@ describe('Data pinning', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should show pinned data tooltip', () => {
|
||||
const { callEndpoint } = simpleWebhookCall({
|
||||
method: 'GET',
|
||||
webhookPath: nanoid(),
|
||||
executeNow: false,
|
||||
});
|
||||
|
||||
ndv.actions.close();
|
||||
workflowPage.actions.executeWorkflow();
|
||||
cy.wait(waitForWebhook);
|
||||
|
||||
// hide other visible popper on workflow execute button
|
||||
workflowPage.getters.canvasNodes().eq(0).click();
|
||||
|
||||
callEndpoint((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
getVisiblePopper().should('have.length', 1);
|
||||
getVisiblePopper()
|
||||
.eq(0)
|
||||
.should(
|
||||
'have.text',
|
||||
'You can pin this output instead of waiting for a test event. Open node to do so.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show pinned data tooltip', () => {
|
||||
cy.createFixtureWorkflow('Pinned_webhook_node.json', 'Test');
|
||||
workflowPage.actions.executeWorkflow();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { simpleWebhookCall, waitForWebhook } from '../composables/webhooks';
|
||||
import { BACKEND_BASE_URL, EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
||||
import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
||||
import { cowBase64 } from '../support/binaryTestFiles';
|
||||
|
@ -9,81 +10,6 @@ const workflowPage = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
const credentialsModal = new CredentialsModal();
|
||||
|
||||
export const waitForWebhook = 500;
|
||||
|
||||
interface SimpleWebhookCallOptions {
|
||||
method: string;
|
||||
webhookPath: string;
|
||||
responseCode?: number;
|
||||
respondWith?: string;
|
||||
executeNow?: boolean;
|
||||
responseData?: string;
|
||||
authentication?: string;
|
||||
}
|
||||
|
||||
export const simpleWebhookCall = (options: SimpleWebhookCallOptions) => {
|
||||
const {
|
||||
authentication,
|
||||
method,
|
||||
webhookPath,
|
||||
responseCode,
|
||||
respondWith,
|
||||
responseData,
|
||||
executeNow = true,
|
||||
} = options;
|
||||
|
||||
workflowPage.actions.addInitialNodeToCanvas('Webhook');
|
||||
workflowPage.actions.openNode('Webhook');
|
||||
|
||||
cy.getByTestId('parameter-input-httpMethod').click();
|
||||
getVisibleSelect().find('.option-headline').contains(method).click();
|
||||
cy.getByTestId('parameter-input-path')
|
||||
.find('.parameter-input')
|
||||
.find('input')
|
||||
.clear()
|
||||
.type(webhookPath);
|
||||
|
||||
if (authentication) {
|
||||
cy.getByTestId('parameter-input-authentication').click();
|
||||
getVisibleSelect().find('.option-headline').contains(authentication).click();
|
||||
}
|
||||
|
||||
if (responseCode) {
|
||||
cy.get('.param-options').click();
|
||||
getVisibleSelect().contains('Response Code').click();
|
||||
cy.get('.parameter-item-wrapper > .parameter-input-list-wrapper').children().click();
|
||||
getVisibleSelect().contains('201').click();
|
||||
}
|
||||
|
||||
if (respondWith) {
|
||||
cy.getByTestId('parameter-input-responseMode').click();
|
||||
getVisibleSelect().find('.option-headline').contains(respondWith).click();
|
||||
}
|
||||
|
||||
if (responseData) {
|
||||
cy.getByTestId('parameter-input-responseData').click();
|
||||
getVisibleSelect().find('.option-headline').contains(responseData).click();
|
||||
}
|
||||
|
||||
const callEndpoint = (cb: (response: Cypress.Response<unknown>) => void) => {
|
||||
cy.request(method, `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then(cb);
|
||||
};
|
||||
|
||||
if (executeNow) {
|
||||
ndv.actions.execute();
|
||||
cy.wait(waitForWebhook);
|
||||
|
||||
callEndpoint((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
ndv.getters.outputPanel().contains('headers');
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
callEndpoint,
|
||||
};
|
||||
};
|
||||
|
||||
describe('Webhook Trigger node', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
|
||||
describe('PAY-1858 context menu', () => {
|
||||
it('can use context menu on saved workflow', () => {
|
||||
WorkflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Test_workflow_filter.json', 'test');
|
||||
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 5);
|
||||
WorkflowPage.actions.deleteNodeFromContextMenu('Then');
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
|
||||
WorkflowPage.actions.hitSaveWorkflow();
|
||||
|
||||
cy.reload();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
WorkflowPage.actions.deleteNodeFromContextMenu('Code');
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
});
|
||||
});
|
|
@ -214,91 +214,6 @@ 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');
|
||||
|
||||
// Check workflow buttons
|
||||
workflowPage.getters.executeWorkflowButton().should('be.visible');
|
||||
workflowPage.getters.clearExecutionDataButton().should('not.exist');
|
||||
workflowPage.getters.stopExecutionButton().should('not.exist');
|
||||
workflowPage.getters.stopExecutionWaitingForWebhookButton().should('not.exist');
|
||||
|
||||
// Execute the workflow
|
||||
workflowPage.getters.zoomToFitButton().click();
|
||||
workflowPage.getters.executeWorkflowButton().click();
|
||||
|
||||
// Check workflow buttons
|
||||
workflowPage.getters.executeWorkflowButton().get('.n8n-spinner').should('be.visible');
|
||||
workflowPage.getters.clearExecutionDataButton().should('not.exist');
|
||||
workflowPage.getters.stopExecutionButton().should('not.exist');
|
||||
workflowPage.getters.stopExecutionWaitingForWebhookButton().should('be.visible');
|
||||
|
||||
workflowPage.getters.canvasNodes().first().dblclick();
|
||||
|
||||
ndv.getters.copyInput().click();
|
||||
|
||||
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
|
||||
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
||||
cy.readClipboard().then((url) => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url,
|
||||
}).then((resp) => {
|
||||
expect(resp.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
|
||||
successToast().should('be.visible');
|
||||
clearNotifications();
|
||||
|
||||
workflowPage.getters.stopExecutionButton().click();
|
||||
// Check canvas nodes after 1st step (workflow passed the manual trigger node
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Webhook')
|
||||
.within(() => cy.get('.fa-check'))
|
||||
.should('exist');
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Wait')
|
||||
.within(() => cy.get('.fa-check').should('not.exist'));
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Wait')
|
||||
.within(() => cy.get('.fa-sync-alt'))
|
||||
.should('exist');
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Set')
|
||||
.within(() => cy.get('.fa-check').should('not.exist'));
|
||||
|
||||
// Check canvas nodes after workflow stopped
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('Webhook')
|
||||
.within(() => cy.get('.fa-check'))
|
||||
.should('exist');
|
||||
|
||||
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');
|
||||
|
||||
// Clear execution data
|
||||
workflowPage.getters.clearExecutionDataButton().should('be.visible');
|
||||
workflowPage.getters.clearExecutionDataButton().click();
|
||||
workflowPage.getters.clearExecutionDataButton().should('not.exist');
|
||||
});
|
||||
|
||||
describe('execution preview', () => {
|
||||
it('when deleting the last execution, it should show empty state', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('Manual Trigger');
|
||||
|
@ -312,8 +227,11 @@ describe('Execution', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// FIXME: Canvas V2: Missing pinned states for `edge-label-wrapper`
|
||||
describe('connections should be colored differently for pinned data', () => {
|
||||
/**
|
||||
* @TODO New Canvas: Different classes for pinned states on edges and nodes
|
||||
*/
|
||||
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
|
||||
describe.skip('connections should be colored differently for pinned data', () => {
|
||||
beforeEach(() => {
|
||||
cy.createFixtureWorkflow('Schedule_pinned.json');
|
||||
workflowPage.actions.deselectAll();
|
||||
|
@ -634,45 +552,4 @@ describe('Execution', () => {
|
|||
|
||||
errorToast().should('contain', 'Problem in node ‘Telegram‘');
|
||||
});
|
||||
|
||||
it('should not show pinned data in production execution', () => {
|
||||
cy.createFixtureWorkflow('Execution-pinned-data-check.json');
|
||||
|
||||
workflowPage.getters.zoomToFitButton().click();
|
||||
cy.intercept('PATCH', '/rest/workflows/*').as('workflowActivate');
|
||||
workflowPage.getters.activatorSwitch().click();
|
||||
|
||||
cy.wait('@workflowActivate');
|
||||
cy.get('body').type('{esc}');
|
||||
workflowPage.actions.openNode('Webhook');
|
||||
|
||||
cy.contains('label', 'Production URL').should('be.visible').click();
|
||||
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
|
||||
cy.get('.webhook-url').click();
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
||||
cy.readClipboard().then((url) => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url,
|
||||
}).then((resp) => {
|
||||
expect(resp.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
|
||||
cy.intercept('GET', '/rest/executions/*').as('getExecution');
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
cy.wait('@getExecution');
|
||||
executionsTab.getters
|
||||
.workflowExecutionPreviewIframe()
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body')
|
||||
.should('not.be.empty')
|
||||
|
||||
.then(cy.wrap)
|
||||
.find('.connection-run-items-label')
|
||||
.filter(':contains("5 items")')
|
||||
.should('have.length', 2);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import { WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
const executionsTab = new WorkflowExecutionsTab();
|
||||
|
||||
describe('ADO-2106 connections should be colored correctly for pinned data in executions preview', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.createFixtureWorkflow('Webhook_set_pinned.json');
|
||||
workflowPage.actions.deselectAll();
|
||||
workflowPage.getters.zoomToFitButton().click();
|
||||
|
||||
workflowPage.getters.getConnectionBetweenNodes('Webhook', 'Set').should('have.class', 'pinned');
|
||||
});
|
||||
|
||||
it('should color connections for pinned data nodes for manual executions', () => {
|
||||
workflowPage.actions.executeWorkflow();
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
executionsTab.getters.successfulExecutionListItems().should('have.length', 1);
|
||||
|
||||
executionsTab.getters
|
||||
.workflowExecutionPreviewIframe()
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body')
|
||||
.should('not.be.empty')
|
||||
|
||||
.then(cy.wrap)
|
||||
.find('.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]')
|
||||
.should('have.class', 'success')
|
||||
.should('have.class', 'has-run')
|
||||
.should('have.class', 'pinned');
|
||||
});
|
||||
});
|
|
@ -1,135 +0,0 @@
|
|||
import { WorkflowPage, NDV } from '../pages';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
||||
describe('ADO-2111 expressions should support pinned data', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
|
||||
it('supports pinned data in expressions unexecuted and executed parent nodes', () => {
|
||||
cy.createFixtureWorkflow('Test_workflow_pinned_data_in_expressions.json', 'Expressions');
|
||||
|
||||
// test previous node unexecuted
|
||||
workflowPage.actions.openNode('NotPinnedWithExpressions');
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.eq(0)
|
||||
.should('include.text', 'Joe\nJoe\nJoan\nJoan\nJoe\nJoan\n\nJoe\nJoan\n\nJoe');
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.eq(1)
|
||||
.should('contain.text', '0,0\nJoe\n\nJoe\n\nJoe\n\nJoe\nJoe');
|
||||
|
||||
// test can resolve correctly based on item
|
||||
ndv.actions.switchInputMode('Table');
|
||||
|
||||
ndv.getters.inputTableRow(2).realHover();
|
||||
cy.wait(50);
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.eq(0)
|
||||
.should('include.text', 'Joe\nJoe\nJoan\nJoan\nJoe\nJoan\n\nJoe\nJoan\n\nJoe');
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.eq(1)
|
||||
.should('contain.text', '0,1\nJoan\n\nJoan\n\nJoan\n\nJoan\nJoan');
|
||||
|
||||
// test previous node executed
|
||||
ndv.actions.execute();
|
||||
ndv.getters.inputTableRow(1).realHover();
|
||||
cy.wait(50);
|
||||
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.eq(0)
|
||||
.should('include.text', 'Joe\nJoe\nJoan\nJoan\nJoe\nJoan\n\nJoe\nJoan\n\nJoe');
|
||||
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.eq(1)
|
||||
.should('contain.text', '0,0\nJoe\n\nJoe\n\nJoe\n\nJoe\nJoe');
|
||||
|
||||
ndv.getters.inputTableRow(2).realHover();
|
||||
cy.wait(50);
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.eq(0)
|
||||
.should('include.text', 'Joe\nJoe\nJoan\nJoan\nJoe\nJoan\n\nJoe\nJoan\n\nJoe');
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.eq(1)
|
||||
.should('contain.text', '0,1\nJoan\n\nJoan\n\nJoan\n\nJoan\nJoan');
|
||||
|
||||
// check it resolved correctly on the backend
|
||||
ndv.getters
|
||||
.outputTbodyCell(1, 0)
|
||||
.should('contain.text', 'Joe\\nJoe\\nJoan\\nJoan\\nJoe\\nJoan\\n\\nJoe\\nJoan\\n\\nJoe');
|
||||
|
||||
ndv.getters
|
||||
.outputTbodyCell(2, 0)
|
||||
.should('contain.text', 'Joe\\nJoe\\nJoan\\nJoan\\nJoe\\nJoan\\n\\nJoe\\nJoan\\n\\nJoe');
|
||||
|
||||
ndv.getters
|
||||
.outputTbodyCell(1, 1)
|
||||
.should('contain.text', '0,0\\nJoe\\n\\nJoe\\n\\nJoe\\n\\nJoe\\nJoe');
|
||||
|
||||
ndv.getters
|
||||
.outputTbodyCell(2, 1)
|
||||
.should('contain.text', '0,1\\nJoan\\n\\nJoan\\n\\nJoan\\n\\nJoan\\nJoan');
|
||||
});
|
||||
|
||||
it('resets expressions after node is unpinned', () => {
|
||||
cy.createFixtureWorkflow('Test_workflow_pinned_data_in_expressions.json', 'Expressions');
|
||||
|
||||
// test previous node unexecuted
|
||||
workflowPage.actions.openNode('NotPinnedWithExpressions');
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.eq(0)
|
||||
.should('include.text', 'Joe\nJoe\nJoan\nJoan\nJoe\nJoan\n\nJoe\nJoan\n\nJoe');
|
||||
ndv.getters
|
||||
.parameterExpressionPreview('value')
|
||||
.eq(1)
|
||||
.should('contain.text', '0,0\nJoe\n\nJoe\n\nJoe\n\nJoe\nJoe');
|
||||
|
||||
ndv.actions.close();
|
||||
|
||||
// unpin pinned node
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('PinnedSet')
|
||||
.eq(0)
|
||||
.find('.node-pin-data-icon')
|
||||
.should('exist');
|
||||
workflowPage.getters.canvasNodeByName('PinnedSet').eq(0).click();
|
||||
workflowPage.actions.hitPinNodeShortcut();
|
||||
workflowPage.getters
|
||||
.canvasNodeByName('PinnedSet')
|
||||
.eq(0)
|
||||
.find('.node-pin-data-icon')
|
||||
.should('not.exist');
|
||||
|
||||
workflowPage.actions.openNode('NotPinnedWithExpressions');
|
||||
ndv.getters.nodeParameters().find('parameter-expression-preview-value').should('not.exist');
|
||||
|
||||
ndv.getters.parameterInput('value').eq(0).click();
|
||||
ndv.getters
|
||||
.inlineExpressionEditorOutput()
|
||||
.should(
|
||||
'have.text',
|
||||
'[Execute node ‘PinnedSet’ for preview][Execute node ‘PinnedSet’ for preview][Execute node ‘PinnedSet’ for preview][Execute node ‘PinnedSet’ for preview][Execute node ‘PinnedSet’ for preview][Execute node ‘PinnedSet’ for preview][Execute previous nodes for preview][Execute previous nodes for preview][undefined]',
|
||||
);
|
||||
|
||||
// close open expression
|
||||
ndv.getters.inputLabel().eq(0).click();
|
||||
|
||||
ndv.getters.parameterInput('value').eq(1).click();
|
||||
ndv.getters
|
||||
.inlineExpressionEditorOutput()
|
||||
.should(
|
||||
'have.text',
|
||||
'0,0[Execute node ‘PinnedSet’ for preview][Execute node ‘PinnedSet’ for preview][Execute previous nodes for preview][Execute previous nodes for preview][Execute previous nodes for preview]',
|
||||
);
|
||||
});
|
||||
});
|
|
@ -118,6 +118,15 @@ describe('NDV', () => {
|
|||
ndv.actions.switchInputMode('Table');
|
||||
ndv.actions.switchOutputMode('Table');
|
||||
|
||||
// Start from linked state
|
||||
ndv.getters.outputLinkRun().then(($el) => {
|
||||
const classList = Array.from($el[0].classList);
|
||||
if (!classList.includes('linked')) {
|
||||
ndv.actions.toggleOutputRunLinking();
|
||||
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
|
||||
}
|
||||
});
|
||||
|
||||
ndv.getters
|
||||
.inputRunSelector()
|
||||
.should('exist')
|
||||
|
@ -243,38 +252,38 @@ describe('NDV', () => {
|
|||
// biome-ignore format:
|
||||
const PINNED_DATA = [
|
||||
{
|
||||
"id": "abc",
|
||||
"historyId": "def",
|
||||
"messages": [
|
||||
id: 'abc',
|
||||
historyId: 'def',
|
||||
messages: [
|
||||
{
|
||||
"id": "abc"
|
||||
}
|
||||
]
|
||||
id: 'abc',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "abc",
|
||||
"historyId": "def",
|
||||
"messages": [
|
||||
id: 'abc',
|
||||
historyId: 'def',
|
||||
messages: [
|
||||
{
|
||||
"id": "abc"
|
||||
id: 'abc',
|
||||
},
|
||||
{
|
||||
"id": "abc"
|
||||
id: 'abc',
|
||||
},
|
||||
{
|
||||
"id": "abc"
|
||||
}
|
||||
]
|
||||
id: 'abc',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "abc",
|
||||
"historyId": "def",
|
||||
"messages": [
|
||||
id: 'abc',
|
||||
historyId: 'def',
|
||||
messages: [
|
||||
{
|
||||
"id": "abc"
|
||||
}
|
||||
]
|
||||
}
|
||||
id: 'abc',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
workflowPage.actions.openNode('Get thread details1');
|
||||
ndv.actions.pastePinnedData(PINNED_DATA);
|
||||
|
|
|
@ -3,24 +3,6 @@ import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
|||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
|
||||
function checkStickiesStyle(
|
||||
top: number,
|
||||
left: number,
|
||||
height: number,
|
||||
width: number,
|
||||
zIndex?: number,
|
||||
) {
|
||||
workflowPage.getters.stickies().should(($el) => {
|
||||
expect($el).to.have.css('top', `${top}px`);
|
||||
expect($el).to.have.css('left', `${left}px`);
|
||||
expect($el).to.have.css('height', `${height}px`);
|
||||
expect($el).to.have.css('width', `${width}px`);
|
||||
if (zIndex) {
|
||||
expect($el).to.have.css('z-index', `${zIndex}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('Canvas Actions', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
|
@ -51,191 +33,8 @@ describe('Canvas Actions', () => {
|
|||
.contains('Guide')
|
||||
.should('have.attr', 'href');
|
||||
});
|
||||
|
||||
it('drags sticky around to top left corner', () => {
|
||||
// used to caliberate move sticky function
|
||||
addDefaultSticky();
|
||||
moveSticky({ top: 0, left: 0 });
|
||||
});
|
||||
|
||||
it('drags sticky around and position/size are saved correctly', () => {
|
||||
addDefaultSticky();
|
||||
moveSticky({ top: 500, left: 500 });
|
||||
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
cy.wait('@createWorkflow');
|
||||
|
||||
cy.reload();
|
||||
cy.waitForLoad();
|
||||
|
||||
stickyShouldBePositionedCorrectly({ top: 500, left: 500 });
|
||||
});
|
||||
|
||||
it('deletes sticky', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
|
||||
workflowPage.actions.deleteSticky();
|
||||
|
||||
workflowPage.getters.stickies().should('have.length', 0);
|
||||
});
|
||||
|
||||
it('edits sticky and updates content as markdown', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
|
||||
workflowPage.getters
|
||||
.stickies()
|
||||
.should('have.text', 'I’m a note\nDouble click to edit me. Guide\n');
|
||||
|
||||
workflowPage.getters.stickies().dblclick();
|
||||
workflowPage.actions.editSticky('# hello world \n ## text text');
|
||||
workflowPage.getters.stickies().find('h1').should('have.text', 'hello world');
|
||||
workflowPage.getters.stickies().find('h2').should('have.text', 'text text');
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the right edge', () => {
|
||||
addDefaultSticky();
|
||||
|
||||
moveSticky({ top: 200, left: 200 });
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="right"]', [100, 100]);
|
||||
checkStickiesStyle(100, 20, 160, 346);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="right"]', [-50, -50]);
|
||||
checkStickiesStyle(100, 20, 160, 302);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the left edge', () => {
|
||||
addDefaultSticky();
|
||||
|
||||
moveSticky({ left: 600, top: 200 });
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="left"]', [100, 100]);
|
||||
checkStickiesStyle(100, 510, 160, 150);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="left"]', [-50, -50]);
|
||||
checkStickiesStyle(100, 466, 160, 194);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the top edge', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
cy.drag('[data-test-id="sticky"]', [100, 100]); // move away from canvas button
|
||||
checkStickiesStyle(300, 620, 160, 240);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="top"]', [100, 100]);
|
||||
checkStickiesStyle(380, 620, 80, 240);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="top"]', [-50, -50]);
|
||||
checkStickiesStyle(324, 620, 136, 240);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the bottom edge', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
cy.drag('[data-test-id="sticky"]', [100, 100]); // move away from canvas button
|
||||
checkStickiesStyle(300, 620, 160, 240);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="bottom"]', [100, 100]);
|
||||
checkStickiesStyle(300, 620, 254, 240);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="bottom"]', [-50, -50]);
|
||||
checkStickiesStyle(300, 620, 198, 240);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the bottom right edge', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
cy.drag('[data-test-id="sticky"]', [-100, -100]); // move away from canvas button
|
||||
checkStickiesStyle(100, 420, 160, 240);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="bottomRight"]', [100, 100]);
|
||||
checkStickiesStyle(100, 420, 254, 346);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="bottomRight"]', [-50, -50]);
|
||||
checkStickiesStyle(100, 420, 198, 302);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the top right edge', () => {
|
||||
addDefaultSticky();
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topRight"]', [100, 100]);
|
||||
checkStickiesStyle(360, 400, 80, 346);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topRight"]', [-50, -50]);
|
||||
checkStickiesStyle(304, 400, 136, 302);
|
||||
});
|
||||
|
||||
it('expands/shrinks sticky from the top left edge, and reach min height/width', () => {
|
||||
addDefaultSticky();
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topLeft"]', [100, 100]);
|
||||
checkStickiesStyle(360, 490, 80, 150);
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topLeft"]', [-150, -150]);
|
||||
checkStickiesStyle(204, 346, 236, 294);
|
||||
});
|
||||
|
||||
it('sets sticky behind node', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('Manual Trigger');
|
||||
addDefaultSticky();
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topLeft"]', [-150, -150]);
|
||||
checkStickiesStyle(124, 256, 316, 384, -121);
|
||||
|
||||
workflowPage.getters
|
||||
.canvasNodes()
|
||||
.eq(0)
|
||||
.should(($el) => {
|
||||
expect($el).to.have.css('z-index', 'auto');
|
||||
});
|
||||
|
||||
workflowPage.actions.addSticky();
|
||||
workflowPage.getters
|
||||
.stickies()
|
||||
.eq(0)
|
||||
.should(($el) => {
|
||||
expect($el).to.have.css('z-index', '-121');
|
||||
});
|
||||
workflowPage.getters
|
||||
.stickies()
|
||||
.eq(1)
|
||||
.should(($el) => {
|
||||
expect($el).to.have.css('z-index', '-38');
|
||||
});
|
||||
|
||||
cy.drag('[data-test-id="sticky"] [data-dir="topLeft"]', [-200, -200], { index: 1 });
|
||||
workflowPage.getters
|
||||
.stickies()
|
||||
.eq(0)
|
||||
.should(($el) => {
|
||||
expect($el).to.have.css('z-index', '-121');
|
||||
});
|
||||
|
||||
workflowPage.getters
|
||||
.stickies()
|
||||
.eq(1)
|
||||
.should(($el) => {
|
||||
expect($el).to.have.css('z-index', '-158');
|
||||
});
|
||||
});
|
||||
|
||||
it('Empty sticky should not error when activating workflow', () => {
|
||||
workflowPage.actions.addSticky();
|
||||
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
|
||||
workflowPage.getters.stickies().dblclick();
|
||||
|
||||
workflowPage.actions.clearSticky();
|
||||
|
||||
workflowPage.actions.addNodeToCanvas('Schedule Trigger');
|
||||
|
||||
workflowPage.actions.activateWorkflow();
|
||||
});
|
||||
});
|
||||
|
||||
type Position = {
|
||||
top: number;
|
||||
left: number;
|
||||
};
|
||||
|
||||
function shouldHaveOneSticky() {
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
}
|
||||
|
@ -263,17 +62,3 @@ function addDefaultSticky() {
|
|||
shouldHaveDefaultSize();
|
||||
shouldBeInDefaultLocation();
|
||||
}
|
||||
|
||||
function stickyShouldBePositionedCorrectly(position: Position) {
|
||||
const yOffset = -100;
|
||||
const xOffset = -180;
|
||||
workflowPage.getters.stickies().should(($el) => {
|
||||
expect($el).to.have.css('top', `${yOffset + position.top}px`);
|
||||
expect($el).to.have.css('left', `${xOffset + position.left}px`);
|
||||
});
|
||||
}
|
||||
|
||||
function moveSticky(target: Position) {
|
||||
cy.drag('[data-test-id="sticky"]', [target.left, target.top], { abs: true });
|
||||
stickyShouldBePositionedCorrectly(target);
|
||||
}
|
||||
|
|
|
@ -1,86 +1,7 @@
|
|||
import { getWorkflowHistoryCloseButton } from '../composables/workflow';
|
||||
import {
|
||||
CODE_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
IF_NODE_NAME,
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
} from '../constants';
|
||||
import { WorkflowExecutionsTab, WorkflowPage as WorkflowPageClass } from '../pages';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
const executionsTab = new WorkflowExecutionsTab();
|
||||
|
||||
const createNewWorkflowAndActivate = () => {
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
workflowPage.actions.activateWorkflow();
|
||||
cy.get('.el-notification .el-notification--error').should('not.exist');
|
||||
};
|
||||
|
||||
const editWorkflowAndDeactivate = () => {
|
||||
workflowPage.getters.canvasNodePlusEndpointByName(SCHEDULE_TRIGGER_NODE_NAME).click();
|
||||
workflowPage.getters.nodeCreatorSearchBar().should('be.visible');
|
||||
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, false);
|
||||
cy.get('.jtk-connector').should('have.length', 1);
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
workflowPage.getters.activatorSwitch().click();
|
||||
workflowPage.actions.zoomToFit();
|
||||
cy.get('.el-notification .el-notification--error').should('not.exist');
|
||||
};
|
||||
|
||||
const editWorkflowMoreAndActivate = () => {
|
||||
cy.drag(workflowPage.getters.getEndpointSelector('plus', EDIT_FIELDS_SET_NODE_NAME), [200, 200], {
|
||||
realMouse: true,
|
||||
});
|
||||
workflowPage.getters.nodeCreatorSearchBar().should('be.visible');
|
||||
|
||||
workflowPage.actions.addNodeToCanvas(CODE_NODE_NAME, false);
|
||||
workflowPage.getters.nodeViewBackground().click(600, 200, { force: true });
|
||||
cy.get('.jtk-connector').should('have.length', 2);
|
||||
workflowPage.actions.zoomToFit();
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
workflowPage.actions.addNodeToCanvas(IF_NODE_NAME);
|
||||
workflowPage.getters.nodeViewBackground().click(600, 200, { force: true });
|
||||
cy.get('.jtk-connector').should('have.length', 2);
|
||||
|
||||
const position = {
|
||||
top: 0,
|
||||
left: 0,
|
||||
};
|
||||
workflowPage.getters
|
||||
.canvasNodeByName(IF_NODE_NAME)
|
||||
.click()
|
||||
.then(($element) => {
|
||||
position.top = $element.position().top;
|
||||
position.left = $element.position().left;
|
||||
});
|
||||
|
||||
cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 200]);
|
||||
workflowPage.getters
|
||||
.canvasNodes()
|
||||
.last()
|
||||
.then(($element) => {
|
||||
const finalPosition = {
|
||||
top: $element.position().top,
|
||||
left: $element.position().left,
|
||||
};
|
||||
|
||||
expect(finalPosition.top).to.be.greaterThan(position.top);
|
||||
expect(finalPosition.left).to.be.greaterThan(position.left);
|
||||
});
|
||||
|
||||
cy.draganddrop(
|
||||
workflowPage.getters.getEndpointSelector('output', CODE_NODE_NAME),
|
||||
workflowPage.getters.getEndpointSelector('input', IF_NODE_NAME),
|
||||
);
|
||||
cy.get('.jtk-connector').should('have.length', 3);
|
||||
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
workflowPage.getters.activatorSwitch().click();
|
||||
cy.get('.el-notification .el-notification--error').should('not.exist');
|
||||
};
|
||||
|
||||
const switchBetweenEditorAndHistory = () => {
|
||||
workflowPage.getters.workflowHistoryButton().click();
|
||||
|
@ -116,62 +37,6 @@ const zoomInAndCheckNodes = () => {
|
|||
workflowPage.getters.canvasNodes().last().should('not.be.visible');
|
||||
};
|
||||
|
||||
describe('Editor actions should work', () => {
|
||||
beforeEach(() => {
|
||||
cy.enableFeature('debugInEditor');
|
||||
cy.enableFeature('workflowHistory');
|
||||
cy.signinAsOwner();
|
||||
createNewWorkflowAndActivate();
|
||||
});
|
||||
|
||||
it('after switching between Editor and Executions', () => {
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
cy.wait(['@getExecutions']);
|
||||
cy.wait(500);
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
editWorkflowAndDeactivate();
|
||||
editWorkflowMoreAndActivate();
|
||||
});
|
||||
|
||||
it('after switching between Editor and Debug', () => {
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
cy.intercept('GET', '/rest/executions/*').as('getExecution');
|
||||
cy.intercept('POST', '/rest/workflows/**/run?**').as('postWorkflowRun');
|
||||
|
||||
editWorkflowAndDeactivate();
|
||||
workflowPage.actions.executeWorkflow();
|
||||
cy.wait(['@postWorkflowRun']);
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
cy.wait(['@getExecutions']);
|
||||
|
||||
executionsTab.getters.executionListItems().should('have.length', 1).first().click();
|
||||
cy.wait(['@getExecution']);
|
||||
|
||||
executionsTab.getters.executionDebugButton().should('have.text', 'Copy to editor').click();
|
||||
editWorkflowMoreAndActivate();
|
||||
});
|
||||
|
||||
it('after switching between Editor and Workflow history', () => {
|
||||
cy.intercept('GET', '/rest/workflow-history/workflow/*/version/*').as('getVersion');
|
||||
cy.intercept('GET', '/rest/workflow-history/workflow/*').as('getHistory');
|
||||
|
||||
editWorkflowAndDeactivate();
|
||||
workflowPage.getters.workflowHistoryButton().click();
|
||||
cy.wait(['@getHistory']);
|
||||
cy.wait(['@getVersion']);
|
||||
|
||||
cy.intercept('GET', '/rest/workflows/*').as('workflowGet');
|
||||
getWorkflowHistoryCloseButton().click();
|
||||
cy.wait(['@workflowGet']);
|
||||
cy.wait(1000);
|
||||
|
||||
editWorkflowMoreAndActivate();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Editor zoom should work after route changes', () => {
|
||||
beforeEach(() => {
|
||||
cy.enableFeature('debugInEditor');
|
||||
|
|
|
@ -38,8 +38,6 @@ import {
|
|||
addToolNodeToParent,
|
||||
clickExecuteWorkflowButton,
|
||||
clickManualChatButton,
|
||||
disableNode,
|
||||
getExecuteWorkflowButton,
|
||||
navigateToNewWorkflowPage,
|
||||
getNodes,
|
||||
openNode,
|
||||
|
@ -73,27 +71,6 @@ describe('Langchain Integration', () => {
|
|||
getManualChatModal().should('not.exist');
|
||||
});
|
||||
|
||||
it('should disable test workflow button', () => {
|
||||
addNodeToCanvas('Schedule Trigger', true);
|
||||
addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true);
|
||||
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||
clickGetBackToCanvas();
|
||||
|
||||
addLanguageModelNodeToParent(
|
||||
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
|
||||
AGENT_NODE_NAME,
|
||||
true,
|
||||
);
|
||||
clickGetBackToCanvas();
|
||||
|
||||
disableNode('Schedule Trigger');
|
||||
|
||||
getExecuteWorkflowButton().should('be.disabled');
|
||||
});
|
||||
|
||||
it('should add nodes to all Agent node input types', () => {
|
||||
addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true);
|
||||
addNodeToCanvas(AGENT_NODE_NAME, true, true);
|
||||
|
@ -368,58 +345,6 @@ describe('Langchain Integration', () => {
|
|||
getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist');
|
||||
getNodes().should('have.length', 3);
|
||||
});
|
||||
it('should render runItems for sub-nodes and allow switching between them', () => {
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
||||
cy.visit(workflowPage.url);
|
||||
cy.createFixtureWorkflow('In_memory_vector_store_fake_embeddings.json');
|
||||
workflowPage.actions.zoomToFit();
|
||||
|
||||
workflowPage.actions.executeNode('Populate VS');
|
||||
cy.get('[data-label="25 items"]').should('exist');
|
||||
|
||||
const assertInputOutputText = (text: string, assertion: 'exist' | 'not.exist') => {
|
||||
ndv.getters.outputPanel().contains(text).should(assertion);
|
||||
ndv.getters.inputPanel().contains(text).should(assertion);
|
||||
};
|
||||
|
||||
workflowPage.actions.openNode('Character Text Splitter');
|
||||
ndv.getters.outputRunSelector().should('exist');
|
||||
ndv.getters.inputRunSelector().should('exist');
|
||||
ndv.getters.inputRunSelector().find('input').should('include.value', '3 of 3');
|
||||
ndv.getters.outputRunSelector().find('input').should('include.value', '3 of 3');
|
||||
assertInputOutputText('Kyiv', 'exist');
|
||||
assertInputOutputText('Berlin', 'not.exist');
|
||||
assertInputOutputText('Prague', 'not.exist');
|
||||
|
||||
ndv.actions.changeOutputRunSelector('2 of 3');
|
||||
assertInputOutputText('Berlin', 'exist');
|
||||
assertInputOutputText('Kyiv', 'not.exist');
|
||||
assertInputOutputText('Prague', 'not.exist');
|
||||
|
||||
ndv.actions.changeOutputRunSelector('1 of 3');
|
||||
assertInputOutputText('Prague', 'exist');
|
||||
assertInputOutputText('Berlin', 'not.exist');
|
||||
assertInputOutputText('Kyiv', 'not.exist');
|
||||
|
||||
ndv.actions.toggleInputRunLinking();
|
||||
ndv.actions.changeOutputRunSelector('2 of 3');
|
||||
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 3');
|
||||
ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 3');
|
||||
ndv.getters.inputPanel().contains('Prague').should('exist');
|
||||
ndv.getters.inputPanel().contains('Berlin').should('not.exist');
|
||||
|
||||
ndv.getters.outputPanel().contains('Berlin').should('exist');
|
||||
ndv.getters.outputPanel().contains('Prague').should('not.exist');
|
||||
|
||||
ndv.actions.toggleInputRunLinking();
|
||||
ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 3');
|
||||
ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 3');
|
||||
assertInputOutputText('Prague', 'exist');
|
||||
assertInputOutputText('Berlin', 'not.exist');
|
||||
assertInputOutputText('Kyiv', 'not.exist');
|
||||
});
|
||||
|
||||
it('should show tool info notice if no existing tools were used during execution', () => {
|
||||
addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
|
||||
|
@ -519,29 +444,6 @@ describe('Langchain Integration', () => {
|
|||
getRunDataInfoCallout().should('not.exist');
|
||||
});
|
||||
|
||||
it('should execute up to Node 1 when using partial execution', () => {
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
cy.visit(workflowPage.url);
|
||||
cy.createFixtureWorkflow('Test_workflow_chat_partial_execution.json');
|
||||
workflowPage.actions.zoomToFit();
|
||||
|
||||
getManualChatModal().should('not.exist');
|
||||
workflowPage.actions.executeNode('Node 1');
|
||||
|
||||
getManualChatModal().should('exist');
|
||||
sendManualChatMessage('Test');
|
||||
|
||||
getManualChatMessages().should('contain', 'this_my_field_1');
|
||||
cy.getByTestId('refresh-session-button').click();
|
||||
cy.get('button').contains('Reset').click();
|
||||
getManualChatMessages().should('not.exist');
|
||||
|
||||
sendManualChatMessage('Another test');
|
||||
getManualChatMessages().should('contain', 'this_my_field_3');
|
||||
getManualChatMessages().should('contain', 'this_my_field_4');
|
||||
});
|
||||
|
||||
it('should execute up to Node 1 when using partial execution', () => {
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
|
|
@ -11,18 +11,16 @@ import {
|
|||
WorkflowPage,
|
||||
CredentialsModal,
|
||||
CredentialsPage,
|
||||
WorkflowExecutionsTab,
|
||||
NDV,
|
||||
MainSidebar,
|
||||
} from '../pages';
|
||||
import { clearNotifications, successToast } from '../pages/notifications';
|
||||
import { getVisibleDropdown, getVisibleModalOverlay, getVisibleSelect } from '../utils';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const credentialsPage = new CredentialsPage();
|
||||
const credentialsModal = new CredentialsModal();
|
||||
const executionsTab = new WorkflowExecutionsTab();
|
||||
const ndv = new NDV();
|
||||
const mainSidebar = new MainSidebar();
|
||||
|
||||
|
@ -36,207 +34,6 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
cy.changeQuota('maxTeamProjects', -1);
|
||||
});
|
||||
|
||||
it('should handle workflows and credentials and menu items', () => {
|
||||
cy.signinAsAdmin();
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('not.have.length');
|
||||
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
|
||||
cy.intercept('POST', '/rest/workflows').as('workflowSave');
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
cy.wait('@workflowSave').then((interception) => {
|
||||
expect(interception.request.body).not.to.have.property('projectId');
|
||||
});
|
||||
|
||||
projects.getHomeButton().click();
|
||||
projects.getProjectTabs().should('have.length', 3);
|
||||
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.credentialCards().should('not.have.length');
|
||||
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
credentialsModal.getters.newCredentialModal().should('be.visible');
|
||||
credentialsModal.getters.newCredentialTypeSelect().should('be.visible');
|
||||
credentialsModal.getters.newCredentialTypeOption('Notion API').click();
|
||||
credentialsModal.getters.newCredentialTypeButton().click();
|
||||
credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890');
|
||||
credentialsModal.actions.setName('My awesome Notion account');
|
||||
|
||||
cy.intercept('POST', '/rest/credentials').as('credentialSave');
|
||||
credentialsModal.actions.save();
|
||||
cy.wait('@credentialSave').then((interception) => {
|
||||
expect(interception.request.body).not.to.have.property('projectId');
|
||||
});
|
||||
|
||||
credentialsModal.actions.close();
|
||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
||||
credentialsPage.getters
|
||||
.credentialCards()
|
||||
.first()
|
||||
.find('.n8n-node-icon img')
|
||||
.should('be.visible');
|
||||
|
||||
projects.getProjectTabWorkflows().click();
|
||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||
|
||||
projects.getMenuItems().should('not.have.length');
|
||||
|
||||
cy.intercept('POST', '/rest/projects').as('projectCreate');
|
||||
projects.getAddProjectButton().click();
|
||||
cy.wait('@projectCreate');
|
||||
projects.getMenuItems().should('have.length', 1);
|
||||
projects.getProjectTabs().should('have.length', 3);
|
||||
|
||||
cy.get('input[name="name"]').type('Development');
|
||||
projects.addProjectMember(INSTANCE_MEMBERS[0].email);
|
||||
|
||||
cy.intercept('PATCH', '/rest/projects/*').as('projectSettingsSave');
|
||||
projects.getProjectSettingsSaveButton().click();
|
||||
cy.wait('@projectSettingsSave').then((interception) => {
|
||||
expect(interception.request.body).to.have.property('name').and.to.equal('Development');
|
||||
expect(interception.request.body).to.have.property('relations').to.have.lengthOf(2);
|
||||
});
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
workflowsPage.getters.workflowCards().should('not.have.length');
|
||||
projects.getProjectTabs().should('have.length', 4);
|
||||
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
|
||||
cy.intercept('POST', '/rest/workflows').as('workflowSave');
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
cy.wait('@workflowSave').then((interception) => {
|
||||
expect(interception.request.body).to.have.property('projectId');
|
||||
});
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.credentialCards().should('not.have.length');
|
||||
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
credentialsModal.getters.newCredentialModal().should('be.visible');
|
||||
credentialsModal.getters.newCredentialTypeSelect().should('be.visible');
|
||||
credentialsModal.getters.newCredentialTypeOption('Notion API').click();
|
||||
credentialsModal.getters.newCredentialTypeButton().click();
|
||||
credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890');
|
||||
credentialsModal.actions.setName('My awesome Notion account');
|
||||
|
||||
cy.intercept('POST', '/rest/credentials').as('credentialSave');
|
||||
credentialsModal.actions.save();
|
||||
cy.wait('@credentialSave').then((interception) => {
|
||||
expect(interception.request.body).to.have.property('projectId');
|
||||
});
|
||||
credentialsModal.actions.close();
|
||||
|
||||
projects.getAddProjectButton().click();
|
||||
projects.getMenuItems().should('have.length', 2);
|
||||
|
||||
let projectId: string;
|
||||
projects.getMenuItems().first().click();
|
||||
cy.intercept('GET', '/rest/credentials*').as('credentialsList');
|
||||
projects.getProjectTabCredentials().click();
|
||||
cy.wait('@credentialsList').then((interception) => {
|
||||
const url = new URL(interception.request.url);
|
||||
const queryParams = new URLSearchParams(url.search);
|
||||
const filter = queryParams.get('filter');
|
||||
expect(filter).to.be.a('string').and.to.contain('projectId');
|
||||
|
||||
if (filter) {
|
||||
projectId = JSON.parse(filter).projectId;
|
||||
}
|
||||
});
|
||||
|
||||
projects.getMenuItems().last().click();
|
||||
cy.intercept('GET', '/rest/credentials*').as('credentialsListProjectId');
|
||||
projects.getProjectTabCredentials().click();
|
||||
cy.wait('@credentialsListProjectId').then((interception) => {
|
||||
const url = new URL(interception.request.url);
|
||||
const queryParams = new URLSearchParams(url.search);
|
||||
const filter = queryParams.get('filter');
|
||||
expect(filter).to.be.a('string').and.to.contain('projectId');
|
||||
|
||||
if (filter) {
|
||||
expect(JSON.parse(filter).projectId).not.to.equal(projectId);
|
||||
}
|
||||
});
|
||||
|
||||
projects.getHomeButton().click();
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
|
||||
cy.intercept('GET', '/rest/credentials*').as('credentialsListUnfiltered');
|
||||
projects.getProjectTabCredentials().click();
|
||||
cy.wait('@credentialsListUnfiltered').then((interception) => {
|
||||
expect(interception.request.url).not.to.contain('filter');
|
||||
});
|
||||
|
||||
let menuItems = cy.getByTestId('menu-item');
|
||||
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
menuItems.filter(':contains("Overview")[class*=active_]').should('exist');
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
menuItems.filter(':contains("Development")[class*=active_]').should('exist');
|
||||
|
||||
cy.intercept('GET', '/rest/workflows/*').as('loadWorkflow');
|
||||
workflowsPage.getters.workflowCards().first().findChildByTestId('card-content').click();
|
||||
|
||||
cy.wait('@loadWorkflow');
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
menuItems.filter(':contains("Development")[class*=active_]').should('exist');
|
||||
|
||||
cy.intercept('GET', '/rest/executions*').as('loadExecutions');
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
|
||||
cy.wait('@loadExecutions');
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
menuItems.filter(':contains("Development")[class*=active_]').should('exist');
|
||||
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
menuItems.filter(':contains("Development")[class*=active_]').should('exist');
|
||||
|
||||
cy.getByTestId('menu-item').filter(':contains("Variables")').click();
|
||||
cy.getByTestId('unavailable-resources-list').should('be.visible');
|
||||
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
menuItems.filter(':contains("Variables")[class*=active_]').should('exist');
|
||||
|
||||
projects.getHomeButton().click();
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
menuItems.filter(':contains("Overview")[class*=active_]').should('exist');
|
||||
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2).first().click();
|
||||
|
||||
cy.wait('@loadWorkflow');
|
||||
cy.getByTestId('execute-workflow-button').should('be.visible');
|
||||
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
menuItems.filter(':contains("Overview")[class*=active_]').should('not.exist');
|
||||
|
||||
menuItems = cy.getByTestId('menu-item');
|
||||
menuItems.filter('[class*=active_]').should('have.length', 1);
|
||||
menuItems.filter(':contains("Development")[class*=active_]').should('exist');
|
||||
});
|
||||
|
||||
it('should not show project add button and projects to a member if not invited to any project', () => {
|
||||
cy.signinAsMember(1);
|
||||
cy.visit(workflowsPage.url);
|
||||
|
@ -245,26 +42,6 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
projects.getMenuItems().should('not.exist');
|
||||
});
|
||||
|
||||
it('should not show viewer role if not licensed', () => {
|
||||
cy.signinAsOwner();
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
projects.getProjectTabSettings().click();
|
||||
|
||||
cy.get(
|
||||
`[data-test-id="user-list-item-${INSTANCE_MEMBERS[0].email}"] [data-test-id="projects-settings-user-role-select"]`,
|
||||
).click();
|
||||
|
||||
cy.get('.el-select-dropdown__item.is-disabled')
|
||||
.should('contain.text', 'Viewer')
|
||||
.get('span:contains("Upgrade")')
|
||||
.filter(':visible')
|
||||
.click();
|
||||
|
||||
getVisibleModalOverlay().should('contain.text', 'Upgrade to unlock additional roles');
|
||||
});
|
||||
|
||||
describe('when starting from scratch', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetDatabase();
|
||||
|
@ -275,7 +52,11 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
cy.changeQuota('maxTeamProjects', -1);
|
||||
});
|
||||
|
||||
it('should filter credentials by project ID when creating new workflow or hard reloading an opened workflow', () => {
|
||||
/**
|
||||
* @TODO: New Canvas - Fix this test
|
||||
*/
|
||||
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
|
||||
it.skip('should filter credentials by project ID when creating new workflow or hard reloading an opened workflow', () => {
|
||||
cy.signinAsOwner();
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
|
@ -753,82 +534,6 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.canvasNodeByName(NOTION_NODE_NAME).should('be.visible').dblclick();
|
||||
ndv.getters.credentialInput().find('input').should('be.enabled');
|
||||
});
|
||||
|
||||
it('should handle viewer role', () => {
|
||||
cy.enableFeature('projectRole:viewer');
|
||||
cy.signinAsOwner();
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
projects.createProject('Development');
|
||||
projects.addProjectMember(INSTANCE_MEMBERS[0].email, 'Viewer');
|
||||
projects.getProjectSettingsSaveButton().click();
|
||||
|
||||
projects.getProjectTabWorkflows().click();
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
projects.createWorkflow('Test_workflow_4_executions_view.json', 'WF with random error');
|
||||
executionsTab.actions.createManualExecutions(2);
|
||||
executionsTab.actions.toggleNodeEnabled('Error');
|
||||
executionsTab.actions.createManualExecutions(2);
|
||||
workflowPage.actions.saveWorkflowUsingKeyboardShortcut();
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
projects.createCredential('Notion API');
|
||||
|
||||
mainSidebar.actions.openUserMenu();
|
||||
cy.getByTestId('user-menu-item-logout').click();
|
||||
|
||||
cy.get('input[name="email"]').type(INSTANCE_MEMBERS[0].email);
|
||||
cy.get('input[name="password"]').type(INSTANCE_MEMBERS[0].password);
|
||||
cy.getByTestId('form-submit-button').click();
|
||||
|
||||
projects.getMenuItems().last().click();
|
||||
projects.getProjectTabExecutions().click();
|
||||
cy.getByTestId('global-execution-list-item').first().find('td:last button').click();
|
||||
getVisibleDropdown()
|
||||
.find('li')
|
||||
.filter(':contains("Retry")')
|
||||
.should('have.class', 'is-disabled');
|
||||
getVisibleDropdown()
|
||||
.find('li')
|
||||
.filter(':contains("Delete")')
|
||||
.should('have.class', 'is-disabled');
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
cy.getByTestId('workflow-card-name').should('be.visible').first().click();
|
||||
workflowPage.getters.nodeViewRoot().should('be.visible');
|
||||
workflowPage.getters.executeWorkflowButton().should('not.exist');
|
||||
workflowPage.getters.nodeCreatorPlusButton().should('not.exist');
|
||||
workflowPage.getters.canvasNodes().should('have.length', 3).last().click();
|
||||
cy.get('body').type('{backspace}');
|
||||
workflowPage.getters.canvasNodes().should('have.length', 3).last().rightclick();
|
||||
getVisibleDropdown()
|
||||
.find('li')
|
||||
.should('be.visible')
|
||||
.filter(
|
||||
':contains("Open"), :contains("Copy"), :contains("Select all"), :contains("Clear selection")',
|
||||
)
|
||||
.should('not.have.class', 'is-disabled');
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
cy.getByTestId('retry-execution-button')
|
||||
.should('be.visible')
|
||||
.find('.is-disabled')
|
||||
.should('exist');
|
||||
cy.get('button:contains("Debug")').should('be.disabled');
|
||||
cy.get('button[title="Retry execution"]').should('be.disabled');
|
||||
cy.get('button[title="Delete this execution"]').should('be.disabled');
|
||||
|
||||
projects.getMenuItems().first().click();
|
||||
projects.getProjectTabCredentials().click();
|
||||
credentialsPage.getters.credentialCards().filter(':contains("Notion")').click();
|
||||
cy.getByTestId('node-credentials-config-container')
|
||||
.should('be.visible')
|
||||
.find('input')
|
||||
.should('not.have.length');
|
||||
});
|
||||
});
|
||||
|
||||
it('should set and update project icon', () => {
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
import {
|
||||
getExecutionPreviewOutputPanelRelatedExecutionLink,
|
||||
getExecutionsSidebar,
|
||||
getWorkflowExecutionPreviewIframe,
|
||||
openExecutionPreviewNode,
|
||||
} from '../composables/executions';
|
||||
import {
|
||||
changeOutputRunSelector,
|
||||
getOutputPanelItemsCount,
|
||||
|
@ -103,38 +97,4 @@ describe('Subworkflow debugging', () => {
|
|||
getOutputTbodyCell(1, 2).should('include.text', 'Terry.Dach@hotmail.com');
|
||||
});
|
||||
});
|
||||
|
||||
it('can inspect parent executions', () => {
|
||||
cy.url().then((workflowUrl) => {
|
||||
openNode('Execute Workflow with param');
|
||||
|
||||
getOutputPanelItemsCount().should('contain.text', '2 items, 1 sub-execution');
|
||||
getOutputPanelRelatedExecutionLink().should('contain.text', 'View sub-execution');
|
||||
getOutputPanelRelatedExecutionLink().should('have.attr', 'href');
|
||||
|
||||
// ensure workflow executed and waited on output
|
||||
getOutputTableHeaders().should('have.length', 2);
|
||||
getOutputTbodyCell(1, 0).should('have.text', 'world Natalie Moore');
|
||||
|
||||
// cypress cannot handle new tabs so removing it
|
||||
getOutputPanelRelatedExecutionLink().invoke('removeAttr', 'target').click();
|
||||
|
||||
getExecutionsSidebar().should('be.visible');
|
||||
getWorkflowExecutionPreviewIframe().should('be.visible');
|
||||
openExecutionPreviewNode('Execute Workflow Trigger');
|
||||
|
||||
getExecutionPreviewOutputPanelRelatedExecutionLink().should(
|
||||
'include.text',
|
||||
'View parent execution',
|
||||
);
|
||||
|
||||
getExecutionPreviewOutputPanelRelatedExecutionLink()
|
||||
.invoke('removeAttr', 'target')
|
||||
.click({ force: true });
|
||||
|
||||
cy.url().then((currentUrl) => {
|
||||
expect(currentUrl === workflowUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -249,6 +249,15 @@ describe('NDV', () => {
|
|||
ndv.actions.switchInputMode('Table');
|
||||
ndv.actions.switchOutputMode('Table');
|
||||
|
||||
// Start from linked state
|
||||
ndv.getters.outputLinkRun().then(($el) => {
|
||||
const classList = Array.from($el[0].classList);
|
||||
if (!classList.includes('linked')) {
|
||||
ndv.actions.toggleOutputRunLinking();
|
||||
ndv.getters.inputTbodyCell(1, 0).click(); // remove tooltip
|
||||
}
|
||||
});
|
||||
|
||||
ndv.getters
|
||||
.inputRunSelector()
|
||||
.should('exist')
|
||||
|
|
|
@ -200,7 +200,14 @@ describe('Workflow Actions', () => {
|
|||
WorkflowPage.getters.nodeConnections().should('have.length', 2);
|
||||
// Check if all nodes have names
|
||||
WorkflowPage.getters.canvasNodes().each((node) => {
|
||||
cy.wrap(node).should('have.attr', 'data-name');
|
||||
cy.ifCanvasVersion(
|
||||
() => {
|
||||
cy.wrap(node).should('have.attr', 'data-name');
|
||||
},
|
||||
() => {
|
||||
cy.wrap(node).should('have.attr', 'data-node-name');
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"cypress:install": "cypress install",
|
||||
"test:e2e:ui": "scripts/run-e2e.js ui",
|
||||
"test:e2e:dev": "scripts/run-e2e.js dev",
|
||||
"test:e2e:dev:v2": "scripts/run-e2e.js dev:v2",
|
||||
"test:e2e:dev:v1": "scripts/run-e2e.js dev:v1",
|
||||
"test:e2e:all": "scripts/run-e2e.js all",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome ci .",
|
||||
|
|
|
@ -96,7 +96,7 @@ export class WorkflowPage extends BasePage {
|
|||
nodeConnections: () =>
|
||||
cy.ifCanvasVersion(
|
||||
() => cy.get('.jtk-connector'),
|
||||
() => cy.getByTestId('edge-label'),
|
||||
() => cy.getByTestId('edge'),
|
||||
),
|
||||
zoomToFitButton: () => cy.getByTestId('zoom-to-fit'),
|
||||
nodeEndpoints: () => cy.get('.jtk-endpoint-connected'),
|
||||
|
@ -182,7 +182,7 @@ export class WorkflowPage extends BasePage {
|
|||
),
|
||||
() =>
|
||||
cy.get(
|
||||
`[data-test-id="edge-label"][data-source-node-name="${sourceNodeName}"][data-target-node-name="${targetNodeName}"]`,
|
||||
`[data-test-id="edge"][data-source-node-name="${sourceNodeName}"][data-target-node-name="${targetNodeName}"]`,
|
||||
),
|
||||
),
|
||||
getConnectionActionsBetweenNodes: (sourceNodeName: string, targetNodeName: string) =>
|
||||
|
@ -430,7 +430,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.canvasViewport().trigger('wheel', {
|
||||
getCanvasPane().trigger('wheel', {
|
||||
force: true,
|
||||
bubbles: true,
|
||||
ctrlKey: true,
|
||||
|
|
|
@ -45,19 +45,23 @@ switch (scenario) {
|
|||
startCommand: 'start',
|
||||
url: 'http://localhost:5678/favicon.ico',
|
||||
testCommand: 'cypress open',
|
||||
customEnv: {
|
||||
CYPRESS_NODE_VIEW_VERSION: 2,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'dev':
|
||||
case 'dev:v1':
|
||||
runTests({
|
||||
startCommand: 'develop',
|
||||
url: 'http://localhost:8080/favicon.ico',
|
||||
testCommand: 'cypress open',
|
||||
customEnv: {
|
||||
CYPRESS_NODE_VIEW_VERSION: 1,
|
||||
CYPRESS_BASE_URL: 'http://localhost:8080',
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'dev:v2':
|
||||
case 'dev':
|
||||
runTests({
|
||||
startCommand: 'develop',
|
||||
url: 'http://localhost:8080/favicon.ico',
|
||||
|
@ -76,6 +80,9 @@ switch (scenario) {
|
|||
startCommand: 'start',
|
||||
url: 'http://localhost:5678/favicon.ico',
|
||||
testCommand: `cypress run --headless ${specParam}`,
|
||||
customEnv: {
|
||||
CYPRESS_NODE_VIEW_VERSION: 2,
|
||||
},
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -38,7 +38,21 @@ beforeEach(() => {
|
|||
data: { status: 'success', message: 'Tested successfully' },
|
||||
}).as('credentialTest');
|
||||
|
||||
cy.intercept('POST', '/rest/license/renew', {});
|
||||
cy.intercept('POST', '/rest/license/renew', {
|
||||
data: {
|
||||
usage: {
|
||||
activeWorkflowTriggers: {
|
||||
limit: -1,
|
||||
value: 0,
|
||||
warningThreshold: 0.8,
|
||||
},
|
||||
},
|
||||
license: {
|
||||
planId: '',
|
||||
planName: 'Community',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
cy.intercept({ pathname: '/api/health' }, { status: 'OK' }).as('healthCheck');
|
||||
cy.intercept({ pathname: '/api/versions/*' }, [
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"dev:fe": "run-p start \"dev:fe:editor --filter=n8n-design-system\"",
|
||||
"dev:fe:editor": "turbo run dev --parallel --env-mode=loose --filter=n8n-editor-ui",
|
||||
"dev:e2e": "cd cypress && pnpm run test:e2e:dev",
|
||||
"dev:e2e:v2": "cd cypress && pnpm run test:e2e:dev:v2",
|
||||
"dev:e2e:v1": "cd cypress && pnpm run test:e2e:dev:v1",
|
||||
"dev:e2e:server": "run-p start dev:fe:editor",
|
||||
"clean": "turbo run clean --parallel",
|
||||
"reset": "node scripts/ensure-zx.mjs && zx scripts/reset.mjs",
|
||||
|
|
|
@ -1431,7 +1431,7 @@ defineExpose({ enterEditMode });
|
|||
</template>
|
||||
<N8nIconButton
|
||||
:icon="linkedRuns ? 'unlink' : 'link'"
|
||||
class="linkRun"
|
||||
:class="['linkRun', linkedRuns ? 'linked' : '']"
|
||||
text
|
||||
type="tertiary"
|
||||
size="small"
|
||||
|
|
|
@ -102,8 +102,8 @@ export const useKeybindings = (
|
|||
function toShortcutString(event: KeyboardEvent) {
|
||||
const { shiftKey, altKey } = event;
|
||||
const ctrlKey = isCtrlKeyPressed(event);
|
||||
const keys = [event.key];
|
||||
const codes = [keyboardEventCodeToKey(event.code)];
|
||||
const keys = 'key' in event ? [event.key] : [];
|
||||
const codes = 'code' in event ? [keyboardEventCodeToKey(event.code)] : [];
|
||||
const modifiers: string[] = [];
|
||||
|
||||
if (shiftKey) {
|
||||
|
|
|
@ -35,7 +35,7 @@ export const useUsageStore = defineStore('usage', () => {
|
|||
const rootStore = useRootStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const state = reactive<UsageState>(DEFAULT_STATE);
|
||||
const state = reactive<UsageState>({ ...DEFAULT_STATE });
|
||||
|
||||
const planName = computed(() => state.data.license.planName || DEFAULT_PLAN_NAME);
|
||||
const planId = computed(() => state.data.license.planId);
|
||||
|
|
|
@ -349,7 +349,9 @@ async function initializeRoute(force = false) {
|
|||
if (!isAlreadyInitialized) {
|
||||
historyStore.reset();
|
||||
|
||||
await loadCredentials();
|
||||
if (!isDemoRoute.value) {
|
||||
await loadCredentials();
|
||||
}
|
||||
|
||||
// If there is no workflow id, treat it as a new workflow
|
||||
if (isNewWorkflowRoute.value || !workflowId.value) {
|
||||
|
|
Loading…
Reference in a new issue