n8n/cypress/e2e/20-workflow-executions.cy.ts

280 lines
11 KiB
TypeScript

import type { RouteHandler } from 'cypress/types/net-stubbing';
import executionOutOfMemoryServerResponse from '../fixtures/responses/execution-out-of-memory-server-response.json';
import { WorkflowPage } from '../pages';
import { WorkflowExecutionsTab } from '../pages/workflow-executions-tab';
import { getVisibleSelect } from '../utils';
const workflowPage = new WorkflowPage();
const executionsTab = new WorkflowExecutionsTab();
const executionsRefreshInterval = 4000;
// Test suite for executions tab
describe('Workflow Executions', () => {
describe('when workflow is saved', () => {
beforeEach(() => {
workflowPage.actions.visit();
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', 'My test workflow');
});
it('should render executions tab correctly', () => {
createMockExecutions();
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
executionsTab.actions.switchToExecutionsTab();
cy.wait(['@getExecutions']);
executionsTab.getters.executionsList().scrollTo(0, 500).wait(0);
executionsTab.getters.executionListItems().should('have.length', 11);
executionsTab.getters.successfulExecutionListItems().should('have.length', 9);
executionsTab.getters.failedExecutionListItems().should('have.length', 2);
executionsTab.getters
.executionListItems()
.first()
.invoke('attr', 'class')
.should('match', /_active_/);
});
it('should not redirect back to execution tab when request is not done before leaving the page', () => {
cy.intercept('GET', '/rest/executions?filter=*');
cy.intercept('GET', '/rest/executions/active?filter=*');
executionsTab.actions.switchToExecutionsTab();
executionsTab.actions.switchToEditorTab();
cy.wait(executionsRefreshInterval);
cy.url().should('not.include', '/executions');
executionsTab.actions.switchToExecutionsTab();
executionsTab.actions.switchToEditorTab();
executionsTab.actions.switchToExecutionsTab();
executionsTab.actions.switchToEditorTab();
executionsTab.actions.switchToExecutionsTab();
executionsTab.actions.switchToEditorTab();
cy.wait(executionsRefreshInterval);
cy.url().should('not.include', '/executions');
executionsTab.actions.switchToExecutionsTab();
cy.wait(1000);
executionsTab.actions.switchToEditorTab();
cy.wait(executionsRefreshInterval);
cy.url().should('not.include', '/executions');
});
it('should not redirect back to execution tab when slow request is not done before leaving the page', () => {
const throttleResponse: RouteHandler = async (req) => {
return await new Promise((resolve) => {
setTimeout(() => resolve(req.continue()), 2000);
});
};
cy.intercept('GET', '/rest/executions?filter=*', throttleResponse);
cy.intercept('GET', '/rest/executions/active?filter=*', throttleResponse);
executionsTab.actions.switchToExecutionsTab();
executionsTab.actions.switchToEditorTab();
cy.wait(executionsRefreshInterval);
cy.url().should('not.include', '/executions');
});
it('should error toast when server error message returned without stack trace', () => {
executionsTab.actions.createManualExecutions(1);
const message = 'Workflow did not finish, possible out-of-memory issue';
cy.intercept('GET', '/rest/executions/*', {
statusCode: 200,
body: executionOutOfMemoryServerResponse,
}).as('getExecution');
executionsTab.actions.switchToExecutionsTab();
cy.wait(['@getExecution']);
executionsTab.getters
.workflowExecutionPreviewIframe()
.should('be.visible')
.its('0.contentDocument.body') // Access the body of the iframe document
.should('not.be.empty') // Ensure the body is not empty
.then(cy.wrap)
.find('.el-notification:has(.el-notification--error)')
.should('be.visible')
.filter(`:contains("${message}")`)
.should('be.visible');
});
it('should show workflow data in executions tab after hard reload and modify name and tags', () => {
executionsTab.actions.switchToExecutionsTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 2);
workflowPage.getters.workflowTags().click();
getVisibleSelect().find('li:contains("Manage tags")').click();
cy.get('button:contains("Add new")').click();
cy.getByTestId('tags-table').find('input').type('nutag').type('{enter}');
cy.get('button:contains("Done")').click();
cy.reload();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.workflowTags().click();
workflowPage.getters.tagsInDropdown().first().should('have.text', 'nutag').click();
workflowPage.getters.tagPills().should('have.length', 3);
let newWorkflowName = 'Renamed workflow';
workflowPage.actions.renameWorkflow(newWorkflowName);
workflowPage.getters.isWorkflowSaved();
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
executionsTab.actions.switchToEditorTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 3);
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
executionsTab.actions.switchToExecutionsTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 3);
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
executionsTab.actions.switchToEditorTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 3);
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
newWorkflowName = 'New workflow';
workflowPage.actions.renameWorkflow(newWorkflowName);
workflowPage.getters.isWorkflowSaved();
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
workflowPage.getters.workflowTags().click();
workflowPage.getters.tagsDropdown().find('.el-tag__close').first().click();
cy.get('body').click(0, 0);
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 2);
executionsTab.actions.switchToExecutionsTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 2);
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
executionsTab.actions.switchToEditorTab();
checkMainHeaderELements();
workflowPage.getters.saveButton().find('button').should('not.exist');
workflowPage.getters.tagPills().should('have.length', 2);
workflowPage.getters
.workflowNameInputContainer()
.invoke('attr', 'title')
.should('eq', newWorkflowName);
});
it('should load items and auto scroll after filter change', () => {
createMockExecutions();
createMockExecutions();
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
executionsTab.actions.switchToExecutionsTab();
cy.wait(['@getExecutions']);
executionsTab.getters.executionsList().scrollTo(0, 500).wait(0);
executionsTab.getters.executionListItems().eq(10).click();
cy.getByTestId('executions-filter-button').click();
cy.getByTestId('executions-filter-status-select').should('be.visible').click();
getVisibleSelect().find('li:contains("Error")').click();
executionsTab.getters.executionListItems().should('have.length', 5);
executionsTab.getters.successfulExecutionListItems().should('have.length', 1);
executionsTab.getters.failedExecutionListItems().should('have.length', 4);
cy.getByTestId('executions-filter-button').click();
cy.getByTestId('executions-filter-status-select').should('be.visible').click();
getVisibleSelect().find('li:contains("Success")').click();
// check if the list is scrolled
executionsTab.getters.executionListItems().eq(10).should('be.visible');
executionsTab.getters.executionsList().then(($el) => {
const { scrollTop, scrollHeight, clientHeight } = $el[0];
expect(scrollTop).to.be.greaterThan(0);
expect(scrollTop + clientHeight).to.be.lessThan(scrollHeight);
// scroll to the bottom
$el[0].scrollTo(0, scrollHeight);
executionsTab.getters.executionListItems().should('have.length', 18);
executionsTab.getters.successfulExecutionListItems().should('have.length', 18);
executionsTab.getters.failedExecutionListItems().should('have.length', 0);
});
cy.getByTestId('executions-filter-button').click();
cy.getByTestId('executions-filter-reset-button').should('be.visible').click();
executionsTab.getters.executionListItems().eq(11).should('be.visible');
});
});
describe('when new workflow is not saved', () => {
beforeEach(() => {
workflowPage.actions.visit();
});
it('should open executions tab', () => {
executionsTab.actions.switchToExecutionsTab();
executionsTab.getters.executionsSidebar().should('be.visible');
executionsTab.getters.executionsEmptyList().should('be.visible');
cy.getByTestId('workflow-execution-no-trigger-content').should('be.visible');
cy.get('button:contains("Add first step")').should('be.visible').click();
cy.getByTestId('node-creator-item-name')
.should('be.visible')
.filter(':contains("Trigger")')
.click();
executionsTab.actions.switchToExecutionsTab();
executionsTab.getters.executionsSidebar().should('be.visible');
executionsTab.getters.executionsEmptyList().should('be.visible');
cy.getByTestId('workflow-execution-no-content').should('be.visible');
workflowPage.getters.saveButton().find('button').should('be.enabled').click();
workflowPage.getters.isWorkflowSaved();
workflowPage.getters.nodeViewRoot().should('be.visible');
});
});
});
const createMockExecutions = () => {
executionsTab.actions.createManualExecutions(5);
// Make some failed executions by enabling Code node with syntax error
executionsTab.actions.toggleNodeEnabled('Error');
workflowPage.getters.disabledNodes().should('have.length', 0);
executionsTab.actions.createManualExecutions(2);
// Then add some more successful ones
executionsTab.actions.toggleNodeEnabled('Error');
workflowPage.getters.disabledNodes().should('have.length', 1);
executionsTab.actions.createManualExecutions(4);
};
const checkMainHeaderELements = () => {
workflowPage.getters.workflowNameInputContainer().should('be.visible');
workflowPage.getters.workflowTagsContainer().should('be.visible');
workflowPage.getters.workflowMenu().should('be.visible');
workflowPage.getters.saveButton().should('be.visible');
};