diff --git a/cypress/e2e/10-undo-redo.cy.ts b/cypress/e2e/10-undo-redo.cy.ts index b01a8137c6..515cc61190 100644 --- a/cypress/e2e/10-undo-redo.cy.ts +++ b/cypress/e2e/10-undo-redo.cy.ts @@ -1,11 +1,13 @@ import { CODE_NODE_NAME, SET_NODE_NAME } from './../constants'; import { SCHEDULE_TRIGGER_NODE_NAME } from '../constants'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +import { NDV } from '../pages/ndv'; // Suite-specific constants const CODE_NODE_NEW_NAME = 'Something else'; const WorkflowPage = new WorkflowPageClass(); +const ndv = new NDV(); describe('Undo/Redo', () => { beforeEach(() => { @@ -205,11 +207,7 @@ describe('Undo/Redo', () => { WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); - WorkflowPage.getters.nodeNameContainerNDV().click(); - WorkflowPage.getters.nodeRenameInput().should('be.visible'); - WorkflowPage.getters.nodeRenameInput().type('{selectall}'); - WorkflowPage.getters.nodeRenameInput().type(CODE_NODE_NEW_NAME); - cy.get('body').type('{enter}'); + ndv.actions.rename(CODE_NODE_NEW_NAME); cy.get('body').type('{esc}'); WorkflowPage.actions.hitUndo(); cy.get('body').type('{esc}'); diff --git a/cypress/e2e/11-inline-expression-editor.cy.ts b/cypress/e2e/11-inline-expression-editor.cy.ts index 78bc9e0474..655c87f114 100644 --- a/cypress/e2e/11-inline-expression-editor.cy.ts +++ b/cypress/e2e/11-inline-expression-editor.cy.ts @@ -12,7 +12,7 @@ describe('Inline expression editor', () => { WorkflowPage.actions.visit(); WorkflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); WorkflowPage.actions.addNodeToCanvas('Hacker News'); - WorkflowPage.actions.openNodeNdv('Hacker News'); + WorkflowPage.actions.openNode('Hacker News'); WorkflowPage.actions.openInlineExpressionEditor(); }); diff --git a/cypress/e2e/13-pinning.cy.ts b/cypress/e2e/13-pinning.cy.ts new file mode 100644 index 0000000000..a35d12493d --- /dev/null +++ b/cypress/e2e/13-pinning.cy.ts @@ -0,0 +1,65 @@ +import { WorkflowPage, NDV } from '../pages'; + +const workflowPage = new WorkflowPage(); +const ndv = new NDV(); + +describe('Data pinning', () => { + beforeEach(() => { + cy.resetAll(); + cy.skipSetup(); + workflowPage.actions.visit(); + cy.waitForLoad(); + }); + + it('Should be able to pin node output', () => { + workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger'); + workflowPage.getters.canvasNodes().first().dblclick(); + ndv.getters.container().should('be.visible'); + ndv.getters.pinDataButton().should('not.exist'); + ndv.getters.editPinnedDataButton().should('be.visible'); + + ndv.actions.execute(); + + ndv.getters.outputDataContainer().should('be.visible'); + ndv.getters.outputDataContainer().get('table').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'); + ndv.getters.outputTableHeaders().eq(1).should('include.text', 'Readable date'); + + ndv.getters.outputTbodyCell(1, 0).invoke('text').then((prevValue) => { + ndv.actions.pinData(); + ndv.actions.close(); + + workflowPage.actions.executeWorkflow(); + workflowPage.actions.openNode('Schedule Trigger'); + + ndv.getters.outputTbodyCell(1, 0).invoke('text').should('eq', prevValue); + }); + }); + + it('Should be be able to set pinned data', () => { + workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger'); + workflowPage.getters.canvasNodes().first().dblclick(); + ndv.getters.container().should('be.visible'); + ndv.getters.pinDataButton().should('not.exist'); + ndv.getters.editPinnedDataButton().should('be.visible'); + + ndv.actions.setPinnedData([{ test: 1 }]); + + ndv.getters.outputTableRows().should('have.length', 2); + ndv.getters.outputTableHeaders().should('have.length', 2); + ndv.getters.outputTableHeaders().first().should('include.text', 'test'); + ndv.getters.outputTbodyCell(1, 0).should('include.text', 1); + + ndv.actions.close(); + + workflowPage.actions.saveWorkflowOnButtonClick(); + + cy.reload(); + workflowPage.actions.openNode('Schedule Trigger'); + + ndv.getters.outputTableHeaders().first().should('include.text', 'test'); + ndv.getters.outputTbodyCell(1, 0).should('include.text', 1); + }); +}); diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts index aaa16681ea..b20f5b5f6d 100644 --- a/cypress/e2e/5-ndv.cy.ts +++ b/cypress/e2e/5-ndv.cy.ts @@ -26,7 +26,7 @@ describe('NDV', () => { workflowPage.actions.addInitialNodeToCanvas('Webhook'); workflowPage.getters.canvasNodes().first().dblclick(); - ndv.getters.nodeExecuteButton().first().click(); + ndv.actions.execute(); ndv.getters.copyInput().click(); cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite'); @@ -49,9 +49,7 @@ describe('NDV', () => { workflowPage.getters.canvasNodes().last().dblclick(); ndv.getters.inputSelect().click(); ndv.getters.inputOption().last().click(); - ndv.getters.inputPanel().within(() => { - ndv.getters.dataContainer().should('contain', 'start'); - }); + ndv.getters.inputDataContainer().should('contain', 'start'); }); it('should show correct validation state for resource locator params', () => { @@ -61,7 +59,7 @@ describe('NDV', () => { cy.get('[class*=hasIssues]').should('have.length', 0); ndv.getters.backToCanvas().click(); // Both credentials and resource locator errors should be visible - workflowPage.actions.openNodeNdv('Typeform'); + workflowPage.actions.openNode('Typeform'); cy.get('.has-issues').should('have.length', 1); cy.get('[class*=hasIssues]').should('have.length', 1); }); @@ -71,11 +69,11 @@ describe('NDV', () => { workflowPage.actions.addNodeToCanvas('Airtable', true); ndv.getters.container().should('be.visible'); cy.get('.has-issues').should('have.length', 0); - workflowPage.getters.ndvParameterInput('table').find('input').eq(1).focus().blur() - workflowPage.getters.ndvParameterInput('application').find('input').eq(1).focus().blur() + ndv.getters.parameterInput('table').find('input').eq(1).focus().blur() + ndv.getters.parameterInput('application').find('input').eq(1).focus().blur() cy.get('.has-issues').should('have.length', 2); ndv.getters.backToCanvas().click(); - workflowPage.actions.openNodeNdv('Airtable'); + workflowPage.actions.openNode('Airtable'); cy.get('.has-issues').should('have.length', 3); cy.get('[class*=hasIssues]').should('have.length', 1); }); @@ -84,7 +82,7 @@ describe('NDV', () => { cy.fixture('Test_workflow_ndv_errors.json').then((data) => { cy.get('body').paste(JSON.stringify(data)); workflowPage.getters.canvasNodes().should('have.have.length', 1); - workflowPage.actions.openNodeNdv('Airtable'); + workflowPage.actions.openNode('Airtable'); cy.get('.has-issues').should('have.length', 3); cy.get('[class*=hasIssues]').should('have.length', 1); }); diff --git a/cypress/e2e/6-code-node.cy.ts b/cypress/e2e/6-code-node.cy.ts index c1157492a0..b833eed003 100644 --- a/cypress/e2e/6-code-node.cy.ts +++ b/cypress/e2e/6-code-node.cy.ts @@ -1,6 +1,8 @@ import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +import { NDV } from '../pages/ndv'; const WorkflowPage = new WorkflowPageClass(); +const ndv = new NDV(); describe('Code node', () => { beforeEach(() => { @@ -12,9 +14,9 @@ describe('Code node', () => { WorkflowPage.actions.visit(); WorkflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); WorkflowPage.actions.addNodeToCanvas('Code'); - WorkflowPage.actions.openNodeNdv('Code'); + WorkflowPage.actions.openNode('Code'); - WorkflowPage.actions.executeNodeFromNdv(); + ndv.actions.execute(); WorkflowPage.getters.successToast().contains('Node executed successfully'); }); @@ -23,11 +25,11 @@ describe('Code node', () => { WorkflowPage.actions.visit(); WorkflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); WorkflowPage.actions.addNodeToCanvas('Code'); - WorkflowPage.actions.openNodeNdv('Code'); - WorkflowPage.getters.ndvParameterInput('mode').click(); - WorkflowPage.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item'); + WorkflowPage.actions.openNode('Code'); + ndv.getters.parameterInput('mode').click(); + ndv.actions.selectOptionInParameterDropdown('mode', 'Run Once for Each Item'); - WorkflowPage.actions.executeNodeFromNdv(); + ndv.actions.execute(); WorkflowPage.getters.successToast().contains('Node executed successfully'); }); diff --git a/cypress/e2e/8-http-request-node.cy.ts b/cypress/e2e/8-http-request-node.cy.ts index e4a78205d4..c8a4e1c9ec 100644 --- a/cypress/e2e/8-http-request-node.cy.ts +++ b/cypress/e2e/8-http-request-node.cy.ts @@ -1,8 +1,8 @@ -import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; -import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +import { WorkflowPage, WorkflowsPage, NDV } from '../pages'; -const WorkflowsPage = new WorkflowsPageClass(); -const WorkflowPage = new WorkflowPageClass(); +const workflowsPage = new WorkflowsPage(); +const workflowPage = new WorkflowPage(); +const ndv = new NDV() describe('HTTP Request node', () => { before(() => { @@ -11,14 +11,14 @@ describe('HTTP Request node', () => { }); it('should make a request with a URL and receive a response', () => { - WorkflowsPage.actions.createWorkflowFromCard(); - WorkflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); - WorkflowPage.actions.addNodeToCanvas('HTTP Request'); - WorkflowPage.actions.openNodeNdv('HTTP Request'); - WorkflowPage.actions.typeIntoParameterInput('url', 'https://catfact.ninja/fact'); + workflowsPage.actions.createWorkflowFromCard(); + workflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); + workflowPage.actions.addNodeToCanvas('HTTP Request'); + workflowPage.actions.openNode('HTTP Request'); + ndv.actions.typeIntoParameterInput('url', 'https://catfact.ninja/fact'); - WorkflowPage.actions.executeNodeFromNdv(); + ndv.actions.execute(); - WorkflowPage.getters.ndvOutputPanel().contains('fact'); + ndv.getters.outputPanel().contains('fact'); }); }); diff --git a/cypress/e2e/9-expression-editor-modal.cy.ts b/cypress/e2e/9-expression-editor-modal.cy.ts index 10b21a2d7a..19bf0dd3ab 100644 --- a/cypress/e2e/9-expression-editor-modal.cy.ts +++ b/cypress/e2e/9-expression-editor-modal.cy.ts @@ -12,7 +12,7 @@ describe('Expression editor modal', () => { WorkflowPage.actions.visit(); WorkflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); WorkflowPage.actions.addNodeToCanvas('Hacker News'); - WorkflowPage.actions.openNodeNdv('Hacker News'); + WorkflowPage.actions.openNode('Hacker News'); WorkflowPage.actions.openExpressionEditorModal(); }); diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index 74cc446d18..574e1bcd85 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -9,8 +9,65 @@ export class NDV extends BasePage { inputSelect: () => cy.getByTestId('ndv-input-select'), inputOption: () => cy.getByTestId('ndv-input-option'), inputPanel: () => cy.getByTestId('ndv-input-panel'), - dataContainer: () => cy.getByTestId('ndv-data-container'), + outputPanel: () => cy.getByTestId('output-panel'), + inputDataContainer: () => this.getters.inputPanel().findChildByTestId('ndv-data-container'), + outputDataContainer: () => this.getters.outputPanel().findChildByTestId('ndv-data-container'), runDataDisplayMode: () => cy.getByTestId('ndv-run-data-display-mode'), 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'), + runDataPaneHeader: () => cy.getByTestId('run-data-pane-header'), + savePinnedDataButton: () => this.getters.runDataPaneHeader().find('button').contains('Save'), + outputTableRows: () => this.getters.outputDataContainer().find('table tr'), + outputTableHeaders: () => this.getters.outputDataContainer().find('table thead th'), + outputTableRow: (row: number) => this.getters.outputTableRows().eq(row), + outputTbodyCell: (row: number, cell: number) => this.getters.outputTableRow(row).find('td').eq(cell), + parameterInput: (parameterName: string) => cy.getByTestId(`parameter-input-${parameterName}`), + nodeNameContainer: () => cy.getByTestId('node-title-container'), + nodeRenameInput: () => cy.getByTestId('node-rename-input'), + }; + + actions = { + pinData: () => { + this.getters.pinDataButton().click(); + }, + editPinnedData: () => { + this.getters.editPinnedDataButton().click(); + }, + execute: () => { + this.getters.nodeExecuteButton().first().click(); + }, + close: () => { + this.getters.backToCanvas().click(); + }, + 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.savePinnedDataButton().click(); + }, + typeIntoParameterInput: (parameterName: string, content: string) => { + this.getters.parameterInput(parameterName).type(content); + }, + selectOptionInParameterDropdown: (parameterName: string, content: string) => { + this.getters + .parameterInput(parameterName) + .find('.option-headline') + .contains(content) + .click(); + }, + rename: (newName: string) => { + this.getters.nodeNameContainer().click(); + this.getters.nodeRenameInput() + .should('be.visible') + .type('{selectall}') + .type(newName); + cy.get('body').type('{enter}'); + }, }; } diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index c5a959303e..1b5a3a067a 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -24,10 +24,6 @@ export class WorkflowPage extends BasePage { canvasNodes: () => cy.getByTestId('canvas-node'), canvasNodeByName: (nodeName: string) => this.getters.canvasNodes().filter(`:contains("${nodeName}")`), - ndvParameterInput: (parameterName: string) => - cy.getByTestId(`parameter-input-${parameterName}`), - ndvOutputPanel: () => cy.getByTestId('output-panel'), - ndvRunDataPaneHeader: () => cy.getByTestId('run-data-pane-header'), successToast: () => cy.get('.el-notification .el-icon-success').parent(), errorToast: () => cy.get('.el-notification .el-icon-error'), activatorSwitch: () => cy.getByTestId('workflow-activate-switch'), @@ -45,8 +41,6 @@ export class WorkflowPage extends BasePage { nodeEndpoints: () => cy.get('.jtk-endpoint-connected'), disabledNodes: () => cy.get('.node-box.disabled'), selectedNodes: () => this.getters.canvasNodes().filter('.jtk-drag-selected'), - nodeNameContainerNDV: () => cy.getByTestId('node-title-container'), - nodeRenameInput: () => cy.getByTestId('node-rename-input'), // Workflow menu items workflowMenuItemDuplicate: () => cy.getByTestId('workflow-menu-item-duplicate'), workflowMenuItemDownload: () => cy.getByTestId('workflow-menu-item-download'), @@ -100,7 +94,7 @@ export class WorkflowPage extends BasePage { if (!preventNdvClose) cy.get('body').type('{esc}'); }, - openNodeNdv: (nodeTypeName: string) => { + openNode: (nodeTypeName: string) => { this.getters.canvasNodeByName(nodeTypeName).dblclick(); }, openExpressionEditorModal: () => { @@ -111,19 +105,6 @@ export class WorkflowPage extends BasePage { cy.contains('Expression').invoke('show').click(); this.getters.inlineExpressionEditorInput().click(); }, - typeIntoParameterInput: (parameterName: string, content: string) => { - this.getters.ndvParameterInput(parameterName).type(content); - }, - selectOptionInParameterDropdown: (parameterName: string, content: string) => { - this.getters - .ndvParameterInput(parameterName) - .find('.option-headline') - .contains(content) - .click(); - }, - executeNodeFromNdv: () => { - cy.contains('Execute node').click(); - }, openWorkflowMenu: () => { this.getters.workflowMenu().click(); }, @@ -178,5 +159,8 @@ export class WorkflowPage extends BasePage { hitPaste: () => { cy.get('body').type(META_KEY, { delay: 500, release: false }).type('P'); }, + executeWorkflow: () => { + this.getters.executeWorkflowButton().click(); + }, }; } diff --git a/packages/editor-ui/src/components/NodeExecuteButton.vue b/packages/editor-ui/src/components/NodeExecuteButton.vue index 8c30d7f6f2..171c8153e0 100644 --- a/packages/editor-ui/src/components/NodeExecuteButton.vue +++ b/packages/editor-ui/src/components/NodeExecuteButton.vue @@ -5,7 +5,6 @@