mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 12:43:11 +03:00
refactor: Set up Cypress as pnpm workspace (no-changelog) (#6049)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
bc3dcf706f
commit
af3ac2db28
8
.github/workflows/e2e-reusable.yml
vendored
8
.github/workflows/e2e-reusable.yml
vendored
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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'
|
||||
@ -165,7 +167,7 @@ jobs:
|
||||
# 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
|
||||
config-file: /__w/n8n/n8n/cypress/cypress.config.js
|
||||
env:
|
||||
NODE_OPTIONS: --dns-result-order=ipv4first
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -18,9 +18,6 @@ nodelinter.config.json
|
||||
packages/**/.turbo
|
||||
.turbo
|
||||
*.tsbuildinfo
|
||||
cypress/videos/*
|
||||
cypress/screenshots/*
|
||||
cypress/downloads/*
|
||||
*.swp
|
||||
CHANGELOG-*.md
|
||||
*.mdx
|
||||
|
24
cypress/.eslintrc.js
Normal file
24
cypress/.eslintrc.js
Normal file
@ -0,0 +1,24 @@
|
||||
const sharedOptions = require('@n8n_io/eslint-config/shared');
|
||||
|
||||
/**
|
||||
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = {
|
||||
extends: ['@n8n_io/eslint-config/base'],
|
||||
|
||||
...sharedOptions(__dirname),
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
3
cypress/.gitignore
vendored
Normal file
3
cypress/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
videos/
|
||||
screenshots/
|
||||
downloads/
|
4
cypress/augmentation.d.ts
vendored
Normal file
4
cypress/augmentation.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module 'cypress-otp' {
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function generateOTPToken(secret: string): string;
|
||||
}
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
@ -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) {
|
||||
|
@ -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")');
|
||||
|
@ -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) {
|
||||
|
@ -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
|
@ -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();
|
||||
|
@ -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';
|
||||
@ -338,8 +342,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));
|
||||
@ -353,8 +357,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', () => {
|
||||
|
@ -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', () => {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import {
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
@ -7,7 +8,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', () => {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { NDV, WorkflowExecutionsTab } from '../pages';
|
||||
import {
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
@ -7,8 +9,6 @@ import {
|
||||
SWITCH_NODE_NAME,
|
||||
MERGE_NODE_NAME,
|
||||
} from './../constants';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { NDV, WorkflowExecutionsTab } from '../pages';
|
||||
|
||||
const WorkflowPage = new WorkflowPageClass();
|
||||
const ExecutionsTab = new WorkflowExecutionsTab();
|
||||
@ -258,7 +258,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
||||
|
||||
WorkflowPage.actions.pinchToZoom(1, 'zoomOut');
|
||||
// Zoom in 1x + Zoom out 1x should reset to default (=1)
|
||||
WorkflowPage.getters.nodeView().should('have.css', 'transform', `matrix(1, 0, 0, 1, 0, 0)`);
|
||||
WorkflowPage.getters.nodeView().should('have.css', 'transform', 'matrix(1, 0, 0, 1, 0, 0)');
|
||||
|
||||
WorkflowPage.actions.pinchToZoom(1, 'zoomOut');
|
||||
WorkflowPage.getters
|
||||
|
@ -136,7 +136,7 @@ describe('Data pinning', () => {
|
||||
|
||||
ndv.actions.pastePinnedData([
|
||||
{
|
||||
test: '1'.repeat(Cypress.env('MAX_PINNED_DATA_SIZE')),
|
||||
test: '1'.repeat(Cypress.env('MAX_PINNED_DATA_SIZE') as number),
|
||||
},
|
||||
]);
|
||||
workflowPage.getters
|
||||
@ -151,10 +151,8 @@ describe('Data pinning', () => {
|
||||
ndv.getters.pinDataButton().should('not.exist');
|
||||
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');
|
||||
ndv.actions.setPinnedData('[ { "name": "First item", "code": 2dsa }]');
|
||||
workflowPage.getters.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', () => {
|
||||
@ -168,6 +166,7 @@ describe('Data pinning', () => {
|
||||
ndv.actions.close();
|
||||
|
||||
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
setExpressionOnStringValueInSet(`{{ $('${HTTP_REQUEST_NODE_NAME}').item`);
|
||||
|
||||
const output = '[Object: {"json": {"http": 123}, "pairedItem": {"item": 0}}]';
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
import { WorkflowPage, NDV } from '../pages';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
const wf = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { WorkflowPage, NDV } from '../pages';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import {
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
} from './../constants';
|
||||
import { WorkflowPage, NDV } from '../pages';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
@ -170,7 +170,7 @@ describe('Data mapping', () => {
|
||||
});
|
||||
|
||||
it('maps expressions from previous nodes', () => {
|
||||
cy.createFixtureWorkflow('Test_workflow_3.json', `My test workflow`);
|
||||
cy.createFixtureWorkflow('Test_workflow_3.json', 'My test workflow');
|
||||
workflowPage.actions.zoomToFit();
|
||||
workflowPage.actions.openNode('Set1');
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { WorkflowPage, WorkflowsPage, NDV } from '../pages';
|
||||
import { BACKEND_BASE_URL } from '../constants';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import type { ExecutionResponse } from '../types';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
||||
describe('Schedule Trigger node', async () => {
|
||||
describe('Schedule Trigger node', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
@ -37,30 +38,34 @@ describe('Schedule Trigger node', async () => {
|
||||
const workflowId = url.split('/').pop();
|
||||
|
||||
cy.wait(1200);
|
||||
cy.request('GET', `${BACKEND_BASE_URL}/rest/executions`).then((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(workflowId).to.not.be.undefined;
|
||||
expect(response.body.data.results.length).to.be.greaterThan(0);
|
||||
const matchingExecutions = response.body.data.results.filter(
|
||||
(execution: any) => execution.workflowId === workflowId,
|
||||
);
|
||||
expect(matchingExecutions).to.have.length(1);
|
||||
|
||||
cy.wait(1200);
|
||||
cy.request('GET', `${BACKEND_BASE_URL}/rest/executions`).then((response) => {
|
||||
cy.request<ExecutionResponse>('GET', `${BACKEND_BASE_URL}/rest/executions`).then(
|
||||
(response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(workflowId).to.not.be.undefined;
|
||||
expect(response.body.data.results.length).to.be.greaterThan(0);
|
||||
const matchingExecutions = response.body.data.results.filter(
|
||||
(execution: any) => execution.workflowId === workflowId,
|
||||
(execution) => execution.workflowId === workflowId,
|
||||
);
|
||||
expect(matchingExecutions).to.have.length(2);
|
||||
expect(matchingExecutions).to.have.length(1);
|
||||
|
||||
workflowPage.actions.activateWorkflow();
|
||||
workflowPage.getters.activatorSwitch().should('not.have.class', 'is-checked');
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.actions.deleteWorkFlow('Schedule Trigger Workflow');
|
||||
});
|
||||
});
|
||||
cy.wait(1200);
|
||||
cy.request<ExecutionResponse>('GET', `${BACKEND_BASE_URL}/rest/executions`).then(
|
||||
(response1) => {
|
||||
expect(response1.status).to.eq(200);
|
||||
expect(response1.body.data.results.length).to.be.greaterThan(0);
|
||||
const matchingExecutions1 = response1.body.data.results.filter(
|
||||
(execution: any) => execution.workflowId === workflowId,
|
||||
);
|
||||
expect(matchingExecutions1).to.have.length(2);
|
||||
|
||||
workflowPage.actions.activateWorkflow();
|
||||
workflowPage.getters.activatorSwitch().should('not.have.class', 'is-checked');
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.actions.deleteWorkFlow('Schedule Trigger Workflow');
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
||||
import { cowBase64 } from '../support/binaryTestFiles';
|
||||
import { BACKEND_BASE_URL, EDIT_FIELDS_SET_NODE_NAME } from '../constants';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
@ -75,7 +75,7 @@ const simpleWebhookCall = (options: SimpleWebhookCallOptions) => {
|
||||
}
|
||||
};
|
||||
|
||||
describe('Webhook Trigger node', async () => {
|
||||
describe('Webhook Trigger node', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
});
|
||||
@ -121,10 +121,12 @@ describe('Webhook Trigger node', async () => {
|
||||
workflowPage.actions.executeWorkflow();
|
||||
cy.wait(waitForWebhook);
|
||||
|
||||
cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body.MyValue).to.eq(1234);
|
||||
});
|
||||
cy.request<{ MyValue: number }>('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then(
|
||||
(response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body.MyValue).to.eq(1234);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should listen for a GET request and respond custom status code 201', () => {
|
||||
@ -161,10 +163,12 @@ describe('Webhook Trigger node', async () => {
|
||||
workflowPage.actions.executeWorkflow();
|
||||
cy.wait(waitForWebhook);
|
||||
|
||||
cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body.MyValue).to.eq(1234);
|
||||
});
|
||||
cy.request<{ MyValue: number }>('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then(
|
||||
(response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body.MyValue).to.eq(1234);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should listen for a GET request and respond with last node binary data', () => {
|
||||
@ -200,10 +204,12 @@ describe('Webhook Trigger node', async () => {
|
||||
workflowPage.actions.executeWorkflow();
|
||||
cy.wait(waitForWebhook);
|
||||
|
||||
cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(Object.keys(response.body).includes('data')).to.be.true;
|
||||
});
|
||||
cy.request<{ data: unknown }>('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then(
|
||||
(response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(Object.keys(response.body).includes('data')).to.be.true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should listen for a GET request and respond with an empty body', () => {
|
||||
@ -217,10 +223,12 @@ describe('Webhook Trigger node', async () => {
|
||||
});
|
||||
ndv.actions.execute();
|
||||
cy.wait(waitForWebhook);
|
||||
cy.request('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body.MyValue).to.be.undefined;
|
||||
});
|
||||
cy.request<{ MyValue: unknown }>('GET', `${BACKEND_BASE_URL}/webhook-test/${webhookPath}`).then(
|
||||
(response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body.MyValue).to.be.undefined;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should listen for a GET request with Basic Authentication', () => {
|
||||
|
@ -187,7 +187,7 @@ describe('User Management', { disableAutoLogin: true }, () => {
|
||||
workflowPage.getters.successToast().should('contain', 'User deleted');
|
||||
});
|
||||
|
||||
it(`should allow user to change their personal data`, () => {
|
||||
it('should allow user to change their personal data', () => {
|
||||
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||
personalSettingsPage.actions.updateFirstAndLastName(
|
||||
updatedPersonalData.newFirstName,
|
||||
@ -199,15 +199,15 @@ describe('User Management', { disableAutoLogin: true }, () => {
|
||||
workflowPage.getters.successToast().should('contain', 'Personal details updated');
|
||||
});
|
||||
|
||||
it(`shouldn't allow user to set weak password`, () => {
|
||||
it("shouldn't allow user to set weak password", () => {
|
||||
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||
personalSettingsPage.getters.changePasswordLink().click();
|
||||
for (let weakPass of updatedPersonalData.invalidPasswords) {
|
||||
for (const weakPass of updatedPersonalData.invalidPasswords) {
|
||||
personalSettingsPage.actions.tryToSetWeakPassword(INSTANCE_OWNER.password, weakPass);
|
||||
}
|
||||
});
|
||||
|
||||
it(`shouldn't allow user to change password if old password is wrong`, () => {
|
||||
it("shouldn't allow user to change password if old password is wrong", () => {
|
||||
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||
personalSettingsPage.getters.changePasswordLink().click();
|
||||
personalSettingsPage.actions.updatePassword('iCannotRemember', updatedPersonalData.newPassword);
|
||||
@ -217,7 +217,7 @@ describe('User Management', { disableAutoLogin: true }, () => {
|
||||
.should('contain', 'Provided current password is incorrect.');
|
||||
});
|
||||
|
||||
it(`should change current user password`, () => {
|
||||
it('should change current user password', () => {
|
||||
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||
personalSettingsPage.getters.changePasswordLink().click();
|
||||
personalSettingsPage.actions.updatePassword(
|
||||
@ -231,7 +231,7 @@ describe('User Management', { disableAutoLogin: true }, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it(`shouldn't allow users to set invalid email`, () => {
|
||||
it("shouldn't allow users to set invalid email", () => {
|
||||
personalSettingsPage.actions.loginAndVisit(
|
||||
INSTANCE_OWNER.email,
|
||||
updatedPersonalData.newPassword,
|
||||
@ -242,7 +242,7 @@ describe('User Management', { disableAutoLogin: true }, () => {
|
||||
personalSettingsPage.actions.tryToSetInvalidEmail(updatedPersonalData.newEmail.split('.')[0]);
|
||||
});
|
||||
|
||||
it(`should change user email`, () => {
|
||||
it('should change user email', () => {
|
||||
personalSettingsPage.actions.loginAndVisit(
|
||||
INSTANCE_OWNER.email,
|
||||
updatedPersonalData.newPassword,
|
||||
|
@ -512,8 +512,9 @@ describe('Execution', () => {
|
||||
expect(interception.request.body).to.have.property('runData').that.is.an('object');
|
||||
const expectedKeys = ['When clicking ‘Test workflow’', 'fetch 5 random users'];
|
||||
|
||||
expect(Object.keys(interception.request.body.runData)).to.have.lengthOf(expectedKeys.length);
|
||||
expect(interception.request.body.runData).to.include.all.keys(expectedKeys);
|
||||
const { runData } = interception.request.body as Record<string, object>;
|
||||
expect(Object.keys(runData)).to.have.lengthOf(expectedKeys.length);
|
||||
expect(runData).to.include.all.keys(expectedKeys);
|
||||
});
|
||||
});
|
||||
|
||||
@ -537,10 +538,9 @@ describe('Execution', () => {
|
||||
expect(interception.request.body).to.have.property('pinData').that.is.an('object');
|
||||
const expectedPinnedDataKeys = ['Webhook'];
|
||||
|
||||
expect(Object.keys(interception.request.body.pinData)).to.have.lengthOf(
|
||||
expectedPinnedDataKeys.length,
|
||||
);
|
||||
expect(interception.request.body.pinData).to.include.all.keys(expectedPinnedDataKeys);
|
||||
const { pinData } = interception.request.body as Record<string, object>;
|
||||
expect(Object.keys(pinData)).to.have.lengthOf(expectedPinnedDataKeys.length);
|
||||
expect(pinData).to.include.all.keys(expectedPinnedDataKeys);
|
||||
});
|
||||
|
||||
workflowPage.getters.clearExecutionDataButton().should('be.visible');
|
||||
@ -558,15 +558,12 @@ describe('Execution', () => {
|
||||
const expectedPinnedDataKeys = ['Webhook'];
|
||||
const expectedRunDataKeys = ['If', 'Webhook'];
|
||||
|
||||
expect(Object.keys(interception.request.body.pinData)).to.have.lengthOf(
|
||||
expectedPinnedDataKeys.length,
|
||||
);
|
||||
expect(interception.request.body.pinData).to.include.all.keys(expectedPinnedDataKeys);
|
||||
const { pinData, runData } = interception.request.body as Record<string, object>;
|
||||
expect(Object.keys(pinData)).to.have.lengthOf(expectedPinnedDataKeys.length);
|
||||
expect(pinData).to.include.all.keys(expectedPinnedDataKeys);
|
||||
|
||||
expect(Object.keys(interception.request.body.runData)).to.have.lengthOf(
|
||||
expectedRunDataKeys.length,
|
||||
);
|
||||
expect(interception.request.body.runData).to.include.all.keys(expectedRunDataKeys);
|
||||
expect(Object.keys(runData)).to.have.lengthOf(expectedRunDataKeys.length);
|
||||
expect(runData).to.include.all.keys(expectedRunDataKeys);
|
||||
});
|
||||
});
|
||||
|
||||
@ -617,6 +614,6 @@ describe('Execution', () => {
|
||||
.within(() => cy.get('.fa-check'))
|
||||
.should('exist');
|
||||
|
||||
workflowPage.getters.errorToast().should('contain', `Problem in node ‘Telegram‘`);
|
||||
workflowPage.getters.errorToast().should('contain', 'Problem in node ‘Telegram‘');
|
||||
});
|
||||
});
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { ICredentialType } from 'n8n-workflow';
|
||||
import {
|
||||
GMAIL_NODE_NAME,
|
||||
HTTP_REQUEST_NODE_NAME,
|
||||
@ -209,7 +210,7 @@ describe('Credentials', () => {
|
||||
req.headers['cache-control'] = 'no-cache, no-store';
|
||||
|
||||
req.on('response', (res) => {
|
||||
const credentials = res.body || [];
|
||||
const credentials: ICredentialType[] = res.body || [];
|
||||
|
||||
const index = credentials.findIndex((c) => c.name === 'slackOAuth2Api');
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { RouteHandler } from 'cypress/types/net-stubbing';
|
||||
import { WorkflowPage } from '../pages';
|
||||
import { WorkflowExecutionsTab } from '../pages/workflow-executions-tab';
|
||||
import type { RouteHandler } from 'cypress/types/net-stubbing';
|
||||
import executionOutOfMemoryServerResponse from '../fixtures/responses/execution-out-of-memory-server-response.json';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
@ -11,7 +11,7 @@ const executionsRefreshInterval = 4000;
|
||||
describe('Current Workflow Executions', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', `My test workflow`);
|
||||
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', 'My test workflow');
|
||||
});
|
||||
|
||||
it('should render executions tab correctly', () => {
|
||||
@ -58,8 +58,8 @@ describe('Current Workflow Executions', () => {
|
||||
});
|
||||
|
||||
it('should not redirect back to execution tab when slow request is not done before leaving the page', () => {
|
||||
const throttleResponse: RouteHandler = (req) => {
|
||||
return new Promise((resolve) => {
|
||||
const throttleResponse: RouteHandler = async (req) => {
|
||||
return await new Promise((resolve) => {
|
||||
setTimeout(() => resolve(req.continue()), 2000);
|
||||
});
|
||||
};
|
||||
@ -89,6 +89,7 @@ describe('Current Workflow Executions', () => {
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body') // Access the body of the iframe document
|
||||
.should('not.be.empty') // Ensure the body is not empty
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
.then(cy.wrap)
|
||||
.find('.el-notification:has(.el-notification--error)')
|
||||
.should('be.visible')
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { ICredentialType } from 'n8n-workflow';
|
||||
import { NodeCreator } from '../pages/features/node-creator';
|
||||
import CustomNodeFixture from '../fixtures/Custom_node.json';
|
||||
import { CredentialsModal, WorkflowPage } from '../pages';
|
||||
@ -33,9 +34,9 @@ describe('Community Nodes', () => {
|
||||
req.headers['cache-control'] = 'no-cache, no-store';
|
||||
|
||||
req.on('response', (res) => {
|
||||
const credentials = res.body || [];
|
||||
const credentials: ICredentialType[] = res.body || [];
|
||||
|
||||
credentials.push(CustomCredential);
|
||||
credentials.push(CustomCredential as ICredentialType);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -37,8 +37,9 @@ describe('ADO-2106 connections should be colored correctly for pinned data in ex
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body')
|
||||
.should('not.be.empty')
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
.then(cy.wrap)
|
||||
.find(`.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]`)
|
||||
.find('.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]')
|
||||
.should('have.class', 'success')
|
||||
.should('have.class', 'has-run')
|
||||
.should('not.have.class', 'pinned');
|
||||
@ -56,8 +57,9 @@ describe('ADO-2106 connections should be colored correctly for pinned data in ex
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument.body')
|
||||
.should('not.be.empty')
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
.then(cy.wrap)
|
||||
.find(`.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]`)
|
||||
.find('.jtk-connector[data-source-node="Webhook"][data-target-node="Set"]')
|
||||
.should('have.class', 'success')
|
||||
.should('have.class', 'has-run')
|
||||
.should('have.class', 'pinned');
|
||||
|
@ -7,7 +7,7 @@ describe('ADO-2230 NDV Pagination Reset', () => {
|
||||
it('should reset pagaintion if data size changes to less than current page', () => {
|
||||
// setup, load workflow with debughelper node with random seed
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('NDV-debug-generate-data.json', `Debug workflow`);
|
||||
cy.createFixtureWorkflow('NDV-debug-generate-data.json', 'Debug workflow');
|
||||
workflowPage.actions.openNode('DebugHelper');
|
||||
|
||||
// execute node outputting 10 pages, check output of first page
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { WorkflowPage, NDV } from '../pages';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { WorkflowPage, NDV } from '../pages';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { Interception } from 'cypress/types/net-stubbing';
|
||||
import { META_KEY } from '../constants';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { getPopper } from '../utils';
|
||||
import { Interception } from 'cypress/types/net-stubbing';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
|
||||
@ -91,7 +91,7 @@ describe('Canvas Actions', () => {
|
||||
|
||||
getPopper().should('be.visible');
|
||||
|
||||
workflowPage.actions.pickColor(2);
|
||||
workflowPage.actions.pickColor();
|
||||
|
||||
workflowPage.actions.toggleColorPalette();
|
||||
|
||||
@ -301,15 +301,6 @@ function stickyShouldBePositionedCorrectly(position: Position) {
|
||||
});
|
||||
}
|
||||
|
||||
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`);
|
||||
});
|
||||
}
|
||||
|
||||
function moveSticky(target: Position) {
|
||||
cy.drag('[data-test-id="sticky"]', [target.left, target.top], { abs: true });
|
||||
stickyShouldBePositionedCorrectly(target);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { MainSidebar } from './../pages/sidebar/main-sidebar';
|
||||
import generateOTPToken from 'cypress-otp';
|
||||
import { INSTANCE_OWNER, INSTANCE_ADMIN, BACKEND_BASE_URL } from '../constants';
|
||||
import { SigninPage } from '../pages';
|
||||
import { PersonalSettingsPage } from '../pages/settings-personal';
|
||||
import { MfaLoginPage } from '../pages/mfa-login';
|
||||
import generateOTPToken from 'cypress-otp';
|
||||
import { MainSidebar } from './../pages/sidebar/main-sidebar';
|
||||
|
||||
const MFA_SECRET = 'KVKFKRCPNZQUYMLXOVYDSQKJKZDTSRLD';
|
||||
|
||||
@ -36,14 +36,14 @@ const mainSidebar = new MainSidebar();
|
||||
|
||||
describe('Two-factor authentication', () => {
|
||||
beforeEach(() => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
void Cypress.session.clearAllSavedSessions();
|
||||
cy.request('POST', `${BACKEND_BASE_URL}/rest/e2e/reset`, {
|
||||
owner: user,
|
||||
members: [],
|
||||
admin,
|
||||
});
|
||||
cy.on('uncaught:exception', (err, runnable) => {
|
||||
expect(err.message).to.include('Not logged in');
|
||||
cy.on('uncaught:exception', (error) => {
|
||||
expect(error.message).to.include('Not logged in');
|
||||
return false;
|
||||
});
|
||||
cy.intercept('GET', '/rest/mfa/qr').as('getMfaQrCode');
|
||||
|
@ -188,7 +188,7 @@ describe('Editor zoom should work after route changes', () => {
|
||||
cy.enableFeature('workflowHistory');
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Lots_of_nodes.json', `Lots of nodes`);
|
||||
cy.createFixtureWorkflow('Lots_of_nodes.json', 'Lots of nodes');
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
});
|
||||
|
||||
|
@ -1,17 +1,4 @@
|
||||
import {
|
||||
AGENT_NODE_NAME,
|
||||
MANUAL_CHAT_TRIGGER_NODE_NAME,
|
||||
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME,
|
||||
AI_TOOL_CALCULATOR_NODE_NAME,
|
||||
AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME,
|
||||
AI_TOOL_CODE_NODE_NAME,
|
||||
AI_TOOL_WIKIPEDIA_NODE_NAME,
|
||||
BASIC_LLM_CHAIN_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
} from './../constants';
|
||||
import { createMockNodeExecutionData, runMockWorkflowExcution } from '../utils';
|
||||
import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils';
|
||||
import {
|
||||
addLanguageModelNodeToParent,
|
||||
addMemoryNodeToParent,
|
||||
@ -42,6 +29,19 @@ import {
|
||||
getManualChatModalLogsTree,
|
||||
sendManualChatMessage,
|
||||
} from '../composables/modals/chat-modal';
|
||||
import {
|
||||
AGENT_NODE_NAME,
|
||||
MANUAL_CHAT_TRIGGER_NODE_NAME,
|
||||
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
AI_MEMORY_WINDOW_BUFFER_MEMORY_NODE_NAME,
|
||||
AI_TOOL_CALCULATOR_NODE_NAME,
|
||||
AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME,
|
||||
AI_TOOL_CODE_NODE_NAME,
|
||||
AI_TOOL_WIKIPEDIA_NODE_NAME,
|
||||
BASIC_LLM_CHAIN_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
} from './../constants';
|
||||
|
||||
describe('Langchain Integration', () => {
|
||||
beforeEach(() => {
|
||||
@ -149,7 +149,7 @@ describe('Langchain Integration', () => {
|
||||
const outputMessage = 'Hi there! How can I assist you today?';
|
||||
|
||||
clickExecuteNode();
|
||||
runMockWorkflowExcution({
|
||||
runMockWorkflowExecution({
|
||||
trigger: () => sendManualChatMessage(inputMessage),
|
||||
runData: [
|
||||
createMockNodeExecutionData(BASIC_LLM_CHAIN_NODE_NAME, {
|
||||
@ -189,7 +189,7 @@ describe('Langchain Integration', () => {
|
||||
const outputMessage = 'Hi there! How can I assist you today?';
|
||||
|
||||
clickExecuteNode();
|
||||
runMockWorkflowExcution({
|
||||
runMockWorkflowExecution({
|
||||
trigger: () => sendManualChatMessage(inputMessage),
|
||||
runData: [
|
||||
createMockNodeExecutionData(AGENT_NODE_NAME, {
|
||||
@ -230,7 +230,7 @@ describe('Langchain Integration', () => {
|
||||
const inputMessage = 'Hello!';
|
||||
const outputMessage = 'Hi there! How can I assist you today?';
|
||||
|
||||
runMockWorkflowExcution({
|
||||
runMockWorkflowExecution({
|
||||
trigger: () => {
|
||||
sendManualChatMessage(inputMessage);
|
||||
},
|
||||
|
@ -6,7 +6,7 @@ const ndv = new NDV();
|
||||
describe('Node IO Filter', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Node_IO_filter.json', `Node IO filter`);
|
||||
cy.createFixtureWorkflow('Node_IO_filter.json', 'Node IO filter');
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
workflowPage.actions.executeWorkflow();
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WorkflowPage } from "../pages";
|
||||
import { WorkflowPage } from '../pages';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
@ -27,7 +27,7 @@ const VALID_NAMES = [
|
||||
];
|
||||
|
||||
describe('Personal Settings', () => {
|
||||
it ('should allow to change first and last name', () => {
|
||||
it('should allow to change first and last name', () => {
|
||||
cy.visit('/settings/personal');
|
||||
VALID_NAMES.forEach((name) => {
|
||||
cy.getByTestId('personal-data-form').find('input[name="firstName"]').clear().type(name[0]);
|
||||
|
@ -50,7 +50,7 @@ describe('Template credentials setup', () => {
|
||||
clickUseWorkflowButtonByTitle('Promote new Shopify products on Twitter and Telegram');
|
||||
|
||||
templateCredentialsSetupPage.getters
|
||||
.title(`Set up 'Promote new Shopify products on Twitter and Telegram' template`)
|
||||
.title("Set up 'Promote new Shopify products on Twitter and Telegram' template")
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
@ -58,7 +58,7 @@ describe('Template credentials setup', () => {
|
||||
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);
|
||||
|
||||
templateCredentialsSetupPage.getters
|
||||
.title(`Set up 'Promote new Shopify products on Twitter and Telegram' template`)
|
||||
.title("Set up 'Promote new Shopify products on Twitter and Telegram' template")
|
||||
.should('be.visible');
|
||||
|
||||
templateCredentialsSetupPage.getters
|
||||
|
@ -64,7 +64,7 @@ describe('Import workflow', () => {
|
||||
workflowPage.getters.workflowMenuItemImportFromFile().click();
|
||||
workflowPage.getters
|
||||
.workflowImportInput()
|
||||
.selectFile('cypress/fixtures/Test_workflow-actions_paste-data.json', { force: true });
|
||||
.selectFile('fixtures/Test_workflow-actions_paste-data.json', { force: true });
|
||||
cy.waitForLoad(false);
|
||||
workflowPage.actions.zoomToFit();
|
||||
workflowPage.getters.canvasNodes().should('have.length', 5);
|
||||
|
@ -1,4 +1,10 @@
|
||||
import { INSTANCE_ADMIN, INSTANCE_MEMBERS, INSTANCE_OWNER, MANUAL_TRIGGER_NODE_NAME, NOTION_NODE_NAME } from '../constants';
|
||||
import {
|
||||
INSTANCE_ADMIN,
|
||||
INSTANCE_MEMBERS,
|
||||
INSTANCE_OWNER,
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
NOTION_NODE_NAME,
|
||||
} from '../constants';
|
||||
import {
|
||||
WorkflowsPage,
|
||||
WorkflowPage,
|
||||
@ -260,7 +266,9 @@ describe('Projects', () => {
|
||||
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.getters
|
||||
.connectionParameter('Internal Integration Secret')
|
||||
.type('1234567890');
|
||||
credentialsModal.actions.setName('Notion account project 1');
|
||||
|
||||
cy.intercept('POST', '/rest/credentials').as('credentialSave');
|
||||
@ -283,7 +291,9 @@ describe('Projects', () => {
|
||||
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.getters
|
||||
.connectionParameter('Internal Integration Secret')
|
||||
.type('1234567890');
|
||||
credentialsModal.actions.setName('Notion account project 2');
|
||||
|
||||
credentialsModal.actions.save();
|
||||
@ -303,12 +313,14 @@ describe('Projects', () => {
|
||||
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.getters
|
||||
.connectionParameter('Internal Integration Secret')
|
||||
.type('1234567890');
|
||||
credentialsModal.actions.setName('Notion account personal project');
|
||||
|
||||
cy.intercept('POST', '/rest/credentials').as('credentialSave');
|
||||
credentialsModal.actions.save();
|
||||
cy.wait('@credentialSave')
|
||||
cy.wait('@credentialSave');
|
||||
credentialsModal.actions.close();
|
||||
|
||||
// Go to the first project and create a workflow
|
||||
@ -318,14 +330,22 @@ describe('Projects', () => {
|
||||
workflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect().find('li').should('have.length', 2).first().should('contain.text', 'Notion account project 1');
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 1');
|
||||
ndv.getters.backToCanvas().click();
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
cy.reload();
|
||||
workflowPage.getters.canvasNodeByName(NOTION_NODE_NAME).should('be.visible').dblclick();
|
||||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect().find('li').should('have.length', 2).first().should('contain.text', 'Notion account project 1');
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 1');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
||||
// Go to the second project and create a workflow
|
||||
@ -335,14 +355,22 @@ describe('Projects', () => {
|
||||
workflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect().find('li').should('have.length', 2).first().should('contain.text', 'Notion account project 2');
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 2');
|
||||
ndv.getters.backToCanvas().click();
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
cy.reload();
|
||||
workflowPage.getters.canvasNodeByName(NOTION_NODE_NAME).should('be.visible').dblclick();
|
||||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect().find('li').should('have.length', 2).first().should('contain.text', 'Notion account project 2');
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 2');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
||||
// Go to the Home project and create a workflow
|
||||
@ -356,15 +384,22 @@ describe('Projects', () => {
|
||||
workflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect().find('li').should('have.length', 2).first().should('contain.text', 'Notion account personal project');
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account personal project');
|
||||
ndv.getters.backToCanvas().click();
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
cy.reload();
|
||||
workflowPage.getters.canvasNodeByName(NOTION_NODE_NAME).should('be.visible').dblclick();
|
||||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect().find('li').should('have.length', 2).first().should('contain.text', 'Notion account personal project');
|
||||
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account personal project');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -12,7 +12,6 @@ describe('Editors', () => {
|
||||
});
|
||||
|
||||
describe('SQL Editor', () => {
|
||||
|
||||
it('should preserve changes when opening-closing Postgres node', () => {
|
||||
workflowPage.actions.addInitialNodeToCanvas('Postgres', {
|
||||
action: 'Execute a SQL query',
|
||||
@ -26,7 +25,11 @@ describe('Editors', () => {
|
||||
.type('{esc}');
|
||||
ndv.actions.close();
|
||||
workflowPage.actions.openNode('Postgres');
|
||||
ndv.getters.sqlEditorContainer().find('.cm-content').type('{end} LIMIT 10', { delay: TYPING_DELAY }).type('{esc}');
|
||||
ndv.getters
|
||||
.sqlEditorContainer()
|
||||
.find('.cm-content')
|
||||
.type('{end} LIMIT 10', { delay: TYPING_DELAY })
|
||||
.type('{esc}');
|
||||
ndv.actions.close();
|
||||
workflowPage.actions.openNode('Postgres');
|
||||
ndv.getters.sqlEditorContainer().should('contain', 'SELECT * FROM `testTable` LIMIT 10');
|
||||
@ -126,7 +129,11 @@ describe('Editors', () => {
|
||||
.type('{esc}');
|
||||
ndv.actions.close();
|
||||
workflowPage.actions.openNode('HTML');
|
||||
ndv.getters.htmlEditorContainer().find('.cm-content').type(`{end}${TEST_ELEMENT_P}`, { delay: TYPING_DELAY, force: true }).type('{esc}');
|
||||
ndv.getters
|
||||
.htmlEditorContainer()
|
||||
.find('.cm-content')
|
||||
.type(`{end}${TEST_ELEMENT_P}`, { delay: TYPING_DELAY, force: true })
|
||||
.type('{esc}');
|
||||
ndv.actions.close();
|
||||
workflowPage.actions.openNode('HTML');
|
||||
ndv.getters.htmlEditorContainer().should('contain', TEST_ELEMENT_H1);
|
||||
|
@ -67,7 +67,7 @@ describe('NDV', () => {
|
||||
});
|
||||
|
||||
it('should disconect Switch outputs if rules order was changed', () => {
|
||||
cy.createFixtureWorkflow('NDV-test-switch_reorder.json', `NDV test switch reorder`);
|
||||
cy.createFixtureWorkflow('NDV-test-switch_reorder.json', 'NDV test switch reorder');
|
||||
workflowPage.actions.zoomToFit();
|
||||
|
||||
workflowPage.actions.executeWorkflow();
|
||||
@ -305,7 +305,7 @@ describe('NDV', () => {
|
||||
it('should display parameter hints correctly', () => {
|
||||
workflowPage.actions.visit();
|
||||
|
||||
cy.createFixtureWorkflow('Test_workflow_3.json', `My test workflow`);
|
||||
cy.createFixtureWorkflow('Test_workflow_3.json', 'My test workflow');
|
||||
workflowPage.actions.openNode('Set1');
|
||||
|
||||
ndv.actions.typeIntoParameterInput('value', '='); // switch to expressions
|
||||
@ -333,7 +333,7 @@ describe('NDV', () => {
|
||||
}
|
||||
ndv.getters.parameterInput('name').click(); // remove focus from input, hide expression preview
|
||||
|
||||
ndv.actions.validateExpressionPreview('value', output || input);
|
||||
ndv.actions.validateExpressionPreview('value', output ?? input);
|
||||
ndv.getters.parameterInput('value').clear();
|
||||
});
|
||||
});
|
||||
@ -436,7 +436,7 @@ describe('NDV', () => {
|
||||
}
|
||||
|
||||
it('should traverse floating nodes with mouse', () => {
|
||||
cy.createFixtureWorkflow('Floating_Nodes.json', `Floating Nodes`);
|
||||
cy.createFixtureWorkflow('Floating_Nodes.json', 'Floating Nodes');
|
||||
workflowPage.getters.canvasNodes().first().dblclick();
|
||||
getFloatingNodeByPosition('inputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
@ -482,7 +482,7 @@ describe('NDV', () => {
|
||||
});
|
||||
|
||||
it('should traverse floating nodes with keyboard', () => {
|
||||
cy.createFixtureWorkflow('Floating_Nodes.json', `Floating Nodes`);
|
||||
cy.createFixtureWorkflow('Floating_Nodes.json', 'Floating Nodes');
|
||||
workflowPage.getters.canvasNodes().first().dblclick();
|
||||
getFloatingNodeByPosition('inputMain').should('not.exist');
|
||||
getFloatingNodeByPosition('outputMain').should('exist');
|
||||
@ -597,7 +597,7 @@ describe('NDV', () => {
|
||||
});
|
||||
|
||||
it('Should render xml and html tags as strings and can search', () => {
|
||||
cy.createFixtureWorkflow('Test_workflow_xml_output.json', `test`);
|
||||
cy.createFixtureWorkflow('Test_workflow_xml_output.json', 'test');
|
||||
|
||||
workflowPage.actions.executeWorkflow();
|
||||
|
||||
@ -741,7 +741,7 @@ describe('NDV', () => {
|
||||
it('should allow selecting item for expressions', () => {
|
||||
workflowPage.actions.visit();
|
||||
|
||||
cy.createFixtureWorkflow('Test_workflow_3.json', `My test workflow`);
|
||||
cy.createFixtureWorkflow('Test_workflow_3.json', 'My test workflow');
|
||||
workflowPage.actions.openNode('Set');
|
||||
|
||||
ndv.actions.typeIntoParameterInput('value', '='); // switch to expressions
|
||||
|
@ -338,7 +338,6 @@ describe('Workflow Actions', () => {
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('{enter}');
|
||||
WorkflowPage.getters.successToast().should('not.exist');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Menu entry Push To Git', () => {
|
||||
|
@ -8,7 +8,7 @@ describe('Expression editor modal', () => {
|
||||
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', () => {
|
||||
|
28
cypress/package.json
Normal file
28
cypress/package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "n8n-cypress",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"cypress:install": "cypress install",
|
||||
"test:e2e:ui": "scripts/run-e2e.js ui",
|
||||
"test:e2e:dev": "scripts/run-e2e.js dev",
|
||||
"test:e2e:all": "scripts/run-e2e.js all",
|
||||
"format": "prettier --write . --ignore-path ../.prettierignore",
|
||||
"lint": "eslint . --quiet",
|
||||
"lintfix": "eslint . --fix",
|
||||
"start": "cd ..; pnpm start"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/uuid": "^8.3.2",
|
||||
"n8n-workflow": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ngneat/falso": "^6.4.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^13.6.2",
|
||||
"cypress-otp": "^1.0.3",
|
||||
"cypress-real-events": "^1.11.0",
|
||||
"start-server-and-test": "^2.0.3",
|
||||
"uuid": "8.3.2"
|
||||
}
|
||||
}
|
@ -4,5 +4,6 @@ export class BannerStack extends BasePage {
|
||||
getters = {
|
||||
banner: () => cy.getByTestId('banner-stack'),
|
||||
};
|
||||
|
||||
actions = {};
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { IE2ETestPage, IE2ETestPageElement } from '../types';
|
||||
import type { IE2ETestPage } from '../types';
|
||||
|
||||
export class BasePage implements IE2ETestPage {
|
||||
getters: Record<string, IE2ETestPageElement> = {};
|
||||
actions: Record<string, (...args: any[]) => void> = {};
|
||||
getters = {};
|
||||
|
||||
actions = {};
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { BasePage } from './base';
|
||||
|
||||
export class CredentialsPage extends BasePage {
|
||||
url = '/home/credentials';
|
||||
|
||||
getters = {
|
||||
emptyListCreateCredentialButton: () => cy.getByTestId('empty-resources-list').find('button'),
|
||||
createCredentialButton: () => cy.getByTestId('resources-list-add'),
|
||||
@ -23,6 +24,7 @@ export class CredentialsPage extends BasePage {
|
||||
filtersTrigger: () => cy.getByTestId('resources-list-filters-trigger'),
|
||||
filtersDropdown: () => cy.getByTestId('resources-list-filters-dropdown'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
search: (searchString: string) => {
|
||||
const searchInput = this.getters.searchInput();
|
||||
|
@ -7,15 +7,14 @@ export function vistDemoPage(theme?: 'dark' | 'light') {
|
||||
cy.visit('/workflows/demo' + query);
|
||||
cy.waitForLoad();
|
||||
cy.window().then((win) => {
|
||||
// @ts-ignore
|
||||
win.preventNodeViewBeforeUnload = true;
|
||||
});
|
||||
}
|
||||
|
||||
export function importWorkflow(workflow: object) {
|
||||
const OPEN_WORKFLOW = {command: 'openWorkflow', workflow};
|
||||
cy.window().then($window => {
|
||||
const OPEN_WORKFLOW = { command: 'openWorkflow', workflow };
|
||||
cy.window().then(($window) => {
|
||||
const message = JSON.stringify(OPEN_WORKFLOW);
|
||||
$window.postMessage(message, '*')
|
||||
$window.postMessage(message, '*');
|
||||
});
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { BasePage } from '../base';
|
||||
import { INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
export class NodeCreator extends BasePage {
|
||||
url = '/workflow/new';
|
||||
|
||||
getters = {
|
||||
plusButton: () => cy.getByTestId('node-creator-plus-button'),
|
||||
canvasAddButton: () => cy.getByTestId('canvas-add-button'),
|
||||
@ -25,6 +25,7 @@ export class NodeCreator extends BasePage {
|
||||
expandedCategories: () =>
|
||||
this.getters.creatorItem().find('>div').filter('.active').invoke('text'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
openNodeCreator: () => {
|
||||
this.getters.plusButton().click();
|
||||
@ -33,31 +34,5 @@ export class NodeCreator extends BasePage {
|
||||
selectNode: (displayName: string) => {
|
||||
this.getters.getCreatorItem(displayName).click();
|
||||
},
|
||||
toggleCategory: (category: string) => {
|
||||
this.getters.getCreatorItem(category).click();
|
||||
},
|
||||
categorizeNodes: (nodes: INodeTypeDescription[]) => {
|
||||
const categorizedNodes = nodes.reduce((acc, node) => {
|
||||
const categories = (node?.codex?.categories || []).map((category: string) =>
|
||||
category.trim(),
|
||||
);
|
||||
|
||||
categories.forEach((category: { [key: string]: INodeTypeDescription[] }) => {
|
||||
// Node creator should show only the latest version of a node
|
||||
const newerVersion = nodes.find(
|
||||
(n: INodeTypeDescription) =>
|
||||
n.name === node.name && (n.version > node.version || Array.isArray(n.version)),
|
||||
);
|
||||
|
||||
if (acc[category] === undefined) {
|
||||
acc[category] = [];
|
||||
}
|
||||
acc[category].push(newerVersion ?? node);
|
||||
});
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return categorizedNodes;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { WorkflowsPage } from './workflows';
|
||||
|
||||
export class MfaLoginPage extends BasePage {
|
||||
url = '/mfa';
|
||||
|
||||
getters = {
|
||||
form: () => cy.getByTestId('mfa-login-form'),
|
||||
token: () => cy.getByTestId('token'),
|
||||
|
@ -28,6 +28,7 @@ export class CredentialsModal extends BasePage {
|
||||
usersSelect: () => cy.getByTestId('project-sharing-select').filter(':visible'),
|
||||
testSuccessTag: () => cy.getByTestId('credentials-config-container-test-success'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
addUser: (email: string) => {
|
||||
this.getters.usersSelect().click();
|
||||
@ -45,7 +46,7 @@ export class CredentialsModal extends BasePage {
|
||||
if (test) cy.wait('@testCredential');
|
||||
this.getters.saveButton().should('contain.text', 'Saved');
|
||||
},
|
||||
saveSharing: (test = false) => {
|
||||
saveSharing: () => {
|
||||
cy.intercept('PUT', '/rest/credentials/*/share').as('shareCredential');
|
||||
this.getters.saveButton().click({ force: true });
|
||||
cy.wait('@shareCredential');
|
||||
|
@ -8,6 +8,7 @@ export class MessageBox extends BasePage {
|
||||
confirm: () => this.getters.modal().find('.btn--confirm').first(),
|
||||
cancel: () => this.getters.modal().find('.btn--cancel').first(),
|
||||
};
|
||||
|
||||
actions = {
|
||||
confirm: () => {
|
||||
this.getters.confirm().click({ force: true });
|
||||
|
@ -7,6 +7,7 @@ export class WorkflowSharingModal extends BasePage {
|
||||
saveButton: () => cy.getByTestId('workflow-sharing-modal-save-button'),
|
||||
closeButton: () => this.getters.modal().find('.el-dialog__close').first(),
|
||||
};
|
||||
|
||||
actions = {
|
||||
addUser: (email: string) => {
|
||||
this.getters.usersSelect().click();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { BasePage } from './base';
|
||||
import { getVisiblePopper, getVisibleSelect } from '../utils';
|
||||
import { BasePage } from './base';
|
||||
|
||||
export class NDV extends BasePage {
|
||||
getters = {
|
||||
@ -158,12 +158,9 @@ export class NDV extends BasePage {
|
||||
this.getters.pinnedDataEditor().click();
|
||||
this.getters
|
||||
.pinnedDataEditor()
|
||||
.type(
|
||||
`{selectall}{backspace}${pinnedData.replace(new RegExp('{', 'g'), '{{}')}`,
|
||||
{
|
||||
delay: 0,
|
||||
},
|
||||
);
|
||||
.type(`{selectall}{backspace}${pinnedData.replace(new RegExp('{', 'g'), '{{}')}`, {
|
||||
delay: 0,
|
||||
});
|
||||
|
||||
this.actions.savePinnedData();
|
||||
},
|
||||
@ -179,7 +176,7 @@ export class NDV extends BasePage {
|
||||
this.actions.savePinnedData();
|
||||
},
|
||||
clearParameterInput: (parameterName: string) => {
|
||||
this.getters.parameterInput(parameterName).type(`{selectall}{backspace}`);
|
||||
this.getters.parameterInput(parameterName).type('{selectall}{backspace}');
|
||||
},
|
||||
typeIntoParameterInput: (
|
||||
parameterName: string,
|
||||
@ -188,7 +185,7 @@ export class NDV extends BasePage {
|
||||
) => {
|
||||
this.getters.parameterInput(parameterName).type(content, opts);
|
||||
},
|
||||
selectOptionInParameterDropdown: (parameterName: string, content: string) => {
|
||||
selectOptionInParameterDropdown: (_: string, content: string) => {
|
||||
getVisibleSelect().find('.option-headline').contains(content).click();
|
||||
},
|
||||
rename: (newName: string) => {
|
||||
@ -286,7 +283,7 @@ export class NDV extends BasePage {
|
||||
parseSpecialCharSequences: false,
|
||||
delay,
|
||||
});
|
||||
this.actions.validateExpressionPreview(fieldName, `node doesn't exist`);
|
||||
this.actions.validateExpressionPreview(fieldName, "node doesn't exist");
|
||||
},
|
||||
openSettings: () => {
|
||||
this.getters.nodeSettingsTab().click();
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { BasePage } from './base';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import { BasePage } from './base';
|
||||
|
||||
export class SettingsLogStreamingPage extends BasePage {
|
||||
url = '/settings/log-streaming';
|
||||
|
||||
getters = {
|
||||
getActionBoxUnlicensed: () => cy.getByTestId('action-box-unlicensed'),
|
||||
getActionBoxLicensed: () => cy.getByTestId('action-box-licensed'),
|
||||
@ -17,6 +18,7 @@ export class SettingsLogStreamingPage extends BasePage {
|
||||
getDestinationDeleteButton: () => cy.getByTestId('destination-delete-button'),
|
||||
getDestinationCards: () => cy.getByTestId('destination-card'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
clickContactUs: () => this.getters.getContactUsButton().click(),
|
||||
clickAddFirstDestination: () => this.getters.getAddFirstDestinationButton().click(),
|
||||
|
@ -1,13 +1,14 @@
|
||||
import generateOTPToken from 'cypress-otp';
|
||||
import { ChangePasswordModal } from './modals/change-password-modal';
|
||||
import { MfaSetupModal } from './modals/mfa-setup-modal';
|
||||
import { BasePage } from './base';
|
||||
import generateOTPToken from 'cypress-otp';
|
||||
|
||||
const changePasswordModal = new ChangePasswordModal();
|
||||
const mfaSetupModal = new MfaSetupModal();
|
||||
|
||||
export class PersonalSettingsPage extends BasePage {
|
||||
url = '/settings/personal';
|
||||
|
||||
secret = '';
|
||||
|
||||
getters = {
|
||||
@ -23,6 +24,7 @@ export class PersonalSettingsPage extends BasePage {
|
||||
themeSelector: () => cy.getByTestId('theme-select'),
|
||||
selectOptionsVisible: () => cy.get('.el-select-dropdown:visible .el-select-dropdown__item'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
changeTheme: (theme: 'System default' | 'Dark' | 'Light') => {
|
||||
this.getters.themeSelector().click();
|
||||
|
@ -2,6 +2,8 @@ import { BasePage } from './base';
|
||||
|
||||
export class SettingsUsagePage extends BasePage {
|
||||
url = '/settings/usage';
|
||||
|
||||
getters = {};
|
||||
|
||||
actions = {};
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ const settingsSidebar = new SettingsSidebar();
|
||||
|
||||
export class SettingsUsersPage extends BasePage {
|
||||
url = '/settings/users';
|
||||
|
||||
getters = {
|
||||
setUpOwnerButton: () => cy.getByTestId('action-box').find('button').first(),
|
||||
inviteButton: () => cy.getByTestId('settings-users-invite-button').last(),
|
||||
@ -34,6 +35,7 @@ export class SettingsUsersPage extends BasePage {
|
||||
deleteUserButton: () => this.getters.confirmDeleteModal().find('button:contains("Delete")'),
|
||||
deleteDataInput: () => cy.getByTestId('delete-data-input').find('input').first(),
|
||||
};
|
||||
|
||||
actions = {
|
||||
goToOwnerSetup: () => this.getters.setUpOwnerButton().click(),
|
||||
loginAndVisit: (email: string, password: string, isOwner: boolean) => {
|
||||
|
@ -2,8 +2,10 @@ import { BasePage } from './base';
|
||||
|
||||
export class SettingsPage extends BasePage {
|
||||
url = '/settings';
|
||||
|
||||
getters = {
|
||||
menuItems: () => cy.getByTestId('menu-item'),
|
||||
};
|
||||
|
||||
actions = {};
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { BasePage } from '../base';
|
||||
import { WorkflowsPage } from '../workflows';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
|
||||
export class MainSidebar extends BasePage {
|
||||
getters = {
|
||||
menuItem: (id: string) => cy.getByTestId('menu-item').get('#' + id),
|
||||
@ -16,6 +14,7 @@ export class MainSidebar extends BasePage {
|
||||
userMenu: () => cy.get('div[class="action-dropdown-container"]'),
|
||||
logo: () => cy.getByTestId('n8n-logo'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
goToSettings: () => {
|
||||
this.getters.settings().should('be.visible');
|
||||
|
@ -6,6 +6,7 @@ export class SettingsSidebar extends BasePage {
|
||||
users: () => this.getters.menuItem('settings-users'),
|
||||
back: () => cy.getByTestId('settings-back'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
goToUsers: () => {
|
||||
this.getters.users().should('be.visible');
|
||||
|
@ -4,6 +4,7 @@ import { WorkflowsPage } from './workflows';
|
||||
|
||||
export class SigninPage extends BasePage {
|
||||
url = '/signin';
|
||||
|
||||
getters = {
|
||||
form: () => cy.getByTestId('auth-form'),
|
||||
email: () => cy.getByTestId('email'),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { CredentialsModal, MessageBox } from './modals';
|
||||
import * as formStep from '../composables/setup-template-form-step';
|
||||
import { overrideFeatureFlag } from '../composables/featureFlags';
|
||||
import { CredentialsModal, MessageBox } from './modals';
|
||||
|
||||
export type TemplateTestData = {
|
||||
id: number;
|
||||
|
@ -17,15 +17,18 @@ export class TemplateWorkflowPage extends BasePage {
|
||||
this.getters.useTemplateButton().click();
|
||||
},
|
||||
|
||||
openTemplate: (template: {
|
||||
workflow: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
user: { username: string };
|
||||
image: { id: number; url: string }[];
|
||||
};
|
||||
}, templateHost: string) => {
|
||||
openTemplate: (
|
||||
template: {
|
||||
workflow: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
user: { username: string };
|
||||
image: Array<{ id: number; url: string }>;
|
||||
};
|
||||
},
|
||||
templateHost: string,
|
||||
) => {
|
||||
cy.intercept('GET', `${templateHost}/api/templates/workflows/${template.workflow.id}`, {
|
||||
statusCode: 200,
|
||||
body: template,
|
||||
|
@ -3,6 +3,7 @@ import Chainable = Cypress.Chainable;
|
||||
|
||||
export class VariablesPage extends BasePage {
|
||||
url = '/variables';
|
||||
|
||||
getters = {
|
||||
unavailableResourcesList: () => cy.getByTestId('unavailable-resources-list'),
|
||||
emptyResourcesList: () => cy.getByTestId('empty-resources-list'),
|
||||
@ -14,7 +15,7 @@ export class VariablesPage extends BasePage {
|
||||
createVariableButton: () => cy.getByTestId('resources-list-add'),
|
||||
variablesRows: () => cy.getByTestId('variables-row'),
|
||||
variablesEditableRows: () =>
|
||||
cy.getByTestId('variables-row').filter((index, row) => !!row.querySelector('input')),
|
||||
cy.getByTestId('variables-row').filter((_, row) => !!row.querySelector('input')),
|
||||
variableRow: (key: string) =>
|
||||
this.getters.variablesRows().contains(key).parents('[data-test-id="variables-row"]'),
|
||||
editableRowCancelButton: (row: Chainable<JQuery<HTMLElement>>) =>
|
||||
|
@ -2,6 +2,7 @@ import { BasePage } from './base';
|
||||
|
||||
export class WorkerViewPage extends BasePage {
|
||||
url = '/settings/workers';
|
||||
|
||||
getters = {
|
||||
workerCards: () => cy.getByTestId('worker-card'),
|
||||
workerCard: (workerId: string) => this.getters.workerCards().contains(workerId),
|
||||
|
@ -26,6 +26,7 @@ export class WorkflowExecutionsTab extends BasePage {
|
||||
executionDebugButton: () => cy.getByTestId('execution-debug-button'),
|
||||
workflowExecutionPreviewIframe: () => cy.getByTestId('workflow-preview-iframe'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
toggleNodeEnabled: (nodeName: string) => {
|
||||
workflowPage.getters.canvasNodeByName(nodeName).click();
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BasePage } from "./base";
|
||||
import { BasePage } from './base';
|
||||
|
||||
export class WorkflowHistoryPage extends BasePage {
|
||||
getters = {
|
||||
workflowHistoryCloseButton: () => cy.getByTestId('workflow-history-close-button'),
|
||||
}
|
||||
getters = {
|
||||
workflowHistoryCloseButton: () => cy.getByTestId('workflow-history-close-button'),
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { META_KEY } from '../constants';
|
||||
import { BasePage } from './base';
|
||||
import { getVisibleSelect } from '../utils';
|
||||
import { BasePage } from './base';
|
||||
import { NodeCreator } from './features/node-creator';
|
||||
|
||||
type CyGetOptions = Parameters<(typeof cy)['get']>[1];
|
||||
@ -8,6 +8,7 @@ type CyGetOptions = Parameters<(typeof cy)['get']>[1];
|
||||
const nodeCreator = new NodeCreator();
|
||||
export class WorkflowPage extends BasePage {
|
||||
url = '/workflow/new';
|
||||
|
||||
getters = {
|
||||
workflowNameInputContainer: () => cy.getByTestId('workflow-name-input', { timeout: 5000 }),
|
||||
workflowNameInput: () =>
|
||||
@ -134,12 +135,12 @@ export class WorkflowPage extends BasePage {
|
||||
colors: () => cy.getByTestId('color'),
|
||||
contextMenuAction: (action: string) => cy.getByTestId(`context-menu-item-${action}`),
|
||||
};
|
||||
|
||||
actions = {
|
||||
visit: (preventNodeViewUnload = true) => {
|
||||
cy.visit(this.url);
|
||||
cy.waitForLoad();
|
||||
cy.window().then((win) => {
|
||||
// @ts-ignore
|
||||
win.preventNodeViewBeforeUnload = preventNodeViewUnload;
|
||||
});
|
||||
},
|
||||
@ -329,15 +330,17 @@ export class WorkflowPage extends BasePage {
|
||||
cy.getByTestId('zoom-to-fit').click();
|
||||
},
|
||||
pinchToZoom: (steps: number, mode: 'zoomIn' | 'zoomOut' = 'zoomIn') => {
|
||||
// Pinch-to-zoom simulates a 'wheel' event with ctrlKey: true (same as zooming by scrolling)
|
||||
this.getters.nodeViewBackground().trigger('wheel', {
|
||||
force: true,
|
||||
bubbles: true,
|
||||
ctrlKey: true,
|
||||
pageX: cy.window().innerWidth / 2,
|
||||
pageY: cy.window().innerHeight / 2,
|
||||
deltaMode: 1,
|
||||
deltaY: mode === 'zoomOut' ? steps : -steps,
|
||||
cy.window().then((win) => {
|
||||
// Pinch-to-zoom simulates a 'wheel' event with ctrlKey: true (same as zooming by scrolling)
|
||||
this.getters.nodeViewBackground().trigger('wheel', {
|
||||
force: true,
|
||||
bubbles: true,
|
||||
ctrlKey: true,
|
||||
pageX: win.innerWidth / 2,
|
||||
pageY: win.innerHeight / 2,
|
||||
deltaMode: 1,
|
||||
deltaY: mode === 'zoomOut' ? steps : -steps,
|
||||
});
|
||||
});
|
||||
},
|
||||
hitUndo: () => {
|
||||
@ -388,11 +391,7 @@ export class WorkflowPage extends BasePage {
|
||||
|
||||
this.actions.addNodeToCanvas(newNodeName, false, false, action);
|
||||
},
|
||||
deleteNodeBetweenNodes: (
|
||||
sourceNodeName: string,
|
||||
targetNodeName: string,
|
||||
newNodeName: string,
|
||||
) => {
|
||||
deleteNodeBetweenNodes: (sourceNodeName: string, targetNodeName: string) => {
|
||||
this.getters.getConnectionBetweenNodes(sourceNodeName, targetNodeName).first().realHover();
|
||||
this.getters
|
||||
.getConnectionActionsBetweenNodes(sourceNodeName, targetNodeName)
|
||||
@ -415,7 +414,7 @@ export class WorkflowPage extends BasePage {
|
||||
.find('[data-test-id="change-sticky-color"]')
|
||||
.click({ force: true });
|
||||
},
|
||||
pickColor: (index: number) => {
|
||||
pickColor: () => {
|
||||
this.getters.colors().eq(1).click();
|
||||
},
|
||||
editSticky: (content: string) => {
|
||||
|
@ -2,6 +2,7 @@ import { BasePage } from './base';
|
||||
|
||||
export class WorkflowsPage extends BasePage {
|
||||
url = '/home/workflows';
|
||||
|
||||
getters = {
|
||||
newWorkflowButtonCard: () => cy.getByTestId('new-workflow-card'),
|
||||
newWorkflowTemplateCard: () => cy.getByTestId('new-workflow-template-card'),
|
||||
|
@ -16,9 +16,7 @@ Cypress.Commands.add('createFixtureWorkflow', (fixtureKey, workflowName) => {
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
// We need to force the click because the input is hidden
|
||||
workflowPage.getters
|
||||
.workflowImportInput()
|
||||
.selectFile(`cypress/fixtures/${fixtureKey}`, { force: true });
|
||||
workflowPage.getters.workflowImportInput().selectFile(`fixtures/${fixtureKey}`, { force: true });
|
||||
|
||||
cy.waitForLoad(false);
|
||||
workflowPage.actions.setWorkflowName(workflowName);
|
||||
@ -46,7 +44,7 @@ Cypress.Commands.add('waitForLoad', (waitForIntercepts = true) => {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('signin', ({ email, password }) => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
void Cypress.session.clearAllSavedSessions();
|
||||
cy.session([email, password], () =>
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
@ -128,7 +126,7 @@ Cypress.Commands.add('paste', { prevSubject: true }, (selector, pastePayload) =>
|
||||
});
|
||||
|
||||
Cypress.Commands.add('drag', (selector, pos, options) => {
|
||||
const index = options?.index || 0;
|
||||
const index = options?.index ?? 0;
|
||||
const [xDiff, yDiff] = pos;
|
||||
const element = typeof selector === 'string' ? cy.get(selector).eq(index) : selector;
|
||||
element.should('exist');
|
||||
|
@ -4,8 +4,8 @@ import './commands';
|
||||
before(() => {
|
||||
cy.resetDatabase();
|
||||
|
||||
Cypress.on('uncaught:exception', (err) => {
|
||||
return !err.message.includes('ResizeObserver');
|
||||
Cypress.on('uncaught:exception', (error) => {
|
||||
return !error.message.includes('ResizeObserver');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Load type definitions that come with Cypress module
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import { Interception } from 'cypress/types/net-stubbing';
|
||||
import type { Interception } from 'cypress/types/net-stubbing';
|
||||
|
||||
interface SigninPayload {
|
||||
email: string;
|
||||
@ -18,7 +18,7 @@ declare global {
|
||||
config(key: keyof SuiteConfigOverrides): boolean;
|
||||
getByTestId(
|
||||
selector: string,
|
||||
...args: (Partial<Loggable & Timeoutable & Withinable & Shadow> | undefined)[]
|
||||
...args: Array<Partial<Loggable & Timeoutable & Withinable & Shadow> | undefined>
|
||||
): Chainable<JQuery<HTMLElement>>;
|
||||
findChildByTestId(childTestId: string): Chainable<JQuery<HTMLElement>>;
|
||||
createFixtureWorkflow(fixtureKey: string, workflowName: string): void;
|
||||
@ -36,7 +36,7 @@ declare global {
|
||||
readClipboard(): Chainable<string>;
|
||||
paste(pastePayload: string): void;
|
||||
drag(
|
||||
selector: string | Cypress.Chainable<JQuery<HTMLElement>>,
|
||||
selector: string | Chainable<JQuery<HTMLElement>>,
|
||||
target: [number, number],
|
||||
options?: { abs?: boolean; index?: number; realMouse?: boolean; clickToFinish?: boolean },
|
||||
): void;
|
||||
@ -45,8 +45,11 @@ declare global {
|
||||
shouldNotHaveConsoleErrors(): void;
|
||||
window(): Chainable<
|
||||
AUTWindow & {
|
||||
innerWidth: number;
|
||||
innerHeight: number;
|
||||
preventNodeViewBeforeUnload?: boolean;
|
||||
featureFlags: {
|
||||
override: (feature: string, value: any) => void;
|
||||
override: (feature: string, value: unknown) => void;
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
@ -7,5 +7,6 @@
|
||||
"types": ["cypress", "node"]
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["**/dist/**/*", "**/node_modules/**/*"]
|
||||
"exclude": ["**/dist/**/*", "**/node_modules/**/*"],
|
||||
"references": [{ "path": "../packages/workflow/tsconfig.build.json" }]
|
||||
}
|
||||
|
@ -1,12 +1,24 @@
|
||||
export type IE2ETestPageElement = (
|
||||
...args: any[]
|
||||
...args: unknown[]
|
||||
) =>
|
||||
| Cypress.Chainable<JQuery<HTMLElement>>
|
||||
| Cypress.Chainable<JQuery<HTMLInputElement>>
|
||||
| Cypress.Chainable<JQuery<HTMLButtonElement>>;
|
||||
|
||||
type Getter = IE2ETestPageElement | ((key: string | number) => IE2ETestPageElement);
|
||||
|
||||
export interface IE2ETestPage {
|
||||
url?: string;
|
||||
getters: Record<string, IE2ETestPageElement>;
|
||||
actions: Record<string, (...args: any[]) => void>;
|
||||
getters: Record<string, Getter>;
|
||||
actions: Record<string, (...args: unknown[]) => void>;
|
||||
}
|
||||
|
||||
interface Execution {
|
||||
workflowId: string;
|
||||
}
|
||||
|
||||
export interface ExecutionResponse {
|
||||
data: {
|
||||
results: Execution[];
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ITaskData } from '../../packages/workflow/src';
|
||||
import { IPinData } from '../../packages/workflow';
|
||||
import type { IDataObject, IPinData, ITaskData, ITaskDataConnections } from 'n8n-workflow';
|
||||
import { clickExecuteWorkflowButton } from '../composables/workflow';
|
||||
|
||||
export function createMockNodeExecutionData(
|
||||
@ -10,7 +9,7 @@ export function createMockNodeExecutionData(
|
||||
executionStatus = 'success',
|
||||
jsonData,
|
||||
...rest
|
||||
}: Partial<ITaskData> & { jsonData?: Record<string, object> },
|
||||
}: Partial<ITaskData> & { jsonData?: Record<string, IDataObject> },
|
||||
): Record<string, ITaskData> {
|
||||
return {
|
||||
[name]: {
|
||||
@ -29,7 +28,7 @@ export function createMockNodeExecutionData(
|
||||
];
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
}, {} as ITaskDataConnections)
|
||||
: data,
|
||||
source: [null],
|
||||
...rest,
|
||||
@ -75,7 +74,7 @@ export function createMockWorkflowExecutionData({
|
||||
};
|
||||
}
|
||||
|
||||
export function runMockWorkflowExcution({
|
||||
export function runMockWorkflowExecution({
|
||||
trigger,
|
||||
lastNodeExecuted,
|
||||
runData,
|
||||
@ -105,7 +104,7 @@ export function runMockWorkflowExcution({
|
||||
|
||||
cy.wait('@runWorkflow');
|
||||
|
||||
const resolvedRunData = {};
|
||||
const resolvedRunData: Record<string, ITaskData> = {};
|
||||
runData.forEach((nodeExecution) => {
|
||||
const nodeName = Object.keys(nodeExecution)[0];
|
||||
const nodeRunData = nodeExecution[nodeName];
|
||||
|
13
package.json
13
package.json
@ -31,23 +31,13 @@
|
||||
"test:frontend": "pnpm --filter=@n8n/chat --filter=@n8n/codemirror-lang --filter=n8n-design-system --filter=n8n-editor-ui test",
|
||||
"watch": "turbo run watch --parallel",
|
||||
"webhook": "./packages/cli/bin/n8n webhook",
|
||||
"worker": "./packages/cli/bin/n8n worker",
|
||||
"cypress:install": "cypress install",
|
||||
"cypress:open": "CYPRESS_BASE_URL=http://localhost:8080 cypress open",
|
||||
"test:e2e:ui": "scripts/run-e2e.js ui",
|
||||
"test:e2e:dev": "scripts/run-e2e.js dev",
|
||||
"test:e2e:all": "scripts/run-e2e.js all"
|
||||
"worker": "./packages/cli/bin/n8n worker"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@n8n_io/eslint-config": "workspace:*",
|
||||
"@ngneat/falso": "^6.4.0",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"@vitest/coverage-v8": "^1.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^13.6.2",
|
||||
"cypress-otp": "^1.0.3",
|
||||
"cypress-real-events": "^1.11.0",
|
||||
"jest": "^29.6.2",
|
||||
"jest-environment-jsdom": "^29.6.2",
|
||||
"jest-expect-message": "^1.1.3",
|
||||
@ -58,7 +48,6 @@
|
||||
"p-limit": "^3.1.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"run-script-os": "^1.0.7",
|
||||
"start-server-and-test": "^2.0.3",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tsc-alias": "^1.8.7",
|
||||
|
@ -50,9 +50,6 @@ importers:
|
||||
'@n8n_io/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:packages/@n8n_io/eslint-config
|
||||
'@ngneat/falso':
|
||||
specifier: ^6.4.0
|
||||
version: 6.4.0
|
||||
'@types/jest':
|
||||
specifier: ^29.5.3
|
||||
version: 29.5.3
|
||||
@ -62,18 +59,6 @@ importers:
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1))
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
cypress:
|
||||
specifier: ^13.6.2
|
||||
version: 13.6.2
|
||||
cypress-otp:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
cypress-real-events:
|
||||
specifier: ^1.11.0
|
||||
version: 1.11.0(cypress@13.6.2)
|
||||
jest:
|
||||
specifier: ^29.6.2
|
||||
version: 29.6.2(@types/node@18.16.16)
|
||||
@ -104,9 +89,6 @@ importers:
|
||||
run-script-os:
|
||||
specifier: ^1.0.7
|
||||
version: 1.1.6
|
||||
start-server-and-test:
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3
|
||||
supertest:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
@ -138,6 +120,37 @@ importers:
|
||||
specifier: ^2.0.19
|
||||
version: 2.0.19(typescript@5.4.2)
|
||||
|
||||
cypress:
|
||||
dependencies:
|
||||
'@ngneat/falso':
|
||||
specifier: ^6.4.0
|
||||
version: 6.4.0
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
cypress:
|
||||
specifier: ^13.6.2
|
||||
version: 13.6.2
|
||||
cypress-otp:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
cypress-real-events:
|
||||
specifier: ^1.11.0
|
||||
version: 1.11.0(cypress@13.6.2)
|
||||
start-server-and-test:
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3
|
||||
uuid:
|
||||
specifier: 8.3.2
|
||||
version: 8.3.2
|
||||
devDependencies:
|
||||
'@types/uuid':
|
||||
specifier: ^8.3.2
|
||||
version: 8.3.4
|
||||
n8n-workflow:
|
||||
specifier: workspace:*
|
||||
version: link:../packages/workflow
|
||||
|
||||
packages/@n8n/chat:
|
||||
dependencies:
|
||||
highlight.js:
|
||||
@ -5733,9 +5746,6 @@ packages:
|
||||
'@types/uuid@8.3.4':
|
||||
resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
|
||||
|
||||
'@types/uuid@9.0.0':
|
||||
resolution: {integrity: sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==}
|
||||
|
||||
'@types/uuid@9.0.7':
|
||||
resolution: {integrity: sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==}
|
||||
|
||||
@ -13156,10 +13166,6 @@ packages:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||
hasBin: true
|
||||
|
||||
uuid@9.0.0:
|
||||
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
|
||||
hasBin: true
|
||||
|
||||
uuid@9.0.1:
|
||||
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
|
||||
hasBin: true
|
||||
@ -13341,6 +13347,9 @@ packages:
|
||||
vue-component-type-helpers@2.0.19:
|
||||
resolution: {integrity: sha512-cN3f1aTxxKo4lzNeQAkVopswuImUrb5Iurll9Gaw5cqpnbTAxtEMM1mgi6ou4X79OCyqYv1U1mzBHJkzmiK82w==}
|
||||
|
||||
vue-component-type-helpers@2.0.21:
|
||||
resolution: {integrity: sha512-3NaicyZ7N4B6cft4bfb7dOnPbE9CjLcx+6wZWAg5zwszfO4qXRh+U52dN5r5ZZfc6iMaxKCEcoH9CmxxoFZHLg==}
|
||||
|
||||
vue-demi@0.14.5:
|
||||
resolution: {integrity: sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==}
|
||||
engines: {node: '>=12'}
|
||||
@ -18969,7 +18978,7 @@ snapshots:
|
||||
ts-dedent: 2.2.0
|
||||
type-fest: 2.19.0
|
||||
vue: 3.4.21(typescript@5.4.2)
|
||||
vue-component-type-helpers: 2.0.19
|
||||
vue-component-type-helpers: 2.0.21
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- prettier
|
||||
@ -19573,8 +19582,6 @@ snapshots:
|
||||
|
||||
'@types/uuid@8.3.4': {}
|
||||
|
||||
'@types/uuid@9.0.0': {}
|
||||
|
||||
'@types/uuid@9.0.7': {}
|
||||
|
||||
'@types/validator@13.7.10': {}
|
||||
@ -21459,7 +21466,7 @@ snapshots:
|
||||
cli-table3: 0.6.3
|
||||
commander: 6.2.1
|
||||
common-tags: 1.8.2
|
||||
dayjs: 1.11.6
|
||||
dayjs: 1.11.10
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
enquirer: 2.3.6
|
||||
eventemitter2: 6.4.7
|
||||
@ -24542,11 +24549,11 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/asn1': 0.2.0
|
||||
'@types/node': 18.16.16
|
||||
'@types/uuid': 9.0.0
|
||||
'@types/uuid': 9.0.7
|
||||
asn1: 0.2.6
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
strict-event-emitter-types: 2.0.0
|
||||
uuid: 9.0.0
|
||||
uuid: 9.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -28334,8 +28341,6 @@ snapshots:
|
||||
|
||||
uuid@8.3.2: {}
|
||||
|
||||
uuid@9.0.0: {}
|
||||
|
||||
uuid@9.0.1: {}
|
||||
|
||||
v3-infinite-loading@1.2.2: {}
|
||||
@ -28515,6 +28520,8 @@ snapshots:
|
||||
|
||||
vue-component-type-helpers@2.0.19: {}
|
||||
|
||||
vue-component-type-helpers@2.0.21: {}
|
||||
|
||||
vue-demi@0.14.5(vue@3.4.21(typescript@5.4.2)):
|
||||
dependencies:
|
||||
vue: 3.4.21(typescript@5.4.2)
|
||||
|
@ -2,3 +2,4 @@ packages:
|
||||
- packages/*
|
||||
- packages/@n8n/*
|
||||
- packages/@n8n_io/*
|
||||
- cypress
|
||||
|
Loading…
Reference in New Issue
Block a user