mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -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 {
|
export class WorkflowPage extends BasePage {
|
||||||
url = '/workflow/new';
|
url = '/workflow/new';
|
||||||
getters = {
|
getters = {
|
||||||
workflowNameInputContainer: () => cy
|
workflowNameInputContainer: () => cy.getByTestId('workflow-name-input', { timeout: 5000 }),
|
||||||
.getByTestId('workflow-name-input', { timeout: 5000 }),
|
|
||||||
workflowNameInput: () => this.getters.workflowNameInputContainer().then(($el) => cy.wrap($el.find('input'))),
|
workflowNameInput: () => this.getters.workflowNameInputContainer().then(($el) => cy.wrap($el.find('input'))),
|
||||||
workflowImportInput: () => cy.getByTestId('workflow-import-input'),
|
workflowImportInput: () => cy.getByTestId('workflow-import-input'),
|
||||||
workflowTags: () => cy.getByTestId('workflow-tags'),
|
workflowTags: () => cy.getByTestId('workflow-tags'),
|
||||||
workflowTagsContainer: () => cy.getByTestId('workflow-tags-container'),
|
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'),
|
newTagLink: () => cy.getByTestId('new-tag-link'),
|
||||||
saveButton: () => cy.getByTestId('workflow-save-button'),
|
saveButton: () => cy.getByTestId('workflow-save-button'),
|
||||||
nodeCreatorSearchBar: () => cy.getByTestId('node-creator-search-bar'),
|
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'),
|
isWorkflowActivated: () => this.getters.activatorSwitch().should('have.class', 'is-checked'),
|
||||||
expressionModalInput: () => cy.getByTestId('expression-modal-input'),
|
expressionModalInput: () => cy.getByTestId('expression-modal-input'),
|
||||||
expressionModalOutput: () => cy.getByTestId('expression-modal-output'),
|
expressionModalOutput: () => cy.getByTestId('expression-modal-output'),
|
||||||
|
|
||||||
|
nodeViewRoot: () => cy.getByTestId('node-view-root'),
|
||||||
|
copyPasteInput: () => cy.getByTestId('hidden-copy-paste'),
|
||||||
|
canvasNodes: () => cy.getByTestId('canvas-node'),
|
||||||
};
|
};
|
||||||
actions = {
|
actions = {
|
||||||
visit: () => {
|
visit: () => {
|
||||||
|
@ -86,10 +92,9 @@ export class WorkflowPage extends BasePage {
|
||||||
cy.get('body').type('{enter}');
|
cy.get('body').type('{enter}');
|
||||||
},
|
},
|
||||||
addTags: (tags: string[]) => {
|
addTags: (tags: string[]) => {
|
||||||
this.getters.newTagLink().click();
|
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
cy.get('body').type(tag);
|
this.getters.workflowTagsInput().type(tag);
|
||||||
cy.get('body').type('{enter}');
|
this.getters.workflowTagsInput().type('{enter}');
|
||||||
});
|
});
|
||||||
cy.get('body').type('{enter}');
|
cy.get('body').type('{enter}');
|
||||||
},
|
},
|
||||||
|
|
|
@ -128,3 +128,15 @@ Cypress.Commands.add('resetAll', () => {
|
||||||
Cypress.Commands.add('setupOwner', (payload) => {
|
Cypress.Commands.add('setupOwner', (payload) => {
|
||||||
cy.task('setup-owner', 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;
|
setupOwner(payload: SetupPayload): void;
|
||||||
skipSetup(): void;
|
skipSetup(): void;
|
||||||
resetAll(): void;
|
resetAll(): void;
|
||||||
|
paste(pastePayload: string): void,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ export const copyPaste = Vue.extend({
|
||||||
hiddenInput.setAttribute('type', 'text');
|
hiddenInput.setAttribute('type', 'text');
|
||||||
hiddenInput.setAttribute('id', 'hidden-input-copy-paste');
|
hiddenInput.setAttribute('id', 'hidden-input-copy-paste');
|
||||||
hiddenInput.setAttribute('class', 'hidden-copy-paste');
|
hiddenInput.setAttribute('class', 'hidden-copy-paste');
|
||||||
|
hiddenInput.setAttribute('data-test-id', 'hidden-copy-paste');
|
||||||
this.hiddenInput = hiddenInput;
|
this.hiddenInput = hiddenInput;
|
||||||
|
|
||||||
document.body.append(hiddenInput);
|
document.body.append(hiddenInput);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<div
|
<div
|
||||||
class="node-view-root"
|
class="node-view-root"
|
||||||
id="node-view-root"
|
id="node-view-root"
|
||||||
|
data-test-id="node-view-root"
|
||||||
@dragover="onDragOver"
|
@dragover="onDragOver"
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
>
|
>
|
||||||
|
|
Loading…
Reference in a new issue