Skip to content

Commit

Permalink
Merge branch 'master' into ADO-1834-touch-device-detection
Browse files Browse the repository at this point in the history
* master:
  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)
  refactor(core): Merge event bus controllers and remove dead code (no-changelog) (#9688)
  ci: Fix e2e tests (no-changelog) (#9689)
  refactor(core): Use `@Licensed()` in event bus controller (no-changelog) (#9687)
  fix(editor): Node background for executing nodes in dark mode (#9682)
  fix(editor): Prevent saving already saved workflows (#9670)
  fix(editor): Fix node connection showing incorrect item count during … (#9684)
  refactor: Set up Cypress as pnpm workspace (no-changelog) (#6049)
  • Loading branch information
MiloradFilipovic committed Jun 11, 2024
2 parents 0d74c88 + 90f8b91 commit 72783ec
Show file tree
Hide file tree
Showing 200 changed files with 3,841 additions and 2,414 deletions.
11 changes: 6 additions & 5 deletions .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 Expand Up @@ -87,7 +87,7 @@ jobs:
git fetch origin pull/${{ inputs.pr_number }}/head
git checkout FETCH_HEAD
- uses: pnpm/action-setup@v2.4.0
- uses: pnpm/action-setup@v4.0.0

- name: Install dependencies
run: pnpm install --frozen-lockfile
Expand All @@ -103,6 +103,7 @@ jobs:
VUE_APP_MAX_PINNED_DATA_SIZE: 16384

- name: Cypress install
working-directory: cypress
run: pnpm cypress:install

- name: Cache build artifacts
Expand Down Expand Up @@ -138,7 +139,7 @@ jobs:
git fetch origin pull/${{ inputs.pr_number }}/head
git checkout FETCH_HEAD
- uses: pnpm/action-setup@v2.4.0
- uses: pnpm/action-setup@v4.0.0

- name: Restore cached pnpm modules
uses: actions/cache/restore@v4.0.0
Expand All @@ -155,6 +156,7 @@ jobs:
- name: Cypress run
uses: cypress-io/github-action@v6.6.1
with:
working-directory: cypress
install: false
start: pnpm start
wait-on: 'http://localhost:5678'
Expand All @@ -164,8 +166,7 @@ jobs:
# We have to provide custom ci-build-id key to make sure that this workflow could be run multiple times
# in the same parent workflow
ci-build-id: ${{ needs.prepare.outputs.uuid }}
spec: '/__w/n8n/n8n/cypress/${{ inputs.spec }}'
config-file: /__w/n8n/n8n/cypress.config.js
spec: '${{ inputs.spec }}'
env:
NODE_OPTIONS: --dns-result-order=ipv4first
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
Expand Down
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ nodelinter.config.json
packages/**/.turbo
.turbo
*.tsbuildinfo
cypress/videos/*
cypress/screenshots/*
cypress/downloads/*
*.swp
CHANGELOG-*.md
*.mdx
Expand Down
34 changes: 34 additions & 0 deletions cypress/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const sharedOptions = require('@n8n_io/eslint-config/shared');

/**
* @type {import('@types/eslint').ESLint.ConfigData}
*/
module.exports = {
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',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'@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',
},
};
3 changes: 3 additions & 0 deletions cypress/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
videos/
screenshots/
downloads/
4 changes: 4 additions & 0 deletions cypress/augmentation.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module 'cypress-otp' {
// eslint-disable-next-line import/no-default-export
export default function generateOTPToken(secret: string): string;
}
2 changes: 1 addition & 1 deletion cypress/composables/becomeTemplateCreatorCta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const getCloseBecomeTemplateCreatorCtaButton = () =>
//#region Actions

export const interceptCtaRequestWithResponse = (becomeCreator: boolean) => {
return cy.intercept('GET', `/rest/cta/become-creator`, {
return cy.intercept('GET', '/rest/cta/become-creator', {
body: becomeCreator,
});
};
Expand Down
2 changes: 1 addition & 1 deletion cypress/composables/modals/credential-modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function closeCredentialModal() {
getCredentialModalCloseButton().click();
}

export function setCredentialValues(values: Record<string, any>, save = true) {
export function setCredentialValues(values: Record<string, string>, save = true) {
Object.entries(values).forEach(([key, value]) => {
setCredentialConnectionParameterInputByName(key, value);
});
Expand Down
4 changes: 2 additions & 2 deletions cypress/composables/ndv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Getters
*/

import { getVisibleSelect } from "../utils";
import { getVisibleSelect } from '../utils';

export function getCredentialSelect(eq = 0) {
return cy.getByTestId('node-credentials-select').eq(eq);
Expand Down Expand Up @@ -75,7 +75,7 @@ export function setParameterInputByName(name: string, value: string) {
}

export function toggleParameterCheckboxInputByName(name: string) {
getParameterInputByName(name).find('input[type="checkbox"]').realClick()
getParameterInputByName(name).find('input[type="checkbox"]').realClick();
}

export function setParameterSelectByContent(name: string, content: string) {
Expand Down
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/composables/setup-workflow-credentials-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
* Getters
*/

export const getSetupWorkflowCredentialsButton = () => cy.get(`button:contains("Set up template")`);
export const getSetupWorkflowCredentialsButton = () => cy.get('button:contains("Set up template")');
2 changes: 1 addition & 1 deletion cypress/composables/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function getNodeByName(name: string) {
export function disableNode(name: string) {
const target = getNodeByName(name);
target.rightclick(name ? 'center' : 'topLeft', { force: true });
cy.getByTestId(`context-menu-item-toggle_activation`).click();
cy.getByTestId('context-menu-item-toggle_activation').click();
}

export function getConnectionBySourceAndTarget(source: string, target: string) {
Expand Down
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
6 changes: 6 additions & 0 deletions cypress.config.js → cypress/cypress.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ module.exports = defineConfig({
screenshotOnRunFailure: true,
experimentalInteractiveRunEvents: true,
experimentalSessionAndOrigin: true,
specPattern: 'e2e/**/*.ts',
supportFile: 'support/e2e.ts',
fixturesFolder: 'fixtures',
downloadsFolder: 'downloads',
screenshotsFolder: 'screenshots',
videosFolder: 'videos',
},
env: {
MAX_PINNED_DATA_SIZE: process.env.VUE_APP_MAX_PINNED_DATA_SIZE
Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/1-workflows.cy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { v4 as uuid } from 'uuid';
import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows';
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
import { v4 as uuid } from 'uuid';

const WorkflowsPage = new WorkflowsPageClass();
const WorkflowPage = new WorkflowPageClass();
Expand Down
21 changes: 12 additions & 9 deletions cypress/e2e/10-undo-redo.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { CODE_NODE_NAME, SET_NODE_NAME, EDIT_FIELDS_SET_NODE_NAME } from './../constants';
import { SCHEDULE_TRIGGER_NODE_NAME } from '../constants';
import {
SCHEDULE_TRIGGER_NODE_NAME,
CODE_NODE_NAME,
SET_NODE_NAME,
EDIT_FIELDS_SET_NODE_NAME,
} from '../constants';
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
import { MessageBox as MessageBoxClass } from '../pages/modals/message-box';
import { NDV } from '../pages/ndv';
Expand Down Expand Up @@ -118,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 @@ -204,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 Expand Up @@ -338,8 +341,8 @@ describe('Undo/Redo', () => {
WorkflowPage.getters.nodeConnections().should('have.length', 1);
cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')).should('have.length', 1);
cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch'))
.should('have.css', 'left', `637px`)
.should('have.css', 'top', `501px`);
.should('have.css', 'left', '637px')
.should('have.css', 'top', '501px');

cy.fixture('Test_workflow_form_switch.json').then((data) => {
cy.get('body').paste(JSON.stringify(data));
Expand All @@ -353,8 +356,8 @@ describe('Undo/Redo', () => {
WorkflowPage.getters.nodeConnections().should('have.length', 1);
cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch')).should('have.length', 1);
cy.get(WorkflowPage.getters.getEndpointSelector('input', 'Switch'))
.should('have.css', 'left', `637px`)
.should('have.css', 'top', `501px`);
.should('have.css', 'left', '637px')
.should('have.css', 'top', '501px');
});

it('should not undo/redo when NDV or a modal is open', () => {
Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/11-inline-expression-editor.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('Inline expression editor', () => {
beforeEach(() => {
WorkflowPage.actions.visit();
WorkflowPage.actions.addInitialNodeToCanvas('Schedule');
cy.on('uncaught:exception', (err) => err.name !== 'ExpressionError');
cy.on('uncaught:exception', (error) => error.name !== 'ExpressionError');
});

describe('Static data', () => {
Expand Down
15 changes: 8 additions & 7 deletions cypress/e2e/12-canvas-actions.cy.ts
Original file line number Diff line number Diff line change
@@ -1,3 +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 All @@ -7,7 +9,6 @@ import {
IF_NODE_NAME,
HTTP_REQUEST_NODE_NAME,
} from './../constants';
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';

const WorkflowPage = new WorkflowPageClass();
describe('Canvas Actions', () => {
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
Loading

0 comments on commit 72783ec

Please sign in to comment.