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