diff --git a/cypress/e2e/10-settings-log-streaming.cy.ts b/cypress/e2e/10-settings-log-streaming.cy.ts index 1261940df2..9acec76f42 100644 --- a/cypress/e2e/10-settings-log-streaming.cy.ts +++ b/cypress/e2e/10-settings-log-streaming.cy.ts @@ -1,4 +1,6 @@ import { SettingsLogStreamingPage } from '../pages'; +import { getVisibleModalOverlay } from '../utils/modal'; +import { getVisibleDropdown } from '../utils'; const settingsLogStreamingPage = new SettingsLogStreamingPage(); @@ -19,6 +21,7 @@ describe('Log Streaming Settings', () => { }); it('should show the add destination modal', () => { + cy.enableFeature('logStreaming'); cy.visit('/settings/log-streaming'); settingsLogStreamingPage.actions.clickAddFirstDestination(); cy.wait(100); @@ -27,7 +30,7 @@ describe('Log Streaming Settings', () => { settingsLogStreamingPage.getters.getSelectDestinationButton().should('be.visible'); settingsLogStreamingPage.getters.getSelectDestinationButton().should('have.attr', 'disabled'); settingsLogStreamingPage.getters - .getDestinationModalDialog() + .getDestinationModal() .invoke('css', 'width') .then((widthStr) => parseInt((widthStr as unknown as string).replace('px', ''))) .should('be.lessThan', 500); @@ -36,65 +39,67 @@ describe('Log Streaming Settings', () => { settingsLogStreamingPage.getters .getSelectDestinationButton() .should('not.have.attr', 'disabled'); - settingsLogStreamingPage.getters.getDestinationModal().click(1, 1); + getVisibleModalOverlay().click(1, 1); settingsLogStreamingPage.getters.getDestinationModal().should('not.exist'); }); it('should create a destination and delete it', () => { + cy.enableFeature('logStreaming'); cy.visit('/settings/log-streaming'); + cy.wait(1000); // Race condition with getDestinationDataFromBackend() settingsLogStreamingPage.actions.clickAddFirstDestination(); cy.wait(100); settingsLogStreamingPage.getters.getDestinationModal().should('be.visible'); settingsLogStreamingPage.getters.getSelectDestinationType().click(); settingsLogStreamingPage.getters.getSelectDestinationTypeItems().eq(0).click(); settingsLogStreamingPage.getters.getSelectDestinationButton().click(); - settingsLogStreamingPage.getters.getDestinationNameInput().click() + settingsLogStreamingPage.getters.getDestinationNameInput().click(); - settingsLogStreamingPage.getters.getDestinationNameInput().find('input').clear().type('Destination 0'); + settingsLogStreamingPage.getters + .getDestinationNameInput() + .find('input') + .clear() + .type('Destination 0'); settingsLogStreamingPage.getters.getDestinationSaveButton().click(); cy.wait(100); - settingsLogStreamingPage.getters.getDestinationModal().click(1, 1); + getVisibleModalOverlay().click(1, 1); cy.reload(); settingsLogStreamingPage.getters.getDestinationCards().eq(0).click(); settingsLogStreamingPage.getters.getDestinationDeleteButton().should('be.visible').click(); cy.get('.el-message-box').should('be.visible').find('.btn--cancel').click(); settingsLogStreamingPage.getters.getDestinationDeleteButton().click(); cy.get('.el-message-box').should('be.visible').find('.btn--confirm').click(); - cy.reload(); }); it('should create a destination and delete it via card actions', () => { + cy.enableFeature('logStreaming'); cy.visit('/settings/log-streaming'); + cy.wait(1000); // Race condition with getDestinationDataFromBackend() settingsLogStreamingPage.actions.clickAddFirstDestination(); cy.wait(100); settingsLogStreamingPage.getters.getDestinationModal().should('be.visible'); settingsLogStreamingPage.getters.getSelectDestinationType().click(); settingsLogStreamingPage.getters.getSelectDestinationTypeItems().eq(0).click(); settingsLogStreamingPage.getters.getSelectDestinationButton().click(); - settingsLogStreamingPage.getters.getDestinationNameInput().click() - settingsLogStreamingPage.getters.getDestinationNameInput().find('input').clear().type('Destination 1'); + settingsLogStreamingPage.getters.getDestinationNameInput().click(); + settingsLogStreamingPage.getters + .getDestinationNameInput() + .find('input') + .clear() + .type('Destination 1'); settingsLogStreamingPage.getters.getDestinationSaveButton().should('not.have.attr', 'disabled'); settingsLogStreamingPage.getters.getDestinationSaveButton().click(); cy.wait(100); - settingsLogStreamingPage.getters.getDestinationModal().click(1, 1); + getVisibleModalOverlay().click(1, 1); cy.reload(); - settingsLogStreamingPage.getters - .getDestinationCards() - .eq(0) - .find('.el-dropdown-selfdefine') - .click(); - cy.get('.el-dropdown-menu').find('.el-dropdown-menu__item').eq(0).click(); + settingsLogStreamingPage.getters.getDestinationCards().eq(0).find('.el-dropdown').click(); + getVisibleDropdown().find('.el-dropdown-menu__item').eq(0).click(); settingsLogStreamingPage.getters.getDestinationSaveButton().should('not.exist'); - settingsLogStreamingPage.getters.getDestinationModal().click(1, 1); + getVisibleModalOverlay().click(1, 1); - settingsLogStreamingPage.getters - .getDestinationCards() - .eq(0) - .find('.el-dropdown-selfdefine') - .click(); - cy.get('.el-dropdown-menu').find('.el-dropdown-menu__item').eq(1).click(); + settingsLogStreamingPage.getters.getDestinationCards().eq(0).find('.el-dropdown').click(); + getVisibleDropdown().find('.el-dropdown-menu__item').eq(1).click(); cy.get('.el-message-box').should('be.visible').find('.btn--confirm').click(); - cy.reload(); }); }); diff --git a/cypress/e2e/10-undo-redo.cy.ts b/cypress/e2e/10-undo-redo.cy.ts index 059777e3b9..3a65beedb2 100644 --- a/cypress/e2e/10-undo-redo.cy.ts +++ b/cypress/e2e/10-undo-redo.cy.ts @@ -119,18 +119,15 @@ describe('Undo/Redo', () => { WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150]); WorkflowPage.getters - .canvasNodes() - .last() + .canvasNodeByName('Code') .should('have.attr', 'style', 'left: 740px; top: 320px;'); WorkflowPage.actions.hitUndo(); WorkflowPage.getters - .canvasNodes() - .last() + .canvasNodeByName('Code') .should('have.attr', 'style', 'left: 640px; top: 220px;'); WorkflowPage.actions.hitRedo(); WorkflowPage.getters - .canvasNodes() - .last() + .canvasNodeByName('Code') .should('have.attr', 'style', 'left: 740px; top: 320px;'); }); @@ -138,7 +135,10 @@ describe('Undo/Redo', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.getters.nodeConnections().realHover(); - cy.get('.connection-actions .delete').filter(':visible').should('be.visible').click(); + cy.get('.connection-actions .delete') + .filter(':visible') + .should('be.visible') + .click({ force: true }); WorkflowPage.getters.nodeConnections().should('have.length', 0); WorkflowPage.actions.hitUndo(); WorkflowPage.getters.nodeConnections().should('have.length', 1); @@ -256,6 +256,9 @@ describe('Undo/Redo', () => { }); it('should undo/redo multiple steps', () => { + const initialPosition = 'left: 420px; top: 220px;'; + const movedPosition = 'left: 540px; top: 360px;'; + WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); @@ -266,8 +269,10 @@ describe('Undo/Redo', () => { WorkflowPage.getters.canvasNodes().last().click(); WorkflowPage.actions.hitDisableNodeShortcut(); // Move first one + WorkflowPage.getters.canvasNodes().first().should('have.attr', 'style', initialPosition); WorkflowPage.getters.canvasNodes().first().click(); cy.drag('[data-test-id="canvas-node"].jtk-drag-selected', [50, 150]); + WorkflowPage.getters.canvasNodes().first().should('have.attr', 'style', movedPosition); // Delete the set node WorkflowPage.getters.canvasNodeByName(SET_NODE_NAME).click().click(); cy.get('body').type('{backspace}'); @@ -278,10 +283,7 @@ describe('Undo/Redo', () => { WorkflowPage.getters.nodeConnections().should('have.length', 3); // Second undo: Should move first node to it's original position WorkflowPage.actions.hitUndo(); - WorkflowPage.getters - .canvasNodes() - .first() - .should('have.attr', 'style', 'left: 420px; top: 220px;'); + WorkflowPage.getters.canvasNodes().first().should('have.attr', 'style', initialPosition); // Third undo: Should enable last node WorkflowPage.actions.hitUndo(); WorkflowPage.getters.disabledNodes().should('have.length', 0); @@ -291,10 +293,7 @@ describe('Undo/Redo', () => { WorkflowPage.getters.disabledNodes().should('have.length', 1); // Second redo: Should move the first node WorkflowPage.actions.hitRedo(); - WorkflowPage.getters - .canvasNodes() - .first() - .should('have.attr', 'style', 'left: 540px; top: 360px;'); + WorkflowPage.getters.canvasNodes().first().should('have.attr', 'style', movedPosition); // Third redo: Should delete the Set node WorkflowPage.actions.hitRedo(); WorkflowPage.getters.canvasNodes().should('have.length', 3); diff --git a/cypress/e2e/12-canvas-actions.cy.ts b/cypress/e2e/12-canvas-actions.cy.ts index d336294f48..a4eaebc7be 100644 --- a/cypress/e2e/12-canvas-actions.cy.ts +++ b/cypress/e2e/12-canvas-actions.cy.ts @@ -66,7 +66,6 @@ describe('Canvas Actions', () => { WorkflowPage.getters.nodeViewBackground().click({ force: true }); }); - it('should add a connected node using plus endpoint', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); cy.get('.plus-endpoint').should('be.visible').click(); diff --git a/cypress/e2e/12-canvas.cy.ts b/cypress/e2e/12-canvas.cy.ts index 625b8b98f8..e98b9dea9e 100644 --- a/cypress/e2e/12-canvas.cy.ts +++ b/cypress/e2e/12-canvas.cy.ts @@ -107,7 +107,7 @@ describe('Canvas Node Manipulation and Navigation', () => { WorkflowPage.actions.zoomToFit(); cy.get('.plus-draggable-endpoint').filter(':visible').should('not.have.class', 'ep-success'); - cy.get('.jtk-connector.success').should('have.length', 4); + cy.get('.jtk-connector.success').should('have.length', 3); cy.get('.jtk-connector').should('have.length', 4); }); diff --git a/cypress/e2e/14-data-transformation-expressions.cy.ts b/cypress/e2e/14-data-transformation-expressions.cy.ts index 099e79ae7d..fc303c603b 100644 --- a/cypress/e2e/14-data-transformation-expressions.cy.ts +++ b/cypress/e2e/14-data-transformation-expressions.cy.ts @@ -7,12 +7,10 @@ describe('Data transformation expressions', () => { beforeEach(() => { wf.actions.visit(); - cy.window().then( - (win) => { - // @ts-ignore - win.preventNodeViewBeforeUnload = true; - }, - ); + cy.window().then((win) => { + // @ts-ignore + win.preventNodeViewBeforeUnload = true; + }); }); it('$json + native string methods', () => { @@ -85,7 +83,7 @@ describe('Data transformation expressions', () => { ndv.getters.inlineExpressionEditorInput().clear().type(input); ndv.actions.execute(); - ndv.getters.outputDataContainer().find('[class*=value_]').should('exist') + ndv.getters.outputDataContainer().find('[class*=value_]').should('exist'); ndv.getters.outputDataContainer().find('[class*=value_]').should('contain', output); }); @@ -100,7 +98,7 @@ describe('Data transformation expressions', () => { ndv.getters.inlineExpressionEditorInput().clear().type(input); ndv.actions.execute(); - ndv.getters.outputDataContainer().find('[class*=value_]').should('exist') + ndv.getters.outputDataContainer().find('[class*=value_]').should('exist'); ndv.getters.outputDataContainer().find('[class*=value_]').should('contain', output); }); }); @@ -111,7 +109,7 @@ describe('Data transformation expressions', () => { const addSet = () => { wf.actions.addNodeToCanvas('Set', true, true); - ndv.getters.parameterInput('keepOnlySet').find('div[role=switch]').click(); // shorten output + ndv.getters.parameterInput('keepOnlySet').find('.el-switch').click(); // shorten output cy.get('input[placeholder="Add Value"]').click(); cy.get('span').contains('String').click(); ndv.getters.nthParam(3).contains('Expression').invoke('show').click(); // Values to Set > String > Value diff --git a/cypress/e2e/14-mapping.cy.ts b/cypress/e2e/14-mapping.cy.ts index 9ccda6b6a4..fa9bdd78a2 100644 --- a/cypress/e2e/14-mapping.cy.ts +++ b/cypress/e2e/14-mapping.cy.ts @@ -4,6 +4,7 @@ import { SCHEDULE_TRIGGER_NODE_NAME, } from './../constants'; import { WorkflowPage, NDV } from '../pages'; +import { getVisibleSelect } from '../utils'; const workflowPage = new WorkflowPage(); const ndv = new NDV(); @@ -28,11 +29,7 @@ describe('Data mapping', () => { ndv.getters.inputDataContainer().get('table', { timeout: 10000 }).should('exist'); ndv.getters.nodeParameters().find('input[placeholder*="Add Value"]').click(); - ndv.getters - .nodeParameters() - .find('.el-select-dropdown__list li:nth-child(3)') - .should('have.text', 'String') - .click(); + getVisibleSelect().find('li:nth-child(3)').should('have.text', 'String').click(); ndv.getters .parameterInput('name') .should('have.length', 1) @@ -128,7 +125,7 @@ describe('Data mapping', () => { .find('.json-data') .should( 'have.text', - '[{"input":[{"count":0,"with space":"!!","with.dot":"!!","with"quotes":"!!"}]},{"input":[{"count":1}]}]', + '[{"input": [{"count": 0,"with space": "!!","with.dot": "!!","with"quotes": "!!"}]},{"input": [{"count": 1}]}]', ) .find('span') .contains('"count"') @@ -178,6 +175,7 @@ describe('Data mapping', () => { it('maps expressions from previous nodes', () => { cy.createFixtureWorkflow('Test_workflow_3.json', `My test workflow`); + workflowPage.actions.zoomToFit(); workflowPage.actions.openNode('Set1'); ndv.actions.selectInputNode(SCHEDULE_TRIGGER_NODE_NAME); @@ -245,7 +243,8 @@ describe('Data mapping', () => { workflowPage.actions.addNodeToCanvas('Item Lists'); workflowPage.actions.openNode('Item Lists'); - ndv.getters.parameterInput('operation').click().find('li').contains('Sort').click(); + ndv.getters.parameterInput('operation').click(); + getVisibleSelect().find('li').contains('Sort').click(); ndv.getters.nodeParameters().find('button').contains('Add Field To Sort By').click(); @@ -274,6 +273,8 @@ describe('Data mapping', () => { ndv.actions.typeIntoParameterInput('value', 'fun'); ndv.actions.clearParameterInput('value'); // keep focus on param + ndv.actions.dismissMappingTooltip(); + cy.wait(300); ndv.getters.inputDataContainer().should('exist').find('span').contains('count').realMouseDown(); diff --git a/cypress/e2e/15-scheduler-node.cy.ts b/cypress/e2e/15-scheduler-node.cy.ts index d58a541652..0021455619 100644 --- a/cypress/e2e/15-scheduler-node.cy.ts +++ b/cypress/e2e/15-scheduler-node.cy.ts @@ -1,5 +1,6 @@ import { WorkflowPage, WorkflowsPage, NDV } from '../pages'; import { BACKEND_BASE_URL } from '../constants'; +import { getVisibleSelect } from '../utils'; const workflowsPage = new WorkflowsPage(); const workflowPage = new WorkflowPage(); @@ -24,11 +25,7 @@ describe('Schedule Trigger node', async () => { workflowPage.actions.openNode('Schedule Trigger'); cy.getByTestId('parameter-input-field').click(); - cy.getByTestId('parameter-input-field') - .find('.el-select-dropdown') - .find('.option-headline') - .contains('Seconds') - .click(); + getVisibleSelect().find('.option-headline').contains('Seconds').click(); cy.getByTestId('parameter-input-secondsInterval').clear().type('1'); ndv.getters.backToCanvas().click(); diff --git a/cypress/e2e/16-webhook-node.cy.ts b/cypress/e2e/16-webhook-node.cy.ts index 2ba59a8cfb..80a649a952 100644 --- a/cypress/e2e/16-webhook-node.cy.ts +++ b/cypress/e2e/16-webhook-node.cy.ts @@ -2,6 +2,7 @@ import { WorkflowPage, NDV, CredentialsModal } from '../pages'; import { v4 as uuid } from 'uuid'; import { cowBase64 } from '../support/binaryTestFiles'; import { BACKEND_BASE_URL } from '../constants'; +import { getVisibleSelect } from '../utils'; const workflowPage = new WorkflowPage(); const ndv = new NDV(); @@ -34,11 +35,7 @@ const simpleWebhookCall = (options: SimpleWebhookCallOptions) => { workflowPage.actions.openNode('Webhook'); cy.getByTestId('parameter-input-httpMethod').click(); - cy.getByTestId('parameter-input-httpMethod') - .find('.el-select-dropdown') - .find('.option-headline') - .contains(method) - .click(); + getVisibleSelect().find('.option-headline').contains(method).click(); cy.getByTestId('parameter-input-path') .find('.parameter-input') .find('input') @@ -47,11 +44,7 @@ const simpleWebhookCall = (options: SimpleWebhookCallOptions) => { if (authentication) { cy.getByTestId('parameter-input-authentication').click(); - cy.getByTestId('parameter-input-authentication') - .find('.el-select-dropdown') - .find('.option-headline') - .contains(authentication) - .click(); + getVisibleSelect().find('.option-headline').contains(authentication).click(); } if (responseCode) { @@ -64,20 +57,12 @@ const simpleWebhookCall = (options: SimpleWebhookCallOptions) => { if (respondWith) { cy.getByTestId('parameter-input-responseMode').click(); - cy.getByTestId('parameter-input-responseMode') - .find('.el-select-dropdown') - .find('.option-headline') - .contains(respondWith) - .click(); + getVisibleSelect().find('.option-headline').contains(respondWith).click(); } if (responseData) { cy.getByTestId('parameter-input-responseData').click(); - cy.getByTestId('parameter-input-responseData') - .find('.el-select-dropdown') - .find('.option-headline') - .contains(responseData) - .click(); + getVisibleSelect().find('.option-headline').contains(responseData).click(); } if (executeNow) { @@ -136,13 +121,13 @@ describe('Webhook Trigger node', async () => { workflowPage.actions.addNodeToCanvas('Set'); workflowPage.actions.openNode('Set'); cy.get('.add-option').click(); - cy.get('.add-option').find('.el-select-dropdown__item').contains('Number').click(); + getVisibleSelect().find('.el-select-dropdown__item').contains('Number').click(); cy.get('.fixed-collection-parameter') .getByTestId('parameter-input-name') .clear() .type('MyValue'); cy.get('.fixed-collection-parameter').getByTestId('parameter-input-value').clear().type('1234'); - ndv.getters.backToCanvas().click(); + ndv.getters.backToCanvas().click({ force: true }); workflowPage.actions.addNodeToCanvas('Respond to Webhook'); @@ -185,13 +170,18 @@ describe('Webhook Trigger node', async () => { workflowPage.actions.addNodeToCanvas('Set'); workflowPage.actions.openNode('Set'); cy.get('.add-option').click(); - cy.get('.add-option').find('.el-select-dropdown__item').contains('Number').click(); + getVisibleSelect().find('.el-select-dropdown__item').contains('Number').click(); cy.get('.fixed-collection-parameter') .getByTestId('parameter-input-name') + .find('input') .clear() .type('MyValue'); - cy.get('.fixed-collection-parameter').getByTestId('parameter-input-value').clear().type('1234'); - ndv.getters.backToCanvas().click(); + cy.get('.fixed-collection-parameter') + .getByTestId('parameter-input-value') + .find('input') + .clear() + .type('1234'); + ndv.getters.backToCanvas().click({ force: true }); workflowPage.actions.executeWorkflow(); cy.wait(waitForWebhook); @@ -216,7 +206,7 @@ describe('Webhook Trigger node', async () => { workflowPage.actions.addNodeToCanvas('Set'); workflowPage.actions.openNode('Set'); cy.get('.add-option').click(); - cy.get('.add-option').find('.el-select-dropdown__item').contains('String').click(); + getVisibleSelect().find('.el-select-dropdown__item').contains('String').click(); cy.get('.fixed-collection-parameter').getByTestId('parameter-input-name').clear().type('data'); cy.get('.fixed-collection-parameter') .getByTestId('parameter-input-value') @@ -231,11 +221,7 @@ describe('Webhook Trigger node', async () => { workflowPage.actions.openNode('Move Binary Data'); cy.getByTestId('parameter-input-mode').click(); - cy.getByTestId('parameter-input-mode') - .find('.el-select-dropdown') - .find('.option-headline') - .contains('JSON to Binary') - .click(); + getVisibleSelect().find('.option-headline').contains('JSON to Binary').click(); ndv.getters.backToCanvas().click(); workflowPage.actions.executeWorkflow(); @@ -274,7 +260,7 @@ describe('Webhook Trigger node', async () => { }); // add credentials workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.actions.fillCredentialsForm(); @@ -317,7 +303,7 @@ describe('Webhook Trigger node', async () => { }); // add credentials workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.actions.fillCredentialsForm(); diff --git a/cypress/e2e/17-sharing.cy.ts b/cypress/e2e/17-sharing.cy.ts index cf0f4ccd35..acd61b05db 100644 --- a/cypress/e2e/17-sharing.cy.ts +++ b/cypress/e2e/17-sharing.cy.ts @@ -48,7 +48,7 @@ describe('Sharing', { disableAutoLogin: true }, () => { workflowPage.actions.setWorkflowName('Workflow W1'); workflowPage.actions.addInitialNodeToCanvas('Manual Trigger'); workflowPage.actions.addNodeToCanvas('Notion', true, true); - ndv.getters.credentialInput().should('contain', 'Credential C1'); + ndv.getters.credentialInput().find('input').should('have.value', 'Credential C1'); ndv.actions.close(); workflowPage.actions.openShareModal(); @@ -87,16 +87,12 @@ describe('Sharing', { disableAutoLogin: true }, () => { workflowsPage.getters.workflowCards().should('have.length', 1); workflowsPage.getters.workflowCard('Workflow W1').click(); workflowPage.actions.addNodeToCanvas('Airtable', true, true); - ndv.getters.credentialInput().should('contain', 'Credential C2'); + ndv.getters.credentialInput().find('input').should('have.value', 'Credential C2'); ndv.actions.close(); workflowPage.actions.saveWorkflowOnButtonClick(); workflowPage.actions.openNode('Notion'); - ndv.getters - .credentialInput() - .find('input') - .should('have.value', 'Credential C1') - .should('be.disabled'); + ndv.getters.credentialInput().should('have.value', 'Credential C1').should('be.disabled'); ndv.actions.close(); }); @@ -116,11 +112,7 @@ describe('Sharing', { disableAutoLogin: true }, () => { workflowsPage.getters.workflowCards().should('have.length', 2); workflowsPage.getters.workflowCard('Workflow W1').click(); workflowPage.actions.openNode('Notion'); - ndv.getters - .credentialInput() - .find('input') - .should('have.value', 'Credential C1') - .should('be.disabled'); + ndv.getters.credentialInput().should('have.value', 'Credential C1').should('be.disabled'); ndv.actions.close(); cy.waitForLoad(); diff --git a/cypress/e2e/17-workflow-tags.cy.ts b/cypress/e2e/17-workflow-tags.cy.ts index 56b548747d..966a506e7b 100644 --- a/cypress/e2e/17-workflow-tags.cy.ts +++ b/cypress/e2e/17-workflow-tags.cy.ts @@ -30,7 +30,7 @@ describe('Workflow tags', () => { } cy.contains('Done').click(); - wf.getters.createTagButton().click(); + wf.getters.tagsDropdown().click(); wf.getters.tagsInDropdown().should('have.length', 5); wf.getters.tagPills().should('have.length', 0); // none attached }); @@ -45,7 +45,7 @@ describe('Workflow tags', () => { }); cy.contains('Done').click(); - wf.getters.createTagButton().click(); + wf.getters.tagsDropdown().click(); wf.getters.tagsInDropdown().should('have.length', 0); // none stored wf.getters.tagPills().should('have.length', 0); // none attached }); @@ -57,7 +57,8 @@ describe('Workflow tags', () => { cy.contains('Create a tag').click(); cy.getByTestId('tags-table').find('input').type(first).type('{enter}'); - cy.getByTestId('edit-tag-button').click({ force: true }); + cy.getByTestId('tags-table').should('contain.text', first); + cy.getByTestId('edit-tag-button').eq(-1).click({ force: true }); cy.wait(300); cy.getByTestId('tags-table') .find('.el-input--large') @@ -65,7 +66,7 @@ describe('Workflow tags', () => { .type(' Updated') .type('{enter}'); cy.contains('Done').click(); - wf.getters.createTagButton().click(); + wf.getters.tagsDropdown().click(); wf.getters.tagsInDropdown().should('have.length', 1); // one stored wf.getters.tagsInDropdown().contains('Updated').should('exist'); wf.getters.tagPills().should('have.length', 0); // none attached @@ -76,7 +77,7 @@ describe('Workflow tags', () => { wf.actions.addTags(TEST_TAGS); wf.getters.nthTagPill(1).click(); wf.getters.tagsDropdown().find('.el-tag__close').first().click(); - cy.get('body').type('{enter}'); + cy.get('body').click(0, 0); wf.getters.tagPills().should('have.length', TEST_TAGS.length - 1); }); @@ -84,8 +85,8 @@ describe('Workflow tags', () => { wf.getters.createTagButton().click(); wf.actions.addTags(TEST_TAGS); wf.getters.nthTagPill(1).click(); - wf.getters.tagsDropdown().find('li.selected').first().click(); - cy.get('body').type('{enter}'); + wf.getters.tagsInDropdown().filter('.selected').first().click(); + cy.get('body').click(0, 0); wf.getters.tagPills().should('have.length', TEST_TAGS.length - 1); }); }); diff --git a/cypress/e2e/18-user-management.cy.ts b/cypress/e2e/18-user-management.cy.ts index 6af5ba6b60..cbc7a165f1 100644 --- a/cypress/e2e/18-user-management.cy.ts +++ b/cypress/e2e/18-user-management.cy.ts @@ -58,19 +58,19 @@ describe('User Management', { disableAutoLogin: true }, () => { it('should delete user and their data', () => { usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true); usersSettingsPage.actions.opedDeleteDialog(INSTANCE_MEMBERS[0].email); - usersSettingsPage.getters.deleteDataRadioButton().realClick(); + usersSettingsPage.getters.deleteDataRadioButton().click(); usersSettingsPage.getters.deleteDataInput().type('delete all data'); - usersSettingsPage.getters.deleteUserButton().realClick(); + usersSettingsPage.getters.deleteUserButton().click(); workflowPage.getters.successToast().should('contain', 'User deleted'); }); it('should delete user and transfer their data', () => { usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true); usersSettingsPage.actions.opedDeleteDialog(INSTANCE_MEMBERS[1].email); - usersSettingsPage.getters.transferDataRadioButton().realClick(); - usersSettingsPage.getters.userSelectDropDown().realClick(); - usersSettingsPage.getters.userSelectOptions().first().realClick(); - usersSettingsPage.getters.deleteUserButton().realClick(); + usersSettingsPage.getters.transferDataRadioButton().click(); + usersSettingsPage.getters.userSelectDropDown().click(); + usersSettingsPage.getters.userSelectOptions().first().click(); + usersSettingsPage.getters.deleteUserButton().click(); workflowPage.getters.successToast().should('contain', 'User deleted'); }); diff --git a/cypress/e2e/2-credentials.cy.ts b/cypress/e2e/2-credentials.cy.ts index 7d4f743a98..78dbe055aa 100644 --- a/cypress/e2e/2-credentials.cy.ts +++ b/cypress/e2e/2-credentials.cy.ts @@ -4,8 +4,6 @@ import { PIPEDRIVE_NODE_NAME, HTTP_REQUEST_NODE_NAME, NEW_QUERY_AUTH_ACCOUNT_NAME, -} from './../constants'; -import { GMAIL_NODE_NAME, NEW_GOOGLE_ACCOUNT_NAME, NEW_TRELLO_ACCOUNT_NAME, @@ -13,6 +11,7 @@ import { TRELLO_NODE_NAME, } from '../constants'; import { CredentialsPage, CredentialsModal, WorkflowPage, NDV } from '../pages'; +import { getVisibleSelect } from '../utils'; const credentialsPage = new CredentialsPage(); const credentialsModal = new CredentialsModal(); @@ -90,13 +89,16 @@ describe('Credentials', () => { workflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2); credentialsModal.getters.credentialAuthTypeRadioButtons().first().click(); credentialsModal.actions.fillCredentialsForm(); cy.get('.el-message-box').find('button').contains('Close').click(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_GOOGLE_ACCOUNT_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_GOOGLE_ACCOUNT_NAME); }); it('should show multiple credential types in the same dropdown', () => { @@ -107,7 +109,7 @@ describe('Credentials', () => { cy.get('body').type('{enter}'); workflowPage.getters.nodeCredentialsSelect().click(); // Add oAuth credentials - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2); credentialsModal.getters.credentialAuthTypeRadioButtons().first().click(); @@ -115,13 +117,14 @@ describe('Credentials', () => { cy.get('.el-message-box').find('button').contains('Close').click(); workflowPage.getters.nodeCredentialsSelect().click(); // Add Service account credentials - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2); credentialsModal.getters.credentialAuthTypeRadioButtons().last().click(); credentialsModal.actions.fillCredentialsForm(); // Both (+ the 'Create new' option) should be in the dropdown - workflowPage.getters.nodeCredentialsSelect().find('li').should('have.length.greaterThan', 3); + workflowPage.getters.nodeCredentialsSelect().click(); + getVisibleSelect().find('li').should('have.length.greaterThan', 2); }); it('should correctly render required and optional credentials', () => { @@ -132,18 +135,18 @@ describe('Credentials', () => { // Select incoming authentication nodeDetailsView.getters.parameterInput('incomingAuthentication').should('exist'); nodeDetailsView.getters.parameterInput('incomingAuthentication').click(); - nodeDetailsView.getters.parameterInput('incomingAuthentication').find('li').first().click(); + getVisibleSelect().find('li').first().click(); // There should be two credential fields workflowPage.getters.nodeCredentialsSelect().should('have.length', 2); workflowPage.getters.nodeCredentialsSelect().first().click(); - workflowPage.getters.nodeCredentialsSelect().first().find('li').last().click(); + getVisibleSelect().find('li').last().click(); // This one should show auth type selector credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2); cy.get('body').type('{esc}'); workflowPage.getters.nodeCredentialsSelect().last().click(); - workflowPage.getters.nodeCredentialsSelect().last().find('li').last().click(); + getVisibleSelect().find('li').last().click(); // This one should not show auth type selector credentialsModal.getters.credentialsAuthTypeSelector().should('not.exist'); }); @@ -155,10 +158,13 @@ describe('Credentials', () => { workflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsAuthTypeSelector().should('not.exist'); credentialsModal.actions.fillCredentialsForm(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_TRELLO_ACCOUNT_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_TRELLO_ACCOUNT_NAME); }); it('should delete credentials from NDV', () => { @@ -168,16 +174,22 @@ describe('Credentials', () => { workflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.actions.fillCredentialsForm(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_NOTION_ACCOUNT_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_NOTION_ACCOUNT_NAME); workflowPage.getters.nodeCredentialsEditButton().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.deleteButton().click(); cy.get('.el-message-box').find('button').contains('Yes').click(); workflowPage.getters.successToast().contains('Credential deleted'); - workflowPage.getters.nodeCredentialsSelect().should('not.contain', NEW_TRELLO_ACCOUNT_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('not.have.value', NEW_TRELLO_ACCOUNT_NAME); }); it('should rename credentials from NDV', () => { @@ -187,17 +199,18 @@ describe('Credentials', () => { workflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.actions.fillCredentialsForm(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_TRELLO_ACCOUNT_NAME); - workflowPage.getters.nodeCredentialsEditButton().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.name().click(); credentialsModal.actions.renameCredential(NEW_CREDENTIAL_NAME); credentialsModal.getters.saveButton().click(); credentialsModal.getters.closeButton().click(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_CREDENTIAL_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_CREDENTIAL_NAME); }); it('should setup generic authentication for HTTP node', () => { @@ -207,20 +220,20 @@ describe('Credentials', () => { workflowPage.getters.canvasNodes().last().click(); cy.get('body').type('{enter}'); nodeDetailsView.getters.parameterInput('authentication').click(); - nodeDetailsView.getters.parameterInput('authentication').find('li').should('have.length', 3); - nodeDetailsView.getters.parameterInput('authentication').find('li').last().click(); + getVisibleSelect().find('li').should('have.length', 3); + getVisibleSelect().find('li').last().click(); nodeDetailsView.getters.parameterInput('genericAuthType').should('exist'); nodeDetailsView.getters.parameterInput('genericAuthType').click(); - nodeDetailsView.getters - .parameterInput('genericAuthType') - .find('li') - .should('have.length.greaterThan', 0); - nodeDetailsView.getters.parameterInput('genericAuthType').find('li').last().click(); + getVisibleSelect().find('li').should('have.length.greaterThan', 0); + getVisibleSelect().find('li').last().click(); workflowPage.getters.nodeCredentialsSelect().should('exist'); workflowPage.getters.nodeCredentialsSelect().click(); - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.actions.fillCredentialsForm(); - workflowPage.getters.nodeCredentialsSelect().should('contain', NEW_QUERY_AUTH_ACCOUNT_NAME); + workflowPage.getters + .nodeCredentialsSelect() + .find('input') + .should('have.value', NEW_QUERY_AUTH_ACCOUNT_NAME); }); }); diff --git a/cypress/e2e/21-community-nodes.cy.ts b/cypress/e2e/21-community-nodes.cy.ts index cfc76e46e0..39f572ba5c 100644 --- a/cypress/e2e/21-community-nodes.cy.ts +++ b/cypress/e2e/21-community-nodes.cy.ts @@ -4,6 +4,7 @@ import { CredentialsModal, WorkflowPage } from '../pages'; import CustomNodeWithN8nCredentialFixture from '../fixtures/Custom_node_n8n_credential.json'; import CustomNodeWithCustomCredentialFixture from '../fixtures/Custom_node_custom_credential.json'; import CustomCredential from '../fixtures/Custom_credential.json'; +import { getVisibleSelect } from '../utils'; const credentialsModal = new CredentialsModal(); const nodeCreatorFeature = new NodeCreator(); @@ -20,9 +21,13 @@ describe('Community Nodes', () => { req.on('response', (res) => { const nodes = res.body || []; - nodes.push(CustomNodeFixture, CustomNodeWithN8nCredentialFixture, CustomNodeWithCustomCredentialFixture); + nodes.push( + CustomNodeFixture, + CustomNodeWithN8nCredentialFixture, + CustomNodeWithCustomCredentialFixture, + ); }); - }) + }); cy.intercept('/types/credentials.json', { middleware: true }, (req) => { req.headers['cache-control'] = 'no-cache, no-store'; @@ -31,8 +36,8 @@ describe('Community Nodes', () => { const credentials = res.body || []; credentials.push(CustomCredential); - }) - }) + }); + }); workflowPage.actions.visit(); }); @@ -45,7 +50,7 @@ describe('Community Nodes', () => { nodeCreatorFeature.getters .getCreatorItem(customNode) - .findChildByTestId('node-creator-item-tooltip') + .find('.el-tooltip__trigger') .should('exist'); nodeCreatorFeature.actions.selectNode(customNode); @@ -65,16 +70,9 @@ describe('Community Nodes', () => { secondParameter().find('label').contains('Resource').should('exist'); secondParameter().find('input.el-input__inner').should('have.value', 'option2'); secondParameter().find('.el-select').click(); - secondParameter().find('.el-select-dropdown__list').should('exist'); // Check if all options are rendered and select the fourth one - secondParameter().find('.el-select-dropdown__list').children().should('have.length', 4); - secondParameter() - .find('.el-select-dropdown__list') - .children() - .eq(3) - .contains('option4') - .should('exist') - .click(); + getVisibleSelect().find('li').should('have.length', 4); + getVisibleSelect().find('li').eq(3).contains('option4').should('exist').click(); secondParameter().find('input.el-input__inner').should('have.value', 'option4'); }); diff --git a/cypress/e2e/24-ndv-paired-item.cy.ts b/cypress/e2e/24-ndv-paired-item.cy.ts index 3bbd2f0b23..a2bb527723 100644 --- a/cypress/e2e/24-ndv-paired-item.cy.ts +++ b/cypress/e2e/24-ndv-paired-item.cy.ts @@ -28,59 +28,36 @@ describe('NDV', () => { ndv.actions.switchOutputMode('Table'); // input to output - ndv.getters.inputTableRow(1) + ndv.getters + .inputTableRow(1) .should('exist') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); - ndv.getters.inputTableRow(1) - .realHover(); - ndv.getters.outputTableRow(4) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.inputTableRow(1).realHover(); + ndv.getters.outputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.inputTableRow(2) - .realHover(); - ndv.getters.outputTableRow(2) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); - - ndv.getters.inputTableRow(3) - .realHover(); - ndv.getters.outputTableRow(6) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.inputTableRow(2).realHover(); + ndv.getters.outputTableRow(2).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + + ndv.getters.inputTableRow(3).realHover(); + ndv.getters.outputTableRow(6).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); // output to input - ndv.getters.outputTableRow(1) - .realHover(); - ndv.getters.inputTableRow(4) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(1).realHover(); + ndv.getters.inputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(4) - .realHover(); - ndv.getters.inputTableRow(1) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(4).realHover(); + ndv.getters.inputTableRow(1).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(2) - .realHover(); - ndv.getters.inputTableRow(2) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); - - ndv.getters.outputTableRow(6) - .realHover(); - ndv.getters.inputTableRow(3) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(2).realHover(); + ndv.getters.inputTableRow(2).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); - ndv.getters.outputTableRow(1) - .realHover(); - ndv.getters.inputTableRow(4) - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters.outputTableRow(6).realHover(); + ndv.getters.inputTableRow(3).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); + + ndv.getters.outputTableRow(1).realHover(); + ndv.getters.inputTableRow(4).invoke('attr', 'data-test-id').should('equal', 'hovering-item'); }); it('maps paired input and output items based on selected input node', () => { @@ -92,9 +69,11 @@ describe('NDV', () => { workflowPage.actions.openNode('Set2'); ndv.getters.inputPanel().contains('6 items').should('exist'); - ndv.getters.outputRunSelector() + ndv.getters + .outputRunSelector() + .find('input') .should('exist') - .should('include.text', '2 of 2 (6 items)'); + .should('have.value', '2 of 2 (6 items)'); ndv.actions.switchInputMode('Table'); ndv.actions.switchOutputMode('Table'); @@ -106,7 +85,8 @@ describe('NDV', () => { ndv.actions.selectInputNode('Set1'); ndv.getters.backToCanvas().realHover(); // reset to default hover - ndv.getters.inputTableRow(1) + ndv.getters + .inputTableRow(1) .should('have.text', '1000') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); @@ -119,7 +99,8 @@ describe('NDV', () => { ndv.actions.changeOutputRunSelector('1 of 2 (6 items)'); ndv.getters.backToCanvas().realHover(); // reset to default hover - ndv.getters.inputTableRow(1) + ndv.getters + .inputTableRow(1) .should('have.text', '1111') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); @@ -137,11 +118,13 @@ describe('NDV', () => { workflowPage.actions.executeWorkflow(); workflowPage.actions.openNode('Set3'); - ndv.getters.inputRunSelector() + ndv.getters + .inputRunSelector() .should('exist') .find('input') .should('include.value', '2 of 2 (6 items)'); - ndv.getters.outputRunSelector() + ndv.getters + .outputRunSelector() .should('exist') .find('input') .should('include.value', '2 of 2 (6 items)'); @@ -150,23 +133,19 @@ describe('NDV', () => { ndv.actions.switchOutputMode('Table'); ndv.actions.changeOutputRunSelector('1 of 2 (6 items)'); - ndv.getters.inputRunSelector().find('input') - .should('include.value', '1 of 2 (6 items)'); - ndv.getters.outputRunSelector().find('input') - .should('include.value', '1 of 2 (6 items)'); + ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 2 (6 items)'); + ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 2 (6 items)'); - ndv.getters.inputTableRow(1) + ndv.getters + .inputTableRow(1) .should('have.text', '1111') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); - ndv.getters.outputTableRow(1) - .should('have.text', '1111') - .realHover(); + ndv.getters.outputTableRow(1).should('have.text', '1111').realHover(); - ndv.getters.outputTableRow(3) - .should('have.text', '4444') - .realHover(); - ndv.getters.inputTableRow(3) + ndv.getters.outputTableRow(3).should('have.text', '4444').realHover(); + ndv.getters + .inputTableRow(3) .should('have.text', '4444') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); @@ -174,18 +153,16 @@ describe('NDV', () => { ndv.actions.changeOutputRunSelector('2 of 2 (6 items)'); cy.wait(50); - ndv.getters.inputTableRow(1) - .should('have.text', '1000') - .realHover(); - ndv.getters.outputTableRow(1) + ndv.getters.inputTableRow(1).should('have.text', '1000').realHover(); + ndv.getters + .outputTableRow(1) .should('have.text', '1000') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); - ndv.getters.outputTableRow(3) - .should('have.text', '2000') - .realHover(); - ndv.getters.inputTableRow(3) + ndv.getters.outputTableRow(3).should('have.text', '2000').realHover(); + ndv.getters + .inputTableRow(3) .should('have.text', '2000') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); @@ -200,15 +177,18 @@ describe('NDV', () => { workflowPage.actions.openNode('Set2'); ndv.getters.inputPanel().contains('6 items').should('exist'); - ndv.getters.outputRunSelector() + ndv.getters + .outputRunSelector() + .find('input') .should('exist') - .should('include.text', '2 of 2 (6 items)'); + .should('have.value', '2 of 2 (6 items)'); ndv.actions.switchInputMode('Table'); ndv.actions.switchOutputMode('Table'); ndv.getters.backToCanvas().realHover(); // reset to default hover - ndv.getters.inputTableRow(1) + ndv.getters + .inputTableRow(1) .should('have.text', '1111') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); @@ -218,28 +198,32 @@ describe('NDV', () => { ndv.actions.selectInputNode('Code1'); ndv.getters.inputTableRow(1).realHover(); - ndv.getters.inputTableRow(1) + ndv.getters + .inputTableRow(1) .should('have.text', '1000') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); - ndv.getters.outputTableRow(1) - .should('have.text', '1000'); + ndv.getters.outputTableRow(1).should('have.text', '1000'); ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); ndv.actions.selectInputNode('Code'); ndv.getters.inputTableRow(1).realHover(); - ndv.getters.inputTableRow(1) - .should('have.text', '6666') - .invoke('attr', 'data-test-id') - .should('equal', 'hovering-item'); + ndv.getters + .inputTableRow(1) + .should('have.text', '6666') + .invoke('attr', 'data-test-id') + .should('equal', 'hovering-item'); ndv.getters.outputHoveringItem().should('not.exist'); ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); ndv.actions.selectInputNode('When clicking'); ndv.getters.inputTableRow(1).realHover(); - ndv.getters.inputTableRow(1).should('have.text', "This is an item, but it's empty.").realHover(); + ndv.getters + .inputTableRow(1) + .should('have.text', "This is an item, but it's empty.") + .realHover(); ndv.getters.outputHoveringItem().should('have.length', 6); ndv.getters.parameterExpressionPreview('value').should('include.text', '1000'); }); @@ -256,18 +240,16 @@ describe('NDV', () => { ndv.actions.switchOutputMode('Table'); ndv.actions.switchOutputBranch('False Branch (2 items)'); - ndv.getters.outputTableRow(1) - .should('have.text', '8888') - .realHover(); - ndv.getters.inputTableRow(5) + ndv.getters.outputTableRow(1).should('have.text', '8888').realHover(); + ndv.getters + .inputTableRow(5) .should('have.text', '8888') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); - ndv.getters.outputTableRow(2) - .should('have.text', '9999') - .realHover(); - ndv.getters.inputTableRow(6) + ndv.getters.outputTableRow(2).should('have.text', '9999').realHover(); + ndv.getters + .inputTableRow(6) .should('have.text', '9999') .invoke('attr', 'data-test-id') .should('equal', 'hovering-item'); @@ -277,31 +259,21 @@ describe('NDV', () => { workflowPage.actions.openNode('Set5'); ndv.actions.switchInputBranch('True Branch'); - ndv.actions.changeOutputRunSelector('1 of 2 (2 items)') - ndv.getters.outputTableRow(1) - .should('have.text', '8888') - .realHover(); + ndv.actions.changeOutputRunSelector('1 of 2 (2 items)'); + ndv.getters.outputTableRow(1).should('have.text', '8888').realHover(); ndv.getters.inputHoveringItem().should('not.exist'); - ndv.getters.inputTableRow(1) - .should('have.text', '1111') - .realHover(); + ndv.getters.inputTableRow(1).should('have.text', '1111').realHover(); ndv.getters.outputHoveringItem().should('not.exist'); ndv.actions.switchInputBranch('False Branch'); - ndv.getters.inputTableRow(1) - .should('have.text', '8888') - .realHover(); + ndv.getters.inputTableRow(1).should('have.text', '8888').realHover(); - ndv.actions.changeOutputRunSelector('2 of 2 (4 items)') - ndv.getters.outputTableRow(1) - .should('have.text', '1111') - .realHover(); + ndv.actions.changeOutputRunSelector('2 of 2 (4 items)'); + ndv.getters.outputTableRow(1).should('have.text', '1111').realHover(); - ndv.actions.changeOutputRunSelector('1 of 2 (2 items)') - ndv.getters.inputTableRow(1) - .should('have.text', '8888') - .realHover(); + ndv.actions.changeOutputRunSelector('1 of 2 (2 items)'); + ndv.getters.inputTableRow(1).should('have.text', '8888').realHover(); ndv.getters.outputHoveringItem().should('have.text', '8888'); // todo there's a bug here need to fix ADO-534 // ndv.getters.outputHoveringItem().should('not.exist'); diff --git a/cypress/e2e/25-stickies.cy.ts b/cypress/e2e/25-stickies.cy.ts index afb2db3b5f..d0e219575f 100644 --- a/cypress/e2e/25-stickies.cy.ts +++ b/cypress/e2e/25-stickies.cy.ts @@ -2,7 +2,13 @@ import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; const workflowPage = new WorkflowPageClass(); -function checkStickiesStyle( top: number, left: number, height: number, width: number, zIndex?: number) { +function checkStickiesStyle( + top: number, + left: number, + height: number, + width: number, + zIndex?: number, +) { workflowPage.getters.stickies().should(($el) => { expect($el).to.have.css('top', `${top}px`); expect($el).to.have.css('left', `${left}px`); @@ -18,22 +24,23 @@ describe('Canvas Actions', () => { beforeEach(() => { workflowPage.actions.visit(); - cy.window().then( - (win) => { - // @ts-ignore - win.preventNodeViewBeforeUnload = true; - }, - ); + cy.window().then((win) => { + // @ts-ignore + win.preventNodeViewBeforeUnload = true; + }); }); - it('adds sticky to canvas with default text and position', () => { workflowPage.getters.addStickyButton().should('not.be.visible'); - addDefaultSticky() - workflowPage.getters.stickies().eq(0) + addDefaultSticky(); + workflowPage.getters + .stickies() + .eq(0) .should('have.text', 'I’m a note\nDouble click to edit me. Guide\n') - .find('a').contains('Guide').should('have.attr', 'href'); + .find('a') + .contains('Guide') + .should('have.attr', 'href'); }); it('drags sticky around to top left corner', () => { @@ -57,18 +64,19 @@ describe('Canvas Actions', () => { it('deletes sticky', () => { workflowPage.actions.addSticky(); - workflowPage.getters.stickies().should('have.length', 1) + workflowPage.getters.stickies().should('have.length', 1); workflowPage.actions.deleteSticky(); - workflowPage.getters.stickies().should('have.length', 0) + workflowPage.getters.stickies().should('have.length', 0); }); it('edits sticky and updates content as markdown', () => { workflowPage.actions.addSticky(); - workflowPage.getters.stickies() - .should('have.text', 'I’m a note\nDouble click to edit me. Guide\n') + workflowPage.getters + .stickies() + .should('have.text', 'I’m a note\nDouble click to edit me. Guide\n'); workflowPage.getters.stickies().dblclick(); workflowPage.actions.editSticky('# hello world \n ## text text'); @@ -159,32 +167,41 @@ describe('Canvas Actions', () => { cy.drag('[data-test-id="sticky"] [data-dir="topLeft"]', [-150, -150]); checkStickiesStyle(124, 256, 316, 384, -121); - workflowPage.getters.canvasNodes().eq(0) + workflowPage.getters + .canvasNodes() + .eq(0) .should(($el) => { expect($el).to.have.css('z-index', 'auto'); }); workflowPage.actions.addSticky(); - workflowPage.getters.stickies().eq(0) + workflowPage.getters + .stickies() + .eq(0) .should(($el) => { expect($el).to.have.css('z-index', '-121'); }); - workflowPage.getters.stickies().eq(1) + workflowPage.getters + .stickies() + .eq(1) .should(($el) => { expect($el).to.have.css('z-index', '-38'); }); cy.drag('[data-test-id="sticky"] [data-dir="topLeft"]', [-200, -200], { index: 1 }); - workflowPage.getters.stickies().eq(0) + workflowPage.getters + .stickies() + .eq(0) .should(($el) => { expect($el).to.have.css('z-index', '-121'); }); - workflowPage.getters.stickies().eq(1) + workflowPage.getters + .stickies() + .eq(1) .should(($el) => { expect($el).to.have.css('z-index', '-158'); }); - }); }); @@ -198,15 +215,20 @@ type BoundingBox = { width: number; top: number; left: number; -} +}; function dragRightEdge(curr: BoundingBox, move: number) { - workflowPage.getters.stickies().first().then(($el) => { - const { left, top, height, width } = curr; - cy.drag(`[data-test-id="sticky"] [data-dir="right"]`, [left + width + move, 0], { abs: true }); - stickyShouldBePositionedCorrectly({ top, left }); - stickyShouldHaveCorrectSize([height, width * 1.5 + move]); - }); + workflowPage.getters + .stickies() + .first() + .then(($el) => { + const { left, top, height, width } = curr; + cy.drag(`[data-test-id="sticky"] [data-dir="right"]`, [left + width + move, 0], { + abs: true, + }); + stickyShouldBePositionedCorrectly({ top, left }); + stickyShouldHaveCorrectSize([height, width * 1.5 + move]); + }); } function shouldHaveOneSticky() { @@ -214,17 +236,20 @@ function shouldHaveOneSticky() { } function shouldBeInDefaultLocation() { - workflowPage.getters.stickies().eq(0).should(($el) => { - expect($el).to.have.css('height', '160px'); - expect($el).to.have.css('width', '240px'); - }) + workflowPage.getters + .stickies() + .eq(0) + .should(($el) => { + expect($el).to.have.css('height', '160px'); + expect($el).to.have.css('width', '240px'); + }); } function shouldHaveDefaultSize() { workflowPage.getters.stickies().should(($el) => { expect($el).to.have.css('height', '160px'); expect($el).to.have.css('width', '240px'); - }) + }); } function addDefaultSticky() { @@ -237,21 +262,19 @@ function addDefaultSticky() { function stickyShouldBePositionedCorrectly(position: Position) { const yOffset = -100; const xOffset = -180; - workflowPage.getters.stickies() - .should(($el) => { - expect($el).to.have.css('top', `${yOffset + position.top}px`); - expect($el).to.have.css('left', `${xOffset + position.left}px`); - }); + workflowPage.getters.stickies().should(($el) => { + expect($el).to.have.css('top', `${yOffset + position.top}px`); + expect($el).to.have.css('left', `${xOffset + position.left}px`); + }); } function stickyShouldHaveCorrectSize(size: [number, number]) { const yOffset = 0; const xOffset = 0; - workflowPage.getters.stickies() - .should(($el) => { - expect($el).to.have.css('height', `${yOffset + size[0]}px`); - expect($el).to.have.css('width', `${xOffset + size[1]}px`); - }); + workflowPage.getters.stickies().should(($el) => { + expect($el).to.have.css('height', `${yOffset + size[0]}px`); + expect($el).to.have.css('width', `${xOffset + size[1]}px`); + }); } function moveSticky(target: Position) { diff --git a/cypress/e2e/26-resource-locator.cy.ts b/cypress/e2e/26-resource-locator.cy.ts index cedcfd628e..5231d3fe29 100644 --- a/cypress/e2e/26-resource-locator.cy.ts +++ b/cypress/e2e/26-resource-locator.cy.ts @@ -1,4 +1,5 @@ import { WorkflowPage, NDV, CredentialsModal } from '../pages'; +import { getVisibleSelect } from '../utils'; const workflowPage = new WorkflowPage(); const ndv = new NDV(); @@ -32,7 +33,7 @@ describe('Resource Locator', () => { workflowPage.actions.addNodeToCanvas('Google Sheets', true, true); workflowPage.getters.nodeCredentialsSelect().click(); // Add oAuth credentials - workflowPage.getters.nodeCredentialsSelect().find('li').last().click(); + getVisibleSelect().find('li').last().click(); credentialsModal.getters.credentialsEditModal().should('be.visible'); credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2); credentialsModal.getters.credentialAuthTypeRadioButtons().first().click(); diff --git a/cypress/e2e/4-node-creator.cy.ts b/cypress/e2e/4-node-creator.cy.ts index fb0887a683..fdd4e0e7d2 100644 --- a/cypress/e2e/4-node-creator.cy.ts +++ b/cypress/e2e/4-node-creator.cy.ts @@ -1,6 +1,7 @@ import { NodeCreator } from '../pages/features/node-creator'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; import { NDV } from '../pages/ndv'; +import { getVisibleSelect } from '../utils'; const nodeCreatorFeature = new NodeCreator(); const WorkflowPage = new WorkflowPageClass(); @@ -85,7 +86,7 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.getCreatorItem(editImageNode).click(); nodeCreatorFeature.getters.activeSubcategory().should('have.text', editImageNode); nodeCreatorFeature.getters.getCreatorItem('Crop Image').click(); - NDVModal.getters.parameterInput('operation').should('contain.text', 'Crop'); + NDVModal.getters.parameterInput('operation').find('input').should('have.value', 'Crop'); }); it('should search through actions and confirm added action', () => { @@ -95,9 +96,9 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.activeSubcategory().should('have.text', 'FTP'); nodeCreatorFeature.getters.searchBar().find('input').clear().type('file'); // Navigate to rename action which should be the 4th item - nodeCreatorFeature.getters.searchBar().find('input').type('{uparrow}{uparrow}{rightarrow}'); - NDVModal.getters.parameterInput('operation').should('contain.text', 'Rename'); - }) + nodeCreatorFeature.getters.searchBar().find('input').type('{uparrow}{rightarrow}'); + NDVModal.getters.parameterInput('operation').find('input').should('have.value', 'Rename'); + }); it('should not show actions for single action nodes', () => { const singleActionNodes = [ @@ -110,19 +111,22 @@ describe('Node Creator', () => { 'Spontit', 'Vonage', 'Send Email', - 'Toggl Trigger' - ] - const doubleActionNode = 'OpenWeatherMap' + 'Toggl Trigger', + ]; + const doubleActionNode = 'OpenWeatherMap'; nodeCreatorFeature.actions.openNodeCreator(); singleActionNodes.forEach((node) => { nodeCreatorFeature.getters.searchBar().find('input').clear().type(node); - nodeCreatorFeature.getters.getCreatorItem(node).find('button[class*="panelIcon"]').should('not.exist'); - }) + nodeCreatorFeature.getters + .getCreatorItem(node) + .find('button[class*="panelIcon"]') + .should('not.exist'); + }); nodeCreatorFeature.getters.searchBar().find('input').clear().type(doubleActionNode); nodeCreatorFeature.getters.getCreatorItem(doubleActionNode).click(); nodeCreatorFeature.getters.creatorItem().should('have.length', 4); - }) + }); it('should have "Actions" section collapsed when opening actions view from Trigger root view', () => { nodeCreatorFeature.actions.openNodeCreator(); @@ -131,10 +135,19 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.getCategoryItem('Actions').should('exist'); nodeCreatorFeature.getters.getCategoryItem('Triggers').should('exist'); - nodeCreatorFeature.getters.getCategoryItem('Triggers').parent().should('not.have.attr', 'data-category-collapsed'); - nodeCreatorFeature.getters.getCategoryItem('Actions').parent().should('have.attr', 'data-category-collapsed', 'true'); - nodeCreatorFeature.getters.getCategoryItem('Actions').click() - nodeCreatorFeature.getters.getCategoryItem('Actions').parent().should('not.have.attr', 'data-category-collapsed'); + nodeCreatorFeature.getters + .getCategoryItem('Triggers') + .parent() + .should('have.attr', 'data-category-collapsed', 'false'); + nodeCreatorFeature.getters + .getCategoryItem('Actions') + .parent() + .should('have.attr', 'data-category-collapsed', 'true'); + nodeCreatorFeature.getters.getCategoryItem('Actions').click(); + nodeCreatorFeature.getters + .getCategoryItem('Actions') + .parent() + .should('have.attr', 'data-category-collapsed', 'false'); }); it('should have "Triggers" section collapsed when opening actions view from Regular root view', () => { @@ -145,17 +158,33 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n'); nodeCreatorFeature.getters.getCreatorItem('n8n').click(); - nodeCreatorFeature.getters.getCategoryItem('Actions').parent().should('not.have.attr', 'data-category-collapsed'); - nodeCreatorFeature.getters.getCategoryItem('Actions').click() - nodeCreatorFeature.getters.getCategoryItem('Actions').parent().should('have.attr', 'data-category-collapsed'); - nodeCreatorFeature.getters.getCategoryItem('Triggers').parent().should('have.attr', 'data-category-collapsed'); - nodeCreatorFeature.getters.getCategoryItem('Triggers').click() - nodeCreatorFeature.getters.getCategoryItem('Triggers').parent().should('not.have.attr', 'data-category-collapsed'); + nodeCreatorFeature.getters + .getCategoryItem('Actions') + .parent() + .should('have.attr', 'data-category-collapsed', 'false'); + nodeCreatorFeature.getters.getCategoryItem('Actions').click(); + nodeCreatorFeature.getters + .getCategoryItem('Actions') + .parent() + .should('have.attr', 'data-category-collapsed', 'true'); + nodeCreatorFeature.getters + .getCategoryItem('Triggers') + .parent() + .should('have.attr', 'data-category-collapsed', 'true'); + nodeCreatorFeature.getters.getCategoryItem('Triggers').click(); + nodeCreatorFeature.getters + .getCategoryItem('Triggers') + .parent() + .should('have.attr', 'data-category-collapsed', 'false'); }); it('should show callout and two suggested nodes if node has no trigger actions', () => { nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.getters.searchBar().find('input').clear().type('Customer Datastore (n8n training)'); + nodeCreatorFeature.getters + .searchBar() + .find('input') + .clear() + .type('Customer Datastore (n8n training)'); nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click(); cy.getByTestId('actions-panel-no-triggers-callout').should('be.visible'); @@ -165,28 +194,32 @@ describe('Node Creator', () => { it('should show intro callout if user has not made a production execution', () => { nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.getters.searchBar().find('input').clear().type('Customer Datastore (n8n training)'); + nodeCreatorFeature.getters + .searchBar() + .find('input') + .clear() + .type('Customer Datastore (n8n training)'); nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click(); cy.getByTestId('actions-panel-activation-callout').should('be.visible'); nodeCreatorFeature.getters.activeSubcategory().find('button').click(); - nodeCreatorFeature.getters.searchBar().find('input').clear() + nodeCreatorFeature.getters.searchBar().find('input').clear(); nodeCreatorFeature.getters.getCreatorItem('On a schedule').click(); // Setup 1s interval execution cy.getByTestId('parameter-input-field').click(); - cy.getByTestId('parameter-input-field') - .find('.el-select-dropdown') - .find('.option-headline') - .contains('Seconds') - .click(); + getVisibleSelect().find('.option-headline').contains('Seconds').click(); cy.getByTestId('parameter-input-secondsInterval').clear().type('1'); NDVModal.actions.close(); nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.getters.searchBar().find('input').clear().type('Customer Datastore (n8n training)'); + nodeCreatorFeature.getters + .searchBar() + .find('input') + .clear() + .type('Customer Datastore (n8n training)'); nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click(); nodeCreatorFeature.getters.getCreatorItem('Get All People').click(); NDVModal.actions.close(); @@ -197,11 +230,15 @@ describe('Node Creator', () => { // Wait for schedule 1s execution to mark user as having made a production execution cy.wait(1500); - cy.reload() + cy.reload(); // Action callout should not be visible after user has made a production execution nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.getters.searchBar().find('input').clear().type('Customer Datastore (n8n training)'); + nodeCreatorFeature.getters + .searchBar() + .find('input') + .clear() + .type('Customer Datastore (n8n training)'); nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click(); cy.getByTestId('actions-panel-activation-callout').should('not.exist'); @@ -210,7 +247,11 @@ describe('Node Creator', () => { it('should show Trigger and Actions sections during search', () => { nodeCreatorFeature.actions.openNodeCreator(); - nodeCreatorFeature.getters.searchBar().find('input').clear().type('Customer Datastore (n8n training)'); + nodeCreatorFeature.getters + .searchBar() + .find('input') + .clear() + .type('Customer Datastore (n8n training)'); nodeCreatorFeature.getters.getCreatorItem('Customer Datastore (n8n training)').click(); nodeCreatorFeature.getters.searchBar().find('input').clear().type('Non existent action name'); @@ -228,7 +269,8 @@ describe('Node Creator', () => { { name: 'canvas add button', handler: () => nodeCreatorFeature.getters.canvasAddButton().click(), - }, { + }, + { name: 'plus button', handler: () => nodeCreatorFeature.getters.plusButton().click(), }, @@ -238,10 +280,10 @@ describe('Node Creator', () => { // name: 'tab key', // handler: () => cy.realPress('Tab'), // }, - ] + ]; sourcesWithAppend.forEach((source) => { it(`should append manual trigger when source is ${source.name}`, () => { - source.handler() + source.handler(); nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n'); nodeCreatorFeature.getters.getCreatorItem('n8n').click(); nodeCreatorFeature.getters.getCategoryItem('Actions').click(); @@ -251,6 +293,7 @@ describe('Node Creator', () => { }); }); + // @TODO FIX ADDING 2 NODES IN ONE GO it('should not append manual trigger when source is canvas related', () => { nodeCreatorFeature.getters.canvasAddButton().click(); nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n'); @@ -258,8 +301,8 @@ describe('Node Creator', () => { nodeCreatorFeature.getters.getCategoryItem('Actions').click(); nodeCreatorFeature.getters.getCreatorItem('Create a credential').click(); NDVModal.actions.close(); - WorkflowPage.actions.deleteNode('When clicking "Execute Workflow"') - WorkflowPage.getters.canvasNodePlusEndpointByName('n8n').click() + WorkflowPage.actions.deleteNode('When clicking "Execute Workflow"'); + WorkflowPage.getters.canvasNodePlusEndpointByName('n8n').click(); nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n'); nodeCreatorFeature.getters.getCreatorItem('n8n').click(); nodeCreatorFeature.getters.getCategoryItem('Actions').click(); @@ -267,8 +310,8 @@ describe('Node Creator', () => { NDVModal.actions.close(); WorkflowPage.getters.canvasNodes().should('have.length', 2); WorkflowPage.actions.zoomToFit(); - WorkflowPage.actions.addNodeBetweenNodes('n8n', 'n8n1', 'Item Lists', 'Summarize') + WorkflowPage.actions.addNodeBetweenNodes('n8n', 'n8n1', 'Item Lists', 'Summarize'); WorkflowPage.getters.canvasNodes().should('have.length', 3); - }) + }); }); }); diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts index 6e3f38ca5b..67010b4a13 100644 --- a/cypress/e2e/5-ndv.cy.ts +++ b/cypress/e2e/5-ndv.cy.ts @@ -117,7 +117,7 @@ describe('NDV', () => { setupSchemaWorkflow(); ndv.getters.outputDisplayMode().children().should('have.length', 3); ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Table'); - ndv.getters.outputDisplayMode().contains('Schema').click(); + ndv.actions.switchOutputMode('Schema'); ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema'); schemaKeys.forEach((key) => { @@ -130,7 +130,7 @@ describe('NDV', () => { }); it('should preserve schema view after execution', () => { setupSchemaWorkflow(); - ndv.getters.outputDisplayMode().contains('Schema').click(); + ndv.actions.switchOutputMode('Schema'); ndv.actions.execute(); ndv.getters.outputDisplayMode().find('[class*=active]').should('contain', 'Schema'); }); @@ -142,7 +142,7 @@ describe('NDV', () => { .outputPanel() .find('[data-test-id=run-data-schema-item]') .filter(':contains("objectValue")'); - ndv.getters.outputDisplayMode().contains('Schema').click(); + ndv.actions.switchOutputMode('Schema'); expandedObjectProps.forEach((key) => { ndv.getters @@ -173,9 +173,9 @@ describe('NDV', () => { ndv.actions.execute(); ndv.getters.outputPanel().contains('25 items').should('exist'); ndv.getters.outputPanel().find('[class*=_pagination]').should('exist'); - ndv.getters.outputDisplayMode().contains('Schema').click(); + ndv.actions.switchOutputMode('Schema'); ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist'); - ndv.getters.outputDisplayMode().contains('JSON').click(); + ndv.actions.switchOutputMode('JSON'); ndv.getters.outputPanel().find('[class*=_pagination]').should('exist'); }); it('should display large schema', () => { @@ -188,7 +188,7 @@ describe('NDV', () => { ndv.getters.outputPanel().contains('20 items').should('exist'); ndv.getters.outputPanel().find('[class*=_pagination]').should('exist'); - ndv.getters.outputDisplayMode().contains('Schema').click(); + ndv.actions.switchOutputMode('Schema'); ndv.getters.outputPanel().find('[class*=_pagination]').should('not.exist'); ndv.getters .outputPanel() diff --git a/cypress/e2e/7-workflow-actions.cy.ts b/cypress/e2e/7-workflow-actions.cy.ts index 7f9ee21c7c..fbba00e27b 100644 --- a/cypress/e2e/7-workflow-actions.cy.ts +++ b/cypress/e2e/7-workflow-actions.cy.ts @@ -6,9 +6,11 @@ import { } from '../constants'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows'; +import { getVisibleDropdown, getVisibleSelect } from '../utils'; const NEW_WORKFLOW_NAME = 'Something else'; -const IMPORT_WORKFLOW_URL = 'https://gist.githubusercontent.com/OlegIvaniv/010bd3f45c8a94f8eb7012e663a8b671/raw/3afea1aec15573cc168d9af7e79395bd76082906/test-workflow.json'; +const IMPORT_WORKFLOW_URL = + 'https://gist.githubusercontent.com/OlegIvaniv/010bd3f45c8a94f8eb7012e663a8b671/raw/3afea1aec15573cc168d9af7e79395bd76082906/test-workflow.json'; const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow'; const DUPLICATE_WORKFLOW_TAG = 'Duplicate'; @@ -67,11 +69,11 @@ describe('Workflow Actions', () => { it('should not save workflow if canvas is loading', () => { let interceptCalledCount = 0; - // There's no way in Cypress to check if intercept was not called + // There's no way in Cypress to check if intercept was not called // so we'll count the number of times it was called - cy.intercept('PATCH', '/rest/workflows/*', () => { - interceptCalledCount++; - }).as('saveWorkflow'); + cy.intercept('PATCH', '/rest/workflows/*', () => { + interceptCalledCount++; + }).as('saveWorkflow'); WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.actions.saveWorkflowOnButtonClick(); @@ -84,11 +86,11 @@ describe('Workflow Actions', () => { (req) => { // Delay the response to give time for the save to be triggered req.on('response', async (res) => { - await new Promise((resolve) => setTimeout(resolve, 2000)) + await new Promise((resolve) => setTimeout(resolve, 2000)); res.send(); - }) - } - ) + }); + }, + ); cy.reload(); cy.get('.el-loading-mask').should('exist'); cy.get('body').type(META_KEY, { release: false }).type('s'); @@ -99,7 +101,7 @@ describe('Workflow Actions', () => { cy.get('body').type(META_KEY, { release: false }).type('s'); cy.wait('@saveWorkflow'); cy.wrap(null).then(() => expect(interceptCalledCount).to.eq(1)); - }) + }); it('should copy nodes', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); @@ -127,7 +129,7 @@ describe('Workflow Actions', () => { cy.get('.el-message-box').should('be.visible'); cy.get('.el-message-box').find('input').type(IMPORT_WORKFLOW_URL); cy.get('body').type('{enter}'); - cy.waitForLoad(false) + cy.waitForLoad(false); WorkflowPage.actions.zoomToFit(); WorkflowPage.getters.canvasNodes().should('have.length', 2); WorkflowPage.getters.nodeConnections().should('have.length', 1); @@ -137,7 +139,7 @@ describe('Workflow Actions', () => { WorkflowPage.getters .workflowImportInput() .selectFile('cypress/fixtures/Test_workflow-actions_paste-data.json', { force: true }); - cy.waitForLoad(false) + cy.waitForLoad(false); WorkflowPage.actions.zoomToFit(); WorkflowPage.getters.canvasNodes().should('have.length', 2); WorkflowPage.getters.nodeConnections().should('have.length', 1); @@ -157,57 +159,33 @@ describe('Workflow Actions', () => { WorkflowPage.getters.workflowMenuItemSettings().click(); // Change all settings // totalWorkflows + 1 (current workflow) + 1 (no workflow option) - WorkflowPage.getters.workflowSettingsErrorWorkflowSelect().find('li').should('have.length', totalWorkflows + 2); - WorkflowPage.getters - .workflowSettingsErrorWorkflowSelect() + WorkflowPage.getters.workflowSettingsErrorWorkflowSelect().click(); + getVisibleSelect() .find('li') - .last() - .click({ force: true }); - WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').should('exist'); - WorkflowPage.getters.workflowSettingsTimezoneSelect().find('li').eq(1).click({ force: true }); - WorkflowPage.getters - .workflowSettingsSaveFiledExecutionsSelect() - .find('li') - .should('have.length', 3); - WorkflowPage.getters - .workflowSettingsSaveFiledExecutionsSelect() - .find('li') - .last() - .click({ force: true }); - WorkflowPage.getters - .workflowSettingsSaveSuccessExecutionsSelect() - .find('li') - .should('have.length', 3); - WorkflowPage.getters - .workflowSettingsSaveSuccessExecutionsSelect() - .find('li') - .last() - .click({ force: true }); - WorkflowPage.getters - .workflowSettingsSaveManualExecutionsSelect() - .find('li') - .should('have.length', 3); - WorkflowPage.getters - .workflowSettingsSaveManualExecutionsSelect() - .find('li') - .last() - .click({ force: true }); - WorkflowPage.getters - .workflowSettingsSaveExecutionProgressSelect() - .find('li') - .should('have.length', 3); - WorkflowPage.getters - .workflowSettingsSaveExecutionProgressSelect() - .find('li') - .last() - .click({ force: true }); + .should('have.length', totalWorkflows + 2); + getVisibleSelect().find('li').last().click({ force: true }); + WorkflowPage.getters.workflowSettingsTimezoneSelect().click(); + getVisibleSelect().find('li').should('exist'); + getVisibleSelect().find('li').eq(1).click({ force: true }); + WorkflowPage.getters.workflowSettingsSaveFiledExecutionsSelect().click(); + getVisibleSelect().find('li').should('have.length', 3); + getVisibleSelect().find('li').last().click({ force: true }); + WorkflowPage.getters.workflowSettingsSaveSuccessExecutionsSelect().click(); + getVisibleSelect().find('li').should('have.length', 3); + getVisibleSelect().find('li').last().click({ force: true }); + WorkflowPage.getters.workflowSettingsSaveManualExecutionsSelect().click(); + getVisibleSelect().find('li').should('have.length', 3); + getVisibleSelect().find('li').last().click({ force: true }); + WorkflowPage.getters.workflowSettingsSaveExecutionProgressSelect().click(); + getVisibleSelect().find('li').should('have.length', 3); + getVisibleSelect().find('li').last().click({ force: true }); WorkflowPage.getters.workflowSettingsTimeoutWorkflowSwitch().click(); WorkflowPage.getters.workflowSettingsTimeoutForm().find('input').first().type('1'); // Save settings WorkflowPage.getters.workflowSettingsSaveButton().click(); WorkflowPage.getters.workflowSettingsModal().should('not.exist'); WorkflowPage.getters.successToast().should('exist'); - }) + }); }); it('should not be able to delete unsaved workflow', () => { @@ -245,7 +223,7 @@ describe('Workflow Actions', () => { .find('.el-select__tags input') .type(DUPLICATE_WORKFLOW_TAG); WorkflowPage.getters.duplicateWorkflowModal().find('.el-select__tags input').type('{enter}'); - WorkflowPage.getters.duplicateWorkflowModal().find('.el-select__tags input').type('{enter}'); + WorkflowPage.getters.duplicateWorkflowModal().find('.el-select__tags input').type('{esc}'); WorkflowPage.getters .duplicateWorkflowModal() .find('button') diff --git a/cypress/pages/credentials.ts b/cypress/pages/credentials.ts index 7d3bf7ac98..733c030caa 100644 --- a/cypress/pages/credentials.ts +++ b/cypress/pages/credentials.ts @@ -5,7 +5,7 @@ export class CredentialsPage extends BasePage { getters = { emptyListCreateCredentialButton: () => cy.getByTestId('empty-resources-list').find('button'), createCredentialButton: () => cy.getByTestId('resources-list-add'), - searchInput: () => cy.getByTestId('resources-list-search').find('input'), + searchInput: () => cy.getByTestId('resources-list-search'), emptyList: () => cy.getByTestId('resources-list-empty'), credentialCards: () => cy.getByTestId('resources-list-item'), credentialCard: (credentialName: string) => @@ -17,8 +17,8 @@ export class CredentialsPage extends BasePage { this.getters.credentialCard(credentialName).findChildByTestId('credential-card-actions'), credentialDeleteButton: () => cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Delete'), - sort: () => cy.getByTestId('resources-list-sort'), - sortOption: (label: string) => this.getters.sort().contains(label).first(), + sort: () => cy.getByTestId('resources-list-sort').first(), + sortOption: (label: string) => cy.getByTestId('resources-list-sort-item').contains(label).first(), filtersTrigger: () => cy.getByTestId('resources-list-filters-trigger'), filtersDropdown: () => cy.getByTestId('resources-list-filters-dropdown'), }; diff --git a/cypress/pages/modals/credentials-modal.ts b/cypress/pages/modals/credentials-modal.ts index 99d712e7e3..312e9edbf5 100644 --- a/cypress/pages/modals/credentials-modal.ts +++ b/cypress/pages/modals/credentials-modal.ts @@ -20,7 +20,7 @@ export class CredentialsModal extends BasePage { credentialsEditModal: () => cy.getByTestId('credential-edit-dialog'), credentialsAuthTypeSelector: () => cy.getByTestId('node-auth-type-selector'), credentialAuthTypeRadioButtons: () => - this.getters.credentialsAuthTypeSelector().find('label[role=radio]'), + this.getters.credentialsAuthTypeSelector().find('label.el-radio'), credentialInputs: () => cy.getByTestId('credential-connection-parameter'), menu: () => this.getters.editCredentialModal().get('.menu-container'), menuItem: (name: string) => this.getters.menu().get('.n8n-menu-item').contains(name), @@ -42,7 +42,7 @@ export class CredentialsModal extends BasePage { }, save: (test = false) => { cy.intercept('POST', '/rest/credentials').as('saveCredential'); - this.getters.saveButton().click(); + this.getters.saveButton().click({ force: true }); cy.wait('@saveCredential'); if (test) cy.wait('@testCredential'); diff --git a/cypress/pages/modals/message-box.ts b/cypress/pages/modals/message-box.ts index cfa38368b8..95f259ccb4 100644 --- a/cypress/pages/modals/message-box.ts +++ b/cypress/pages/modals/message-box.ts @@ -5,15 +5,15 @@ export class MessageBox extends BasePage { modal: () => cy.get('.el-message-box', { withinSubject: null }), header: () => this.getters.modal().find('.el-message-box__title'), content: () => this.getters.modal().find('.el-message-box__content'), - confirm: () => this.getters.modal().find('.btn--confirm'), - cancel: () => this.getters.modal().find('.btn--cancel'), + confirm: () => this.getters.modal().find('.btn--confirm').first(), + cancel: () => this.getters.modal().find('.btn--cancel').first(), }; actions = { confirm: () => { - this.getters.confirm().click(); + this.getters.confirm().click({ force: true}); }, cancel: () => { - this.getters.cancel().click(); + this.getters.cancel().click({ force: true}); }, }; } diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index 3d580c1bf4..bde403a0fe 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -1,4 +1,5 @@ import { BasePage } from './base'; +import { getVisibleSelect } from '../utils'; export class NDV extends BasePage { getters = { @@ -101,10 +102,11 @@ export class NDV extends BasePage { this.getters.parameterInput(parameterName).type(content); }, selectOptionInParameterDropdown: (parameterName: string, content: string) => { - this.getters.parameterInput(parameterName).find('.option-headline').contains(content).click(); + getVisibleSelect().find('.option-headline').contains(content).click(); }, dismissMappingTooltip: () => { cy.getByTestId('dismiss-mapping-tooltip').click(); + cy.getByTestId('dismiss-mapping-tooltip').should('not.be.visible'); }, rename: (newName: string) => { this.getters.nodeNameContainer().click(); @@ -139,11 +141,11 @@ export class NDV extends BasePage { }, changeInputRunSelector: (runName: string) => { this.getters.inputRunSelector().click(); - cy.get('.el-select-dropdown:visible .el-select-dropdown__item').contains(runName).click(); + getVisibleSelect().find('.el-select-dropdown__item').contains(runName).click(); }, changeOutputRunSelector: (runName: string) => { this.getters.outputRunSelector().click(); - cy.get('.el-select-dropdown:visible .el-select-dropdown__item').contains(runName).click(); + getVisibleSelect().find('.el-select-dropdown__item').contains(runName).click(); }, toggleOutputRunLinking: () => { this.getters.outputRunSelector().find('button').click(); @@ -159,7 +161,7 @@ export class NDV extends BasePage { }, setRLCValue: (paramName: string, value: string) => { this.getters.resourceLocatorModeSelector(paramName).click(); - this.getters.resourceLocatorModeSelector(paramName).find('li').last().click(); + getVisibleSelect().find('li').last().click(); this.getters.resourceLocatorInput(paramName).type(value); }, validateExpressionPreview: (paramName: string, value: string) => { diff --git a/cypress/pages/settings-log-streaming.ts b/cypress/pages/settings-log-streaming.ts index b95fcb38bf..2d056a4444 100644 --- a/cypress/pages/settings-log-streaming.ts +++ b/cypress/pages/settings-log-streaming.ts @@ -1,4 +1,5 @@ import { BasePage } from './base'; +import { getVisibleSelect } from '../utils'; export class SettingsLogStreamingPage extends BasePage { url = '/settings/log-streaming'; @@ -6,11 +7,9 @@ export class SettingsLogStreamingPage extends BasePage { getActionBoxUnlicensed: () => cy.getByTestId('action-box-unlicensed'), getActionBoxLicensed: () => cy.getByTestId('action-box-licensed'), getDestinationModal: () => cy.getByTestId('destination-modal'), - getDestinationModalDialog: () => this.getters.getDestinationModal().find('.el-dialog'), getSelectDestinationType: () => cy.getByTestId('select-destination-type'), getDestinationNameInput: () => cy.getByTestId('subtitle-showing-type'), - getSelectDestinationTypeItems: () => - this.getters.getSelectDestinationType().find('.el-select-dropdown__item'), + getSelectDestinationTypeItems: () => getVisibleSelect().find('.el-select-dropdown__item'), getSelectDestinationButton: () => cy.getByTestId('select-destination-button'), getContactUsButton: () => this.getters.getActionBoxUnlicensed().find('button'), getAddFirstDestinationButton: () => this.getters.getActionBoxLicensed().find('button'), diff --git a/cypress/pages/settings-personal.ts b/cypress/pages/settings-personal.ts index a454837011..0b129a29bc 100644 --- a/cypress/pages/settings-personal.ts +++ b/cypress/pages/settings-personal.ts @@ -11,7 +11,7 @@ export class PersonalSettingsPage extends BasePage { lastNameInput: () => cy.getByTestId('lastName').find('input').first(), emailInputContainer: () => cy.getByTestId('email'), emailInput: () => cy.getByTestId('email').find('input').first(), - changePasswordLink: () => cy.getByTestId('change-password-link').find('a').first(), + changePasswordLink: () => cy.getByTestId('change-password-link').first(), saveSettingsButton: () => cy.getByTestId('save-settings-button'), }; actions = { @@ -34,7 +34,10 @@ export class PersonalSettingsPage extends BasePage { }, tryToSetWeakPassword: (oldPassword: string, newPassword: string) => { this.actions.updatePassword(oldPassword, newPassword); - changePasswordModal.getters.newPasswordInputContainer().find('div[class^="_errorInput"]').should('exist'); + changePasswordModal.getters + .newPasswordInputContainer() + .find('div[class^="_errorInput"]') + .should('exist'); }, updateEmail: (newEmail: string) => { this.getters.emailInput().type('{selectall}').type(newEmail).type('{enter}'); diff --git a/cypress/pages/settings-users.ts b/cypress/pages/settings-users.ts index 41ae187114..d9a2e32df9 100644 --- a/cypress/pages/settings-users.ts +++ b/cypress/pages/settings-users.ts @@ -4,8 +4,8 @@ import { WorkflowPage } from './workflow'; import { WorkflowsPage } from './workflows'; import { BasePage } from './base'; -const workflowPage = new WorkflowPage(); -const workflowsPage = new WorkflowsPage(); +const workflowPage = new WorkflowPage(); +const workflowsPage = new WorkflowsPage(); const mainSidebar = new MainSidebar(); const settingsSidebar = new SettingsSidebar(); @@ -18,11 +18,15 @@ export class SettingsUsersPage extends BasePage { inviteUsersModalEmailsInput: () => cy.getByTestId('emails').find('input').first(), userListItems: () => cy.get('[data-test-id^="user-list-item"]'), userItem: (email: string) => cy.getByTestId(`user-list-item-${email.toLowerCase()}`), - userActionsToggle: (email: string) => this.getters.userItem(email).find('[data-test-id="action-toggle"]'), - deleteUserAction: () => cy.getByTestId('action-toggle-dropdown').find('li:contains("Delete"):visible'), + userActionsToggle: (email: string) => + this.getters.userItem(email).find('[data-test-id="action-toggle"]'), + deleteUserAction: () => + cy.getByTestId('action-toggle-dropdown').find('li:contains("Delete"):visible'), confirmDeleteModal: () => cy.getByTestId('deleteUser-modal').last(), - transferDataRadioButton: () => this.getters.confirmDeleteModal().find('[role="radio"]').first(), - deleteDataRadioButton: () => this.getters.confirmDeleteModal().find('[role="radio"]').last(), + transferDataRadioButton: () => + this.getters.confirmDeleteModal().find('.el-radio .el-radio__input').first(), + deleteDataRadioButton: () => + this.getters.confirmDeleteModal().find('.el-radio .el-radio__input').last(), userSelectDropDown: () => this.getters.confirmDeleteModal().find('.n8n-select'), userSelectOptions: () => cy.get('.el-select-dropdown:visible .el-select-dropdown__item'), deleteUserButton: () => this.getters.confirmDeleteModal().find('button:contains("Delete")'), diff --git a/cypress/pages/variables.ts b/cypress/pages/variables.ts index 721d874351..6091e5cf1b 100644 --- a/cypress/pages/variables.ts +++ b/cypress/pages/variables.ts @@ -10,7 +10,7 @@ export class VariablesPage extends BasePage { goToUpgrade: () => cy.getByTestId('go-to-upgrade'), actionBox: () => cy.getByTestId('action-box'), emptyResourcesListNewVariableButton: () => this.getters.emptyResourcesList().find('button'), - searchBar: () => cy.getByTestId('resources-list-search').find('input'), + searchBar: () => cy.getByTestId('resources-list-search'), createVariableButton: () => cy.getByTestId('resources-list-add'), variablesRows: () => cy.getByTestId('variables-row'), variablesEditableRows: () => diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index d324314dcc..72aca5977a 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -1,5 +1,6 @@ import { META_KEY } from '../constants'; import { BasePage } from './base'; +import { getVisibleSelect } from '../utils'; export class WorkflowPage extends BasePage { url = '/workflow/new'; @@ -16,7 +17,7 @@ export class WorkflowPage extends BasePage { nthTagPill: (n: number) => cy.get(`[data-test-id="workflow-tags-container"] span.tags > span:nth-child(${n})`), tagsDropdown: () => cy.getByTestId('workflow-tags-dropdown'), - tagsInDropdown: () => cy.getByTestId('workflow-tags-dropdown').find('li').filter('.tag'), + tagsInDropdown: () => getVisibleSelect().find('li').filter('.tag'), createTagButton: () => cy.getByTestId('new-tag-link'), saveButton: () => cy.getByTestId('workflow-save-button'), nodeCreatorSearchBar: () => cy.getByTestId('node-creator-search-bar'), @@ -37,8 +38,8 @@ export class WorkflowPage extends BasePage { canvasNodePlusEndpointByName: (nodeName: string, index = 0) => { return cy.get(this.getters.getEndpointSelector('plus', nodeName, index)); }, - successToast: () => cy.get('.el-notification .el-icon-success').parent(), - errorToast: () => cy.get('.el-notification .el-icon-error'), + successToast: () => cy.get('.el-notification .el-notification--success').parent(), + errorToast: () => cy.get('.el-notification .el-notification--error'), activatorSwitch: () => cy.getByTestId('workflow-activate-switch'), workflowMenu: () => cy.getByTestId('workflow-menu'), firstStepButton: () => cy.getByTestId('canvas-add-button'), @@ -84,7 +85,8 @@ export class WorkflowPage extends BasePage { duplicateWorkflowModal: () => cy.getByTestId('duplicate-modal'), nodeViewBackground: () => cy.getByTestId('node-view-background'), nodeView: () => cy.getByTestId('node-view'), - inlineExpressionEditorInput: () => cy.getByTestId('inline-expression-editor-input').find('[role=textbox]'), + inlineExpressionEditorInput: () => + cy.getByTestId('inline-expression-editor-input').find('[role=textbox]'), inlineExpressionEditorOutput: () => cy.getByTestId('inline-expression-editor-output'), zoomInButton: () => cy.getByTestId('zoom-in-button'), zoomOutButton: () => cy.getByTestId('zoom-out-button'), @@ -92,8 +94,10 @@ export class WorkflowPage extends BasePage { executeWorkflowButton: () => cy.getByTestId('execute-workflow-button'), clearExecutionDataButton: () => cy.getByTestId('clear-execution-data-button'), stopExecutionButton: () => cy.getByTestId('stop-execution-button'), - stopExecutionWaitingForWebhookButton: () => cy.getByTestId('stop-execution-waiting-for-webhook-button'), + stopExecutionWaitingForWebhookButton: () => + cy.getByTestId('stop-execution-waiting-for-webhook-button'), nodeCredentialsSelect: () => cy.getByTestId('node-credentials-select'), + nodeCredentialsCreateOption: () => cy.getByTestId('node-credentials-select-item-new'), nodeCredentialsEditButton: () => cy.getByTestId('credential-edit-button'), nodeCreatorItems: () => cy.getByTestId('item-iterator-item'), ndvParameters: () => cy.getByTestId('parameter-item'), @@ -134,17 +138,17 @@ export class WorkflowPage extends BasePage { this.getters.nodeCreatorSearchBar().type(nodeDisplayName); this.getters.nodeCreatorSearchBar().type('{enter}'); - cy.wait(500) + cy.wait(500); cy.get('body').then((body) => { - if(body.find('[data-test-id=node-creator]').length > 0) { - if(action) { - cy.contains(action).click() + if (body.find('[data-test-id=node-creator]').length > 0) { + if (action) { + cy.contains(action).click(); } else { // Select the first action - cy.get('[data-keyboard-nav-type="action"]').eq(0).click() + cy.get('[data-keyboard-nav-type="action"]').eq(0).click(); } } - }) + }); if (!preventNdvClose) cy.get('body').type('{esc}'); }, @@ -157,7 +161,8 @@ export class WorkflowPage extends BasePage { }, openTagManagerModal: () => { this.getters.createTagButton().click(); - this.getters.tagsDropdown().find('li.manage-tags').first().click(); + this.getters.tagsDropdown().click(); + getVisibleSelect().find('li.manage-tags').first().click(); }, openInlineExpressionEditor: () => { cy.contains('Expression').invoke('show').click(); @@ -209,7 +214,7 @@ export class WorkflowPage extends BasePage { this.getters.workflowTagsInput().type(tag); this.getters.workflowTagsInput().type('{enter}'); }); - cy.get('body').type('{enter}'); + cy.get('body').click(0, 0); // For a brief moment the Element UI tag component shows the tags as(+X) string // so we need to wait for it to disappear this.getters.workflowTagsContainer().should('not.contain', `+${tags.length}`); @@ -241,7 +246,12 @@ export class WorkflowPage extends BasePage { executeWorkflow: () => { this.getters.executeWorkflowButton().click(); }, - addNodeBetweenNodes: (sourceNodeName: string, targetNodeName: string, newNodeName: string, action?: string) => { + addNodeBetweenNodes: ( + sourceNodeName: string, + targetNodeName: string, + newNodeName: string, + action?: string, + ) => { this.getters.getConnectionBetweenNodes(sourceNodeName, targetNodeName).first().realHover(); this.getters .getConnectionActionsBetweenNodes(sourceNodeName, targetNodeName) @@ -268,18 +278,10 @@ export class WorkflowPage extends BasePage { this.getters.addStickyButton().click(); }, deleteSticky: () => { - this.getters.stickies().eq(0) - .realHover() - .find('[data-test-id="delete-sticky"]') - .click(); + this.getters.stickies().eq(0).realHover().find('[data-test-id="delete-sticky"]').click(); }, editSticky: (content: string) => { - this.getters.stickies() - .dblclick() - .find('textarea') - .clear() - .type(content) - .type('{esc}'); + this.getters.stickies().dblclick().find('textarea').clear().type(content).type('{esc}'); }, }; } diff --git a/cypress/pages/workflows.ts b/cypress/pages/workflows.ts index d5700d0784..416528e85c 100644 --- a/cypress/pages/workflows.ts +++ b/cypress/pages/workflows.ts @@ -5,7 +5,7 @@ export class WorkflowsPage extends BasePage { getters = { newWorkflowButtonCard: () => cy.getByTestId('new-workflow-card'), newWorkflowTemplateCard: () => cy.getByTestId('new-workflow-template-card'), - searchBar: () => cy.getByTestId('resources-list-search').find('input'), + searchBar: () => cy.getByTestId('resources-list-search'), createWorkflowButton: () => cy.getByTestId('resources-list-add'), workflowCards: () => cy.getByTestId('resources-list-item'), workflowCard: (workflowName: string) => diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 6c7adaea8f..1a1925b6a4 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -53,12 +53,12 @@ Cypress.Commands.add('signin', ({ email, password }) => { }); Cypress.Commands.add('signout', () => { - cy.request('POST', '/rest/logout'); + cy.request('POST', `${BACKEND_BASE_URL}/rest/logout`); cy.getCookie(N8N_AUTH_COOKIE).should('not.exist'); }); Cypress.Commands.add('interceptREST', (method, url) => { - cy.intercept(method, `http://localhost:5678/rest${url}`); + cy.intercept(method, `${BACKEND_BASE_URL}/rest${url}`); }); const setFeature = (feature: string, enabled: boolean) => diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index dc3079282f..a750918c6d 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -6,6 +6,10 @@ before(() => { owner: INSTANCE_OWNER, members: INSTANCE_MEMBERS, }); + + Cypress.on('uncaught:exception', (err) => { + return !err.message.includes('ResizeObserver'); + }); }); beforeEach(() => { diff --git a/cypress/utils/index.ts b/cypress/utils/index.ts new file mode 100644 index 0000000000..1929454b18 --- /dev/null +++ b/cypress/utils/index.ts @@ -0,0 +1 @@ +export * from './popper'; diff --git a/cypress/utils/modal.ts b/cypress/utils/modal.ts new file mode 100644 index 0000000000..4b260ca9e8 --- /dev/null +++ b/cypress/utils/modal.ts @@ -0,0 +1,3 @@ +export function getVisibleModalOverlay() { + return cy.get('.el-overlay .el-overlay-dialog').filter(':visible'); +} diff --git a/cypress/utils/popper.ts b/cypress/utils/popper.ts new file mode 100644 index 0000000000..846b2ec88e --- /dev/null +++ b/cypress/utils/popper.ts @@ -0,0 +1,11 @@ +export function getVisiblePopper() { + return cy.get('.el-popper').filter(':visible'); +} + +export function getVisibleSelect() { + return getVisiblePopper().filter('.el-select__popper'); +} + +export function getVisibleDropdown() { + return getVisiblePopper().filter('.el-dropdown__popper'); +} diff --git a/package.json b/package.json index 4784ccb846..015172f52a 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "@ngneat/falso": "^6.1.0", "@types/jest": "^29.5.0", "@types/supertest": "^2.0.12", - "@vitejs/plugin-vue2": "^2.2.0", "@vitest/coverage-c8": "^0.28.5", "c8": "^7.12.0", "cross-env": "^7.0.3", @@ -64,7 +63,6 @@ "typescript": "*", "vite": "^4.0.4", "vitest": "^0.28.5", - "vue-template-compiler": "^2.7.14", "vue-tsc": "^1.0.24" }, "pnpm": { @@ -92,7 +90,6 @@ "qqjs>globby": "^11.1.0" }, "patchedDependencies": { - "element-ui@2.15.12": "patches/element-ui@2.15.12.patch", "typedi@0.10.0": "patches/typedi@0.10.0.patch", "@sentry/cli@2.17.0": "patches/@sentry__cli@2.17.0.patch", "pkce-challenge@3.0.0": "patches/pkce-challenge@3.0.0.patch", diff --git a/packages/@n8n_io/eslint-config/frontend.js b/packages/@n8n_io/eslint-config/frontend.js index e36d4c33b7..a93c510e20 100644 --- a/packages/@n8n_io/eslint-config/frontend.js +++ b/packages/@n8n_io/eslint-config/frontend.js @@ -4,7 +4,7 @@ module.exports = { plugins: ['vue'], - extends: ['plugin:vue/essential', '@vue/typescript', './base'], + extends: ['plugin:vue/vue3-essential', '@vue/typescript', './base'], env: { browser: true, @@ -37,6 +37,12 @@ module.exports = { 'vue/no-unused-components': 'error', 'vue/multi-word-component-names': 'off', + // TODO: fix these + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/unbound-method': 'off', + // TODO: remove these 'vue/no-mutating-props': 'warn', 'vue/no-side-effects-in-computed-properties': 'warn', diff --git a/packages/design-system/.storybook/font-awesome-icons.js b/packages/design-system/.storybook/font-awesome-icons.js deleted file mode 100644 index 8713b3b661..0000000000 --- a/packages/design-system/.storybook/font-awesome-icons.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * These icons are only defined for storybook build - * Editor icons are defined seperately - */ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { fas } from '@fortawesome/free-solid-svg-icons'; - -library.add(fas); diff --git a/packages/design-system/.storybook/main.js b/packages/design-system/.storybook/main.js index d3a302a1b4..8db2096942 100644 --- a/packages/design-system/.storybook/main.js +++ b/packages/design-system/.storybook/main.js @@ -1,59 +1,42 @@ -const path = require('path'); +const { mergeConfig } = require('vite'); +const { resolve } = require('path'); -/** - * @type {import('@storybook/types').StorybookConfig} - */ module.exports = { - framework: { - name: '@storybook/vue-webpack5', - options: {}, - }, - stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.{ts,js}'], + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'], addons: [ + '@storybook/addon-styling', '@storybook/addon-links', '@storybook/addon-essentials', - { - name: '@storybook/addon-postcss', - options: { - postcssLoaderOptions: { - implementation: require('postcss'), - }, - }, - }, - 'storybook-addon-themes', + // Disabled until this is actually used rather otherwise its a blank tab + // '@storybook/addon-interactions', + '@storybook/addon-a11y', + 'storybook-dark-mode', ], - webpackFinal: async (config) => { - config.module.rules.push({ - test: /\.scss$/, - oneOf: [ - { - resourceQuery: /module/, - use: [ - 'vue-style-loader', - { - loader: 'css-loader', - options: { - modules: { - localIdentName: '[path][name]__[local]--[hash:base64:5]', - }, - }, - }, - 'sass-loader', - ], - include: path.resolve(__dirname, '../'), - }, - { - use: ['vue-style-loader', 'css-loader', 'sass-loader'], - include: path.resolve(__dirname, '../'), - }, - ], + staticDirs: ['../public'], + framework: { + name: '@storybook/vue3-vite', + options: {}, + }, + disableTelemetry: true, + async viteFinal(config, { configType }) { + // return the customized config + return mergeConfig(config, { + // customize the Vite config here + resolve: { + alias: [ + { + find: /^@n8n-design-system\//, + replacement: `${resolve(__dirname, '..')}/src/`, + }, + { + find: /^n8n-design-system$/, + replacement: `${resolve(__dirname, '..')}/src/main.ts`, + }, + ], + }, }); - - config.resolve.alias = { - ...config.resolve.alias, - '@/': path.resolve(__dirname, '../src/'), - }; - - return config; + }, + docs: { + autodocs: true, }, }; diff --git a/packages/design-system/.storybook/preview.js b/packages/design-system/.storybook/preview.js index 1e01ec1dd4..49708b358a 100644 --- a/packages/design-system/.storybook/preview.js +++ b/packages/design-system/.storybook/preview.js @@ -1,23 +1,24 @@ -import './font-awesome-icons'; +import { setup } from '@storybook/vue3'; + import './storybook.scss'; -import ElementUI from 'element-ui'; -import lang from 'element-ui/lib/locale/lang/en'; -import locale from 'element-ui/lib/locale'; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { fas } from '@fortawesome/free-solid-svg-icons'; + +import ElementPlus from 'element-plus'; +import lang from 'element-plus/lib/locale/lang/en'; import { N8nPlugin } from '../src/plugin'; -import Vue from 'vue'; +setup((app) => { + library.add(fas); -Vue.use(ElementUI); -Vue.use(N8nPlugin); + app.use(ElementPlus, { + locale: lang, + }); -locale.use(lang); - -// https://github.com/storybookjs/storybook/issues/6153 -Vue.prototype.toJSON = function () { - return this; -}; + app.use(N8nPlugin); +}); export const parameters = { actions: { diff --git a/packages/design-system/.storybook/storybook.scss b/packages/design-system/.storybook/storybook.scss index 125f7d57a8..ab48133c1e 100644 --- a/packages/design-system/.storybook/storybook.scss +++ b/packages/design-system/.storybook/storybook.scss @@ -1,12 +1,17 @@ @use './fonts.scss'; -@use '~/src/css/base.scss' with ( - $font-path: '~element-ui/lib/theme-chalk/fonts' -); +@use '../src/css/base.scss'; // @TODO CHECK IF NEEDED with ( +// $font-path: 'element-ui/lib/theme-chalk/fonts' +//); -@use '~/src/css/reset.scss'; -@use '~/src/css/index.scss'; +@use '../src/css/reset.scss'; +@use '../src/css/index.scss'; .multi-container > * { margin-bottom: 10px; } + +#storybook-root > div:not([class]) > *, +#storybook-root > * { + margin: var(--spacing-5xs); +} diff --git a/packages/design-system/package.json b/packages/design-system/package.json index d121f71adf..0e7b3b07a8 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -40,38 +40,43 @@ "devDependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/free-solid-svg-icons": "^5.15.4", - "@fortawesome/vue-fontawesome": "^2.0.9", - "@storybook/addon-actions": "^7.0.7", - "@storybook/addon-docs": "^7.0.7", - "@storybook/addon-essentials": "^7.0.7", - "@storybook/addon-links": "^7.0.7", - "@storybook/addon-postcss": "^3.0.0-alpha.1", - "@storybook/vue": "^7.0.7", - "@storybook/vue-webpack5": "^7.0.7", + "@fortawesome/vue-fontawesome": "^3.0.3", + "@storybook/addon-a11y": "^7.0.21", + "@storybook/addon-actions": "^7.0.21", + "@storybook/addon-docs": "^7.0.21", + "@storybook/addon-essentials": "^7.0.21", + "@storybook/addon-links": "^7.0.21", + "@storybook/addon-postcss": "3.0.0-alpha.1", + "@storybook/addon-styling": "^1.3.0", + "@storybook/vue3": "^7.0.21", + "@storybook/vue3-vite": "^7.0.21", "@testing-library/jest-dom": "^5.16.5", "@testing-library/user-event": "^14.4.3", - "@testing-library/vue": "^5.8.3", + "@testing-library/vue": "^6.6.1", "@types/markdown-it": "^12.2.3", "@types/markdown-it-emoji": "^2.0.2", "@types/markdown-it-link-attributes": "^3.0.1", - "@types/sanitize-html": "^2.8.0", - "autoprefixer": "^10.4.13", - "core-js": "^3.27.2", + "@types/sanitize-html": "^2.9.0", + "@vitejs/plugin-vue": "^4.2.3", + "@vue/test-utils": "^2.4.1", + "autoprefixer": "^10.4.14", + "core-js": "^3.31.0", "jsdom": "21.1.0", - "sass": "^1.58.0", - "sass-loader": "^13.2.0", - "storybook": "^7.0.7", - "storybook-addon-themes": "^6.1.0" + "sass": "^1.63.4", + "sass-loader": "^13.3.2", + "storybook": "^7.0.21", + "storybook-addon-themes": "^6.1.0", + "storybook-dark-mode": "^3.0.0" }, "dependencies": { - "element-ui": "~2.15.12", + "element-plus": "^2.3.6", "markdown-it": "^13.0.1", "markdown-it-emoji": "^2.0.2", "markdown-it-link-attributes": "^4.0.1", "markdown-it-task-lists": "^2.1.1", "sanitize-html": "2.10.0", - "vue": "^2.7.14", - "vue2-boring-avatars": "^0.3.8", + "vue": "^3.3.4", + "vue-boring-avatars": "^1.3.0", "xss": "^1.0.14" } } diff --git a/packages/design-system/public/assets/images/storybook-logo-dark.png b/packages/design-system/public/assets/images/storybook-logo-dark.png new file mode 100644 index 0000000000..fa5e878f0b Binary files /dev/null and b/packages/design-system/public/assets/images/storybook-logo-dark.png differ diff --git a/packages/design-system/public/assets/images/storybook-logo-light.png b/packages/design-system/public/assets/images/storybook-logo-light.png new file mode 100644 index 0000000000..3a99ee7e71 Binary files /dev/null and b/packages/design-system/public/assets/images/storybook-logo-light.png differ diff --git a/packages/design-system/src/__tests__/setup.ts b/packages/design-system/src/__tests__/setup.ts index 7b0828bfa8..1734bac34f 100644 --- a/packages/design-system/src/__tests__/setup.ts +++ b/packages/design-system/src/__tests__/setup.ts @@ -1 +1,5 @@ import '@testing-library/jest-dom'; +import { config } from '@vue/test-utils'; +import { N8nPlugin } from '@/plugin'; + +config.global.plugins = [N8nPlugin]; diff --git a/packages/design-system/src/components/N8nActionBox/ActionBox.stories.ts b/packages/design-system/src/components/N8nActionBox/ActionBox.stories.ts index dfc6d27cf7..b8d90ca151 100644 --- a/packages/design-system/src/components/N8nActionBox/ActionBox.stories.ts +++ b/packages/design-system/src/components/N8nActionBox/ActionBox.stories.ts @@ -1,6 +1,6 @@ import N8nActionBox from './ActionBox.vue'; import { action } from '@storybook/addon-actions'; -import type { StoryFn } from '@storybook/vue'; +import type { StoryFn } from '@storybook/vue3'; export default { title: 'Atoms/ActionBox', @@ -9,8 +9,8 @@ export default { calloutTheme: { control: { type: 'select', - options: ['info', 'success', 'warning', 'danger', 'custom'], }, + options: ['info', 'success', 'warning', 'danger', 'custom'], }, }, parameters: { @@ -23,11 +23,12 @@ const methods = { }; const Template: StoryFn = (args, { argTypes }) => ({ + setup: () => ({ args }), props: Object.keys(argTypes), components: { N8nActionBox, }, - template: '', + template: '', methods, }); diff --git a/packages/design-system/src/components/N8nActionBox/ActionBox.vue b/packages/design-system/src/components/N8nActionBox/ActionBox.vue index a21f6e00d5..3995e08349 100644 --- a/packages/design-system/src/components/N8nActionBox/ActionBox.vue +++ b/packages/design-system/src/components/N8nActionBox/ActionBox.vue @@ -20,7 +20,7 @@ :label="buttonText" :type="buttonType" size="large" - @click="$emit('click', $event)" + @click="$emit('click:button', $event)" /> { 'Long description that you should know something is the way it is because of how it is. ', buttonText: 'Do something', }, - stubs: ['n8n-heading', 'n8n-text', 'n8n-button', 'n8n-callout'], + global: { + stubs: ['n8n-heading', 'n8n-text', 'n8n-button', 'n8n-callout'], + }, }); expect(wrapper.html()).toMatchSnapshot(); }); diff --git a/packages/design-system/src/components/N8nActionBox/__tests__/__snapshots__/ActionBox.spec.ts.snap b/packages/design-system/src/components/N8nActionBox/__tests__/__snapshots__/ActionBox.spec.ts.snap index 40c7e9f605..491d107483 100644 --- a/packages/design-system/src/components/N8nActionBox/__tests__/__snapshots__/ActionBox.spec.ts.snap +++ b/packages/design-system/src/components/N8nActionBox/__tests__/__snapshots__/ActionBox.spec.ts.snap @@ -1,15 +1,15 @@ // Vitest Snapshot v1 exports[`N8NActionBox > should render correctly 1`] = ` -"
-
😿
+"
+
😿
- Headline you need to know +
- Long description that you should know something is the way it is because of how it is. +
- - + +
" `; diff --git a/packages/design-system/src/components/N8nActionDropdown/ActionDropdown.stories.ts b/packages/design-system/src/components/N8nActionDropdown/ActionDropdown.stories.ts index feb43c23bf..8e229231df 100644 --- a/packages/design-system/src/components/N8nActionDropdown/ActionDropdown.stories.ts +++ b/packages/design-system/src/components/N8nActionDropdown/ActionDropdown.stories.ts @@ -1,5 +1,5 @@ import N8nActionDropdown from './ActionDropdown.vue'; -import type { StoryFn } from '@storybook/vue'; +import type { StoryFn } from '@storybook/vue3'; export default { title: 'Atoms/ActionDropdown', @@ -8,8 +8,8 @@ export default { placement: { control: { type: 'select', - options: ['top', 'top-end', 'top-start', 'bottom', 'bottom-end', 'bottom-start'], }, + options: ['top', 'top-end', 'top-start', 'bottom', 'bottom-end', 'bottom-start'], }, activatorIcon: { control: { @@ -19,18 +19,19 @@ export default { trigger: { control: { type: 'select', - options: ['click', 'hover'], }, + options: ['click', 'hover'], }, }, }; const template: StoryFn = (args, { argTypes }) => ({ + setup: () => ({ args }), props: Object.keys(argTypes), components: { N8nActionDropdown, }, - template: '', + template: '', }); export const defaultActionDropdown = template.bind({}); diff --git a/packages/design-system/src/components/N8nActionDropdown/ActionDropdown.vue b/packages/design-system/src/components/N8nActionDropdown/ActionDropdown.vue index 0c21776633..e37ddfa1e8 100644 --- a/packages/design-system/src/components/N8nActionDropdown/ActionDropdown.vue +++ b/packages/design-system/src/components/N8nActionDropdown/ActionDropdown.vue @@ -6,7 +6,7 @@ @command="onSelect" ref="elementDropdown" > -
+