diff --git a/cypress/e2e/7-workflow-actions.cy.ts b/cypress/e2e/7-workflow-actions.cy.ts index b7f948744c..5775ab9502 100644 --- a/cypress/e2e/7-workflow-actions.cy.ts +++ b/cypress/e2e/7-workflow-actions.cy.ts @@ -5,6 +5,7 @@ import { SCHEDULE_TRIGGER_NODE_NAME, } from '../constants'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; const NEW_WORKFLOW_NAME = 'Something else'; const IMPORT_WORKFLOW_URL = 'https://gist.githubusercontent.com/OlegIvaniv/010bd3f45c8a94f8eb7012e663a8b671/raw/3afea1aec15573cc168d9af7e79395bd76082906/test-workflow.json'; @@ -12,6 +13,7 @@ const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow'; const DUPLICATE_WORKFLOW_TAG = 'Duplicate'; const WorkflowPage = new WorkflowPageClass(); +const WorkflowPages = new WorkflowsPageClass(); describe('Workflow Actions', () => { before(() => { @@ -66,6 +68,42 @@ describe('Workflow Actions', () => { .should('eq', NEW_WORKFLOW_NAME); }); + it('should not save workflow if canvas is loading', () => { + let interceptCalledCount = 0; + + // There's no way in Cypress to check if intercept was not called + // so we'll count the number of times it was called + cy.intercept('PATCH', '/rest/workflows/*', () => { + interceptCalledCount++; + }).as('saveWorkflow'); + + WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); + WorkflowPage.actions.saveWorkflowOnButtonClick(); + cy.intercept( + { + url: '/rest/workflows/*', + method: 'GET', + middleware: true, + }, + (req) => { + // Delay the response to give time for the save to be triggered + req.on('response', async (res) => { + await new Promise((resolve) => setTimeout(resolve, 2000)) + res.send(); + }) + } + ) + cy.reload(); + cy.get('.el-loading-mask').should('exist'); + cy.get('body').type(META_KEY, { release: false }).type('s'); + cy.get('body').type(META_KEY, { release: false }).type('s'); + cy.get('body').type(META_KEY, { release: false }).type('s'); + cy.wrap(null).then(() => expect(interceptCalledCount).to.eq(0)); + WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); + cy.get('body').type(META_KEY, { release: false }).type('s'); + cy.wait('@saveWorkflow'); + cy.wrap(null).then(() => expect(interceptCalledCount).to.eq(1)); + }) it('should copy nodes', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); @@ -110,64 +148,70 @@ describe('Workflow Actions', () => { }); it('should update workflow settings', () => { - WorkflowPage.actions.visit(); - // Open settings dialog - WorkflowPage.actions.saveWorkflowOnButtonClick(); - WorkflowPage.getters.workflowMenu().should('be.visible'); - WorkflowPage.getters.workflowMenu().click(); - WorkflowPage.getters.workflowMenuItemSettings().should('be.visible'); - WorkflowPage.getters.workflowMenuItemSettings().click(); - // Change all settings - WorkflowPage.getters.workflowSettingsErrorWorkflowSelect().find('li').should('have.length', 7); - WorkflowPage.getters - .workflowSettingsErrorWorkflowSelect() - .find('li') - .last() - .click({ force: true }); - WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').should('exist'); - WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').eq(1).click({ force: true }); - WorkflowPage.getters - .workflowSettingsSaveFiledExecutionsSelect() - .find('li') - .should('have.length', 3); - WorkflowPage.getters - .workflowSettingsSaveFiledExecutionsSelect() - .find('li') - .last() - .click({ force: true }); - WorkflowPage.getters - .workflowSettingsSaveSuccessExecutionsSelect() - .find('li') - .should('have.length', 3); - WorkflowPage.getters - .workflowSettingsSaveSuccessExecutionsSelect() - .find('li') - .last() - .click({ force: true }); - WorkflowPage.getters - .workflowSettingsSaveManualExecutionsSelect() - .find('li') - .should('have.length', 3); - WorkflowPage.getters - .workflowSettingsSaveManualExecutionsSelect() - .find('li') - .last() - .click({ force: true }); - WorkflowPage.getters - .workflowSettingsSaveExecutionProgressSelect() - .find('li') - .should('have.length', 3); - WorkflowPage.getters - .workflowSettingsSaveExecutionProgressSelect() - .find('li') - .last() - .click({ force: true }); - WorkflowPage.getters.workflowSettingsTimeoutWorkflowSwitch().click(); - WorkflowPage.getters.workflowSettingsTimeoutForm().find('input').first().type('1'); - // Save settings - WorkflowPage.getters.workflowSettingsSaveButton().click(); - WorkflowPage.getters.workflowSettingsModal().should('not.exist'); - WorkflowPage.getters.successToast().should('exist'); + cy.visit(WorkflowPages.url); + WorkflowPages.getters.workflowCards().then((cards) => { + const totalWorkflows = cards.length; + + WorkflowPage.actions.visit(); + // Open settings dialog + WorkflowPage.actions.saveWorkflowOnButtonClick(); + WorkflowPage.getters.workflowMenu().should('be.visible'); + WorkflowPage.getters.workflowMenu().click(); + WorkflowPage.getters.workflowMenuItemSettings().should('be.visible'); + WorkflowPage.getters.workflowMenuItemSettings().click(); + // Change all settings + // totalWorkflows + 1 (current workflow) + 1 (no workflow option) + WorkflowPage.getters.workflowSettingsErrorWorkflowSelect().find('li').should('have.length', totalWorkflows + 2); + WorkflowPage.getters + .workflowSettingsErrorWorkflowSelect() + .find('li') + .last() + .click({ force: true }); + WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').should('exist'); + WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').eq(1).click({ force: true }); + WorkflowPage.getters + .workflowSettingsSaveFiledExecutionsSelect() + .find('li') + .should('have.length', 3); + WorkflowPage.getters + .workflowSettingsSaveFiledExecutionsSelect() + .find('li') + .last() + .click({ force: true }); + WorkflowPage.getters + .workflowSettingsSaveSuccessExecutionsSelect() + .find('li') + .should('have.length', 3); + WorkflowPage.getters + .workflowSettingsSaveSuccessExecutionsSelect() + .find('li') + .last() + .click({ force: true }); + WorkflowPage.getters + .workflowSettingsSaveManualExecutionsSelect() + .find('li') + .should('have.length', 3); + WorkflowPage.getters + .workflowSettingsSaveManualExecutionsSelect() + .find('li') + .last() + .click({ force: true }); + WorkflowPage.getters + .workflowSettingsSaveExecutionProgressSelect() + .find('li') + .should('have.length', 3); + WorkflowPage.getters + .workflowSettingsSaveExecutionProgressSelect() + .find('li') + .last() + .click({ force: true }); + WorkflowPage.getters.workflowSettingsTimeoutWorkflowSwitch().click(); + WorkflowPage.getters.workflowSettingsTimeoutForm().find('input').first().type('1'); + // Save settings + WorkflowPage.getters.workflowSettingsSaveButton().click(); + WorkflowPage.getters.workflowSettingsModal().should('not.exist'); + WorkflowPage.getters.successToast().should('exist'); + }) }); it('should not be able to delete unsaved workflow', () => { diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index a17c52c9cf..2a91adf783 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -177,7 +177,7 @@ export class WorkflowPage extends BasePage { }, saveWorkflowUsingKeyboardShortcut: () => { cy.intercept('POST', '/rest/workflows').as('createWorkflow'); - cy.get('body').type('{meta}', { release: false }).type('s'); + cy.get('body').type(META_KEY, { release: false }).type('s'); }, deleteNode: (name: string) => { this.getters.canvasNodeByName(name).first().click(); diff --git a/cypress/pages/workflows.ts b/cypress/pages/workflows.ts index 4a30b92ecb..d5700d0784 100644 --- a/cypress/pages/workflows.ts +++ b/cypress/pages/workflows.ts @@ -36,8 +36,10 @@ export class WorkflowsPage extends BasePage { cy.visit(this.url); this.getters.workflowCardActions(name).click(); this.getters.workflowDeleteButton().click(); + cy.intercept('DELETE', '/rest/workflows/*').as('deleteWorkflow'); cy.get('button').contains('delete').click(); + cy.wait('@deleteWorkflow'); }, }; } diff --git a/packages/cli/src/api/e2e.api.ts b/packages/cli/src/api/e2e.api.ts index efecd7c34c..ba972f89bf 100644 --- a/packages/cli/src/api/e2e.api.ts +++ b/packages/cli/src/api/e2e.api.ts @@ -12,6 +12,7 @@ import { Container } from 'typedi'; import config from '@/config'; import * as Db from '@/Db'; import type { Role } from '@db/entities/Role'; +import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; import { RoleRepository } from '@db/repositories'; import { hashPassword } from '@/UserManagement/UserManagementHelper'; import { eventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; @@ -108,10 +109,18 @@ const resetLogStreaming = async () => { } }; +const removeActiveWorkflows = async () => { + const workflowRunner = Container.get(ActiveWorkflowRunner); + + workflowRunner.removeAllQueuedWorkflowActivations(); + await workflowRunner.removeAll(); +}; + export const e2eController = Router(); e2eController.post('/db/reset', async (req, res) => { await resetLogStreaming(); + await removeActiveWorkflows(); await truncateAll(); await setupUserManagement(); diff --git a/packages/editor-ui/src/components/ExpressionEdit.vue b/packages/editor-ui/src/components/ExpressionEdit.vue index 51d17d6690..c7110a45ac 100644 --- a/packages/editor-ui/src/components/ExpressionEdit.vue +++ b/packages/editor-ui/src/components/ExpressionEdit.vue @@ -45,7 +45,7 @@