test(editor): Add more workflow actions tests (#4799)

*  Making workflow actions tests skip setup, changing suite number
* 🔥 Removing unnecessary imports and vars
*  Adding workflow tags and copy/paste tests
*  Added tests for copying and pasting nodes
* Update cypress/support/commands.ts
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
* 👌 Moving paste data to fixtures
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
This commit is contained in:
Milorad FIlipović 2022-12-05 14:31:14 +01:00 committed by GitHub
parent 1b7952a516
commit e07e32f14d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 169 additions and 79 deletions

View file

@ -1,74 +0,0 @@
import { randFirstName, randLastName } from "@ngneat/falso";
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from "../constants";
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
const NEW_WORKFLOW_NAME = 'Something else';
const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
const email = DEFAULT_USER_EMAIL;
const password = DEFAULT_USER_PASSWORD;
const firstName = randFirstName();
const lastName = randLastName();
const WorkflowPage = new WorkflowPageClass();
describe('Workflow Actions', () => {
before(() => {
cy.resetAll();
cy.setup({ email, firstName, lastName, password });
});
beforeEach(() => {
cy.on('uncaught:exception', (err, runnable) => {
expect(err.message).to.include('Not logged in');
return false;
})
cy.signin({ email, password });
WorkflowPage.actions.visit();
});
it('should be able to save on button click', () => {
WorkflowPage.actions.saveWorkflowOnButtonClick();
WorkflowPage.getters.isWorkflowSaved();
});
it('should save workflow on keyboard shortcut', () => {
WorkflowPage.actions.saveWorkflowUsingKeyboardShortcut();
WorkflowPage.getters.isWorkflowSaved();
});
it('should not be able to activate unsaved workflow', () => {
WorkflowPage.getters.activatorSwitch().find('input').first().should('be.disabled');
});
it('should not be able to activate workflow without trigger node', () => {
// Manual trigger is not enough to activate the workflow
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.actions.saveWorkflowOnButtonClick();
WorkflowPage.getters.activatorSwitch().find('input').first().should('be.disabled');
});
it('should be able to activate workflow', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.saveWorkflowOnButtonClick();
WorkflowPage.actions.activateWorkflow();
WorkflowPage.getters.isWorkflowActivated();
});
it('should save new workflow after renaming', () => {
WorkflowPage.actions.renameWorkflow(NEW_WORKFLOW_NAME);
WorkflowPage.getters.isWorkflowSaved();
});
it('should rename workflow', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.saveWorkflowOnButtonClick();
WorkflowPage.actions.renameWorkflow(NEW_WORKFLOW_NAME);
WorkflowPage.getters.isWorkflowSaved();
WorkflowPage.getters.workflowNameInputContainer().invoke('attr', 'title').should('eq', NEW_WORKFLOW_NAME);
});
});

View file

@ -0,0 +1,107 @@
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
const NEW_WORKFLOW_NAME = 'Something else';
const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
const CODE_NODE = 'Code'
const TEST_WF_TAGS = ['Tag 1', 'Tag 2', 'Tag 3'];
const WorkflowPage = new WorkflowPageClass();
describe('Workflow Actions', () => {
beforeEach(() => {
cy.resetAll();
cy.skipSetup();
WorkflowPage.actions.visit();
});
it('should be able to save on button click', () => {
WorkflowPage.actions.saveWorkflowOnButtonClick();
WorkflowPage.getters.isWorkflowSaved();
});
it('should save workflow on keyboard shortcut', () => {
WorkflowPage.actions.saveWorkflowUsingKeyboardShortcut();
WorkflowPage.getters.isWorkflowSaved();
});
it('should not be able to activate unsaved workflow', () => {
WorkflowPage.getters.activatorSwitch().find('input').first().should('be.disabled');
});
it('should not be able to activate workflow without trigger node', () => {
// Manual trigger is not enough to activate the workflow
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.actions.saveWorkflowOnButtonClick();
WorkflowPage.getters.activatorSwitch().find('input').first().should('be.disabled');
});
it('should be able to activate workflow', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.saveWorkflowOnButtonClick();
WorkflowPage.actions.activateWorkflow();
WorkflowPage.getters.isWorkflowActivated();
});
it('should save new workflow after renaming', () => {
WorkflowPage.actions.renameWorkflow(NEW_WORKFLOW_NAME);
WorkflowPage.getters.isWorkflowSaved();
});
it('should rename workflow', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.saveWorkflowOnButtonClick();
WorkflowPage.actions.renameWorkflow(NEW_WORKFLOW_NAME);
WorkflowPage.getters.isWorkflowSaved();
WorkflowPage.getters.workflowNameInputContainer().invoke('attr', 'title').should('eq', NEW_WORKFLOW_NAME);
});
it('should add tags', () => {
WorkflowPage.getters.newTagLink().click();
WorkflowPage.actions.addTags(TEST_WF_TAGS);
WorkflowPage.getters.isWorkflowSaved();
WorkflowPage.getters.workflowTagElements().should('have.length', TEST_WF_TAGS.length);
});
it('should add more tags', () => {
WorkflowPage.getters.newTagLink().click();
WorkflowPage.actions.addTags(TEST_WF_TAGS);
WorkflowPage.getters.workflowTagElements().first().click();
WorkflowPage.actions.addTags(['Another one']);
WorkflowPage.getters.workflowTagElements().should('have.length', TEST_WF_TAGS.length + 1);
});
it('should remove tags by clicking X in tag', () => {
WorkflowPage.getters.newTagLink().click();
WorkflowPage.actions.addTags(TEST_WF_TAGS);
WorkflowPage.getters.workflowTagElements().first().click();
WorkflowPage.getters.workflowTagsContainer().find('.el-tag__close').first().click();
cy.get('body').type('{enter}');
WorkflowPage.getters.workflowTagElements().should('have.length', TEST_WF_TAGS.length - 1);
});
it('should remove tags from dropdown', () => {
WorkflowPage.getters.newTagLink().click();
WorkflowPage.actions.addTags(TEST_WF_TAGS);
WorkflowPage.getters.workflowTagElements().first().click();
WorkflowPage.getters.workflowTagsDropdown().find('li').first().click();
cy.get('body').type('{enter}');
WorkflowPage.getters.workflowTagElements().should('have.length', TEST_WF_TAGS.length - 1);
});
it('should copy nodes', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE);
cy.get('body').type('{meta}', { release: false }).type('a');
cy.get('body').type('{meta}', { release: false }).type('c');
WorkflowPage.getters.successToast().should('exist');
});
it('should paste nodes', () => {
cy.fixture('Test_workflow-actions_paste-data.json').then(data => {
cy.get('body').paste(JSON.stringify(data));
WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
});
});
});

View file

@ -0,0 +1,37 @@
{
"meta": {
"instanceId": "1a30c82b98a30444ad25bce513655a5e02be772d361403542c23172be6062f04"
},
"nodes": [{
"parameters": {
"rule": {
"interval": [{}]
}
},
"id": "a898563b-d2a4-4b15-a979-366872e801b0",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1,
"position": [420, 260]
}, {
"parameters": {
"options": {}
},
"id": "b9a13e3d-bfa5-4873-959f-fd3d67e380d9",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [640, 260]
}],
"connections": {
"Schedule Trigger": {
"main": [
[{
"node": "Set",
"type": "main",
"index": 0
}]
]
}
}
}

View file

@ -3,12 +3,14 @@ import { BasePage } from './base';
export class WorkflowPage extends BasePage {
url = '/workflow/new';
getters = {
workflowNameInputContainer: () => cy
.getByTestId('workflow-name-input', { timeout: 5000 }),
workflowNameInputContainer: () => cy.getByTestId('workflow-name-input', { timeout: 5000 }),
workflowNameInput: () => this.getters.workflowNameInputContainer().then(($el) => cy.wrap($el.find('input'))),
workflowImportInput: () => cy.getByTestId('workflow-import-input'),
workflowTags: () => cy.getByTestId('workflow-tags'),
workflowTagsContainer: () => cy.getByTestId('workflow-tags-container'),
workflowTagsInput: () => this.getters.workflowTagsContainer().then(($el) => cy.wrap($el.find('input').first())),
workflowTagElements: () => this.getters.workflowTagsContainer().find('span.tags').children(),
workflowTagsDropdown: () => cy.getByTestId('workflow-tags-dropdown'),
newTagLink: () => cy.getByTestId('new-tag-link'),
saveButton: () => cy.getByTestId('workflow-save-button'),
nodeCreatorSearchBar: () => cy.getByTestId('node-creator-search-bar'),
@ -29,6 +31,10 @@ export class WorkflowPage extends BasePage {
isWorkflowActivated: () => this.getters.activatorSwitch().should('have.class', 'is-checked'),
expressionModalInput: () => cy.getByTestId('expression-modal-input'),
expressionModalOutput: () => cy.getByTestId('expression-modal-output'),
nodeViewRoot: () => cy.getByTestId('node-view-root'),
copyPasteInput: () => cy.getByTestId('hidden-copy-paste'),
canvasNodes: () => cy.getByTestId('canvas-node'),
};
actions = {
visit: () => {
@ -86,10 +92,9 @@ export class WorkflowPage extends BasePage {
cy.get('body').type('{enter}');
},
addTags: (tags: string[]) => {
this.getters.newTagLink().click();
tags.forEach(tag => {
cy.get('body').type(tag);
cy.get('body').type('{enter}');
this.getters.workflowTagsInput().type(tag);
this.getters.workflowTagsInput().type('{enter}');
});
cy.get('body').type('{enter}');
},

View file

@ -128,3 +128,15 @@ Cypress.Commands.add('resetAll', () => {
Cypress.Commands.add('setupOwner', (payload) => {
cy.task('setup-owner', payload);
});
Cypress.Commands.add('paste', { prevSubject: true }, (selector, pastePayload) => {
// https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
cy.wrap(selector).then($destination => {
const pasteEvent = Object.assign(new Event('paste', { bubbles: true, cancelable: true }), {
clipboardData: {
getData: () => pastePayload
}
});
$destination[0].dispatchEvent(pasteEvent);
});
});

View file

@ -24,6 +24,7 @@ declare global {
setupOwner(payload: SetupPayload): void;
skipSetup(): void;
resetAll(): void;
paste(pastePayload: string): void,
}
}
}

View file

@ -56,6 +56,7 @@ export const copyPaste = Vue.extend({
hiddenInput.setAttribute('type', 'text');
hiddenInput.setAttribute('id', 'hidden-input-copy-paste');
hiddenInput.setAttribute('class', 'hidden-copy-paste');
hiddenInput.setAttribute('data-test-id', 'hidden-copy-paste');
this.hiddenInput = hiddenInput;
document.body.append(hiddenInput);

View file

@ -3,6 +3,7 @@
<div
class="node-view-root"
id="node-view-root"
data-test-id="node-view-root"
@dragover="onDragOver"
@drop="onDrop"
>