mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
test: Setup e2e tests for workflow actions (#4724)
* ✅ Adding first batch of workflow actions tests * ✅ Adding loading handling logic and new workflow actions tests * ✅ Added workflow activation and rename tests * 👌 Addressing review feedback * 🔥 Removing leftover commented code
This commit is contained in:
parent
1579d05fd1
commit
cb3bfc32f7
70
cypress/e2e/5-workflow-actions.cy.ts
Normal file
70
cypress/e2e/5-workflow-actions.cy.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
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 username = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
|
describe('Workflow Actions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.signup(username, firstName, lastName, password);
|
||||||
|
cy.on('uncaught:exception', (err, runnable) => {
|
||||||
|
expect(err.message).to.include('Not logged in');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.signin(username, password);
|
||||||
|
|
||||||
|
WorkflowPage.actions.visit();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to save on button slick', () => {
|
||||||
|
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
// In Element UI, disabled button turn into spans 🤷♂️
|
||||||
|
WorkflowPage.getters.saveButton().should('match', 'span');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save workflow on keyboard shortcut', () => {
|
||||||
|
WorkflowPage.actions.saveWorkflowUsingKeyboardShortcut();
|
||||||
|
WorkflowPage.getters.saveButton().should('match', 'span');
|
||||||
|
});
|
||||||
|
|
||||||
|
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.activatorSwitch().should('have.class', 'is-checked');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save new workflow after renaming', () => {
|
||||||
|
WorkflowPage.actions.renameWorkflow(NEW_WORKFLOW_NAME);
|
||||||
|
WorkflowPage.getters.saveButton().should('match', 'span');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should rename workflow', () => {
|
||||||
|
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||||
|
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
WorkflowPage.actions.renameWorkflow(NEW_WORKFLOW_NAME);
|
||||||
|
WorkflowPage.getters.saveButton().should('match', 'span');
|
||||||
|
WorkflowPage.getters.workflowNameInput().invoke('attr', 'title').should('eq', NEW_WORKFLOW_NAME);
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,13 +3,10 @@ import { BasePage } from './base';
|
||||||
export class WorkflowPage extends BasePage {
|
export class WorkflowPage extends BasePage {
|
||||||
url = '/workflow/new';
|
url = '/workflow/new';
|
||||||
getters = {
|
getters = {
|
||||||
workflowNameInput: () =>
|
workflowNameInput: () => cy.getByTestId('workflow-name-input'),
|
||||||
cy
|
|
||||||
.getByTestId('workflow-name-input', { timeout: 5000 })
|
|
||||||
.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'),
|
||||||
saveButton: () => cy.getByTestId('save-button'),
|
saveButton: () => cy.getByTestId('workflow-save-button'),
|
||||||
|
|
||||||
nodeCreatorSearchBar: () => cy.getByTestId('node-creator-search-bar'),
|
nodeCreatorSearchBar: () => cy.getByTestId('node-creator-search-bar'),
|
||||||
nodeCreatorPlusButton: () => cy.getByTestId('node-creator-plus-button'),
|
nodeCreatorPlusButton: () => cy.getByTestId('node-creator-plus-button'),
|
||||||
|
@ -24,6 +21,9 @@ export class WorkflowPage extends BasePage {
|
||||||
ndvParameterInput: (parameterName: string) =>
|
ndvParameterInput: (parameterName: string) =>
|
||||||
cy.getByTestId(`parameter-input-${parameterName}`),
|
cy.getByTestId(`parameter-input-${parameterName}`),
|
||||||
ndvOutputPanel: () => cy.getByTestId('output-panel'),
|
ndvOutputPanel: () => cy.getByTestId('output-panel'),
|
||||||
|
activatorSwitch: () => cy.getByTestId('workflow-activate-switch'),
|
||||||
|
workflowMenu: () => cy.getByTestId('workflow-menu'),
|
||||||
|
firstStepButton: () => cy.getByTestId('canvas-add-button'),
|
||||||
};
|
};
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
|
@ -46,5 +46,30 @@ export class WorkflowPage extends BasePage {
|
||||||
executeNodeFromNdv: () => {
|
executeNodeFromNdv: () => {
|
||||||
cy.contains('Execute node').click();
|
cy.contains('Execute node').click();
|
||||||
},
|
},
|
||||||
|
visit: () => {
|
||||||
|
cy.visit(this.url);
|
||||||
|
cy.getByTestId('node-view-loader', { timeout: 5000 }).should('not.exist');
|
||||||
|
cy.get('.el-loading-mask', { timeout: 5000 }).should('not.exist');
|
||||||
|
},
|
||||||
|
openWorkflowMenu: () => {
|
||||||
|
this.getters.workflowMenu().click();
|
||||||
|
},
|
||||||
|
saveWorkflowOnButtonClick: () => {
|
||||||
|
this.getters.saveButton().click();
|
||||||
|
},
|
||||||
|
saveWorkflowUsingKeyboardShortcut: () => {
|
||||||
|
cy.get('body').type('{meta}', { release: false }).type('s');
|
||||||
|
},
|
||||||
|
activateWorkflow: () => {
|
||||||
|
this.getters.activatorSwitch().find('input').first().should('be.enabled');
|
||||||
|
this.getters.activatorSwitch().click();
|
||||||
|
cy.get('body').type('{esc}');
|
||||||
|
},
|
||||||
|
renameWorkflow: (newName: string) => {
|
||||||
|
this.getters.workflowNameInput().click();
|
||||||
|
cy.get('body').type('{selectall}');
|
||||||
|
cy.get('body').type(newName);
|
||||||
|
cy.get('body').type('{enter}');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu data-test-id="action-toggle-dropdown">
|
<el-dropdown-menu :data-test-id="testId">
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
v-for="action in actions"
|
v-for="action in actions"
|
||||||
:key="action.value"
|
:key="action.value"
|
||||||
|
@ -85,6 +85,10 @@ export default Vue.extend({
|
||||||
default: 'default',
|
default: 'default',
|
||||||
validator: (value: string): boolean => ['default', 'dark'].includes(value),
|
validator: (value: string): boolean => ['default', 'dark'].includes(value),
|
||||||
},
|
},
|
||||||
|
testId: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onCommand(value: string) {
|
onCommand(value: string) {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
:name="workflowName"
|
:name="workflowName"
|
||||||
:limit="value"
|
:limit="value"
|
||||||
:custom="true"
|
:custom="true"
|
||||||
|
testId="workflow-name-input"
|
||||||
>
|
>
|
||||||
<template #default="{ shortenedName }">
|
<template #default="{ shortenedName }">
|
||||||
<InlineTextEdit
|
<InlineTextEdit
|
||||||
|
@ -17,7 +18,6 @@
|
||||||
@submit="onNameSubmit"
|
@submit="onNameSubmit"
|
||||||
placeholder="Enter workflow name"
|
placeholder="Enter workflow name"
|
||||||
class="name"
|
class="name"
|
||||||
data-test-id="workflow-name-input"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</ShortenName>
|
</ShortenName>
|
||||||
|
@ -80,11 +80,12 @@
|
||||||
type="secondary"
|
type="secondary"
|
||||||
:saved="!this.isDirty && !this.isNewWorkflow"
|
:saved="!this.isDirty && !this.isNewWorkflow"
|
||||||
:disabled="isWorkflowSaving"
|
:disabled="isWorkflowSaving"
|
||||||
|
data-test-id="workflow-save-button"
|
||||||
@click="onSaveButtonClick"
|
@click="onSaveButtonClick"
|
||||||
/>
|
/>
|
||||||
<div :class="$style.workflowMenuContainer">
|
<div :class="$style.workflowMenuContainer">
|
||||||
<input :class="$style.hiddenInput" type="file" ref="importFile" data-test-id="workflow-import-input" @change="handleFileImport()">
|
<input :class="$style.hiddenInput" type="file" ref="importFile" data-test-id="workflow-import-input" @change="handleFileImport()">
|
||||||
<n8n-action-dropdown :items="workflowMenuItems" @select="onWorkflowMenuSelect" />
|
<n8n-action-dropdown :items="workflowMenuItems" data-test-id="workflow-menu" @select="onWorkflowMenuSelect" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</PushConnectionTracker>
|
</PushConnectionTracker>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<span :title="name">
|
<span :title="name" :data-test-id="testId">
|
||||||
<slot :shortenedName="shortenedName"></slot>
|
<slot :shortenedName="shortenedName"></slot>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -13,7 +13,7 @@ const WORKFLOW_NAME_END_COUNT_TO_KEEP = 4;
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: "ShortenName",
|
name: "ShortenName",
|
||||||
props: ["name", "limit"],
|
props: ["name", "limit", "testId"],
|
||||||
computed: {
|
computed: {
|
||||||
shortenedName(): string {
|
shortenedName(): string {
|
||||||
return shorten(this.name, this.limit || DEFAULT_WORKFLOW_NAME_LIMIT, WORKFLOW_NAME_END_COUNT_TO_KEEP);
|
return shorten(this.name, this.limit || DEFAULT_WORKFLOW_NAME_LIMIT, WORKFLOW_NAME_END_COUNT_TO_KEEP);
|
||||||
|
|
|
@ -16,11 +16,13 @@
|
||||||
v-loading="updatingWorkflowActivation"
|
v-loading="updatingWorkflowActivation"
|
||||||
:value="workflowActive"
|
:value="workflowActive"
|
||||||
@change="activeChanged"
|
@change="activeChanged"
|
||||||
:title="workflowActive ? $locale.baseText('workflowActivator.deactivateWorkflow') : $locale.baseText('workflowActivator.activateWorkflow')"
|
:title="workflowActive ? $locale.baseText('workflowActivator.deactivateWorkflow') : $locale.baseText('workflowActivator.activateWorkflow')"
|
||||||
:disabled="disabled || updatingWorkflowActivation"
|
:disabled="disabled || updatingWorkflowActivation"
|
||||||
:active-color="getActiveColor"
|
:active-color="getActiveColor"
|
||||||
inactive-color="#8899AA"
|
inactive-color="#8899AA"
|
||||||
element-loading-spinner="el-icon-loading">
|
element-loading-spinner="el-icon-loading"
|
||||||
|
data-test-id="workflow-activate-switch"
|
||||||
|
>
|
||||||
</el-switch>
|
</el-switch>
|
||||||
</n8n-tooltip>
|
</n8n-tooltip>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.wrapper">
|
<div :class="$style.wrapper" data-test-id="node-view-loader">
|
||||||
<div :class="$style.spinner">
|
<div :class="$style.spinner">
|
||||||
<n8n-spinner />
|
<n8n-spinner />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
:showTooltip="!containsTrigger && showTriggerMissingTooltip"
|
:showTooltip="!containsTrigger && showTriggerMissingTooltip"
|
||||||
:position="canvasStore.canvasAddButtonPosition"
|
:position="canvasStore.canvasAddButtonPosition"
|
||||||
@hook:mounted="canvasStore.setRecenteredCanvasAddButtonPosition"
|
@hook:mounted="canvasStore.setRecenteredCanvasAddButtonPosition"
|
||||||
|
data-test-id="canvas-add-button"
|
||||||
/>
|
/>
|
||||||
<div v-for="nodeData in nodes" :key="nodeData.id">
|
<div v-for="nodeData in nodes" :key="nodeData.id">
|
||||||
<node
|
<node
|
||||||
|
|
Loading…
Reference in a new issue