From 50f7538779638574a4a7fe57a793ff04bfc641e9 Mon Sep 17 00:00:00 2001 From: OlegIvaniv Date: Fri, 11 Nov 2022 09:07:14 +0100 Subject: [PATCH] refactor(editor): Add Workflows view e2e tests (#4573) --- cypress/e2e/1-workflows.cy.ts | 84 +++++++++++++++++++ cypress/fixtures/Test_workflow_1.json | 69 +++++++++++++++ cypress/fixtures/Test_workflow_2.json | 64 ++++++++++++++ cypress/pages/workflow.ts | 11 +++ cypress/pages/workflows.ts | 23 ++++- cypress/support/commands.ts | 19 ++++- cypress/support/index.ts | 2 + cypress/types.ts | 2 +- .../N8nActionToggle/ActionToggle.vue | 2 +- .../components/MainHeader/WorkflowDetails.vue | 5 +- .../src/components/WorkflowActivator.vue | 2 +- .../editor-ui/src/components/WorkflowCard.vue | 6 +- .../layouts/ResourcesListLayout.vue | 3 +- .../editor-ui/src/views/WorkflowsView.vue | 6 +- 14 files changed, 287 insertions(+), 11 deletions(-) create mode 100644 cypress/e2e/1-workflows.cy.ts create mode 100644 cypress/fixtures/Test_workflow_1.json create mode 100644 cypress/fixtures/Test_workflow_2.json create mode 100644 cypress/pages/workflow.ts diff --git a/cypress/e2e/1-workflows.cy.ts b/cypress/e2e/1-workflows.cy.ts new file mode 100644 index 0000000000..31fd637fbd --- /dev/null +++ b/cypress/e2e/1-workflows.cy.ts @@ -0,0 +1,84 @@ +import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from "../constants"; +import { randFirstName, randLastName } from "@ngneat/falso"; +import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; +import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; +import { v4 as uuid } from 'uuid'; + +const username = DEFAULT_USER_EMAIL; +const password = DEFAULT_USER_PASSWORD; +const firstName = randFirstName(); +const lastName = randLastName(); +const WorkflowsPage = new WorkflowsPageClass(); +const WorkflowPage = new WorkflowPageClass(); + +describe('Workflows flow', () => { + 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); + cy.visit(WorkflowsPage.url); + }); + + it('should create a new workflow using empty state card', () => { + WorkflowsPage.get('newWorkflowButtonCard').should('be.visible'); + WorkflowsPage.get('newWorkflowButtonCard').click(); + + cy.createFixtureWorkflow('Test_workflow_1.json', `Empty State Card Workflow ${uuid()}`); + + WorkflowPage.get('workflowTags').should('contain.text', 'some-tag-1'); + WorkflowPage.get('workflowTags').should('contain.text', 'some-tag-2'); + }) + + it('should create a new workflow using add workflow button', () => { + WorkflowsPage.get('newWorkflowButtonCard').should('not.exist'); + WorkflowsPage.get('createWorkflowButton').click(); + + cy.createFixtureWorkflow('Test_workflow_2.json', `Add Workflow Button Workflow ${uuid()}`); + + WorkflowPage.get('workflowTags').should('contain.text', 'other-tag-1'); + WorkflowPage.get('workflowTags').should('contain.text', 'other-tag-2'); + }) + + it('should search for a workflow', () => { + WorkflowsPage.get('searchBar').type('Empty State Card Workflow'); + + WorkflowsPage.get('workflowCards').should('have.length', 1); + WorkflowsPage.get('workflowCard', 'Empty State Card Workflow').should('contain.text', 'Empty State Card Workflow'); + + WorkflowsPage.get('searchBar').clear().type('Add Workflow Button Workflow'); + + WorkflowsPage.get('workflowCards').should('have.length', 1); + WorkflowsPage.get('workflowCard', 'Add Workflow Button Workflow').should('contain.text', 'Add Workflow Button Workflow'); + + WorkflowsPage.get('searchBar').clear().type('Some non-existent workflow'); + WorkflowsPage.get('workflowCards').should('not.exist'); + cy.contains('No workflows found').should('be.visible'); + }) + + it('should delete all the workflows', () => { + WorkflowsPage.get('workflowCards').should('have.length', 2); + + WorkflowsPage.get('workflowCards').each(($el) => { + const workflowName = $el.find('[data-test-id="workflow-card-name"]').text(); + + WorkflowsPage.get('workflowCardActions', workflowName).click(); + WorkflowsPage.get('workflowDeleteButton').click(); + cy.get('button').contains('delete').click(); + }) + + WorkflowsPage.get('newWorkflowButtonCard').should('be.visible'); + WorkflowsPage.get('newWorkflowTemplateCard').should('be.visible'); + }) + + it('should contain empty state cards', () => { + WorkflowsPage.get('newWorkflowButtonCard').should('be.visible'); + WorkflowsPage.get('newWorkflowTemplateCard').should('be.visible'); + }); + +}); diff --git a/cypress/fixtures/Test_workflow_1.json b/cypress/fixtures/Test_workflow_1.json new file mode 100644 index 0000000000..f2bd7934a2 --- /dev/null +++ b/cypress/fixtures/Test_workflow_1.json @@ -0,0 +1,69 @@ +{ + "name": "Test workflow 1", + "nodes": [ + { + "parameters": {}, + "id": "a2f85497-260d-4489-a957-2b7d88e2f33d", + "name": "On clicking 'execute'", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 220, + 260 + ] + }, + { + "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": "9493d278-1ede-47c9-bedf-92ac3a737c65", + "name": "Code", + "type": "n8n-nodes-base.code", + "typeVersion": 1, + "position": [ + 400, + 260 + ] + } + ], + "pinData": {}, + "connections": { + "On clicking 'execute'": { + "main": [ + [ + { + "node": "Code", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code": { + "main": [ + [] + ] + } + }, + "active": false, + "settings": {}, + "hash": "a59c7b1c97b1741597afae0fcd43ebef", + "id": 3, + "meta": { + "instanceId": "a5280676597d00ecd0ea712da7f9cf2ce90174a791a309112731f6e44d162f35" + }, + "tags": [ + { + "name": "some-tag-1", + "createdAt": "2022-11-10T13:43:34.001Z", + "updatedAt": "2022-11-10T13:43:34.001Z", + "id": "6" + }, + { + "name": "some-tag-2", + "createdAt": "2022-11-10T13:43:39.778Z", + "updatedAt": "2022-11-10T13:43:39.778Z", + "id": "7" + } + ] +} diff --git a/cypress/fixtures/Test_workflow_2.json b/cypress/fixtures/Test_workflow_2.json new file mode 100644 index 0000000000..f1cef7c65c --- /dev/null +++ b/cypress/fixtures/Test_workflow_2.json @@ -0,0 +1,64 @@ +{ + "name": "Test workflow 2", + "nodes": [ + { + "parameters": {}, + "id": "624e0991-5dac-468b-b872-a9d35cb2c7d1", + "name": "On clicking 'execute'", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 360, + 260 + ] + }, + { + "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": "48823b3a-ec82-4a05-84b8-24ac2747e648", + "name": "Code", + "type": "n8n-nodes-base.code", + "typeVersion": 1, + "position": [ + 580, + 260 + ] + } + ], + "pinData": {}, + "connections": { + "On clicking 'execute'": { + "main": [ + [ + { + "node": "Code", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": {}, + "hash": "4d2e29ffcae2a12bdd28a7abe9681a6b", + "id": 4, + "meta": { + "instanceId": "a5280676597d00ecd0ea712da7f9cf2ce90174a791a309112731f6e44d162f35" + }, + "tags": [ + { + "name": "other-tag-1", + "createdAt": "2022-11-10T13:45:43.821Z", + "updatedAt": "2022-11-10T13:45:43.821Z", + "id": "8" + }, + { + "name": "other-tag-2", + "createdAt": "2022-11-10T13:45:46.881Z", + "updatedAt": "2022-11-10T13:45:46.881Z", + "id": "9" + } + ] +} \ No newline at end of file diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts new file mode 100644 index 0000000000..b0b57b0c61 --- /dev/null +++ b/cypress/pages/workflow.ts @@ -0,0 +1,11 @@ +import { BasePage } from "./base"; + +export class WorkflowPage extends BasePage { + url = '/workflow/new'; + elements = { + workflowNameInput: () => cy.getByTestId('workflow-name-input').then($el => cy.wrap($el.find('input'))), + workflowImportInput: () => cy.getByTestId('workflow-import-input'), + workflowTags: () => cy.getByTestId('workflow-tags'), + saveButton: () => cy.getByTestId('save-button'), + }; +} diff --git a/cypress/pages/workflows.ts b/cypress/pages/workflows.ts index 1814659826..fb75e98049 100644 --- a/cypress/pages/workflows.ts +++ b/cypress/pages/workflows.ts @@ -2,5 +2,26 @@ import { BasePage } from "./base"; export class WorkflowsPage extends BasePage { url = '/workflows'; - elements = {} + elements = { + newWorkflowButtonCard: () => cy.getByTestId('new-workflow-card'), + newWorkflowTemplateCard: () => cy.getByTestId('new-workflow-template-card'), + searchBar: () => cy.getByTestId('resources-list-search'), + createWorkflowButton: () => cy.getByTestId('resources-list-add'), + workflowCards: () => cy.getByTestId(`workflow-card`), + workflowCard: (workflowName: string) => cy.getByTestId(`workflow-card`) + .contains(workflowName) + .parents('[data-test-id="workflow-card"]'), + workflowTags: (workflowName: string) => this.elements.workflowCard(workflowName) + .findChildByTestId('workflow-card-tags'), + workflowActivator: (workflowName: string) => this.elements.workflowCard(workflowName) + .findChildByTestId('workflow-card-activator'), + workflowActivatorStatus: (workflowName: string) => this.elements.workflowActivator(workflowName) + .findChildByTestId('workflow-activator-status'), + workflowCardActions: (workflowName: string) => this.elements.workflowCard(workflowName) + .findChildByTestId('workflow-card-actions'), + workflowDeleteButton: () => cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Delete') + // Not yet implemented + // myWorkflows: () => cy.getByTestId('my-workflows'), + // allWorkflows: () => cy.getByTestId('all-workflows'), + }; } diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 30d12998ce..61ed3f10a0 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -26,12 +26,29 @@ import { WorkflowsPage, SigninPage, SignupPage } from "../pages"; import { N8N_AUTH_COOKIE } from "../constants"; - +import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; Cypress.Commands.add('getByTestId', (selector, ...args) => { return cy.get(`[data-test-id="${selector}"]`, ...args) }) +Cypress.Commands.add('createFixtureWorkflow', (fixtureKey, workflowName) => { + const WorkflowPage = new WorkflowPageClass() + + // We need to force the click because the input is hidden + WorkflowPage.get('workflowImportInput').selectFile(`cypress/fixtures/${fixtureKey}`, { force: true}); + WorkflowPage.get('workflowNameInput').should('be.disabled'); + WorkflowPage.get('workflowNameInput').parent().click() + WorkflowPage.get('workflowNameInput').should('be.enabled'); + WorkflowPage.get('workflowNameInput').clear().type(workflowName).type('{enter}'); + + WorkflowPage.get('saveButton').should('contain', 'Saved'); +}) + +Cypress.Commands.add('findChildByTestId', { prevSubject: true }, (subject: Cypress.Chainable>, childTestId) => { + return subject.find(`[data-test-id="${childTestId}"]`); +}) + Cypress.Commands.add( 'signin', (email, password) => { diff --git a/cypress/support/index.ts b/cypress/support/index.ts index a0fde42d50..1519e68fb0 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -5,6 +5,8 @@ declare global { namespace Cypress { interface Chainable { getByTestId(selector: string, ...args: (Partial | undefined)[]): Chainable> + findChildByTestId(childTestId: string): Chainable> + createFixtureWorkflow(fixtureKey: string, workflowName: string): void; signin(email: string, password: string): void; signup(email: string, firstName: string, lastName: string, password: string): void; } diff --git a/cypress/types.ts b/cypress/types.ts index 7ac318589f..0166f8f903 100644 --- a/cypress/types.ts +++ b/cypress/types.ts @@ -1,4 +1,4 @@ -export type IE2ETestPageElement = (...args: unknown[]) => +export type IE2ETestPageElement = (...args: any[]) => | Cypress.Chainable> | Cypress.Chainable>; diff --git a/packages/design-system/src/components/N8nActionToggle/ActionToggle.vue b/packages/design-system/src/components/N8nActionToggle/ActionToggle.vue index 3c05ef7f93..6efe41185d 100644 --- a/packages/design-system/src/components/N8nActionToggle/ActionToggle.vue +++ b/packages/design-system/src/components/N8nActionToggle/ActionToggle.vue @@ -14,7 +14,7 @@ :size="iconSize" /> - + @@ -36,6 +37,7 @@ :placeholder="$locale.baseText('workflowDetails.chooseOrCreateATag')" ref="dropdown" class="tags-edit" + data-test-id="workflow-tags-dropdown" />
@@ -71,7 +74,7 @@ @click="onSaveButtonClick" />
- +
diff --git a/packages/editor-ui/src/components/WorkflowActivator.vue b/packages/editor-ui/src/components/WorkflowActivator.vue index 28258e0f02..27d9ddb51c 100644 --- a/packages/editor-ui/src/components/WorkflowActivator.vue +++ b/packages/editor-ui/src/components/WorkflowActivator.vue @@ -1,6 +1,6 @@ diff --git a/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue b/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue index 5f34048c2b..75720314ad 100644 --- a/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue +++ b/packages/editor-ui/src/components/layouts/ResourcesListLayout.vue @@ -8,7 +8,7 @@
- + {{ $locale.baseText(`${resourceKey}.add`) }}
@@ -53,6 +53,7 @@ size="medium" clearable ref="search" + data-test-id="resources-list-search" > diff --git a/packages/editor-ui/src/views/WorkflowsView.vue b/packages/editor-ui/src/views/WorkflowsView.vue index 822157c0ab..998ad45133 100644 --- a/packages/editor-ui/src/views/WorkflowsView.vue +++ b/packages/editor-ui/src/views/WorkflowsView.vue @@ -24,13 +24,13 @@
- + {{ $locale.baseText('workflows.empty.startFromScratch') }} - + {{ $locale.baseText('workflows.empty.browseTemplates') }} @@ -85,7 +85,7 @@ export default mixins( showMessage, debounceHelper, ).extend({ - name: 'SettingsPersonalView', + name: 'WorkflowsView', components: { ResourcesListLayout, TemplateCard,