mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
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:
parent
1b7952a516
commit
e07e32f14d
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
107
cypress/e2e/7-workflow-actions.cy.ts
Normal file
107
cypress/e2e/7-workflow-actions.cy.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
37
cypress/fixtures/Test_workflow-actions_paste-data.json
Normal file
37
cypress/fixtures/Test_workflow-actions_paste-data.json
Normal 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
|
||||
}]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}');
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ declare global {
|
|||
setupOwner(payload: SetupPayload): void;
|
||||
skipSetup(): void;
|
||||
resetAll(): void;
|
||||
paste(pastePayload: string): void,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<div
|
||||
class="node-view-root"
|
||||
id="node-view-root"
|
||||
data-test-id="node-view-root"
|
||||
@dragover="onDragOver"
|
||||
@drop="onDrop"
|
||||
>
|
||||
|
|
Loading…
Reference in a new issue