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:
Milorad FIlipović 2022-11-25 15:32:09 +01:00 committed by GitHub
parent 1579d05fd1
commit cb3bfc32f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 116 additions and 13 deletions

View 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);
});
});

View file

@ -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}');
},
}; };
} }

View file

@ -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) {

View file

@ -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>

View file

@ -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);

View file

@ -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>

View file

@ -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>

View file

@ -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