test(editor): Add e2e tests for executions preview (#5458)

*  Added initial tests for executions preview
* 🔥 Removing unneeded actions
* 👌 Renaming test suite, moving mock executions logic to util function
This commit is contained in:
Milorad FIlipović
2023-02-14 11:39:19 +01:00
committed by GitHub
parent 856238721a
commit 3b9eec77ec
8 changed files with 211 additions and 14 deletions

View File

@@ -0,0 +1,40 @@
import { WorkflowPage } from "../pages";
import { WorkflowExecutionsTab } from "../pages/workflow-executions-tab";
const workflowPage = new WorkflowPage();
const executionsTab = new WorkflowExecutionsTab();
// Test suite for executions tab
describe('Current Workflow Executions', () => {
before(() => {
cy.resetAll();
cy.skipSetup();
workflowPage.actions.visit();
cy.waitForLoad();
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', `My test workflow`);
createMockExecutions();
});
it('should render executions tab correctly', () => {
cy.waitForLoad();
executionsTab.getters.executionListItems().should('have.length', 11);
executionsTab.getters.successfulExecutionListItems().should('have.length', 9);
executionsTab.getters.failedExecutionListItems().should('have.length', 2);
executionsTab.getters.executionListItems().first().invoke('attr','class').should('match', /_active_/);
});
});
const createMockExecutions = () => {
workflowPage.actions.turnOnManualExecutionSaving();
executionsTab.actions.createManualExecutions(5);
// Make some failed executions by enabling Code node with syntax error
executionsTab.actions.toggleNodeEnabled('Error');
executionsTab.actions.createManualExecutions(2);
// Then add some more successful ones
executionsTab.actions.toggleNodeEnabled('Error');
executionsTab.actions.createManualExecutions(4);
executionsTab.actions.switchToExecutionsTab();
cy.waitForLoad();
}

View File

@@ -0,0 +1,69 @@
{
"meta": {
"instanceId": "6b85439d79c07750ea49eced4bc2a12b283cfcba0ab2917cd4f3fee36080e869"
},
"nodes": [
{
"parameters": {
"jsCode": "// Loop over input items and add a new field\n// called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n error\n}\n\nreturn $input.all();"
},
"id": "d0ab7e12-0e1b-4c08-8081-83107794f37d",
"name": "Error",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
680,
460
],
"disabled": true
},
{
"parameters": {},
"id": "f5026145-66c1-463c-8ac8-46a1309a6632",
"name": "On clicking 'execute'",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
460,
460
]
},
{
"parameters": {
"jsCode": "// Loop over input items and add a new field\n// called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"id": "9926f884-348a-4af0-872e-dd7c8b3da811",
"name": "Code",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
900,
460
]
}
],
"connections": {
"Error": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"On clicking 'execute'": {
"main": [
[
{
"node": "Error",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,40 @@
import { BasePage } from "./base";
import { WorkflowPage } from "./workflow";
const workflowPage = new WorkflowPage();
export class WorkflowExecutionsTab extends BasePage {
getters = {
executionsTabButton: () => cy.getByTestId('radio-button-executions'),
executionsSidebar: () => cy.getByTestId('executions-sidebar'),
autoRefreshCheckBox: () => cy.getByTestId('auto-refresh-checkbox'),
executionsList: () => cy.getByTestId('current-executions-list'),
executionListItems: () => this.getters.executionsList().find('div.execution-card'),
successfulExecutionListItems: () => cy.get('[data-test-execution-status="success"]'),
failedExecutionListItems: () => cy.get('[data-test-execution-status="error"]'),
executionCard: (executionId: string) => cy.getByTestId(`execution-details-${executionId}`),
executionPreviewDetails: () => cy.get('[data-test-id^="execution-preview-details-"]'),
executionPreviewDetailsById: (executionId: string) => cy.getByTestId(`execution-preview-details-${executionId}`),
executionPreviewTime: () => this.getters.executionPreviewDetails().find('[data-test-id="execution-time"]'),
executionPreviewStatus: () => this.getters.executionPreviewDetails().find('[data-test-id="execution-preview-label"]'),
executionPreviewId: () => this.getters.executionPreviewDetails().find('[data-test-id="execution-preview-id"]'),
};
actions = {
toggleNodeEnabled: (nodeName: string) => {
workflowPage.getters.canvasNodeByName(nodeName).click();
cy.get('body').type('d', { force: true });
},
createManualExecutions: (count: number) => {
for (let i=0; i<count; i++) {
workflowPage.actions.executeWorkflow();
cy.wait(300);
}
},
switchToExecutionsTab: () => {
this.getters.executionsTabButton().click();
},
switchToEditorTab: () => {
workflowPage.getters.editorTabButton().click();
}
};
};

View File

@@ -103,6 +103,7 @@ export class WorkflowPage extends BasePage {
cy.get( cy.get(
`.connection-actions[data-source-node="${sourceNodeName}"][data-target-node="${targetNodeName}"]`, `.connection-actions[data-source-node="${sourceNodeName}"][data-target-node="${targetNodeName}"]`,
), ),
editorTabButton: () => cy.getByTestId('radio-button-workflow'),
}; };
actions = { actions = {
visit: () => { visit: () => {
@@ -230,5 +231,15 @@ export class WorkflowPage extends BasePage {
.first() .first()
.click({ force: true }); .click({ force: true });
}, },
turnOnManualExecutionSaving: () => {
this.getters.workflowMenu().click();
this.getters.workflowMenuItemSettings().click();
this.getters
.workflowSettingsSaveManualExecutionsSelect()
.find('li:contains("Yes")')
.click({ force: true });
this.getters.workflowSettingsSaveButton().click();
this.getters.successToast().should('exist');
},
}; };
} }

View File

@@ -17,6 +17,7 @@
[$style[size]]: true, [$style[size]]: true,
[$style.disabled]: disabled, [$style.disabled]: disabled,
}" }"
:data-test-id="`radio-button-${value}`"
@click="$emit('click')" @click="$emit('click')"
> >
{{ label }} {{ label }}

View File

@@ -14,11 +14,12 @@
name: VIEWS.EXECUTION_PREVIEW, name: VIEWS.EXECUTION_PREVIEW,
params: { workflowId: currentWorkflow, executionId: execution.id }, params: { workflowId: currentWorkflow, executionId: execution.id },
}" }"
:data-test-execution-status="executionUIDetails.name"
> >
<div :class="$style.description"> <div :class="$style.description">
<n8n-text color="text-dark" :bold="true" size="medium">{{ <n8n-text color="text-dark" :bold="true" size="medium" data-test-id="execution-time">
executionUIDetails.startTime {{ executionUIDetails.startTime }}
}}</n8n-text> </n8n-text>
<div :class="$style.executionStatus"> <div :class="$style.executionStatus">
<n8n-spinner <n8n-spinner
v-if="executionUIDetails.name === 'running'" v-if="executionUIDetails.name === 'running'"
@@ -62,6 +63,7 @@
:class="[$style.icon, $style.retry]" :class="[$style.icon, $style.retry]"
:items="retryExecutionActions" :items="retryExecutionActions"
activatorIcon="redo" activatorIcon="redo"
data-test-id="retry-execution-button"
@select="onRetryMenuItemSelect" @select="onRetryMenuItemSelect"
/> />
<n8n-tooltip v-if="execution.mode === 'manual'" placement="top"> <n8n-tooltip v-if="execution.mode === 'manual'" placement="top">

View File

@@ -17,9 +17,10 @@
<div <div
:class="{ [$style.executionDetails]: true, [$style.sidebarCollapsed]: sidebarCollapsed }" :class="{ [$style.executionDetails]: true, [$style.sidebarCollapsed]: sidebarCollapsed }"
v-if="activeExecution" v-if="activeExecution"
:data-test-id="`execution-preview-details-${executionId}`"
> >
<div> <div>
<n8n-text size="large" color="text-base" :bold="true">{{ <n8n-text size="large" color="text-base" :bold="true" data-test-id="execution-time">{{
executionUIDetails.startTime executionUIDetails.startTime
}}</n8n-text }}</n8n-text
><br /> ><br />
@@ -28,9 +29,13 @@
size="small" size="small"
:class="[$style.spinner, 'mr-4xs']" :class="[$style.spinner, 'mr-4xs']"
/> />
<n8n-text size="medium" :class="[$style.status, $style[executionUIDetails.name]]">{{ <n8n-text
executionUIDetails.label size="medium"
}}</n8n-text> :class="[$style.status, $style[executionUIDetails.name]]"
data-test-id="execution-preview-label"
>
{{ executionUIDetails.label }}
</n8n-text>
<n8n-text v-if="executionUIDetails.name === 'running'" color="text-base" size="medium"> <n8n-text v-if="executionUIDetails.name === 'running'" color="text-base" size="medium">
{{ {{
$locale.baseText('executionDetails.runningTimeRunning', { $locale.baseText('executionDetails.runningTimeRunning', {
@@ -39,7 +44,12 @@
}} }}
| ID#{{ activeExecution.id }} | ID#{{ activeExecution.id }}
</n8n-text> </n8n-text>
<n8n-text v-else-if="executionUIDetails.name !== 'waiting'" color="text-base" size="medium"> <n8n-text
v-else-if="executionUIDetails.name !== 'waiting'"
color="text-base"
size="medium"
data-test-id="execution-preview-id"
>
{{ {{
$locale.baseText('executionDetails.runningTimeFinished', { $locale.baseText('executionDetails.runningTimeFinished', {
interpolate: { time: executionUIDetails.runningTime }, interpolate: { time: executionUIDetails.runningTime },
@@ -80,6 +90,7 @@
type="tertiary" type="tertiary"
:title="$locale.baseText('executionsList.retryExecution')" :title="$locale.baseText('executionsList.retryExecution')"
icon="redo" icon="redo"
data-test-id="execution-preview-retry-button"
@blur="onRetryButtonBlur" @blur="onRetryButtonBlur"
/> />
</span> </span>
@@ -99,6 +110,7 @@
icon="trash" icon="trash"
size="large" size="large"
type="tertiary" type="tertiary"
data-test-id="execution-preview-delete-button"
@click="onDeleteExecution" @click="onDeleteExecution"
/> />
</div> </div>

View File

@@ -1,18 +1,32 @@
<template> <template>
<div :class="['executions-sidebar', $style.container]" ref="container"> <div
:class="['executions-sidebar', $style.container]"
ref="container"
data-test-id="executions-sidebar"
>
<div :class="$style.heading"> <div :class="$style.heading">
<n8n-heading tag="h2" size="medium" color="text-dark"> <n8n-heading tag="h2" size="medium" color="text-dark">
{{ $locale.baseText('generic.executions') }} {{ $locale.baseText('generic.executions') }}
</n8n-heading> </n8n-heading>
</div> </div>
<div :class="$style.controls"> <div :class="$style.controls">
<el-checkbox v-model="autoRefresh" @change="onAutoRefreshToggle">{{ <el-checkbox
$locale.baseText('executionsList.autoRefresh') v-model="autoRefresh"
}}</el-checkbox> @change="onAutoRefreshToggle"
data-test-id="auto-refresh-checkbox"
>
{{ $locale.baseText('executionsList.autoRefresh') }}
</el-checkbox>
<n8n-popover trigger="click"> <n8n-popover trigger="click">
<template #reference> <template #reference>
<div :class="$style.filterButton"> <div :class="$style.filterButton">
<n8n-button icon="filter" type="tertiary" size="medium" :active="statusFilterApplied"> <n8n-button
icon="filter"
type="tertiary"
size="medium"
:active="statusFilterApplied"
data-test-id="executions-filter-button"
>
<n8n-badge v-if="statusFilterApplied" theme="primary" class="mr-4xs">1</n8n-badge> <n8n-badge v-if="statusFilterApplied" theme="primary" class="mr-4xs">1</n8n-badge>
{{ $locale.baseText('executionsList.filters') }} {{ $locale.baseText('executionsList.filters') }}
</n8n-button> </n8n-button>
@@ -33,6 +47,7 @@
ref="typeInput" ref="typeInput"
:class="$style['type-input']" :class="$style['type-input']"
:placeholder="$locale.baseText('generic.any')" :placeholder="$locale.baseText('generic.any')"
data-test-id="execution-status-select"
@change="onFilterChange" @change="onFilterChange"
> >
<n8n-option <n8n-option
@@ -40,6 +55,7 @@
:key="item.id" :key="item.id"
:label="item.name" :label="item.name"
:value="item.id" :value="item.id"
:data-test-id="`execution-status-${item.id}`"
> >
</n8n-option> </n8n-option>
</n8n-select> </n8n-select>
@@ -60,7 +76,12 @@
</n8n-link> </n8n-link>
</n8n-info-tip> </n8n-info-tip>
</div> </div>
<div :class="$style.executionList" ref="executionList" @scroll="loadMore(20)"> <div
:class="$style.executionList"
ref="executionList"
data-test-id="current-executions-list"
@scroll="loadMore(20)"
>
<div v-if="loading" class="mr-m"> <div v-if="loading" class="mr-m">
<n8n-loading :class="$style.loader" variant="p" :rows="1" /> <n8n-loading :class="$style.loader" variant="p" :rows="1" />
<n8n-loading :class="$style.loader" variant="p" :rows="1" /> <n8n-loading :class="$style.loader" variant="p" :rows="1" />
@@ -77,6 +98,7 @@
:key="execution.id" :key="execution.id"
:execution="execution" :execution="execution"
:ref="`execution-${execution.id}`" :ref="`execution-${execution.id}`"
:data-test-id="`execution-details-${execution.id}`"
@refresh="onRefresh" @refresh="onRefresh"
@retryExecution="onRetryExecution" @retryExecution="onRetryExecution"
/> />