diff --git a/cypress/e2e/11-inline-expression-editor.cy.ts b/cypress/e2e/11-inline-expression-editor.cy.ts index cbc4736e2b..10bc16fde8 100644 --- a/cypress/e2e/11-inline-expression-editor.cy.ts +++ b/cypress/e2e/11-inline-expression-editor.cy.ts @@ -10,7 +10,7 @@ describe('Inline expression editor', () => { beforeEach(() => { WorkflowPage.actions.visit(); - WorkflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); + WorkflowPage.actions.addInitialNodeToCanvas('Manual'); WorkflowPage.actions.addNodeToCanvas('Hacker News'); WorkflowPage.actions.openNode('Hacker News'); WorkflowPage.actions.openInlineExpressionEditor(); @@ -50,6 +50,7 @@ describe('Inline expression editor', () => { }); it('should resolve array resolvables', () => { + WorkflowPage.getters.inlineExpressionEditorInput().clear(); WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); WorkflowPage.getters.inlineExpressionEditorInput().type('[1, 2, 3]'); WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^\[Array: \[1,2,3\]\]$/); @@ -63,8 +64,9 @@ describe('Inline expression editor', () => { }); it('should resolve $parameter[]', () => { + WorkflowPage.getters.inlineExpressionEditorInput().clear(); WorkflowPage.getters.inlineExpressionEditorInput().type('{{'); WorkflowPage.getters.inlineExpressionEditorInput().type('$parameter["operation"]'); - WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^get$/); + WorkflowPage.getters.inlineExpressionEditorOutput().contains(/^getAll$/); }); }); diff --git a/cypress/e2e/12-canvas.cy.ts b/cypress/e2e/12-canvas.cy.ts index ac0bbd7bad..13d1bc6392 100644 --- a/cypress/e2e/12-canvas.cy.ts +++ b/cypress/e2e/12-canvas.cy.ts @@ -96,6 +96,7 @@ describe('Canvas Actions', () => { it('should add merge node and test connections', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); for (let i = 0; i < 2; i++) { WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME, true); WorkflowPage.getters.nodeViewBackground().click(600 + i * 100, 200, { force: true }); @@ -138,6 +139,7 @@ describe('Canvas Actions', () => { it('should add nodes and check execution success', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); for (let i = 0; i < 3; i++) { WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME, true); } @@ -235,6 +237,7 @@ describe('Canvas Actions', () => { 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(); cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150]); @@ -316,6 +319,7 @@ describe('Canvas Actions', () => { 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}'); @@ -326,6 +330,7 @@ describe('Canvas Actions', () => { 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(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); cy.wait(500); cy.get('body').type('{shift}', { release: false }).type('{leftArrow}'); @@ -334,6 +339,7 @@ describe('Canvas Actions', () => { it('should delete connections by pressing the delete button', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + 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 }); @@ -342,6 +348,7 @@ describe('Canvas Actions', () => { 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); @@ -349,6 +356,7 @@ describe('Canvas Actions', () => { it('should disable node by pressing the disable button', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.getters .canvasNodes() @@ -360,6 +368,7 @@ describe('Canvas Actions', () => { it('should disable node using keyboard shortcut', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.getters.canvasNodes().last().click(); WorkflowPage.actions.hitDisableNodeShortcut(); @@ -368,6 +377,7 @@ describe('Canvas Actions', () => { it('should disable multiple nodes', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); cy.get('body').type('{esc}'); cy.get('body').type('{esc}'); @@ -389,6 +399,7 @@ describe('Canvas Actions', () => { it('should duplicate node', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.getters .canvasNodes() diff --git a/cypress/e2e/13-pinning.cy.ts b/cypress/e2e/13-pinning.cy.ts index 0b5f7b2683..f21320ca9c 100644 --- a/cypress/e2e/13-pinning.cy.ts +++ b/cypress/e2e/13-pinning.cy.ts @@ -12,8 +12,7 @@ describe('Data pinning', () => { }); it('Should be able to pin node output', () => { - workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger'); - workflowPage.getters.canvasNodes().first().dblclick(); + workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true}); ndv.getters.container().should('be.visible'); ndv.getters.pinDataButton().should('not.exist'); ndv.getters.editPinnedDataButton().should('be.visible'); @@ -21,7 +20,9 @@ describe('Data pinning', () => { ndv.actions.execute(); ndv.getters.outputDataContainer().should('be.visible'); - ndv.getters.outputDataContainer().get('table').should('be.visible'); + // We hover over the table to get rid of the pinning tooltip which would overlay the table + // slightly and cause the test to fail + ndv.getters.outputDataContainer().get('table').realHover().should('be.visible'); ndv.getters.outputTableRows().should('have.length', 2); ndv.getters.outputTableHeaders().should('have.length.at.least', 10); ndv.getters.outputTableHeaders().first().should('include.text', 'timestamp'); @@ -42,8 +43,7 @@ describe('Data pinning', () => { }); it('Should be be able to set pinned data', () => { - workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger'); - workflowPage.getters.canvasNodes().first().dblclick(); + workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger', { keepNdvOpen: true}); ndv.getters.container().should('be.visible'); ndv.getters.pinDataButton().should('not.exist'); ndv.getters.editPinnedDataButton().should('be.visible'); diff --git a/cypress/e2e/14-data-transformation-expressions.cy.ts b/cypress/e2e/14-data-transformation-expressions.cy.ts index 6f6b802926..a5186a8af5 100644 --- a/cypress/e2e/14-data-transformation-expressions.cy.ts +++ b/cypress/e2e/14-data-transformation-expressions.cy.ts @@ -26,7 +26,8 @@ describe('Data transformation expressions', () => { ndv.getters.inlineExpressionEditorInput().clear().type(input); ndv.actions.execute(); - ndv.getters.outputDataContainer().should('be.visible').contains(output); + ndv.getters.outputDataContainer().should('be.visible') + ndv.getters.outputDataContainer().contains(output); }); it('$json + n8n string methods', () => { @@ -40,7 +41,8 @@ describe('Data transformation expressions', () => { ndv.getters.inlineExpressionEditorInput().clear().type(input); ndv.actions.execute(); - ndv.getters.outputDataContainer().should('be.visible').contains(output); + ndv.getters.outputDataContainer().should('be.visible') + ndv.getters.outputDataContainer().contains(output); }); it('$json + native numeric methods', () => { @@ -54,7 +56,8 @@ describe('Data transformation expressions', () => { ndv.getters.inlineExpressionEditorInput().clear().type(input); ndv.actions.execute(); - ndv.getters.outputDataContainer().should('be.visible').contains(output); + ndv.getters.outputDataContainer().should('be.visible') + ndv.getters.outputDataContainer().contains(output); }); it('$json + n8n numeric methods', () => { @@ -68,7 +71,8 @@ describe('Data transformation expressions', () => { ndv.getters.inlineExpressionEditorInput().clear().type(input); ndv.actions.execute(); - ndv.getters.outputDataContainer().should('be.visible').contains(output); + ndv.getters.outputDataContainer().should('be.visible') + ndv.getters.outputDataContainer().contains(output); }); it('$json + native array methods', () => { @@ -82,7 +86,8 @@ describe('Data transformation expressions', () => { ndv.getters.inlineExpressionEditorInput().clear().type(input); ndv.actions.execute(); - ndv.getters.outputDataContainer().should('be.visible').contains(output); + ndv.getters.outputDataContainer().should('be.visible') + ndv.getters.outputDataContainer().contains(output); }); it('$json + n8n array methods', () => { diff --git a/cypress/e2e/14-mapping.cy.ts b/cypress/e2e/14-mapping.cy.ts index ade387e454..846eafb6e5 100644 --- a/cypress/e2e/14-mapping.cy.ts +++ b/cypress/e2e/14-mapping.cy.ts @@ -1,8 +1,12 @@ -import { WorkflowPage, NDV, CanvasNode } from '../pages'; +import { + MANUAL_TRIGGER_NODE_NAME, + MANUAL_TRIGGER_NODE_DISPLAY_NAME, + SCHEDULE_TRIGGER_NODE_NAME, +} from './../constants'; +import { WorkflowPage, NDV } from '../pages'; const workflowPage = new WorkflowPage(); const ndv = new NDV(); -const canvasNode = new CanvasNode(); describe('Data mapping', () => { beforeEach(() => { @@ -20,7 +24,7 @@ describe('Data mapping', () => { cy.fixture('Test_workflow-actions_paste-data.json').then((data) => { cy.get('body').paste(JSON.stringify(data)); }); - canvasNode.actions.openNode('Set'); + workflowPage.actions.openNode('Set'); ndv.actions.executePrevious(); ndv.actions.switchInputMode('Table'); ndv.getters.inputDataContainer().get('table', { timeout: 10000 }).should('exist'); @@ -42,7 +46,7 @@ describe('Data mapping', () => { cy.get('body').paste(JSON.stringify(data)); }); - canvasNode.actions.openNode('Set'); + workflowPage.actions.openNode('Set'); ndv.actions.switchInputMode('Table'); ndv.getters.inputDataContainer().get('table', { timeout: 10000 }).should('exist'); @@ -87,7 +91,7 @@ describe('Data mapping', () => { cy.get('body').paste(JSON.stringify(data)); }); - canvasNode.actions.openNode('Set'); + workflowPage.actions.openNode('Set'); ndv.actions.switchInputMode('JSON'); ndv.getters.inputDataContainer().should('exist').find('.json-data') @@ -115,7 +119,7 @@ describe('Data mapping', () => { cy.get('body').paste(JSON.stringify(data)); }); - canvasNode.actions.openNode('Set'); + workflowPage.actions.openNode('Set'); ndv.actions.clearParameterInput('value'); cy.get('body').type('{esc}'); @@ -142,22 +146,22 @@ describe('Data mapping', () => { it('maps expressions from previous nodes', () => { cy.createFixtureWorkflow('Test_workflow_3.json', `My test workflow`); - canvasNode.actions.openNode('Set1'); + workflowPage.actions.openNode('Set1'); - ndv.actions.selectInputNode('Schedule Trigger'); + ndv.actions.selectInputNode(SCHEDULE_TRIGGER_NODE_NAME); ndv.getters.inputDataContainer() .find('span').contains('count') .realMouseDown(); ndv.actions.mapToParameter('value'); - ndv.getters.inlineExpressionEditorInput().should('have.text', '{{ $node["Schedule Trigger"].json.input[0].count }}'); + ndv.getters.inlineExpressionEditorInput().should('have.text', `{{ $node["${SCHEDULE_TRIGGER_NODE_NAME}"].json.input[0].count }}`); ndv.getters.parameterExpressionPreview('value') .should('not.exist'); ndv.actions.switchInputMode('Table'); ndv.actions.mapDataFromHeader(1, 'value'); - ndv.getters.inlineExpressionEditorInput().should('have.text', '{{ $node["Schedule Trigger"].json.input[0].count }} {{ $node["Schedule Trigger"].json.input }}'); + ndv.getters.inlineExpressionEditorInput().should('have.text', `{{ $node["${SCHEDULE_TRIGGER_NODE_NAME}"].json.input[0].count }} {{ $node["${SCHEDULE_TRIGGER_NODE_NAME}"].json.input }}`); ndv.getters.parameterExpressionPreview('value') .should('not.exist'); @@ -175,8 +179,9 @@ describe('Data mapping', () => { }); it('maps keys to path', () => { - workflowPage.actions.addInitialNodeToCanvas('Manual Trigger', {keepNdvOpen: true}); - + workflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + workflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click(); + workflowPage.actions.openNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME); ndv.actions.setPinnedData([ { input: [ @@ -201,7 +206,7 @@ describe('Data mapping', () => { ndv.actions.close(); workflowPage.actions.addNodeToCanvas('Item Lists'); - canvasNode.actions.openNode('Item Lists'); + workflowPage.actions.openNode('Item Lists'); ndv.getters.parameterInput('operation') .click() diff --git a/cypress/e2e/2-credentials.cy.ts b/cypress/e2e/2-credentials.cy.ts index 0249ab0bbd..2ab6473702 100644 --- a/cypress/e2e/2-credentials.cy.ts +++ b/cypress/e2e/2-credentials.cy.ts @@ -253,7 +253,7 @@ describe('Credentials', () => { it('should render custom node with n8n credential', () => { workflowPage.actions.visit(); - workflowPage.actions.addNodeToCanvas('Manual Trigger'); + workflowPage.actions.addNodeToCanvas('Manual'); workflowPage.actions.addNodeToCanvas('E2E Node with native n8n credential', true, true); workflowPage.getters.nodeCredentialsLabel().click(); cy.contains('Create New Credential').click(); @@ -263,7 +263,7 @@ describe('Credentials', () => { it('should render custom node with custom credential', () => { workflowPage.actions.visit(); - workflowPage.actions.addNodeToCanvas('Manual Trigger'); + workflowPage.actions.addNodeToCanvas('Manual'); workflowPage.actions.addNodeToCanvas('E2E Node with custom credential', true, true); workflowPage.getters.nodeCredentialsLabel().click(); cy.contains('Create New Credential').click(); diff --git a/cypress/e2e/4-node-creator.cy.ts b/cypress/e2e/4-node-creator.cy.ts index e70fddc777..4f43684c93 100644 --- a/cypress/e2e/4-node-creator.cy.ts +++ b/cypress/e2e/4-node-creator.cy.ts @@ -1,13 +1,16 @@ import { NodeCreator } from '../pages/features/node-creator'; -import { INodeTypeDescription } from 'n8n-workflow'; import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants'; import { randFirstName, randLastName } from '@ngneat/falso'; +import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +import { NDV } from '../pages/ndv'; const email = DEFAULT_USER_EMAIL; const password = DEFAULT_USER_PASSWORD; const firstName = randFirstName(); const lastName = randLastName(); const nodeCreatorFeature = new NodeCreator(); +const WorkflowPage = new WorkflowPageClass(); +const NDVModal = new NDV(); describe('Node Creator', () => { before(() => { @@ -27,30 +30,22 @@ describe('Node Creator', () => { nodeCreatorFeature.getters .nodeCreator() - .contains('When should this workflow run?') + .contains('Select a trigger') .should('be.visible'); - nodeCreatorFeature.getters.nodeCreatorTabs().should('not.exist'); - }); - - it('should see all tabs when opening via plus button', () => { - nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.getters.nodeCreatorTabs().should('exist'); - nodeCreatorFeature.getters.selectedTab().should('have.text', 'Trigger'); }); it('should navigate subcategory', () => { nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.getters.getCreatorItem('On App Event').click(); - nodeCreatorFeature.getters.activeSubcategory().should('have.text', 'On App Event'); + nodeCreatorFeature.getters.getCreatorItem('On app event').click(); + nodeCreatorFeature.getters.activeSubcategory().should('have.text', 'On app event'); // Go back nodeCreatorFeature.getters.activeSubcategory().find('button').click(); - nodeCreatorFeature.getters.activeSubcategory().should('not.exist'); + nodeCreatorFeature.getters.activeSubcategory().should('not.have.text', 'On app event'); }); it('should search for nodes', () => { nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.getters.selectedTab().should('have.text', 'Trigger'); nodeCreatorFeature.getters.searchBar().find('input').type('manual'); nodeCreatorFeature.getters.creatorItem().should('have.length', 1); @@ -72,92 +67,63 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.creatorItem().should('have.length', 0); nodeCreatorFeature.getters.searchBar().find('input').clear(); - nodeCreatorFeature.getters.getCreatorItem('On App Event').click(); + nodeCreatorFeature.getters.getCreatorItem('On app event').click(); nodeCreatorFeature.getters.searchBar().find('input').clear().type('edit image'); - nodeCreatorFeature.getters.creatorItem().should('have.length', 0); - nodeCreatorFeature.getters - .noResults() - .should('exist') - .should('contain.text', 'To see all results, click here'); - - nodeCreatorFeature.getters.noResults().contains('click here').click(); - nodeCreatorFeature.getters.nodeCreatorTabs().should('exist'); + nodeCreatorFeature.getters.getCreatorItem('Results in other categories (1)').should('exist'); + nodeCreatorFeature.getters.creatorItem().should('have.length', 2); nodeCreatorFeature.getters.getCreatorItem('Edit Image').should('exist'); - nodeCreatorFeature.getters.selectedTab().should('have.text', 'All'); - nodeCreatorFeature.getters.searchBar().find('button').click(); - nodeCreatorFeature.getters.searchBar().find('input').should('be.empty'); + nodeCreatorFeature.getters.searchBar().find('input').clear().type('edit image123123'); + nodeCreatorFeature.getters.creatorItem().should('have.length', 0); }); - it('should add manual trigger node', () => { + it('should check correct view panels', () => { nodeCreatorFeature.getters.canvasAddButton().click(); - nodeCreatorFeature.getters.getCreatorItem('Manually').click(); - - // TODO: Replace once we have canvas feature utils - cy.get('span').contains('Back to canvas').click(); + WorkflowPage.actions.addNodeToCanvas('Manual', false); nodeCreatorFeature.getters.canvasAddButton().should('not.be.visible'); nodeCreatorFeature.getters.nodeCreator().should('not.exist'); // TODO: Replace once we have canvas feature utils - cy.get('div').contains('Add first step').should('exist'); + cy.get('div').contains('Add first step').should('be.hidden'); + nodeCreatorFeature.actions.openNodeCreator() + nodeCreatorFeature.getters + .nodeCreator() + .contains('What happens next?') + .should('be.visible'); + + nodeCreatorFeature.getters.getCreatorItem('Add another trigger').click(); + nodeCreatorFeature.getters.nodeCreator().contains('Select a trigger').should('be.visible'); + nodeCreatorFeature.getters.activeSubcategory().find('button').should('exist'); + nodeCreatorFeature.getters.activeSubcategory().find('button').click(); + nodeCreatorFeature.getters + .nodeCreator() + .contains('What happens next?') + .should('be.visible'); }); - it('check if non-core nodes are rendered', () => { - cy.wait('@nodesIntercept').then((interception) => { - const nodes = interception.response?.body as INodeTypeDescription[]; - const categorizedNodes = nodeCreatorFeature.actions.categorizeNodes(nodes); - nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.actions.selectTab('All'); - - const categories = Object.keys(categorizedNodes); - categories.forEach((category: string) => { - // Core Nodes contains subcategories which we'll test separately - if (category === 'Core Nodes') return; - - nodeCreatorFeature.actions.toggleCategory(category); - - // Check if all nodes are present - nodeCreatorFeature.getters.nodeItemName().then(($elements) => { - const visibleNodes: string[] = []; - $elements.each((_, element) => { - visibleNodes.push(element.textContent?.trim() || ''); - }); - const visibleCategoryNodes = (categorizedNodes[category] as INodeTypeDescription[]) - .filter((node) => !node.hidden) - .map((node) => node.displayName?.trim()); - - cy.wrap(visibleCategoryNodes).each((categoryNode: string) => { - expect(visibleNodes).to.include(categoryNode); - }); - }); - - nodeCreatorFeature.actions.toggleCategory(category); - }); - }); - }); + it('should add node to canvas from actions panel', () => { + const editImageNode = 'Edit Image'; + nodeCreatorFeature.actions.openNodeCreator(); + nodeCreatorFeature.getters.searchBar().find('input').clear().type(editImageNode); + nodeCreatorFeature.getters.getCreatorItem(editImageNode).click(); + nodeCreatorFeature.getters.activeSubcategory().should('have.text', editImageNode); + nodeCreatorFeature.getters.getCreatorItem('Crop Image').click(); + NDVModal.getters.parameterInput('operation').should('contain.text', 'Crop'); + }) it('should render and select community node', () => { cy.intercept('GET', '/types/nodes.json').as('nodesIntercept'); cy.wait('@nodesIntercept').then(() => { - const customCategory = 'Custom Category'; const customNode = 'E2E Node'; - const customNodeDescription = 'Demonstrate rendering of node'; nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.actions.selectTab('All'); + nodeCreatorFeature.getters.searchBar().find('input').clear().type(customNode); - nodeCreatorFeature.getters.getCreatorItem(customCategory).should('exist'); - - nodeCreatorFeature.actions.toggleCategory(customCategory); nodeCreatorFeature.getters .getCreatorItem(customNode) .findChildByTestId('node-creator-item-tooltip') .should('exist'); - nodeCreatorFeature.getters - .getCreatorItem(customNode) - .contains(customNodeDescription) - .should('exist'); nodeCreatorFeature.actions.selectNode(customNode); // TODO: Replace once we have canvas feature utils diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts index c0e12a6ba2..350ad3a185 100644 --- a/cypress/e2e/5-ndv.cy.ts +++ b/cypress/e2e/5-ndv.cy.ts @@ -17,7 +17,7 @@ describe('NDV', () => { it('should show up when double clicked on a node and close when Back to canvas clicked', () => { - workflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); + workflowPage.actions.addInitialNodeToCanvas('Manual'); workflowPage.getters.canvasNodes().first().dblclick(); ndv.getters.container().should('be.visible'); ndv.getters.backToCanvas().click(); @@ -67,8 +67,8 @@ describe('NDV', () => { }); it('should show validation errors only after blur or re-opening of NDV', () => { - workflowPage.actions.addNodeToCanvas('Manual Trigger'); - workflowPage.actions.addNodeToCanvas('Airtable', true, true); + workflowPage.actions.addNodeToCanvas('Manual'); + workflowPage.actions.addNodeToCanvas('Airtable', true, true, 'Read data from a table'); ndv.getters.container().should('be.visible'); cy.get('.has-issues').should('have.length', 0); ndv.getters.parameterInput('table').find('input').eq(1).focus().blur(); diff --git a/cypress/e2e/6-code-node.cy.ts b/cypress/e2e/6-code-node.cy.ts index b833eed003..540e4825a0 100644 --- a/cypress/e2e/6-code-node.cy.ts +++ b/cypress/e2e/6-code-node.cy.ts @@ -12,7 +12,7 @@ describe('Code node', () => { it('should execute the placeholder in all-items mode successfully', () => { WorkflowPage.actions.visit(); - WorkflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); + WorkflowPage.actions.addInitialNodeToCanvas('Manual'); WorkflowPage.actions.addNodeToCanvas('Code'); WorkflowPage.actions.openNode('Code'); @@ -23,7 +23,7 @@ describe('Code node', () => { it('should execute the placeholder in each-item mode successfully', () => { WorkflowPage.actions.visit(); - WorkflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); + WorkflowPage.actions.addInitialNodeToCanvas('Manual'); WorkflowPage.actions.addNodeToCanvas('Code'); WorkflowPage.actions.openNode('Code'); ndv.getters.parameterInput('mode').click(); diff --git a/cypress/e2e/8-http-request-node.cy.ts b/cypress/e2e/8-http-request-node.cy.ts index eb4d93c171..cc62b5182f 100644 --- a/cypress/e2e/8-http-request-node.cy.ts +++ b/cypress/e2e/8-http-request-node.cy.ts @@ -14,7 +14,7 @@ describe('HTTP Request node', () => { cy.visit(workflowsPage.url); workflowsPage.actions.createWorkflowFromCard(); - workflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); + workflowPage.actions.addInitialNodeToCanvas('Manual'); workflowPage.actions.addNodeToCanvas('HTTP Request'); workflowPage.actions.openNode('HTTP Request'); ndv.actions.typeIntoParameterInput('url', 'https://catfact.ninja/fact'); diff --git a/cypress/e2e/9-expression-editor-modal.cy.ts b/cypress/e2e/9-expression-editor-modal.cy.ts index 1a8909872d..957c0505f5 100644 --- a/cypress/e2e/9-expression-editor-modal.cy.ts +++ b/cypress/e2e/9-expression-editor-modal.cy.ts @@ -10,13 +10,14 @@ describe('Expression editor modal', () => { beforeEach(() => { WorkflowPage.actions.visit(); - WorkflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); + WorkflowPage.actions.addInitialNodeToCanvas('Manual'); WorkflowPage.actions.addNodeToCanvas('Hacker News'); WorkflowPage.actions.openNode('Hacker News'); WorkflowPage.actions.openExpressionEditorModal(); }); it('should resolve primitive resolvables', () => { + WorkflowPage.getters.expressionModalInput().clear(); WorkflowPage.getters.expressionModalInput().type('{{ 1 + 2'); WorkflowPage.getters.expressionModalOutput().contains(/^3$/); WorkflowPage.getters.expressionModalInput().clear(); @@ -31,6 +32,7 @@ describe('Expression editor modal', () => { }); it('should resolve object resolvables', () => { + WorkflowPage.getters.expressionModalInput().clear(); WorkflowPage.getters .expressionModalInput() .type('{{ { a : 1 }', { parseSpecialCharSequences: false }); @@ -45,6 +47,7 @@ describe('Expression editor modal', () => { }); it('should resolve array resolvables', () => { + WorkflowPage.getters.expressionModalInput().clear(); WorkflowPage.getters.expressionModalInput().type('{{ [1, 2, 3]'); WorkflowPage.getters.expressionModalOutput().contains(/^\[Array: \[1,2,3\]\]$/); @@ -55,7 +58,8 @@ describe('Expression editor modal', () => { }); it('should resolve $parameter[]', () => { + WorkflowPage.getters.expressionModalInput().clear(); WorkflowPage.getters.expressionModalInput().type('{{ $parameter["operation"]'); - WorkflowPage.getters.expressionModalOutput().contains(/^get$/); + WorkflowPage.getters.expressionModalOutput().contains(/^getAll$/); }); }); diff --git a/cypress/pages/canvas-node.ts b/cypress/pages/canvas-node.ts deleted file mode 100644 index b0d0032320..0000000000 --- a/cypress/pages/canvas-node.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BasePage } from './base'; - -export class CanvasNode extends BasePage { - getters = { - nodes: () => cy.getByTestId('canvas-node'), - nodeByName: (nodeName: string) => - this.getters.nodes().filter(`:contains("${nodeName}")`), - }; - - actions = { - openNode: (nodeName: string) => { - this.getters.nodeByName(nodeName).eq(0).dblclick(); - }, - }; -} diff --git a/cypress/pages/features/node-creator.ts b/cypress/pages/features/node-creator.ts index 7db317a48e..0899b188d5 100644 --- a/cypress/pages/features/node-creator.ts +++ b/cypress/pages/features/node-creator.ts @@ -31,9 +31,6 @@ export class NodeCreator extends BasePage { selectNode: (displayName: string) => { this.getters.getCreatorItem(displayName).click(); }, - selectTab: (tab: string) => { - this.getters.nodeCreatorTabs().contains(tab).click(); - }, toggleCategory: (category: string) => { this.getters.getCreatorItem(category).click(); }, diff --git a/cypress/pages/index.ts b/cypress/pages/index.ts index 7b4c1e0931..35ef30d5ec 100644 --- a/cypress/pages/index.ts +++ b/cypress/pages/index.ts @@ -9,4 +9,3 @@ export * from './settings-users'; export * from './settings-log-streaming'; export * from './sidebar'; export * from './ndv'; -export * from './canvas-node'; diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index 9cc0285a4f..94b648ccf4 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -19,7 +19,7 @@ export class NDV extends BasePage { digital: () => cy.getByTestId('ndv-run-data-display-mode'), pinDataButton: () => cy.getByTestId('ndv-pin-data'), editPinnedDataButton: () => cy.getByTestId('ndv-edit-pinned-data'), - pinnedDataEditor: () => this.getters.outputPanel().find('.monaco-editor'), + pinnedDataEditor: () => this.getters.outputPanel().find('.monaco-editor[role=code]'), runDataPaneHeader: () => cy.getByTestId('run-data-pane-header'), savePinnedDataButton: () => this.getters.runDataPaneHeader().find('button').contains('Save'), outputTableRows: () => this.getters.outputDataContainer().find('table tr'), @@ -71,10 +71,9 @@ export class NDV extends BasePage { setPinnedData: (data: object) => { this.getters.editPinnedDataButton().click(); - const editor = this.getters.pinnedDataEditor(); - editor.click(); - editor.type(`{selectall}{backspace}`); - editor.type(JSON.stringify(data).replace(new RegExp('{', 'g'), '{{}')); + this.getters.pinnedDataEditor().click(); + this.getters.pinnedDataEditor().type(`{selectall}{backspace}`); + this.getters.pinnedDataEditor().type(JSON.stringify(data).replace(new RegExp('{', 'g'), '{{}')); this.actions.savePinnedData(); }, diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index bf150e038a..2094726db8 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -24,7 +24,7 @@ export class WorkflowPage extends BasePage { canvasPlusButton: () => cy.getByTestId('canvas-plus-button'), canvasNodes: () => cy.getByTestId('canvas-node'), canvasNodeByName: (nodeName: string) => - this.getters.canvasNodes().filter(`:contains("${nodeName}")`), + this.getters.canvasNodes().filter(`:contains(${nodeName})`), getEndpointSelector: (type: 'input' | 'output' | 'plus', nodeName: string, index = 0) => { return `[data-endpoint-name='${nodeName}'][data-endpoint-type='${type}'][data-input-index='${index}']`; }, @@ -124,6 +124,7 @@ export class WorkflowPage extends BasePage { nodeDisplayName: string, plusButtonClick = true, preventNdvClose?: boolean, + action?: string, ) => { if (plusButtonClick) { this.getters.nodeCreatorPlusButton().click(); @@ -131,11 +132,21 @@ export class WorkflowPage extends BasePage { this.getters.nodeCreatorSearchBar().type(nodeDisplayName); this.getters.nodeCreatorSearchBar().type('{enter}'); + cy.wait(500) + cy.get('body').then((body) => { + if(body.find('[data-test-id=node-creator]').length > 0) { + if(action) { + cy.contains(action).click() + } else { + cy.getByTestId('item-iterator-item').eq(1).click() + } + } + }) if (!preventNdvClose) cy.get('body').type('{esc}'); }, openNode: (nodeTypeName: string) => { - this.getters.canvasNodeByName(nodeTypeName).dblclick(); + this.getters.canvasNodeByName(nodeTypeName).first().dblclick(); }, openExpressionEditorModal: () => { cy.contains('Expression').invoke('show').click(); diff --git a/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue b/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue index 4e0aef14a9..ebac11ac37 100644 --- a/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue +++ b/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue @@ -13,7 +13,7 @@
- + import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; -import TriggerIcon from './TriggerIcon.vue'; import N8nTooltip from '../N8nTooltip'; export interface Props { @@ -60,14 +59,14 @@ defineEmits<{ align-items: center; cursor: pointer; z-index: 1; - padding: 11px 8px 11px 0; + padding: var(--spacing-xs) var(--spacing-2xs) var(--spacing-xs) 0; &.hasAction { user-select: none; } } .creatorNode:hover .panelIcon { - color: var(--color-text-light); + color: var(--action-arrow-color-hover, var(--color-text-light)); } .panelIcon { @@ -76,7 +75,7 @@ defineEmits<{ justify-content: flex-end; align-items: center; margin-left: var(--spacing-2xs); - color: var(--color-text-lighter); + color: var(--action-arrow-color, var(--color-text-lighter)); cursor: pointer; background: transparent; border: none; @@ -110,11 +109,12 @@ defineEmits<{ font-size: var(--font-size-2xs); line-height: 1rem; font-weight: 400; - color: var(--node-creator-description-colo, var(--color-text-base)); + color: var(--node-creator-description-colos, var(--color-text-base)); } .triggerIcon { - margin-left: var(--spacing-2xs); + margin-left: var(--spacing-3xs); + color: var(--color-primary); } diff --git a/packages/design-system/src/components/N8nNodeCreatorNode/TriggerIcon.vue b/packages/design-system/src/components/N8nNodeCreatorNode/TriggerIcon.vue deleted file mode 100644 index dda6b227e7..0000000000 --- a/packages/design-system/src/components/N8nNodeCreatorNode/TriggerIcon.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - - - diff --git a/packages/design-system/src/components/N8nNodeIcon/NodeIcon.vue b/packages/design-system/src/components/N8nNodeIcon/NodeIcon.vue index 80948018a3..5520a1870d 100644 --- a/packages/design-system/src/components/N8nNodeIcon/NodeIcon.vue +++ b/packages/design-system/src/components/N8nNodeIcon/NodeIcon.vue @@ -12,7 +12,7 @@ -
+
@@ -103,11 +103,11 @@ export default Vue.extend({ diff --git a/packages/editor-ui/src/components/Node/NodeCreator/NodeCreator.vue b/packages/editor-ui/src/components/Node/NodeCreator/NodeCreator.vue index bd73ced378..77415e2f06 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/NodeCreator.vue +++ b/packages/editor-ui/src/components/Node/NodeCreator/NodeCreator.vue @@ -14,20 +14,17 @@ @mouseup="onMouseUp" data-test-id="node-creator" > - +
diff --git a/packages/editor-ui/src/components/Node/NodeCreator/TriggerHelperPanel.vue b/packages/editor-ui/src/components/Node/NodeCreator/TriggerHelperPanel.vue deleted file mode 100644 index 3ce89f63d1..0000000000 --- a/packages/editor-ui/src/components/Node/NodeCreator/TriggerHelperPanel.vue +++ /dev/null @@ -1,449 +0,0 @@ - - - - - diff --git a/packages/editor-ui/src/components/Node/NodeCreator/TypeSelector.vue b/packages/editor-ui/src/components/Node/NodeCreator/TypeSelector.vue deleted file mode 100644 index 38a28670df..0000000000 --- a/packages/editor-ui/src/components/Node/NodeCreator/TypeSelector.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - - diff --git a/packages/editor-ui/src/components/Node/NodeCreator/ViewItem.vue b/packages/editor-ui/src/components/Node/NodeCreator/ViewItem.vue new file mode 100644 index 0000000000..b331301137 --- /dev/null +++ b/packages/editor-ui/src/components/Node/NodeCreator/ViewItem.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/packages/editor-ui/src/components/Node/NodeCreator/useMainPanelView.ts b/packages/editor-ui/src/components/Node/NodeCreator/useMainPanelView.ts new file mode 100644 index 0000000000..05ef090da0 --- /dev/null +++ b/packages/editor-ui/src/components/Node/NodeCreator/useMainPanelView.ts @@ -0,0 +1,198 @@ +import { getCurrentInstance, computed } from 'vue'; +import { + CORE_NODES_CATEGORY, + WEBHOOK_NODE_TYPE, + OTHER_TRIGGER_NODES_SUBCATEGORY, + EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, + MANUAL_TRIGGER_NODE_TYPE, + SCHEDULE_TRIGGER_NODE_TYPE, + REGULAR_NODE_FILTER, + TRANSFORM_DATA_SUBCATEGORY, + FILES_SUBCATEGORY, + FLOWS_CONTROL_SUBCATEGORY, + HELPERS_SUBCATEGORY, + TRIGGER_NODE_FILTER, +} from '@/constants'; +import { useNodeCreatorStore } from '@/stores/nodeCreator'; + +export default () => { + const instance = getCurrentInstance(); + const nodeCreatorStore = useNodeCreatorStore(); + + const VIEWS = [ + { + value: REGULAR_NODE_FILTER, + title: instance?.proxy.$locale.baseText('nodeCreator.triggerHelperPanel.whatHappensNext'), + items: [ + { + key: '*', + type: 'subcategory', + properties: { + subcategory: 'App Regular Nodes', + icon: 'globe', + }, + }, + { + type: 'subcategory', + key: TRANSFORM_DATA_SUBCATEGORY, + category: CORE_NODES_CATEGORY, + properties: { + subcategory: TRANSFORM_DATA_SUBCATEGORY, + icon: 'pen', + }, + }, + { + type: 'subcategory', + key: HELPERS_SUBCATEGORY, + category: CORE_NODES_CATEGORY, + properties: { + subcategory: HELPERS_SUBCATEGORY, + icon: 'toolbox', + }, + }, + { + type: 'subcategory', + key: FLOWS_CONTROL_SUBCATEGORY, + category: CORE_NODES_CATEGORY, + properties: { + subcategory: FLOWS_CONTROL_SUBCATEGORY, + icon: 'code-branch', + }, + }, + { + type: 'subcategory', + key: FILES_SUBCATEGORY, + category: CORE_NODES_CATEGORY, + properties: { + subcategory: FILES_SUBCATEGORY, + icon: 'file-alt', + }, + }, + { + key: TRIGGER_NODE_FILTER, + type: 'view', + properties: { + title: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.addAnotherTrigger', + ), + icon: 'bolt', + withTopBorder: true, + description: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.addAnotherTriggerDescription', + ), + }, + }, + ], + }, + { + value: TRIGGER_NODE_FILTER, + title: instance?.proxy.$locale.baseText('nodeCreator.triggerHelperPanel.selectATrigger'), + description: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.selectATriggerDescription', + ), + items: [ + { + key: '*', + type: 'subcategory', + properties: { + subcategory: 'App Trigger Nodes', + icon: 'satellite-dish', + }, + }, + { + key: SCHEDULE_TRIGGER_NODE_TYPE, + type: 'node', + category: [CORE_NODES_CATEGORY], + properties: { + nodeType: { + group: [], + name: SCHEDULE_TRIGGER_NODE_TYPE, + displayName: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.scheduleTriggerDisplayName', + ), + description: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.scheduleTriggerDescription', + ), + icon: 'fa:clock', + }, + }, + }, + { + key: WEBHOOK_NODE_TYPE, + type: 'node', + category: [CORE_NODES_CATEGORY], + properties: { + nodeType: { + group: [], + name: WEBHOOK_NODE_TYPE, + displayName: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.webhookTriggerDisplayName', + ), + description: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.webhookTriggerDescription', + ), + iconData: { + type: 'file', + icon: 'webhook', + fileBuffer: '/static/webhook-icon.svg', + }, + }, + }, + }, + { + key: MANUAL_TRIGGER_NODE_TYPE, + type: 'node', + category: [CORE_NODES_CATEGORY], + properties: { + nodeType: { + group: [], + name: MANUAL_TRIGGER_NODE_TYPE, + displayName: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.manualTriggerDisplayName', + ), + description: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.manualTriggerDescription', + ), + icon: 'fa:mouse-pointer', + }, + }, + }, + { + key: EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, + type: 'node', + category: [CORE_NODES_CATEGORY], + properties: { + nodeType: { + group: [], + name: EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, + displayName: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.workflowTriggerDisplayName', + ), + description: instance?.proxy.$locale.baseText( + 'nodeCreator.triggerHelperPanel.workflowTriggerDescription', + ), + icon: 'fa:sign-out-alt', + }, + }, + }, + { + type: 'subcategory', + key: OTHER_TRIGGER_NODES_SUBCATEGORY, + category: CORE_NODES_CATEGORY, + properties: { + subcategory: OTHER_TRIGGER_NODES_SUBCATEGORY, + icon: 'folder-open', + }, + }, + ], + }, + ]; + + const activeView = computed(() => { + return VIEWS.find((v) => v.value === nodeCreatorStore.selectedView) || VIEWS[0]; + }); + + return { + activeView, + }; +}; diff --git a/packages/editor-ui/src/components/PersonalizationModal.vue b/packages/editor-ui/src/components/PersonalizationModal.vue index b0606f5209..c4c4c6f399 100644 --- a/packages/editor-ui/src/components/PersonalizationModal.vue +++ b/packages/editor-ui/src/components/PersonalizationModal.vue @@ -1,12 +1,8 @@