Skip to content

Commit

Permalink
Merge branch 'master' into ado-2111
Browse files Browse the repository at this point in the history
* master:
  fix(editor): Add back credential type icon (no-changelog) (#9704)
  feat(editor): Add canvas edge toolbar hover show/hide support (no-changelog) (#9699)
  ci: Fix custom docker builds (no-changelog) (#9702)
  test: Fix e2e for projects missing instance owner (no-changelog) (#9703)
  ci: Refactor e2e tests to be less flaky (no-changelog) (#9695)
  feat(editor): Add move resources option to workflows and credentials on (#9654)
  fix: Introduce `HooksService` (#8962)
  fix(editor): Improve large data warning in input/output panel (#9671)
  ci(editor): Enforce type-safety in @n8n/chat builds as well (no-changelog) (#9685)
  fix(editor): Un-skip workflow save test (no-changelog) (#9698)
  refactor(core): Remove more dead code from event bus (no-changelog) (#9697)
  ci: Remove unused WaitTracker mocking (no-changelog) (#9694)
  feat: Update NPS Value Survey (#9638)
  refactor(core): Remove event bus channel (no-changelog) (#9663)
  refactor(core): Remove event bus helpers (no-changelog) (#9690)
  • Loading branch information
MiloradFilipovic committed Jun 12, 2024
2 parents 871c76f + f9aa340 commit 171a288
Show file tree
Hide file tree
Showing 145 changed files with 3,611 additions and 1,942 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2e-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ on:
containers:
description: 'Number of containers to run tests in.'
required: false
default: '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]'
default: '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]'
type: string
pr_number:
description: 'PR number to run tests for.'
Expand Down
12 changes: 11 additions & 1 deletion cypress/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ const sharedOptions = require('@n8n_io/eslint-config/shared');
* @type {import('@types/eslint').ESLint.ConfigData}
*/
module.exports = {
extends: ['@n8n_io/eslint-config/base'],
extends: ['@n8n_io/eslint-config/base', 'plugin:cypress/recommended'],

...sharedOptions(__dirname),

plugins: ['cypress'],

env: {
'cypress/globals': true,
},

rules: {
// TODO: remove these rules
'@typescript-eslint/no-explicit-any': 'off',
Expand All @@ -20,5 +26,9 @@ module.exports = {
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/promise-function-async': 'off',
'n8n-local-rules/no-uncaught-json-parse': 'off',

'cypress/no-assigning-return-values': 'warn',
'cypress/no-unnecessary-waiting': 'warn',
'cypress/unsafe-to-chain-command': 'warn',
},
};
41 changes: 40 additions & 1 deletion cypress/composables/projects.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { CredentialsModal, WorkflowPage } from '../pages';

const workflowPage = new WorkflowPage();
const credentialsModal = new CredentialsModal();

export const getHomeButton = () => cy.getByTestId('project-home-menu-item');
export const getMenuItems = () => cy.getByTestId('project-menu-item');
export const getAddProjectButton = () => cy.getByTestId('add-project-menu-item');
Expand All @@ -11,8 +16,42 @@ export const getProjectSettingsCancelButton = () =>
export const getProjectSettingsDeleteButton = () =>
cy.getByTestId('project-settings-delete-button');
export const getProjectMembersSelect = () => cy.getByTestId('project-members-select');

export const addProjectMember = (email: string) => {
getProjectMembersSelect().click();
getProjectMembersSelect().get('.el-select-dropdown__item').contains(email.toLowerCase()).click();
};
export const getProjectNameInput = () => cy.get('#projectName');
export const getResourceMoveModal = () => cy.getByTestId('project-move-resource-modal');
export const getResourceMoveConfirmModal = () =>
cy.getByTestId('project-move-resource-confirm-modal');
export const getProjectMoveSelect = () => cy.getByTestId('project-move-resource-modal-select');

export function createProject(name: string) {
getAddProjectButton().should('be.visible').click();

getProjectNameInput()
.should('be.visible')
.should('be.focused')
.should('have.value', 'My project')
.clear()
.type(name);
getProjectSettingsSaveButton().click();
}

export function createWorkflow(fixtureKey: string, name: string) {
workflowPage.getters.workflowImportInput().selectFile(`fixtures/${fixtureKey}`, { force: true });
workflowPage.actions.setWorkflowName(name);
workflowPage.getters.saveButton().should('contain', 'Saved');
workflowPage.actions.zoomToFit();
}

export function createCredential(name: string) {
credentialsModal.getters.newCredentialModal().should('be.visible');
credentialsModal.getters.newCredentialTypeSelect().should('be.visible');
credentialsModal.getters.newCredentialTypeOption('Notion API').click();
credentialsModal.getters.newCredentialTypeButton().click();
credentialsModal.getters.connectionParameter('Internal Integration Secret').type('1234567890');
credentialsModal.actions.setName(name);
credentialsModal.actions.save();
credentialsModal.actions.close();
}
2 changes: 1 addition & 1 deletion cypress/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME = 'OpenAI Chat Model'
export const AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME = 'Auto-fixing Output Parser';
export const WEBHOOK_NODE_NAME = 'Webhook';

export const META_KEY = Cypress.platform === 'darwin' ? '{meta}' : '{ctrl}';
export const META_KEY = Cypress.platform === 'darwin' ? 'meta' : 'ctrl';

export const NEW_GOOGLE_ACCOUNT_NAME = 'Gmail account';
export const NEW_TRELLO_ACCOUNT_NAME = 'Trello account';
Expand Down
5 changes: 2 additions & 3 deletions cypress/e2e/10-undo-redo.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,7 @@ describe('Undo/Redo', () => {
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.get('body').type('{esc}');
cy.get('body').type('{esc}');
WorkflowPage.actions.selectAll();
cy.get('body').type('{backspace}');
WorkflowPage.actions.hitDeleteAllNodes();
WorkflowPage.getters.canvasNodes().should('have.have.length', 0);
WorkflowPage.actions.hitUndo();
WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
Expand Down Expand Up @@ -208,7 +207,7 @@ describe('Undo/Redo', () => {
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.get('body').type('{esc}');
cy.get('body').type('{esc}');
WorkflowPage.actions.selectAll();
WorkflowPage.actions.hitSelectAll();
WorkflowPage.actions.hitDisableNodeShortcut();
WorkflowPage.getters.disabledNodes().should('have.length', 2);
WorkflowPage.actions.hitUndo();
Expand Down
13 changes: 7 additions & 6 deletions cypress/e2e/12-canvas-actions.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
import { successToast } from '../pages/notifications';
import {
MANUAL_TRIGGER_NODE_NAME,
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
Expand Down Expand Up @@ -166,8 +167,8 @@ describe('Canvas Actions', () => {
.findChildByTestId('execute-node-button')
.click({ force: true });
WorkflowPage.actions.executeNode(CODE_NODE_NAME);
WorkflowPage.getters.successToast().should('have.length', 2);
WorkflowPage.getters.successToast().should('contain.text', 'Node executed successfully');
successToast().should('have.length', 2);
successToast().should('contain.text', 'Node executed successfully');
});

it('should disable and enable node', () => {
Expand Down Expand Up @@ -198,19 +199,19 @@ describe('Canvas Actions', () => {
it('should copy selected nodes', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.selectAll();
WorkflowPage.actions.hitSelectAll();

WorkflowPage.actions.hitCopy();
WorkflowPage.getters.successToast().should('contain', 'Copied!');
successToast().should('contain', 'Copied!');

WorkflowPage.actions.copyNode(CODE_NODE_NAME);
WorkflowPage.getters.successToast().should('contain', 'Copied!');
successToast().should('contain', 'Copied!');
});

it('should select/deselect all nodes', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.actions.selectAll();
WorkflowPage.actions.hitSelectAll();
WorkflowPage.getters.selectedNodes().should('have.length', 2);
WorkflowPage.actions.deselectAll();
WorkflowPage.getters.selectedNodes().should('have.length', 0);
Expand Down
18 changes: 8 additions & 10 deletions cypress/e2e/12-canvas.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.wait(500);
WorkflowPage.actions.selectAll();
cy.get('body').type('{backspace}');
WorkflowPage.actions.hitDeleteAllNodes();
WorkflowPage.getters.canvasNodes().should('have.length', 0);

WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
Expand All @@ -181,8 +180,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
cy.wait(500);
WorkflowPage.actions.selectAll();
cy.get('body').type('{backspace}');
WorkflowPage.actions.hitDeleteAllNodes();
WorkflowPage.getters.canvasNodes().should('have.length', 0);

WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
Expand Down Expand Up @@ -315,7 +313,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
cy.get('body').type('{esc}');

// Keyboard shortcut
WorkflowPage.actions.selectAll();
WorkflowPage.actions.hitSelectAll();
WorkflowPage.actions.hitDisableNodeShortcut();
WorkflowPage.getters.disabledNodes().should('have.length', 2);
WorkflowPage.actions.hitDisableNodeShortcut();
Expand All @@ -324,12 +322,12 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.hitDisableNodeShortcut();
WorkflowPage.getters.disabledNodes().should('have.length', 1);
WorkflowPage.actions.selectAll();
WorkflowPage.actions.hitSelectAll();
WorkflowPage.actions.hitDisableNodeShortcut();
WorkflowPage.getters.disabledNodes().should('have.length', 2);

// Context menu
WorkflowPage.actions.selectAll();
WorkflowPage.actions.hitSelectAll();
WorkflowPage.actions.openContextMenu();
WorkflowPage.actions.contextMenuAction('toggle_activation');
WorkflowPage.getters.disabledNodes().should('have.length', 0);
Expand All @@ -341,7 +339,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.actions.openContextMenu();
WorkflowPage.actions.contextMenuAction('toggle_activation');
WorkflowPage.getters.disabledNodes().should('have.length', 1);
WorkflowPage.actions.selectAll();
WorkflowPage.actions.hitSelectAll();
WorkflowPage.actions.openContextMenu();
WorkflowPage.actions.contextMenuAction('toggle_activation');
WorkflowPage.getters.disabledNodes().should('have.length', 2);
Expand Down Expand Up @@ -383,8 +381,8 @@ describe('Canvas Node Manipulation and Navigation', () => {
WorkflowPage.getters.canvasNodes().should('have.length', 3);
WorkflowPage.getters.nodeConnections().should('have.length', 1);

WorkflowPage.actions.selectAll();
WorkflowPage.actions.hitDuplicateNodeShortcut();
WorkflowPage.actions.hitSelectAll();
WorkflowPage.actions.hitDuplicateNode();
WorkflowPage.getters.canvasNodes().should('have.length', 5);
});

Expand Down
7 changes: 3 additions & 4 deletions cypress/e2e/13-pinning.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BACKEND_BASE_URL,
} from '../constants';
import { WorkflowPage, NDV } from '../pages';
import { errorToast } from '../pages/notifications';

const workflowPage = new WorkflowPage();
const ndv = new NDV();
Expand Down Expand Up @@ -139,9 +140,7 @@ describe('Data pinning', () => {
test: '1'.repeat(Cypress.env('MAX_PINNED_DATA_SIZE') as number),
},
]);
workflowPage.getters
.errorToast()
.should('contain', 'Workflow has reached the maximum allowed pinned data size');
errorToast().should('contain', 'Workflow has reached the maximum allowed pinned data size');
});

it('Should show an error when pin data JSON in invalid', () => {
Expand All @@ -152,7 +151,7 @@ describe('Data pinning', () => {
ndv.getters.editPinnedDataButton().should('be.visible');

ndv.actions.setPinnedData('[ { "name": "First item", "code": 2dsa }]');
workflowPage.getters.errorToast().should('contain', 'Unable to save due to invalid JSON');
errorToast().should('contain', 'Unable to save due to invalid JSON');
});

it('Should be able to reference paired items in a node located before pinned data', () => {
Expand Down
3 changes: 2 additions & 1 deletion cypress/e2e/1338-ADO-ndv-missing-input-panel.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { v4 as uuid } from 'uuid';
import { NDV, WorkflowPage as WorkflowPageClass } from '../pages';
import { successToast } from '../pages/notifications';

const workflowPage = new WorkflowPageClass();
const ndv = new NDV();
Expand All @@ -16,7 +17,7 @@ describe('ADO-1338-ndv-missing-input-panel', () => {
workflowPage.getters.zoomToFitButton().click();
workflowPage.getters.executeWorkflowButton().click();
// Check success toast (works because Cypress waits enough for the element to show after the http request node has finished)
workflowPage.getters.successToast().should('be.visible');
successToast().should('be.visible');

workflowPage.actions.openNode('Discourse1');
ndv.getters.inputPanel().should('be.visible');
Expand Down
18 changes: 9 additions & 9 deletions cypress/e2e/17-sharing.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {

let workflowW2Url = '';
it('should create C1, W1, W2, share W1 with U3, as U2', () => {
cy.signin(INSTANCE_MEMBERS[0]);
cy.signinAsMember(0);

cy.visit(credentialsPage.url);
credentialsPage.getters.emptyListCreateCredentialButton().click();
Expand Down Expand Up @@ -67,7 +67,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
});

it('should create C2, share C2 with U1 and U2, as U3', () => {
cy.signin(INSTANCE_MEMBERS[1]);
cy.signinAsMember(1);

cy.visit(credentialsPage.url);
credentialsPage.getters.emptyListCreateCredentialButton().click();
Expand All @@ -83,7 +83,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
});

it('should open W1, add node using C2 as U3', () => {
cy.signin(INSTANCE_MEMBERS[1]);
cy.signinAsMember(1);

cy.visit(workflowsPage.url);
workflowsPage.getters.workflowCards().should('have.length', 1);
Expand All @@ -99,7 +99,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
});

it('should open W1, add node using C2 as U2', () => {
cy.signin(INSTANCE_MEMBERS[0]);
cy.signinAsMember(0);

cy.visit(workflowsPage.url);
workflowsPage.getters.workflowCards().should('have.length', 2);
Expand All @@ -119,7 +119,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
});

it('should not have access to W2, as U3', () => {
cy.signin(INSTANCE_MEMBERS[1]);
cy.signinAsMember(1);

cy.visit(workflowW2Url);
cy.waitForLoad();
Expand All @@ -128,7 +128,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
});

it('should have access to W1, W2, as U1', () => {
cy.signin(INSTANCE_OWNER);
cy.signinAsOwner();

cy.visit(workflowsPage.url);
workflowsPage.getters.workflowCards().should('have.length', 2);
Expand All @@ -144,15 +144,15 @@ describe('Sharing', { disableAutoLogin: true }, () => {
});

it('should automatically test C2 when opened by U2 sharee', () => {
cy.signin(INSTANCE_MEMBERS[0]);
cy.signinAsMember(0);

cy.visit(credentialsPage.url);
credentialsPage.getters.credentialCard('Credential C2').click();
credentialsModal.getters.testSuccessTag().should('be.visible');
});

it('should work for admin role on credentials created by others (also can share it with themselves)', () => {
cy.signin(INSTANCE_MEMBERS[0]);
cy.signinAsMember(0);

cy.visit(credentialsPage.url);
credentialsPage.getters.createCredentialButton().click();
Expand All @@ -164,7 +164,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
credentialsModal.actions.close();

cy.signout();
cy.signin(INSTANCE_ADMIN);
cy.signinAsAdmin();
cy.visit(credentialsPage.url);
credentialsPage.getters.credentialCard('Credential C3').click();
credentialsModal.getters.testSuccessTag().should('be.visible');
Expand Down
Loading

0 comments on commit 171a288

Please sign in to comment.