mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 13:27:31 -08:00
ci: Refactor e2e tests to delete boilerplate code (no-changelog) (#6524)
This commit is contained in:
parent
abe7f71627
commit
0e071724ee
|
@ -10,3 +10,5 @@ packages/**/.turbo
|
||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
packages/cli/dist/**/e2e.*
|
||||||
|
packages/cli/dist/ReloadNodesAndCredentials.*
|
||||||
|
|
16
.github/workflows/ci-pull-requests.yml
vendored
16
.github/workflows/ci-pull-requests.yml
vendored
|
@ -1,4 +1,4 @@
|
||||||
name: Build, unit/smoke test and lint branch
|
name: Build, unit test and lint branch
|
||||||
|
|
||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
|
|
||||||
|
@ -108,20 +108,6 @@ jobs:
|
||||||
ESLINT_PLUGIN_DIFF_COMMIT: ${{ github.event.pull_request.base.ref }}
|
ESLINT_PLUGIN_DIFF_COMMIT: ${{ github.event.pull_request.base.ref }}
|
||||||
run: pnpm lint
|
run: pnpm lint
|
||||||
|
|
||||||
smoke-test:
|
|
||||||
name: E2E [Electron/Node 18]
|
|
||||||
uses: ./.github/workflows/e2e-reusable.yml
|
|
||||||
with:
|
|
||||||
branch: ${{ github.event.pull_request.base.ref }}
|
|
||||||
user: ${{ github.event.inputs.user || 'PR User' }}
|
|
||||||
spec: ${{ github.event.inputs.spec || 'e2e/0-smoke.cy.ts' }}
|
|
||||||
record: false
|
|
||||||
parallel: false
|
|
||||||
pr_number: ${{ github.event.number }}
|
|
||||||
containers: '[1]'
|
|
||||||
secrets:
|
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
|
||||||
|
|
||||||
checklist_job:
|
checklist_job:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Checklist job
|
name: Checklist job
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
const fetch = require('node-fetch');
|
|
||||||
const { defineConfig } = require('cypress');
|
const { defineConfig } = require('cypress');
|
||||||
|
|
||||||
const BASE_URL = 'http://localhost:5678';
|
const BASE_URL = 'http://localhost:5678';
|
||||||
|
|
||||||
module.exports = defineConfig({
|
module.exports = defineConfig({
|
||||||
projectId: "5hbsdn",
|
projectId: '5hbsdn',
|
||||||
retries: {
|
retries: {
|
||||||
openMode: 0,
|
openMode: 0,
|
||||||
runMode: 2,
|
runMode: 2,
|
||||||
|
@ -19,31 +18,5 @@ module.exports = defineConfig({
|
||||||
screenshotOnRunFailure: true,
|
screenshotOnRunFailure: true,
|
||||||
experimentalInteractiveRunEvents: true,
|
experimentalInteractiveRunEvents: true,
|
||||||
experimentalSessionAndOrigin: true,
|
experimentalSessionAndOrigin: true,
|
||||||
|
|
||||||
setupNodeEvents(on, config) {
|
|
||||||
on('task', {
|
|
||||||
reset: () => fetch(BASE_URL + '/e2e/db/reset', { method: 'POST' }),
|
|
||||||
'setup-owner': (payload) => {
|
|
||||||
try {
|
|
||||||
return fetch(BASE_URL + '/e2e/db/setup-owner', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error("setup-owner failed with: ", error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'set-feature': ({ feature, enabled }) => {
|
|
||||||
return fetch(BASE_URL + `/e2e/feature/${feature}`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
body: JSON.stringify({ enabled }),
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
})
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,32 @@
|
||||||
export const BACKEND_BASE_URL = 'http://localhost:5678';
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
|
||||||
|
export const BASE_URL = 'http://localhost:5678';
|
||||||
|
export const BACKEND_BASE_URL = 'http://localhost:5678';
|
||||||
export const N8N_AUTH_COOKIE = 'n8n-auth';
|
export const N8N_AUTH_COOKIE = 'n8n-auth';
|
||||||
|
|
||||||
export const DEFAULT_USER_EMAIL = 'nathan@n8n.io';
|
const DEFAULT_USER_PASSWORD = 'CypressTest123';
|
||||||
export const DEFAULT_USER_PASSWORD = 'CypressTest123';
|
|
||||||
|
export const INSTANCE_OWNER = {
|
||||||
|
email: 'nathan@n8n.io',
|
||||||
|
password: DEFAULT_USER_PASSWORD,
|
||||||
|
firstName: randFirstName(),
|
||||||
|
lastName: randLastName(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const INSTANCE_MEMBERS = [
|
||||||
|
{
|
||||||
|
email: 'rebecca@n8n.io',
|
||||||
|
password: DEFAULT_USER_PASSWORD,
|
||||||
|
firstName: randFirstName(),
|
||||||
|
lastName: randLastName(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: 'mustafa@n8n.io',
|
||||||
|
password: DEFAULT_USER_PASSWORD,
|
||||||
|
firstName: randFirstName(),
|
||||||
|
lastName: randLastName(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
|
export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
|
||||||
export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking "Execute Workflow"';
|
export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking "Execute Workflow"';
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Authentication', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.resetAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should setup owner', () => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should sign user in', () => {
|
|
||||||
cy.setupOwner({ email, password, firstName, lastName });
|
|
||||||
cy.on('uncaught:exception', (err, runnable) => {
|
|
||||||
expect(err.message).to.include('Not logged in');
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.signin({ email, password });
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,26 +1,14 @@
|
||||||
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';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
|
|
||||||
const WorkflowsPage = new WorkflowsPageClass();
|
const WorkflowsPage = new WorkflowsPageClass();
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
const multipleWorkflowsCount = 5;
|
const multipleWorkflowsCount = 5;
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Workflows', () => {
|
describe('Workflows', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
cy.visit(WorkflowsPage.url);
|
cy.visit(WorkflowsPage.url);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,8 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { SettingsLogStreamingPage } from '../pages';
|
import { SettingsLogStreamingPage } from '../pages';
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
const settingsLogStreamingPage = new SettingsLogStreamingPage();
|
const settingsLogStreamingPage = new SettingsLogStreamingPage();
|
||||||
|
|
||||||
describe('Log Streaming Settings', () => {
|
describe('Log Streaming Settings', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.signin({ email, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show the unlicensed view when the feature is disabled', () => {
|
it('should show the unlicensed view when the feature is disabled', () => {
|
||||||
cy.visit('/settings/log-streaming');
|
cy.visit('/settings/log-streaming');
|
||||||
settingsLogStreamingPage.getters.getActionBoxUnlicensed().should('be.visible');
|
settingsLogStreamingPage.getters.getActionBoxUnlicensed().should('be.visible');
|
||||||
|
@ -25,7 +11,7 @@ describe('Log Streaming Settings', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show the licensed view when the feature is enabled', () => {
|
it('should show the licensed view when the feature is enabled', () => {
|
||||||
cy.enableFeature('feat:logStreaming');
|
cy.enableFeature('logStreaming');
|
||||||
cy.visit('/settings/log-streaming');
|
cy.visit('/settings/log-streaming');
|
||||||
settingsLogStreamingPage.getters.getActionBoxLicensed().should('be.visible');
|
settingsLogStreamingPage.getters.getActionBoxLicensed().should('be.visible');
|
||||||
settingsLogStreamingPage.getters.getAddFirstDestinationButton().should('be.visible');
|
settingsLogStreamingPage.getters.getAddFirstDestinationButton().should('be.visible');
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { CODE_NODE_NAME, DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD, SET_NODE_NAME } from './../constants';
|
import { CODE_NODE_NAME, SET_NODE_NAME } from './../constants';
|
||||||
import { SCHEDULE_TRIGGER_NODE_NAME } from '../constants';
|
import { SCHEDULE_TRIGGER_NODE_NAME } from '../constants';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { NDV } from '../pages/ndv';
|
import { NDV } from '../pages/ndv';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
|
|
||||||
// Suite-specific constants
|
// Suite-specific constants
|
||||||
const CODE_NODE_NEW_NAME = 'Something else';
|
const CODE_NODE_NEW_NAME = 'Something else';
|
||||||
|
@ -10,18 +9,8 @@ const CODE_NODE_NEW_NAME = 'Something else';
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Undo/Redo', () => {
|
describe('Undo/Redo', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,9 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
describe('Inline expression editor', () => {
|
describe('Inline expression editor', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||||
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
||||||
|
|
|
@ -6,25 +6,12 @@ import {
|
||||||
SET_NODE_NAME,
|
SET_NODE_NAME,
|
||||||
IF_NODE_NAME,
|
IF_NODE_NAME,
|
||||||
HTTP_REQUEST_NODE_NAME,
|
HTTP_REQUEST_NODE_NAME,
|
||||||
DEFAULT_USER_EMAIL,
|
|
||||||
DEFAULT_USER_PASSWORD,
|
|
||||||
} from './../constants';
|
} from './../constants';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
describe('Canvas Actions', () => {
|
describe('Canvas Actions', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,9 @@ import {
|
||||||
SCHEDULE_TRIGGER_NODE_NAME,
|
SCHEDULE_TRIGGER_NODE_NAME,
|
||||||
SET_NODE_NAME,
|
SET_NODE_NAME,
|
||||||
SWITCH_NODE_NAME,
|
SWITCH_NODE_NAME,
|
||||||
IF_NODE_NAME,
|
|
||||||
MERGE_NODE_NAME,
|
MERGE_NODE_NAME,
|
||||||
HTTP_REQUEST_NODE_NAME,
|
|
||||||
DEFAULT_USER_EMAIL,
|
|
||||||
DEFAULT_USER_PASSWORD,
|
|
||||||
} from './../constants';
|
} from './../constants';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
|
@ -23,18 +18,8 @@ const ZOOM_OUT_X1_FACTOR = 0.8;
|
||||||
const ZOOM_OUT_X2_FACTOR = 0.64;
|
const ZOOM_OUT_X2_FACTOR = 0.64;
|
||||||
const RENAME_NODE_NAME = 'Something else';
|
const RENAME_NODE_NAME = 'Something else';
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Canvas Node Manipulation and Navigation', () => {
|
describe('Canvas Node Manipulation and Navigation', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_USER_EMAIL,
|
|
||||||
DEFAULT_USER_PASSWORD,
|
|
||||||
HTTP_REQUEST_NODE_NAME,
|
HTTP_REQUEST_NODE_NAME,
|
||||||
MANUAL_TRIGGER_NODE_NAME,
|
MANUAL_TRIGGER_NODE_NAME,
|
||||||
PIPEDRIVE_NODE_NAME,
|
PIPEDRIVE_NODE_NAME,
|
||||||
|
@ -12,18 +9,8 @@ import { WorkflowPage, NDV } from '../pages';
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Data pinning', () => {
|
describe('Data pinning', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,10 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
|
|
||||||
const wf = new WorkflowPage();
|
const wf = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Data transformation expressions', () => {
|
describe('Data transformation expressions', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
wf.actions.visit();
|
wf.actions.visit();
|
||||||
|
|
||||||
cy.window().then(
|
cy.window().then(
|
||||||
|
|
|
@ -2,27 +2,14 @@ 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,
|
||||||
DEFAULT_USER_EMAIL,
|
|
||||||
DEFAULT_USER_PASSWORD,
|
|
||||||
} from './../constants';
|
} from './../constants';
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Data mapping', () => {
|
describe('Data mapping', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
|
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage, WorkflowsPage, NDV } from '../pages';
|
import { WorkflowPage, WorkflowsPage, NDV } from '../pages';
|
||||||
import { BACKEND_BASE_URL } from '../constants';
|
import { BACKEND_BASE_URL } from '../constants';
|
||||||
|
|
||||||
|
@ -7,18 +5,8 @@ const workflowsPage = new WorkflowsPage();
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Schedule Trigger node', async () => {
|
describe('Schedule Trigger node', async () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,6 @@ import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { cowBase64 } from '../support/binaryTestFiles';
|
import { cowBase64 } from '../support/binaryTestFiles';
|
||||||
import { BACKEND_BASE_URL } from '../constants';
|
import { BACKEND_BASE_URL } from '../constants';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
@ -99,12 +92,7 @@ const simpleWebhookCall = (options: SimpleWebhookCallOptions) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Webhook Trigger node', async () => {
|
describe('Webhook Trigger node', async () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
|
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
import { INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants';
|
||||||
import {
|
import {
|
||||||
CredentialsModal,
|
CredentialsModal,
|
||||||
CredentialsPage,
|
CredentialsPage,
|
||||||
|
@ -28,47 +28,12 @@ const workflowPage = new WorkflowPage();
|
||||||
const workflowSharingModal = new WorkflowSharingModal();
|
const workflowSharingModal = new WorkflowSharingModal();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const instanceOwner = {
|
describe('Sharing', { disableAutoLogin: true }, () => {
|
||||||
email: `${DEFAULT_USER_EMAIL}one`,
|
before(() => cy.enableFeature('sharing', true));
|
||||||
password: DEFAULT_USER_PASSWORD,
|
|
||||||
firstName: 'User',
|
|
||||||
lastName: 'U1',
|
|
||||||
};
|
|
||||||
|
|
||||||
const users = [
|
|
||||||
{
|
|
||||||
email: `${DEFAULT_USER_EMAIL}two`,
|
|
||||||
password: DEFAULT_USER_PASSWORD,
|
|
||||||
firstName: 'User',
|
|
||||||
lastName: 'U2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
email: `${DEFAULT_USER_EMAIL}three`,
|
|
||||||
password: DEFAULT_USER_PASSWORD,
|
|
||||||
firstName: 'User',
|
|
||||||
lastName: 'U3',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
describe('Sharing', () => {
|
|
||||||
before(() => {
|
|
||||||
cy.setupOwner(instanceOwner);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.on('uncaught:exception', (err, runnable) => {
|
|
||||||
expect(err.message).to.include('Not logged in');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should invite User U2 and User U3 to instance', () => {
|
|
||||||
cy.inviteUsers({ instanceOwner, users });
|
|
||||||
});
|
|
||||||
|
|
||||||
let workflowW2Url = '';
|
let workflowW2Url = '';
|
||||||
it('should create C1, W1, W2, share W1 with U3, as U2', () => {
|
it('should create C1, W1, W2, share W1 with U3, as U2', () => {
|
||||||
cy.signin(users[0]);
|
cy.signin(INSTANCE_MEMBERS[0]);
|
||||||
|
|
||||||
cy.visit(credentialsPage.url);
|
cy.visit(credentialsPage.url);
|
||||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||||
|
@ -87,7 +52,7 @@ describe('Sharing', () => {
|
||||||
ndv.actions.close();
|
ndv.actions.close();
|
||||||
|
|
||||||
workflowPage.actions.openShareModal();
|
workflowPage.actions.openShareModal();
|
||||||
workflowSharingModal.actions.addUser(users[1].email);
|
workflowSharingModal.actions.addUser(INSTANCE_MEMBERS[1].email);
|
||||||
workflowSharingModal.actions.save();
|
workflowSharingModal.actions.save();
|
||||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
|
||||||
|
@ -100,7 +65,7 @@ describe('Sharing', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create C2, share C2 with U1 and U2, as U3', () => {
|
it('should create C2, share C2 with U1 and U2, as U3', () => {
|
||||||
cy.signin(users[1]);
|
cy.signin(INSTANCE_MEMBERS[1]);
|
||||||
|
|
||||||
cy.visit(credentialsPage.url);
|
cy.visit(credentialsPage.url);
|
||||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||||
|
@ -109,14 +74,14 @@ describe('Sharing', () => {
|
||||||
credentialsModal.getters.connectionParameter('API Key').type('1234567890');
|
credentialsModal.getters.connectionParameter('API Key').type('1234567890');
|
||||||
credentialsModal.actions.setName('Credential C2');
|
credentialsModal.actions.setName('Credential C2');
|
||||||
credentialsModal.actions.changeTab('Sharing');
|
credentialsModal.actions.changeTab('Sharing');
|
||||||
credentialsModal.actions.addUser(instanceOwner.email);
|
credentialsModal.actions.addUser(INSTANCE_OWNER.email);
|
||||||
credentialsModal.actions.addUser(users[0].email);
|
credentialsModal.actions.addUser(INSTANCE_MEMBERS[0].email);
|
||||||
credentialsModal.actions.save();
|
credentialsModal.actions.save();
|
||||||
credentialsModal.actions.close();
|
credentialsModal.actions.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open W1, add node using C2 as U3', () => {
|
it('should open W1, add node using C2 as U3', () => {
|
||||||
cy.signin(users[1]);
|
cy.signin(INSTANCE_MEMBERS[1]);
|
||||||
|
|
||||||
cy.visit(workflowsPage.url);
|
cy.visit(workflowsPage.url);
|
||||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||||
|
@ -136,7 +101,7 @@ describe('Sharing', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not have access to W2, as U3', () => {
|
it('should not have access to W2, as U3', () => {
|
||||||
cy.signin(users[1]);
|
cy.signin(INSTANCE_MEMBERS[1]);
|
||||||
|
|
||||||
cy.visit(workflowW2Url);
|
cy.visit(workflowW2Url);
|
||||||
cy.waitForLoad();
|
cy.waitForLoad();
|
||||||
|
@ -145,7 +110,7 @@ describe('Sharing', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have access to W1, W2, as U1', () => {
|
it('should have access to W1, W2, as U1', () => {
|
||||||
cy.signin(instanceOwner);
|
cy.signin(INSTANCE_OWNER);
|
||||||
|
|
||||||
cy.visit(workflowsPage.url);
|
cy.visit(workflowsPage.url);
|
||||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||||
|
@ -165,7 +130,7 @@ describe('Sharing', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should automatically test C2 when opened by U2 sharee', () => {
|
it('should automatically test C2 when opened by U2 sharee', () => {
|
||||||
cy.signin(users[0]);
|
cy.signin(INSTANCE_MEMBERS[0]);
|
||||||
|
|
||||||
cy.visit(credentialsPage.url);
|
cy.visit(credentialsPage.url);
|
||||||
credentialsPage.getters.credentialCard('Credential C2').click();
|
credentialsPage.getters.credentialCard('Credential C2').click();
|
||||||
|
|
|
@ -1,23 +1,11 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage } from '../pages';
|
import { WorkflowPage } from '../pages';
|
||||||
|
|
||||||
const wf = new WorkflowPage();
|
const wf = new WorkflowPage();
|
||||||
|
|
||||||
const TEST_TAGS = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4', 'Tag 5'];
|
const TEST_TAGS = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4', 'Tag 5'];
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Workflow tags', () => {
|
describe('Workflow tags', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
wf.actions.visit();
|
wf.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { MainSidebar } from './../pages/sidebar/main-sidebar';
|
import { INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants';
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
import { SettingsUsersPage, WorkflowPage } from '../pages';
|
||||||
import { SettingsSidebar, SettingsUsersPage, WorkflowPage, WorkflowsPage } from '../pages';
|
|
||||||
import { PersonalSettingsPage } from '../pages/settings-personal';
|
import { PersonalSettingsPage } from '../pages/settings-personal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,28 +14,6 @@ import { PersonalSettingsPage } from '../pages/settings-personal';
|
||||||
* C2 - Credential owned by User C, shared with User A and User B
|
* C2 - Credential owned by User C, shared with User A and User B
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const instanceOwner = {
|
|
||||||
email: `${DEFAULT_USER_EMAIL}A`,
|
|
||||||
password: DEFAULT_USER_PASSWORD,
|
|
||||||
firstName: 'User',
|
|
||||||
lastName: 'A',
|
|
||||||
};
|
|
||||||
|
|
||||||
const users = [
|
|
||||||
{
|
|
||||||
email: `${DEFAULT_USER_EMAIL}B`,
|
|
||||||
password: DEFAULT_USER_PASSWORD,
|
|
||||||
firstName: 'User',
|
|
||||||
lastName: 'B',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
email: `${DEFAULT_USER_EMAIL}C`,
|
|
||||||
password: DEFAULT_USER_PASSWORD,
|
|
||||||
firstName: 'User',
|
|
||||||
lastName: 'C',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const updatedPersonalData = {
|
const updatedPersonalData = {
|
||||||
newFirstName: 'Something',
|
newFirstName: 'Something',
|
||||||
newLastName: 'Else',
|
newLastName: 'Else',
|
||||||
|
@ -49,47 +26,38 @@ const usersSettingsPage = new SettingsUsersPage();
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const personalSettingsPage = new PersonalSettingsPage();
|
const personalSettingsPage = new PersonalSettingsPage();
|
||||||
|
|
||||||
describe('User Management', () => {
|
describe('User Management', { disableAutoLogin: true }, () => {
|
||||||
before(() => {
|
before(() => cy.enableFeature('sharing'));
|
||||||
cy.setupOwner(instanceOwner);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.on('uncaught:exception', (err, runnable) => {
|
|
||||||
expect(err.message).to.include('Not logged in');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should invite User B and User C to instance`, () => {
|
|
||||||
cy.inviteUsers({ instanceOwner, users });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should prevent non-owners to access UM settings', () => {
|
it('should prevent non-owners to access UM settings', () => {
|
||||||
usersSettingsPage.actions.loginAndVisit(users[0].email, users[0].password, false);
|
usersSettingsPage.actions.loginAndVisit(
|
||||||
|
INSTANCE_MEMBERS[0].email,
|
||||||
|
INSTANCE_MEMBERS[0].password,
|
||||||
|
false,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow instance owner to access UM settings', () => {
|
it('should allow instance owner to access UM settings', () => {
|
||||||
usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true);
|
usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should properly render UM settings page for instance owners', () => {
|
it('should properly render UM settings page for instance owners', () => {
|
||||||
usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true);
|
usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true);
|
||||||
// All items in user list should be there
|
// All items in user list should be there
|
||||||
usersSettingsPage.getters.userListItems().should('have.length', 3);
|
usersSettingsPage.getters.userListItems().should('have.length', 3);
|
||||||
// List item for current user should have the `Owner` badge
|
// List item for current user should have the `Owner` badge
|
||||||
usersSettingsPage.getters
|
usersSettingsPage.getters
|
||||||
.userItem(instanceOwner.email)
|
.userItem(INSTANCE_OWNER.email)
|
||||||
.find('.n8n-badge:contains("Owner")')
|
.find('.n8n-badge:contains("Owner")')
|
||||||
.should('exist');
|
.should('exist');
|
||||||
// Other users list items should contain action pop-up list
|
// Other users list items should contain action pop-up list
|
||||||
usersSettingsPage.getters.userActionsToggle(users[0].email).should('exist');
|
usersSettingsPage.getters.userActionsToggle(INSTANCE_MEMBERS[0].email).should('exist');
|
||||||
usersSettingsPage.getters.userActionsToggle(users[1].email).should('exist');
|
usersSettingsPage.getters.userActionsToggle(INSTANCE_MEMBERS[1].email).should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete user and their data', () => {
|
it('should delete user and their data', () => {
|
||||||
usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true);
|
usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true);
|
||||||
usersSettingsPage.actions.opedDeleteDialog(users[0].email);
|
usersSettingsPage.actions.opedDeleteDialog(INSTANCE_MEMBERS[0].email);
|
||||||
usersSettingsPage.getters.deleteDataRadioButton().realClick();
|
usersSettingsPage.getters.deleteDataRadioButton().realClick();
|
||||||
usersSettingsPage.getters.deleteDataInput().type('delete all data');
|
usersSettingsPage.getters.deleteDataInput().type('delete all data');
|
||||||
usersSettingsPage.getters.deleteUserButton().realClick();
|
usersSettingsPage.getters.deleteUserButton().realClick();
|
||||||
|
@ -97,8 +65,8 @@ describe('User Management', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete user and transfer their data', () => {
|
it('should delete user and transfer their data', () => {
|
||||||
usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true);
|
usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true);
|
||||||
usersSettingsPage.actions.opedDeleteDialog(users[1].email);
|
usersSettingsPage.actions.opedDeleteDialog(INSTANCE_MEMBERS[1].email);
|
||||||
usersSettingsPage.getters.transferDataRadioButton().realClick();
|
usersSettingsPage.getters.transferDataRadioButton().realClick();
|
||||||
usersSettingsPage.getters.userSelectDropDown().realClick();
|
usersSettingsPage.getters.userSelectDropDown().realClick();
|
||||||
usersSettingsPage.getters.userSelectOptions().first().realClick();
|
usersSettingsPage.getters.userSelectOptions().first().realClick();
|
||||||
|
@ -107,7 +75,7 @@ describe('User Management', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should allow user to change their personal data`, () => {
|
it(`should allow user to change their personal data`, () => {
|
||||||
personalSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password);
|
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||||
personalSettingsPage.actions.updateFirstAndLastName(
|
personalSettingsPage.actions.updateFirstAndLastName(
|
||||||
updatedPersonalData.newFirstName,
|
updatedPersonalData.newFirstName,
|
||||||
updatedPersonalData.newLastName,
|
updatedPersonalData.newLastName,
|
||||||
|
@ -119,14 +87,14 @@ describe('User Management', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`shouldn't allow user to set weak password`, () => {
|
it(`shouldn't allow user to set weak password`, () => {
|
||||||
personalSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password);
|
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||||
for (let weakPass of updatedPersonalData.invalidPasswords) {
|
for (let weakPass of updatedPersonalData.invalidPasswords) {
|
||||||
personalSettingsPage.actions.tryToSetWeakPassword(instanceOwner.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(instanceOwner.email, instanceOwner.password);
|
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||||
personalSettingsPage.actions.updatePassword('iCannotRemember', updatedPersonalData.newPassword);
|
personalSettingsPage.actions.updatePassword('iCannotRemember', updatedPersonalData.newPassword);
|
||||||
workflowPage.getters
|
workflowPage.getters
|
||||||
.errorToast()
|
.errorToast()
|
||||||
|
@ -135,21 +103,21 @@ describe('User Management', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should change current user password`, () => {
|
it(`should change current user password`, () => {
|
||||||
personalSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password);
|
personalSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password);
|
||||||
personalSettingsPage.actions.updatePassword(
|
personalSettingsPage.actions.updatePassword(
|
||||||
instanceOwner.password,
|
INSTANCE_OWNER.password,
|
||||||
updatedPersonalData.newPassword,
|
updatedPersonalData.newPassword,
|
||||||
);
|
);
|
||||||
workflowPage.getters.successToast().should('contain', 'Password updated');
|
workflowPage.getters.successToast().should('contain', 'Password updated');
|
||||||
personalSettingsPage.actions.loginWithNewData(
|
personalSettingsPage.actions.loginWithNewData(
|
||||||
instanceOwner.email,
|
INSTANCE_OWNER.email,
|
||||||
updatedPersonalData.newPassword,
|
updatedPersonalData.newPassword,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`shouldn't allow users to set invalid email`, () => {
|
it(`shouldn't allow users to set invalid email`, () => {
|
||||||
personalSettingsPage.actions.loginAndVisit(
|
personalSettingsPage.actions.loginAndVisit(
|
||||||
instanceOwner.email,
|
INSTANCE_OWNER.email,
|
||||||
updatedPersonalData.newPassword,
|
updatedPersonalData.newPassword,
|
||||||
);
|
);
|
||||||
// try without @ part
|
// try without @ part
|
||||||
|
@ -160,7 +128,7 @@ describe('User Management', () => {
|
||||||
|
|
||||||
it(`should change user email`, () => {
|
it(`should change user email`, () => {
|
||||||
personalSettingsPage.actions.loginAndVisit(
|
personalSettingsPage.actions.loginAndVisit(
|
||||||
instanceOwner.email,
|
INSTANCE_OWNER.email,
|
||||||
updatedPersonalData.newPassword,
|
updatedPersonalData.newPassword,
|
||||||
);
|
);
|
||||||
personalSettingsPage.actions.updateEmail(updatedPersonalData.newEmail);
|
personalSettingsPage.actions.updateEmail(updatedPersonalData.newEmail);
|
||||||
|
|
|
@ -1,24 +1,11 @@
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { NDV, WorkflowPage as WorkflowPageClass, WorkflowsPage } from '../pages';
|
import { NDV, WorkflowPage as WorkflowPageClass, WorkflowsPage } from '../pages';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
|
|
||||||
const workflowsPage = new WorkflowsPage();
|
|
||||||
const workflowPage = new WorkflowPageClass();
|
const workflowPage = new WorkflowPageClass();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Execution', () => {
|
describe('Execution', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,24 +6,14 @@ import {
|
||||||
NEW_QUERY_AUTH_ACCOUNT_NAME,
|
NEW_QUERY_AUTH_ACCOUNT_NAME,
|
||||||
} from './../constants';
|
} from './../constants';
|
||||||
import {
|
import {
|
||||||
DEFAULT_USER_EMAIL,
|
|
||||||
DEFAULT_USER_PASSWORD,
|
|
||||||
GMAIL_NODE_NAME,
|
GMAIL_NODE_NAME,
|
||||||
NEW_GOOGLE_ACCOUNT_NAME,
|
NEW_GOOGLE_ACCOUNT_NAME,
|
||||||
NEW_TRELLO_ACCOUNT_NAME,
|
NEW_TRELLO_ACCOUNT_NAME,
|
||||||
SCHEDULE_TRIGGER_NODE_NAME,
|
SCHEDULE_TRIGGER_NODE_NAME,
|
||||||
TRELLO_NODE_NAME,
|
TRELLO_NODE_NAME,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { CredentialsPage, CredentialsModal, WorkflowPage, NDV } from '../pages';
|
import { CredentialsPage, CredentialsModal, WorkflowPage, NDV } from '../pages';
|
||||||
import CustomNodeWithN8nCredentialFixture from '../fixtures/Custom_node_n8n_credential.json';
|
|
||||||
import CustomNodeWithCustomCredentialFixture from '../fixtures/Custom_node_custom_credential.json';
|
|
||||||
import CustomCredential from '../fixtures/Custom_credential.json';
|
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
const credentialsPage = new CredentialsPage();
|
const credentialsPage = new CredentialsPage();
|
||||||
const credentialsModal = new CredentialsModal();
|
const credentialsModal = new CredentialsModal();
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
|
@ -32,12 +22,7 @@ const nodeDetailsView = new NDV();
|
||||||
const NEW_CREDENTIAL_NAME = 'Something else';
|
const NEW_CREDENTIAL_NAME = 'Something else';
|
||||||
|
|
||||||
describe('Credentials', () => {
|
describe('Credentials', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
cy.visit(credentialsPage.url);
|
cy.visit(credentialsPage.url);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage } from '../pages';
|
import { WorkflowPage } from '../pages';
|
||||||
import { WorkflowExecutionsTab } from '../pages/workflow-executions-tab';
|
import { WorkflowExecutionsTab } from '../pages/workflow-executions-tab';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const executionsTab = new WorkflowExecutionsTab();
|
const executionsTab = new WorkflowExecutionsTab();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
// Test suite for executions tab
|
// Test suite for executions tab
|
||||||
describe('Current Workflow Executions', () => {
|
describe('Current Workflow Executions', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
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`);
|
||||||
createMockExecutions();
|
createMockExecutions();
|
||||||
|
|
|
@ -4,25 +4,15 @@ import { CredentialsModal, WorkflowPage } from '../pages';
|
||||||
import CustomNodeWithN8nCredentialFixture from '../fixtures/Custom_node_n8n_credential.json';
|
import CustomNodeWithN8nCredentialFixture from '../fixtures/Custom_node_n8n_credential.json';
|
||||||
import CustomNodeWithCustomCredentialFixture from '../fixtures/Custom_node_custom_credential.json';
|
import CustomNodeWithCustomCredentialFixture from '../fixtures/Custom_node_custom_credential.json';
|
||||||
import CustomCredential from '../fixtures/Custom_credential.json';
|
import CustomCredential from '../fixtures/Custom_credential.json';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
|
|
||||||
const credentialsModal = new CredentialsModal();
|
const credentialsModal = new CredentialsModal();
|
||||||
const nodeCreatorFeature = new NodeCreator();
|
const nodeCreatorFeature = new NodeCreator();
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
// We separate-out the custom nodes because they require injecting nodes and credentials
|
// We separate-out the custom nodes because they require injecting nodes and credentials
|
||||||
// so the /nodes and /credentials endpoints are intercepted and non-cached.
|
// so the /nodes and /credentials endpoints are intercepted and non-cached.
|
||||||
// We want to keep the other tests as fast as possible so we don't want to break the cache in those.
|
// We want to keep the other tests as fast as possible so we don't want to break the cache in those.
|
||||||
describe('Community Nodes', () => {
|
describe('Community Nodes', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
})
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept('/types/nodes.json', { middleware: true }, (req) => {
|
cy.intercept('/types/nodes.json', { middleware: true }, (req) => {
|
||||||
req.headers['cache-control'] = 'no-cache, no-store';
|
req.headers['cache-control'] = 'no-cache, no-store';
|
||||||
|
@ -43,7 +33,7 @@ describe('Community Nodes', () => {
|
||||||
credentials.push(CustomCredential);
|
credentials.push(CustomCredential);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
cy.signin({ email, password });
|
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,10 @@
|
||||||
import { VariablesPage } from '../pages/variables';
|
import { VariablesPage } from '../pages/variables';
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
|
|
||||||
const variablesPage = new VariablesPage();
|
const variablesPage = new VariablesPage();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Variables', () => {
|
describe('Variables', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show the unlicensed action box when the feature is disabled', () => {
|
it('should show the unlicensed action box when the feature is disabled', () => {
|
||||||
cy.disableFeature('feat:variables');
|
cy.disableFeature('variables', false);
|
||||||
cy.signin({ email, password });
|
|
||||||
cy.visit(variablesPage.url);
|
cy.visit(variablesPage.url);
|
||||||
|
|
||||||
variablesPage.getters.unavailableResourcesList().should('be.visible');
|
variablesPage.getters.unavailableResourcesList().should('be.visible');
|
||||||
|
@ -25,11 +13,10 @@ describe('Variables', () => {
|
||||||
|
|
||||||
describe('licensed', () => {
|
describe('licensed', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.enableFeature('feat:variables');
|
cy.enableFeature('variables');
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
cy.intercept('GET', '/rest/variables').as('loadVariables');
|
cy.intercept('GET', '/rest/variables').as('loadVariables');
|
||||||
|
|
||||||
cy.visit(variablesPage.url);
|
cy.visit(variablesPage.url);
|
||||||
|
|
|
@ -1,23 +1,11 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('NDV', () => {
|
describe('NDV', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
workflowPage.actions.renameWorkflow(uuid());
|
workflowPage.actions.renameWorkflow(uuid());
|
||||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
@ -311,6 +299,9 @@ describe('NDV', () => {
|
||||||
.realHover();
|
.realHover();
|
||||||
|
|
||||||
ndv.actions.changeOutputRunSelector('1 of 2 (2 items)')
|
ndv.actions.changeOutputRunSelector('1 of 2 (2 items)')
|
||||||
|
ndv.getters.inputTableRow(1)
|
||||||
|
.should('have.text', '8888')
|
||||||
|
.realHover();
|
||||||
ndv.getters.outputHoveringItem().should('have.text', '8888');
|
ndv.getters.outputHoveringItem().should('have.text', '8888');
|
||||||
// todo there's a bug here need to fix ADO-534
|
// todo there's a bug here need to fix ADO-534
|
||||||
// ndv.getters.outputHoveringItem().should('not.exist');
|
// ndv.getters.outputHoveringItem().should('not.exist');
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPageClass();
|
const workflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
function checkStickiesStyle( top: number, left: number, height: number, width: number, zIndex?: number) {
|
function checkStickiesStyle( top: number, left: number, height: number, width: number, zIndex?: number) {
|
||||||
workflowPage.getters.stickies().should(($el) => {
|
workflowPage.getters.stickies().should(($el) => {
|
||||||
expect($el).to.have.css('top', `${top}px`);
|
expect($el).to.have.css('top', `${top}px`);
|
||||||
|
@ -22,12 +15,7 @@ function checkStickiesStyle( top: number, left: number, height: number, width: n
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Canvas Actions', () => {
|
describe('Canvas Actions', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
|
|
||||||
cy.window().then(
|
cy.window().then(
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
|
@ -9,18 +7,8 @@ const credentialsModal = new CredentialsModal();
|
||||||
const NO_CREDENTIALS_MESSAGE = 'Please add your credential';
|
const NO_CREDENTIALS_MESSAGE = 'Please add your credential';
|
||||||
const INVALID_CREDENTIALS_MESSAGE = 'Please check your credential';
|
const INVALID_CREDENTIALS_MESSAGE = 'Please check your credential';
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Resource Locator', () => {
|
describe('Resource Locator', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,13 @@
|
||||||
import { NodeCreator } from '../pages/features/node-creator';
|
import { NodeCreator } from '../pages/features/node-creator';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { NDV } from '../pages/ndv';
|
import { NDV } from '../pages/ndv';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
|
|
||||||
const nodeCreatorFeature = new NodeCreator();
|
const nodeCreatorFeature = new NodeCreator();
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
const NDVModal = new NDV();
|
const NDVModal = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Node Creator', () => {
|
describe('Node Creator', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,11 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('NDV', () => {
|
describe('NDV', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
workflowPage.actions.renameWorkflow(uuid());
|
workflowPage.actions.renameWorkflow(uuid());
|
||||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
|
|
@ -1,23 +1,11 @@
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { NDV } from '../pages/ndv';
|
import { NDV } from '../pages/ndv';
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Code node', () => {
|
describe('Code node', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import {
|
import {
|
||||||
CODE_NODE_NAME,
|
CODE_NODE_NAME,
|
||||||
DEFAULT_USER_EMAIL,
|
|
||||||
DEFAULT_USER_PASSWORD,
|
|
||||||
MANUAL_TRIGGER_NODE_NAME,
|
MANUAL_TRIGGER_NODE_NAME,
|
||||||
META_KEY,
|
META_KEY,
|
||||||
SCHEDULE_TRIGGER_NODE_NAME,
|
SCHEDULE_TRIGGER_NODE_NAME,
|
||||||
|
@ -14,20 +11,10 @@ const IMPORT_WORKFLOW_URL = 'https://gist.githubusercontent.com/OlegIvaniv/010bd
|
||||||
const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow';
|
const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow';
|
||||||
const DUPLICATE_WORKFLOW_TAG = 'Duplicate';
|
const DUPLICATE_WORKFLOW_TAG = 'Duplicate';
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
describe('Workflow Actions', () => {
|
describe('Workflow Actions', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('HTTP Request node', () => {
|
describe('HTTP Request node', () => {
|
||||||
before(() => {
|
beforeEach(() => {
|
||||||
cy.setup({ email, firstName, lastName, password });
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should make a request with a URL and receive a response', () => {
|
it('should make a request with a URL and receive a response', () => {
|
||||||
|
|
|
@ -1,21 +1,9 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Expression editor modal', () => {
|
describe('Expression editor modal', () => {
|
||||||
before(() => {
|
|
||||||
cy.setup({ email, firstName, lastName, password });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signin({ email, password });
|
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||||
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
export * from './base';
|
export * from './base';
|
||||||
export * from './credentials';
|
export * from './credentials';
|
||||||
export * from './signin';
|
|
||||||
export * from './signup';
|
|
||||||
export * from './workflows';
|
export * from './workflows';
|
||||||
export * from './workflow';
|
export * from './workflow';
|
||||||
export * from './modals';
|
export * from './modals';
|
||||||
|
|
|
@ -26,9 +26,5 @@ export class MainSidebar extends BasePage {
|
||||||
openUserMenu: () => {
|
openUserMenu: () => {
|
||||||
this.getters.userMenu().find('[role="button"]').last().click();
|
this.getters.userMenu().find('[role="button"]').last().click();
|
||||||
},
|
},
|
||||||
signout: () => {
|
|
||||||
this.actions.openUserMenu();
|
|
||||||
cy.getByTestId('workflow-menu-item-logout').click();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { BasePage } from './base';
|
|
||||||
|
|
||||||
export class SigninPage extends BasePage {
|
|
||||||
url = '/signin';
|
|
||||||
getters = {
|
|
||||||
form: () => cy.getByTestId('auth-form'),
|
|
||||||
email: () => cy.getByTestId('email'),
|
|
||||||
password: () => cy.getByTestId('password'),
|
|
||||||
submit: () => cy.get('button'),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { BasePage } from './base';
|
|
||||||
|
|
||||||
// todo rename to setup
|
|
||||||
export class SignupPage extends BasePage {
|
|
||||||
url = '/setup';
|
|
||||||
getters = {
|
|
||||||
form: () => cy.getByTestId('auth-form'),
|
|
||||||
email: () => cy.getByTestId('email'),
|
|
||||||
firstName: () => cy.getByTestId('firstName'),
|
|
||||||
lastName: () => cy.getByTestId('lastName'),
|
|
||||||
password: () => cy.getByTestId('password'),
|
|
||||||
submit: () => cy.get('button'),
|
|
||||||
skip: () => cy.get('a'),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,32 +1,6 @@
|
||||||
// ***********************************************
|
|
||||||
// This example commands.js shows you how to
|
|
||||||
// create various custom commands and overwrite
|
|
||||||
// existing commands.
|
|
||||||
//
|
|
||||||
// For more comprehensive examples of custom
|
|
||||||
// commands please read more here:
|
|
||||||
// https://on.cypress.io/custom-commands
|
|
||||||
// ***********************************************
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a parent command --
|
|
||||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a child command --
|
|
||||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This is a dual command --
|
|
||||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// -- This will overwrite an existing command --
|
|
||||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
|
||||||
import 'cypress-real-events';
|
import 'cypress-real-events';
|
||||||
import { WorkflowsPage, SigninPage, SignupPage, SettingsUsersPage, WorkflowPage } from '../pages';
|
import { WorkflowPage } from '../pages';
|
||||||
import { N8N_AUTH_COOKIE } from '../constants';
|
import { BASE_URL, N8N_AUTH_COOKIE } from '../constants';
|
||||||
import { MessageBox } from '../pages/modals/message-box';
|
|
||||||
|
|
||||||
Cypress.Commands.add('getByTestId', (selector, ...args) => {
|
Cypress.Commands.add('getByTestId', (selector, ...args) => {
|
||||||
return cy.get(`[data-test-id="${selector}"]`, ...args);
|
return cy.get(`[data-test-id="${selector}"]`, ...args);
|
||||||
|
@ -59,136 +33,35 @@ Cypress.Commands.add('waitForLoad', (waitForIntercepts = true) => {
|
||||||
// we can't set them up here because at this point it would be too late
|
// we can't set them up here because at this point it would be too late
|
||||||
// and the requests would already have been made
|
// and the requests would already have been made
|
||||||
if (waitForIntercepts) {
|
if (waitForIntercepts) {
|
||||||
cy.wait(['@loadSettings', '@loadLogin']);
|
cy.wait(['@loadSettings']);
|
||||||
}
|
}
|
||||||
cy.getByTestId('node-view-loader', { timeout: 20000 }).should('not.exist');
|
cy.getByTestId('node-view-loader', { timeout: 20000 }).should('not.exist');
|
||||||
cy.get('.el-loading-mask', { timeout: 20000 }).should('not.exist');
|
cy.get('.el-loading-mask', { timeout: 20000 }).should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('signin', ({ email, password }) => {
|
Cypress.Commands.add('signin', ({ email, password }) => {
|
||||||
const signinPage = new SigninPage();
|
Cypress.session.clearAllSavedSessions();
|
||||||
const workflowsPage = new WorkflowsPage();
|
cy.session([email, password], () => cy.request('POST', '/rest/login', { email, password }), {
|
||||||
|
validate() {
|
||||||
cy.session(
|
cy.getCookie(N8N_AUTH_COOKIE).should('exist');
|
||||||
[email, password],
|
|
||||||
() => {
|
|
||||||
cy.visit(signinPage.url);
|
|
||||||
|
|
||||||
signinPage.getters.form().within(() => {
|
|
||||||
signinPage.getters.email().type(email);
|
|
||||||
signinPage.getters.password().type(password);
|
|
||||||
signinPage.getters.submit().click();
|
|
||||||
});
|
|
||||||
|
|
||||||
// we should be redirected to /workflows
|
|
||||||
cy.url().should('include', workflowsPage.url);
|
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
validate() {
|
|
||||||
cy.getCookie(N8N_AUTH_COOKIE).should('exist');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('signout', () => {
|
Cypress.Commands.add('signout', () => {
|
||||||
cy.visit('/signout');
|
cy.request('POST', '/rest/logout');
|
||||||
cy.waitForLoad();
|
|
||||||
cy.url().should('include', '/signin');
|
|
||||||
cy.getCookie(N8N_AUTH_COOKIE).should('not.exist');
|
cy.getCookie(N8N_AUTH_COOKIE).should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('signup', ({ firstName, lastName, password, url }) => {
|
|
||||||
const signupPage = new SignupPage();
|
|
||||||
|
|
||||||
cy.visit(url);
|
|
||||||
|
|
||||||
signupPage.getters.form().within(() => {
|
|
||||||
cy.url().then((url) => {
|
|
||||||
cy.intercept('/rest/users/*').as('userSignup')
|
|
||||||
signupPage.getters.firstName().type(firstName);
|
|
||||||
signupPage.getters.lastName().type(lastName);
|
|
||||||
signupPage.getters.password().type(password);
|
|
||||||
signupPage.getters.submit().click();
|
|
||||||
cy.wait('@userSignup');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add('setup', ({ email, firstName, lastName, password }, skipIntercept = false) => {
|
|
||||||
const signupPage = new SignupPage();
|
|
||||||
|
|
||||||
cy.intercept('GET', signupPage.url).as('setupPage');
|
|
||||||
cy.visit(signupPage.url);
|
|
||||||
cy.wait('@setupPage');
|
|
||||||
|
|
||||||
signupPage.getters.form().within(() => {
|
|
||||||
cy.url().then((url) => {
|
|
||||||
if (url.includes(signupPage.url)) {
|
|
||||||
signupPage.getters.email().type(email);
|
|
||||||
signupPage.getters.firstName().type(firstName);
|
|
||||||
signupPage.getters.lastName().type(lastName);
|
|
||||||
signupPage.getters.password().type(password);
|
|
||||||
|
|
||||||
cy.intercept('POST', '/rest/owner/setup').as('setupRequest');
|
|
||||||
signupPage.getters.submit().click();
|
|
||||||
|
|
||||||
if(!skipIntercept) {
|
|
||||||
cy.wait('@setupRequest');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cy.log('User already signed up');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add('interceptREST', (method, url) => {
|
Cypress.Commands.add('interceptREST', (method, url) => {
|
||||||
cy.intercept(method, `http://localhost:5678/rest${url}`);
|
cy.intercept(method, `http://localhost:5678/rest${url}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('inviteUsers', ({ instanceOwner, users }) => {
|
const setFeature = (feature: string, enabled: boolean) =>
|
||||||
const settingsUsersPage = new SettingsUsersPage();
|
cy.request('PATCH', `${BASE_URL}/rest/e2e/feature`, { feature: `feat:${feature}`, enabled });
|
||||||
|
|
||||||
cy.signin(instanceOwner);
|
Cypress.Commands.add('enableFeature', (feature: string) => setFeature(feature, true));
|
||||||
|
Cypress.Commands.add('disableFeature', (feature): string => setFeature(feature, false));
|
||||||
users.forEach((user) => {
|
|
||||||
cy.signin(instanceOwner);
|
|
||||||
cy.visit(settingsUsersPage.url);
|
|
||||||
|
|
||||||
cy.interceptREST('POST', '/users').as('inviteUser');
|
|
||||||
|
|
||||||
settingsUsersPage.getters.inviteButton().click();
|
|
||||||
settingsUsersPage.getters.inviteUsersModal().within((modal) => {
|
|
||||||
settingsUsersPage.getters.inviteUsersModalEmailsInput().type(user.email).type('{enter}');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.wait('@inviteUser').then((interception) => {
|
|
||||||
const inviteLink = interception.response!.body.data[0].user.inviteAcceptUrl;
|
|
||||||
cy.log(JSON.stringify(interception.response!.body.data[0].user));
|
|
||||||
cy.log(inviteLink);
|
|
||||||
cy.signout();
|
|
||||||
cy.signup({ ...user, url: inviteLink });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add('resetAll', () => {
|
|
||||||
cy.task('reset');
|
|
||||||
Cypress.session.clearAllSavedSessions();
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add('setupOwner', (payload) => {
|
|
||||||
cy.task('setup-owner', payload);
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add('enableFeature', (feature) => {
|
|
||||||
cy.task('set-feature', { feature, enabled: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add('disableFeature', (feature) => {
|
|
||||||
cy.task('set-feature', { feature, enabled: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add('grantBrowserPermissions', (...permissions: string[]) => {
|
Cypress.Commands.add('grantBrowserPermissions', (...permissions: string[]) => {
|
||||||
if (Cypress.isBrowser('chrome')) {
|
if (Cypress.isBrowser('chrome')) {
|
||||||
|
|
|
@ -1,28 +1,19 @@
|
||||||
// ***********************************************************
|
import { BASE_URL, INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants';
|
||||||
// This example support/e2e.js is processed and
|
|
||||||
// loaded automatically before your test files.
|
|
||||||
//
|
|
||||||
// This is a great place to put global configuration and
|
|
||||||
// behavior that modifies Cypress.
|
|
||||||
//
|
|
||||||
// You can change the location of this file or turn off
|
|
||||||
// automatically serving support files with the
|
|
||||||
// 'supportFile' configuration option.
|
|
||||||
//
|
|
||||||
// You can read more here:
|
|
||||||
// https://on.cypress.io/configuration
|
|
||||||
// ***********************************************************
|
|
||||||
|
|
||||||
import './commands';
|
import './commands';
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.resetAll();
|
cy.request('POST', `${BASE_URL}/rest/e2e/reset`, {
|
||||||
|
owner: INSTANCE_OWNER,
|
||||||
|
members: INSTANCE_MEMBERS,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load custom nodes and credentials fixtures
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
if (!cy.config('disableAutoLogin')) {
|
||||||
|
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||||
|
}
|
||||||
|
|
||||||
cy.intercept('GET', '/rest/settings').as('loadSettings');
|
cy.intercept('GET', '/rest/settings').as('loadSettings');
|
||||||
cy.intercept('GET', '/rest/login').as('loadLogin');
|
|
||||||
|
|
||||||
// Always intercept the request to test credentials and return a success
|
// Always intercept the request to test credentials and return a success
|
||||||
cy.intercept('POST', '/rest/credentials/test', {
|
cy.intercept('POST', '/rest/credentials/test', {
|
||||||
|
|
|
@ -8,25 +8,14 @@ interface SigninPayload {
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SetupPayload {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SignupPayload extends SetupPayload {
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface InviteUsersPayload {
|
|
||||||
instanceOwner: SigninPayload;
|
|
||||||
users: SetupPayload[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Cypress {
|
namespace Cypress {
|
||||||
|
interface SuiteConfigOverrides {
|
||||||
|
disableAutoLogin: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface Chainable {
|
interface Chainable {
|
||||||
|
config(key: keyof SuiteConfigOverrides): boolean;
|
||||||
getByTestId(
|
getByTestId(
|
||||||
selector: string,
|
selector: string,
|
||||||
...args: (Partial<Loggable & Timeoutable & Withinable & Shadow> | undefined)[]
|
...args: (Partial<Loggable & Timeoutable & Withinable & Shadow> | undefined)[]
|
||||||
|
@ -35,12 +24,7 @@ declare global {
|
||||||
createFixtureWorkflow(fixtureKey: string, workflowName: string): void;
|
createFixtureWorkflow(fixtureKey: string, workflowName: string): void;
|
||||||
signin(payload: SigninPayload): void;
|
signin(payload: SigninPayload): void;
|
||||||
signout(): void;
|
signout(): void;
|
||||||
signup(payload: SignupPayload): void;
|
|
||||||
setup(payload: SetupPayload, skipIntercept?: boolean): void;
|
|
||||||
setupOwner(payload: SetupPayload): void;
|
|
||||||
inviteUsers(payload: InviteUsersPayload): void;
|
|
||||||
interceptREST(method: string, url: string): Chainable<Interception>;
|
interceptREST(method: string, url: string): Chainable<Interception>;
|
||||||
resetAll(): void;
|
|
||||||
enableFeature(feature: string): void;
|
enableFeature(feature: string): void;
|
||||||
disableFeature(feature: string): void;
|
disableFeature(feature: string): void;
|
||||||
waitForLoad(waitForIntercepts?: boolean): void;
|
waitForLoad(waitForIntercepts?: boolean): void;
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
"cypress:open": "CYPRESS_BASE_URL=http://localhost:8080 cypress open",
|
"cypress:open": "CYPRESS_BASE_URL=http://localhost:8080 cypress open",
|
||||||
"test:e2e:ui": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first start-server-and-test start http://localhost:5678/favicon.ico 'cypress open'",
|
"test:e2e:ui": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first start-server-and-test start http://localhost:5678/favicon.ico 'cypress open'",
|
||||||
"test:e2e:dev": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first CYPRESS_BASE_URL=http://localhost:8080 start-server-and-test dev http://localhost:8080/favicon.ico 'cypress open'",
|
"test:e2e:dev": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first CYPRESS_BASE_URL=http://localhost:8080 start-server-and-test dev http://localhost:8080/favicon.ico 'cypress open'",
|
||||||
"test:e2e:smoke": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first start-server-and-test start http://localhost:5678/favicon.ico 'cypress run --headless --spec \"cypress/e2e/0-smoke.cy.ts\"'",
|
|
||||||
"test:e2e:all": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first start-server-and-test start http://localhost:5678/favicon.ico 'cypress run --headless'"
|
"test:e2e:all": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first start-server-and-test start http://localhost:5678/favicon.ico 'cypress run --headless'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -53,7 +52,6 @@
|
||||||
"jest-mock": "^29.5.0",
|
"jest-mock": "^29.5.0",
|
||||||
"jest-mock-extended": "^3.0.4",
|
"jest-mock-extended": "^3.0.4",
|
||||||
"nock": "^13.2.9",
|
"nock": "^13.2.9",
|
||||||
"node-fetch": "^2.6.7",
|
|
||||||
"p-limit": "^3.1.0",
|
"p-limit": "^3.1.0",
|
||||||
"prettier": "^2.8.3",
|
"prettier": "^2.8.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
dist/ReloadNodesAndCredentials.*
|
|
|
@ -59,7 +59,9 @@
|
||||||
"bin",
|
"bin",
|
||||||
"templates",
|
"templates",
|
||||||
"dist",
|
"dist",
|
||||||
"oclif.manifest.json"
|
"oclif.manifest.json",
|
||||||
|
"!dist/**/e2e.*",
|
||||||
|
"!dist/ReloadNodesAndCredentials.*"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@apidevtools/swagger-cli": "4.0.0",
|
"@apidevtools/swagger-cli": "4.0.0",
|
||||||
|
|
|
@ -96,9 +96,8 @@ export class License {
|
||||||
await this.manager.renew();
|
await this.manager.renew();
|
||||||
}
|
}
|
||||||
|
|
||||||
isFeatureEnabled(feature: string): boolean {
|
isFeatureEnabled(feature: LICENSE_FEATURES): boolean {
|
||||||
if (!this.manager) {
|
if (!this.manager) {
|
||||||
getLogger().warn('License manager not initialized');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ import {
|
||||||
EDITOR_UI_DIST_DIR,
|
EDITOR_UI_DIST_DIR,
|
||||||
GENERATED_STATIC_DIR,
|
GENERATED_STATIC_DIR,
|
||||||
inDevelopment,
|
inDevelopment,
|
||||||
|
inE2ETests,
|
||||||
N8N_VERSION,
|
N8N_VERSION,
|
||||||
RESPONSE_ERROR_MESSAGES,
|
RESPONSE_ERROR_MESSAGES,
|
||||||
TEMPLATES_DIR,
|
TEMPLATES_DIR,
|
||||||
|
@ -338,10 +339,6 @@ export class Server extends AbstractServer {
|
||||||
|
|
||||||
this.push = Container.get(Push);
|
this.push = Container.get(Push);
|
||||||
|
|
||||||
if (process.env.E2E_TESTS === 'true') {
|
|
||||||
this.app.use('/e2e', require('./api/e2e.api').e2eController);
|
|
||||||
}
|
|
||||||
|
|
||||||
await super.start();
|
await super.start();
|
||||||
|
|
||||||
const cpus = os.cpus();
|
const cpus = os.cpus();
|
||||||
|
@ -461,7 +458,7 @@ export class Server extends AbstractServer {
|
||||||
return this.frontendSettings;
|
return this.frontendSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerControllers(ignoredEndpoints: Readonly<string[]>) {
|
private async registerControllers(ignoredEndpoints: Readonly<string[]>) {
|
||||||
const { app, externalHooks, activeWorkflowRunner, nodeTypes } = this;
|
const { app, externalHooks, activeWorkflowRunner, nodeTypes } = this;
|
||||||
const repositories = Db.collections;
|
const repositories = Db.collections;
|
||||||
setupAuthMiddlewares(app, ignoredEndpoints, this.restEndpoint);
|
setupAuthMiddlewares(app, ignoredEndpoints, this.restEndpoint);
|
||||||
|
@ -515,6 +512,12 @@ export class Server extends AbstractServer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inE2ETests) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
const { E2EController } = await import('./controllers/e2e.controller');
|
||||||
|
controllers.push(Container.get(E2EController));
|
||||||
|
}
|
||||||
|
|
||||||
controllers.forEach((controller) => registerController(app, config, controller));
|
controllers.forEach((controller) => registerController(app, config, controller));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,7 +593,7 @@ export class Server extends AbstractServer {
|
||||||
|
|
||||||
await handleLdapInit();
|
await handleLdapInit();
|
||||||
|
|
||||||
this.registerControllers(ignoredEndpoints);
|
await this.registerControllers(ignoredEndpoints);
|
||||||
|
|
||||||
this.app.use(`/${this.restEndpoint}/credentials`, credentialsController);
|
this.app.use(`/${this.restEndpoint}/credentials`, credentialsController);
|
||||||
|
|
||||||
|
|
|
@ -1,158 +0,0 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
|
||||||
import { Router } from 'express';
|
|
||||||
import type { Request } from 'express';
|
|
||||||
import bodyParser from 'body-parser';
|
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { Container } from 'typedi';
|
|
||||||
import config from '@/config';
|
|
||||||
import * as Db from '@/Db';
|
|
||||||
import type { Role } from '@db/entities/Role';
|
|
||||||
import { RoleRepository } from '@db/repositories';
|
|
||||||
import { hashPassword } from '@/UserManagement/UserManagementHelper';
|
|
||||||
import { eventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
|
|
||||||
import { License } from '../License';
|
|
||||||
import { LICENSE_FEATURES } from '@/constants';
|
|
||||||
|
|
||||||
if (process.env.E2E_TESTS !== 'true') {
|
|
||||||
console.error('E2E endpoints only allowed during E2E tests');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const enabledFeatures = {
|
|
||||||
[LICENSE_FEATURES.SHARING]: true, //default to true here instead of setting it in config/index.ts for e2e
|
|
||||||
[LICENSE_FEATURES.LDAP]: false,
|
|
||||||
[LICENSE_FEATURES.SAML]: false,
|
|
||||||
[LICENSE_FEATURES.LOG_STREAMING]: false,
|
|
||||||
[LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS]: false,
|
|
||||||
[LICENSE_FEATURES.SOURCE_CONTROL]: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Feature = keyof typeof enabledFeatures;
|
|
||||||
|
|
||||||
Container.get(License).isFeatureEnabled = (feature: Feature) => enabledFeatures[feature] ?? false;
|
|
||||||
|
|
||||||
const tablesToTruncate = [
|
|
||||||
'auth_identity',
|
|
||||||
'auth_provider_sync_history',
|
|
||||||
'event_destinations',
|
|
||||||
'shared_workflow',
|
|
||||||
'shared_credentials',
|
|
||||||
'webhook_entity',
|
|
||||||
'workflows_tags',
|
|
||||||
'credentials_entity',
|
|
||||||
'tag_entity',
|
|
||||||
'workflow_statistics',
|
|
||||||
'workflow_entity',
|
|
||||||
'execution_entity',
|
|
||||||
'settings',
|
|
||||||
'installed_packages',
|
|
||||||
'installed_nodes',
|
|
||||||
'user',
|
|
||||||
'role',
|
|
||||||
'variables',
|
|
||||||
];
|
|
||||||
|
|
||||||
const truncateAll = async () => {
|
|
||||||
const connection = Db.getConnection();
|
|
||||||
|
|
||||||
for (const table of tablesToTruncate) {
|
|
||||||
try {
|
|
||||||
await connection.query(
|
|
||||||
`DELETE FROM ${table}; DELETE FROM sqlite_sequence WHERE name=${table};`,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Dropping Table for E2E Reset error: ', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const setupUserManagement = async () => {
|
|
||||||
const connection = Db.getConnection();
|
|
||||||
await connection.query('INSERT INTO role (name, scope) VALUES ("owner", "global");');
|
|
||||||
const instanceOwnerRole = (await connection.query(
|
|
||||||
'SELECT last_insert_rowid() as insertId',
|
|
||||||
)) as Array<{ insertId: number }>;
|
|
||||||
|
|
||||||
const roles: Array<[Role['name'], Role['scope']]> = [
|
|
||||||
['member', 'global'],
|
|
||||||
['owner', 'workflow'],
|
|
||||||
['owner', 'credential'],
|
|
||||||
['user', 'credential'],
|
|
||||||
['editor', 'workflow'],
|
|
||||||
];
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
roles.map(async ([name, scope]) =>
|
|
||||||
connection.query(`INSERT INTO role (name, scope) VALUES ("${name}", "${scope}");`),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await connection.query(
|
|
||||||
`INSERT INTO user (id, globalRoleId) values ("${uuid()}", ${instanceOwnerRole[0].insertId})`,
|
|
||||||
);
|
|
||||||
await connection.query(
|
|
||||||
"INSERT INTO \"settings\" (key, value, loadOnStartup) values ('userManagement.isInstanceOwnerSetUp', 'false', true)",
|
|
||||||
);
|
|
||||||
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetLogStreaming = async () => {
|
|
||||||
enabledFeatures[LICENSE_FEATURES.LOG_STREAMING] = false;
|
|
||||||
for (const id in eventBus.destinations) {
|
|
||||||
await eventBus.removeDestination(id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const e2eController = Router();
|
|
||||||
|
|
||||||
e2eController.post('/db/reset', async (req, res) => {
|
|
||||||
await resetLogStreaming();
|
|
||||||
await truncateAll();
|
|
||||||
await setupUserManagement();
|
|
||||||
|
|
||||||
res.writeHead(204).end();
|
|
||||||
});
|
|
||||||
|
|
||||||
e2eController.post('/db/setup-owner', bodyParser.json(), async (req, res) => {
|
|
||||||
if (config.get('userManagement.isInstanceOwnerSetUp')) {
|
|
||||||
res.writeHead(500).send({ error: 'Owner already setup' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const globalRole = await Container.get(RoleRepository).findGlobalOwnerRoleOrFail();
|
|
||||||
|
|
||||||
const owner = await Db.collections.User.findOneByOrFail({ globalRoleId: globalRole.id });
|
|
||||||
|
|
||||||
await Db.collections.User.update(owner.id, {
|
|
||||||
email: req.body.email,
|
|
||||||
password: await hashPassword(req.body.password),
|
|
||||||
firstName: req.body.firstName,
|
|
||||||
lastName: req.body.lastName,
|
|
||||||
});
|
|
||||||
|
|
||||||
await Db.collections.Settings.update(
|
|
||||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
|
||||||
{ value: 'true' },
|
|
||||||
);
|
|
||||||
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
|
||||||
|
|
||||||
res.writeHead(204).end();
|
|
||||||
});
|
|
||||||
|
|
||||||
e2eController.patch(
|
|
||||||
'/feature/:feature',
|
|
||||||
bodyParser.json(),
|
|
||||||
async (req: Request<{ feature: Feature }>, res) => {
|
|
||||||
const { feature } = req.params;
|
|
||||||
const { enabled } = req.body;
|
|
||||||
|
|
||||||
enabledFeatures[feature] = enabled === undefined || enabled === true;
|
|
||||||
res.writeHead(204).end();
|
|
||||||
},
|
|
||||||
);
|
|
|
@ -18,7 +18,6 @@ if (inE2ETests) {
|
||||||
N8N_PUBLIC_API_DISABLED: 'true',
|
N8N_PUBLIC_API_DISABLED: 'true',
|
||||||
EXTERNAL_FRONTEND_HOOKS_URLS: '',
|
EXTERNAL_FRONTEND_HOOKS_URLS: '',
|
||||||
N8N_PERSONALIZATION_ENABLED: 'false',
|
N8N_PERSONALIZATION_ENABLED: 'false',
|
||||||
NODE_FUNCTION_ALLOW_EXTERNAL: 'node-fetch',
|
|
||||||
};
|
};
|
||||||
} else if (inTest) {
|
} else if (inTest) {
|
||||||
const testsDir = join(tmpdir(), 'n8n-tests/');
|
const testsDir = join(tmpdir(), 'n8n-tests/');
|
||||||
|
|
154
packages/cli/src/controllers/e2e.controller.ts
Normal file
154
packages/cli/src/controllers/e2e.controller.ts
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import config from '@/config';
|
||||||
|
import type { Role } from '@db/entities/Role';
|
||||||
|
import { RoleRepository, SettingsRepository, UserRepository } from '@db/repositories';
|
||||||
|
import { hashPassword } from '@/UserManagement/UserManagementHelper';
|
||||||
|
import { eventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
|
||||||
|
import { License } from '@/License';
|
||||||
|
import { LICENSE_FEATURES, inE2ETests } from '@/constants';
|
||||||
|
import { NoAuthRequired, Patch, Post, RestController } from '@/decorators';
|
||||||
|
import type { UserSetupPayload } from '@/requests';
|
||||||
|
|
||||||
|
if (!inE2ETests) {
|
||||||
|
console.error('E2E endpoints only allowed during E2E tests');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tablesToTruncate = [
|
||||||
|
'auth_identity',
|
||||||
|
'auth_provider_sync_history',
|
||||||
|
'event_destinations',
|
||||||
|
'shared_workflow',
|
||||||
|
'shared_credentials',
|
||||||
|
'webhook_entity',
|
||||||
|
'workflows_tags',
|
||||||
|
'credentials_entity',
|
||||||
|
'tag_entity',
|
||||||
|
'workflow_statistics',
|
||||||
|
'workflow_entity',
|
||||||
|
'execution_entity',
|
||||||
|
'settings',
|
||||||
|
'installed_packages',
|
||||||
|
'installed_nodes',
|
||||||
|
'user',
|
||||||
|
'role',
|
||||||
|
'variables',
|
||||||
|
];
|
||||||
|
|
||||||
|
type ResetRequest = Request<
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
owner: UserSetupPayload;
|
||||||
|
members: UserSetupPayload[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
@NoAuthRequired()
|
||||||
|
@RestController('/e2e')
|
||||||
|
export class E2EController {
|
||||||
|
private enabledFeatures: Record<LICENSE_FEATURES, boolean> = {
|
||||||
|
[LICENSE_FEATURES.SHARING]: false,
|
||||||
|
[LICENSE_FEATURES.LDAP]: false,
|
||||||
|
[LICENSE_FEATURES.SAML]: false,
|
||||||
|
[LICENSE_FEATURES.LOG_STREAMING]: false,
|
||||||
|
[LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS]: false,
|
||||||
|
[LICENSE_FEATURES.SOURCE_CONTROL]: false,
|
||||||
|
[LICENSE_FEATURES.VARIABLES]: false,
|
||||||
|
[LICENSE_FEATURES.API_DISABLED]: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
license: License,
|
||||||
|
private roleRepo: RoleRepository,
|
||||||
|
private settingsRepo: SettingsRepository,
|
||||||
|
private userRepo: UserRepository,
|
||||||
|
) {
|
||||||
|
license.isFeatureEnabled = (feature: LICENSE_FEATURES) =>
|
||||||
|
this.enabledFeatures[feature] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/reset')
|
||||||
|
async reset(req: ResetRequest) {
|
||||||
|
this.resetFeatures();
|
||||||
|
await this.resetLogStreaming();
|
||||||
|
await this.truncateAll();
|
||||||
|
await this.setupUserManagement(req.body.owner, req.body.members);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('/feature')
|
||||||
|
setFeature(req: Request<{}, {}, { feature: LICENSE_FEATURES; enabled: boolean }>) {
|
||||||
|
const { enabled, feature } = req.body;
|
||||||
|
this.enabledFeatures[feature] = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetFeatures() {
|
||||||
|
for (const feature of Object.keys(this.enabledFeatures)) {
|
||||||
|
this.enabledFeatures[feature as LICENSE_FEATURES] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resetLogStreaming() {
|
||||||
|
for (const id in eventBus.destinations) {
|
||||||
|
await eventBus.removeDestination(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async truncateAll() {
|
||||||
|
for (const table of tablesToTruncate) {
|
||||||
|
try {
|
||||||
|
const { connection } = this.roleRepo.manager;
|
||||||
|
await connection.query(
|
||||||
|
`DELETE FROM ${table}; DELETE FROM sqlite_sequence WHERE name=${table};`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Dropping Table for E2E Reset error: ', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setupUserManagement(owner: UserSetupPayload, members: UserSetupPayload[]) {
|
||||||
|
const roles: Array<[Role['name'], Role['scope']]> = [
|
||||||
|
['owner', 'global'],
|
||||||
|
['member', 'global'],
|
||||||
|
['owner', 'workflow'],
|
||||||
|
['owner', 'credential'],
|
||||||
|
['user', 'credential'],
|
||||||
|
['editor', 'workflow'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const [{ id: globalOwnerRoleId }, { id: globalMemberRoleId }] = await this.roleRepo.save(
|
||||||
|
roles.map(([name, scope], index) => ({ name, scope, id: index.toString() })),
|
||||||
|
);
|
||||||
|
|
||||||
|
const users = [];
|
||||||
|
users.push({
|
||||||
|
id: uuid(),
|
||||||
|
...owner,
|
||||||
|
password: await hashPassword(owner.password),
|
||||||
|
globalRoleId: globalOwnerRoleId,
|
||||||
|
});
|
||||||
|
for (const { password, ...payload } of members) {
|
||||||
|
users.push(
|
||||||
|
this.userRepo.create({
|
||||||
|
id: uuid(),
|
||||||
|
...payload,
|
||||||
|
password: await hashPassword(password),
|
||||||
|
globalRoleId: globalMemberRoleId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userRepo.insert(users);
|
||||||
|
|
||||||
|
await this.settingsRepo.update(
|
||||||
|
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||||
|
{ value: 'true' },
|
||||||
|
);
|
||||||
|
|
||||||
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -181,22 +181,19 @@ export declare namespace MeRequest {
|
||||||
export type SurveyAnswers = AuthenticatedRequest<{}, {}, Record<string, string> | {}>;
|
export type SurveyAnswers = AuthenticatedRequest<{}, {}, Record<string, string> | {}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserSetupPayload {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// /owner
|
// /owner
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
export declare namespace OwnerRequest {
|
export declare namespace OwnerRequest {
|
||||||
type Post = AuthenticatedRequest<
|
type Post = AuthenticatedRequest<{}, {}, UserSetupPayload, {}>;
|
||||||
{},
|
|
||||||
{},
|
|
||||||
Partial<{
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
}>,
|
|
||||||
{}
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
|
@ -98,9 +98,6 @@ importers:
|
||||||
nock:
|
nock:
|
||||||
specifier: ^13.2.9
|
specifier: ^13.2.9
|
||||||
version: 13.2.9
|
version: 13.2.9
|
||||||
node-fetch:
|
|
||||||
specifier: ^2.6.7
|
|
||||||
version: 2.6.7
|
|
||||||
p-limit:
|
p-limit:
|
||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
|
@ -17055,18 +17052,6 @@ packages:
|
||||||
resolution: {integrity: sha512-VzW+TAk2wE4X9maiKMlT+GsPU4OMmR1U9CrHSmd3DFLn2IcZ9VJ6M6BBugGfYUnPCLSYxXdZy17M0BEJyhUTwg==}
|
resolution: {integrity: sha512-VzW+TAk2wE4X9maiKMlT+GsPU4OMmR1U9CrHSmd3DFLn2IcZ9VJ6M6BBugGfYUnPCLSYxXdZy17M0BEJyhUTwg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/node-fetch@2.6.7:
|
|
||||||
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
|
|
||||||
engines: {node: 4.x || >=6.0.0}
|
|
||||||
peerDependencies:
|
|
||||||
encoding: ^0.1.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
encoding:
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
whatwg-url: 5.0.0
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/node-fetch@2.6.8:
|
/node-fetch@2.6.8:
|
||||||
resolution: {integrity: sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==}
|
resolution: {integrity: sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==}
|
||||||
engines: {node: 4.x || >=6.0.0}
|
engines: {node: 4.x || >=6.0.0}
|
||||||
|
|
Loading…
Reference in a new issue