mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
ci: Refactor e2e tests to be less flaky (no-changelog) (#9695)
This commit is contained in:
parent
bc35e8c33d
commit
3d0393c739
2
.github/workflows/e2e-reusable.yml
vendored
2
.github/workflows/e2e-reusable.yml
vendored
|
@ -40,7 +40,7 @@ on:
|
|||
containers:
|
||||
description: 'Number of containers to run tests in.'
|
||||
required: false
|
||||
default: '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]'
|
||||
default: '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]'
|
||||
type: string
|
||||
pr_number:
|
||||
description: 'PR number to run tests for.'
|
||||
|
|
|
@ -4,10 +4,16 @@ const sharedOptions = require('@n8n_io/eslint-config/shared');
|
|||
* @type {import('@types/eslint').ESLint.ConfigData}
|
||||
*/
|
||||
module.exports = {
|
||||
extends: ['@n8n_io/eslint-config/base'],
|
||||
extends: ['@n8n_io/eslint-config/base', 'plugin:cypress/recommended'],
|
||||
|
||||
...sharedOptions(__dirname),
|
||||
|
||||
plugins: ['cypress'],
|
||||
|
||||
env: {
|
||||
'cypress/globals': true,
|
||||
},
|
||||
|
||||
rules: {
|
||||
// TODO: remove these rules
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
|
@ -20,5 +26,9 @@ module.exports = {
|
|||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/promise-function-async': 'off',
|
||||
'n8n-local-rules/no-uncaught-json-parse': 'off',
|
||||
|
||||
'cypress/no-assigning-return-values': 'warn',
|
||||
'cypress/no-unnecessary-waiting': 'warn',
|
||||
'cypress/unsafe-to-chain-command': 'warn',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -59,7 +59,7 @@ export const AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME = 'OpenAI Chat Model'
|
|||
export const AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME = 'Auto-fixing Output Parser';
|
||||
export const WEBHOOK_NODE_NAME = 'Webhook';
|
||||
|
||||
export const META_KEY = Cypress.platform === 'darwin' ? '{meta}' : '{ctrl}';
|
||||
export const META_KEY = Cypress.platform === 'darwin' ? 'meta' : 'ctrl';
|
||||
|
||||
export const NEW_GOOGLE_ACCOUNT_NAME = 'Gmail account';
|
||||
export const NEW_TRELLO_ACCOUNT_NAME = 'Trello account';
|
||||
|
|
|
@ -122,8 +122,7 @@ describe('Undo/Redo', () => {
|
|||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
cy.get('body').type('{esc}');
|
||||
cy.get('body').type('{esc}');
|
||||
WorkflowPage.actions.selectAll();
|
||||
cy.get('body').type('{backspace}');
|
||||
WorkflowPage.actions.hitDeleteAllNodes();
|
||||
WorkflowPage.getters.canvasNodes().should('have.have.length', 0);
|
||||
WorkflowPage.actions.hitUndo();
|
||||
WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
|
||||
|
@ -208,7 +207,7 @@ describe('Undo/Redo', () => {
|
|||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
cy.get('body').type('{esc}');
|
||||
cy.get('body').type('{esc}');
|
||||
WorkflowPage.actions.selectAll();
|
||||
WorkflowPage.actions.hitSelectAll();
|
||||
WorkflowPage.actions.hitDisableNodeShortcut();
|
||||
WorkflowPage.getters.disabledNodes().should('have.length', 2);
|
||||
WorkflowPage.actions.hitUndo();
|
||||
|
|
|
@ -199,7 +199,7 @@ describe('Canvas Actions', () => {
|
|||
it('should copy selected nodes', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.actions.selectAll();
|
||||
WorkflowPage.actions.hitSelectAll();
|
||||
|
||||
WorkflowPage.actions.hitCopy();
|
||||
successToast().should('contain', 'Copied!');
|
||||
|
@ -211,7 +211,7 @@ describe('Canvas Actions', () => {
|
|||
it('should select/deselect all nodes', () => {
|
||||
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
WorkflowPage.actions.selectAll();
|
||||
WorkflowPage.actions.hitSelectAll();
|
||||
WorkflowPage.getters.selectedNodes().should('have.length', 2);
|
||||
WorkflowPage.actions.deselectAll();
|
||||
WorkflowPage.getters.selectedNodes().should('have.length', 0);
|
||||
|
|
|
@ -164,8 +164,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
cy.wait(500);
|
||||
WorkflowPage.actions.selectAll();
|
||||
cy.get('body').type('{backspace}');
|
||||
WorkflowPage.actions.hitDeleteAllNodes();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 0);
|
||||
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
|
@ -181,8 +180,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
|
||||
cy.wait(500);
|
||||
WorkflowPage.actions.selectAll();
|
||||
cy.get('body').type('{backspace}');
|
||||
WorkflowPage.actions.hitDeleteAllNodes();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 0);
|
||||
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
|
@ -315,7 +313,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
cy.get('body').type('{esc}');
|
||||
|
||||
// Keyboard shortcut
|
||||
WorkflowPage.actions.selectAll();
|
||||
WorkflowPage.actions.hitSelectAll();
|
||||
WorkflowPage.actions.hitDisableNodeShortcut();
|
||||
WorkflowPage.getters.disabledNodes().should('have.length', 2);
|
||||
WorkflowPage.actions.hitDisableNodeShortcut();
|
||||
|
@ -324,12 +322,12 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
|
||||
WorkflowPage.actions.hitDisableNodeShortcut();
|
||||
WorkflowPage.getters.disabledNodes().should('have.length', 1);
|
||||
WorkflowPage.actions.selectAll();
|
||||
WorkflowPage.actions.hitSelectAll();
|
||||
WorkflowPage.actions.hitDisableNodeShortcut();
|
||||
WorkflowPage.getters.disabledNodes().should('have.length', 2);
|
||||
|
||||
// Context menu
|
||||
WorkflowPage.actions.selectAll();
|
||||
WorkflowPage.actions.hitSelectAll();
|
||||
WorkflowPage.actions.openContextMenu();
|
||||
WorkflowPage.actions.contextMenuAction('toggle_activation');
|
||||
WorkflowPage.getters.disabledNodes().should('have.length', 0);
|
||||
|
@ -341,7 +339,7 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
WorkflowPage.actions.openContextMenu();
|
||||
WorkflowPage.actions.contextMenuAction('toggle_activation');
|
||||
WorkflowPage.getters.disabledNodes().should('have.length', 1);
|
||||
WorkflowPage.actions.selectAll();
|
||||
WorkflowPage.actions.hitSelectAll();
|
||||
WorkflowPage.actions.openContextMenu();
|
||||
WorkflowPage.actions.contextMenuAction('toggle_activation');
|
||||
WorkflowPage.getters.disabledNodes().should('have.length', 2);
|
||||
|
@ -383,8 +381,8 @@ describe('Canvas Node Manipulation and Navigation', () => {
|
|||
WorkflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
WorkflowPage.getters.nodeConnections().should('have.length', 1);
|
||||
|
||||
WorkflowPage.actions.selectAll();
|
||||
WorkflowPage.actions.hitDuplicateNodeShortcut();
|
||||
WorkflowPage.actions.hitSelectAll();
|
||||
WorkflowPage.actions.hitDuplicateNode();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 5);
|
||||
});
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
|
||||
let workflowW2Url = '';
|
||||
it('should create C1, W1, W2, share W1 with U3, as U2', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[0]);
|
||||
cy.signinAsMember(0);
|
||||
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
|
@ -67,7 +67,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
it('should create C2, share C2 with U1 and U2, as U3', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[1]);
|
||||
cy.signinAsMember(1);
|
||||
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
|
@ -83,7 +83,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
it('should open W1, add node using C2 as U3', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[1]);
|
||||
cy.signinAsMember(1);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||
|
@ -99,7 +99,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
it('should open W1, add node using C2 as U2', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[0]);
|
||||
cy.signinAsMember(0);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
|
@ -119,7 +119,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
it('should not have access to W2, as U3', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[1]);
|
||||
cy.signinAsMember(1);
|
||||
|
||||
cy.visit(workflowW2Url);
|
||||
cy.waitForLoad();
|
||||
|
@ -128,7 +128,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
it('should have access to W1, W2, as U1', () => {
|
||||
cy.signin(INSTANCE_OWNER);
|
||||
cy.signinAsOwner();
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
|
@ -144,7 +144,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
it('should automatically test C2 when opened by U2 sharee', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[0]);
|
||||
cy.signinAsMember(0);
|
||||
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.credentialCard('Credential C2').click();
|
||||
|
@ -152,7 +152,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
it('should work for admin role on credentials created by others (also can share it with themselves)', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[0]);
|
||||
cy.signinAsMember(0);
|
||||
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.createCredentialButton().click();
|
||||
|
@ -164,7 +164,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
|
|||
credentialsModal.actions.close();
|
||||
|
||||
cy.signout();
|
||||
cy.signin(INSTANCE_ADMIN);
|
||||
cy.signinAsAdmin();
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.credentialCard('Credential C3').click();
|
||||
credentialsModal.getters.testSuccessTag().should('be.visible');
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('User Management', { disableAutoLogin: true }, () => {
|
|||
cy.enableFeature('sharing');
|
||||
});
|
||||
|
||||
it.only('should login and logout', () => {
|
||||
it('should login and logout', () => {
|
||||
cy.visit('/');
|
||||
cy.get('input[name="email"]').type(INSTANCE_OWNER.email);
|
||||
cy.get('input[name="password"]').type(INSTANCE_OWNER.password);
|
||||
|
|
|
@ -34,15 +34,12 @@ describe('Canvas Actions', () => {
|
|||
addDefaultSticky();
|
||||
workflowPage.actions.deselectAll();
|
||||
workflowPage.actions.addStickyFromContextMenu();
|
||||
workflowPage.actions.hitAddStickyShortcut();
|
||||
workflowPage.actions.hitAddSticky();
|
||||
|
||||
workflowPage.getters.stickies().should('have.length', 3);
|
||||
|
||||
// Should not add a sticky for ctrl+shift+s
|
||||
cy.get('body')
|
||||
.type(META_KEY, { delay: 500, release: false })
|
||||
.type('{shift}', { release: false })
|
||||
.type('s');
|
||||
cy.get('body').type(`{${META_KEY}+shift+s}`);
|
||||
|
||||
workflowPage.getters.stickies().should('have.length', 3);
|
||||
workflowPage.getters
|
||||
|
|
|
@ -6,13 +6,12 @@ import {
|
|||
getPublicApiUpgradeCTA,
|
||||
} from '../pages';
|
||||
import planData from '../fixtures/Plan_data_opt_in_trial.json';
|
||||
import { INSTANCE_OWNER } from '../constants';
|
||||
|
||||
const mainSidebar = new MainSidebar();
|
||||
const bannerStack = new BannerStack();
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
describe('Cloud', { disableAutoLogin: true }, () => {
|
||||
describe('Cloud', () => {
|
||||
before(() => {
|
||||
const now = new Date();
|
||||
const fiveDaysFromNow = new Date(now.getTime() + 5 * 24 * 60 * 60 * 1000);
|
||||
|
@ -20,22 +19,12 @@ describe('Cloud', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', '/rest/admin/cloud-plan', {
|
||||
body: planData,
|
||||
}).as('getPlanData');
|
||||
|
||||
cy.intercept('GET', '/rest/settings', (req) => {
|
||||
req.on('response', (res) => {
|
||||
res.send({
|
||||
data: {
|
||||
...res.body.data,
|
||||
cy.overrideSettings({
|
||||
deployment: { type: 'cloud' },
|
||||
n8nMetadata: { userId: 1 },
|
||||
},
|
||||
n8nMetadata: { userId: '1' },
|
||||
});
|
||||
});
|
||||
}).as('loadSettings');
|
||||
|
||||
cy.intercept('GET', '/rest/admin/cloud-plan', planData).as('getPlanData');
|
||||
cy.intercept('GET', '/rest/cloud/proxy/user/me', {}).as('getCloudUserInfo');
|
||||
cy.intercept('GET', new RegExp('/rest/projects*')).as('projects');
|
||||
cy.intercept('GET', new RegExp('/rest/roles')).as('roles');
|
||||
});
|
||||
|
@ -49,8 +38,6 @@ describe('Cloud', { disableAutoLogin: true }, () => {
|
|||
|
||||
describe('BannerStack', () => {
|
||||
it('should render trial banner for opt-in cloud user', () => {
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
|
||||
visitWorkflowPage();
|
||||
|
||||
bannerStack.getters.banner().should('be.visible');
|
||||
|
@ -58,21 +45,11 @@ describe('Cloud', { disableAutoLogin: true }, () => {
|
|||
mainSidebar.actions.signout();
|
||||
|
||||
bannerStack.getters.banner().should('not.be.visible');
|
||||
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
|
||||
visitWorkflowPage();
|
||||
|
||||
bannerStack.getters.banner().should('be.visible');
|
||||
|
||||
mainSidebar.actions.signout();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Admin Home', () => {
|
||||
it('Should show admin button', () => {
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
|
||||
visitWorkflowPage();
|
||||
|
||||
mainSidebar.getters.adminPanel().should('be.visible');
|
||||
|
@ -81,8 +58,6 @@ describe('Cloud', { disableAutoLogin: true }, () => {
|
|||
|
||||
describe('Public API', () => {
|
||||
it('Should show upgrade CTA for Public API if user is trialing', () => {
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
|
||||
visitPublicApiPage();
|
||||
cy.wait(['@loadSettings', '@projects', '@roles', '@getPlanData']);
|
||||
|
||||
|
|
|
@ -34,9 +34,8 @@ const signinPage = new SigninPage();
|
|||
const personalSettingsPage = new PersonalSettingsPage();
|
||||
const mainSidebar = new MainSidebar();
|
||||
|
||||
describe('Two-factor authentication', () => {
|
||||
describe('Two-factor authentication', { disableAutoLogin: true }, () => {
|
||||
beforeEach(() => {
|
||||
void Cypress.session.clearAllSavedSessions();
|
||||
cy.request('POST', `${BACKEND_BASE_URL}/rest/e2e/reset`, {
|
||||
owner: user,
|
||||
members: [],
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
HTTP_REQUEST_NODE_NAME,
|
||||
IF_NODE_NAME,
|
||||
INSTANCE_OWNER,
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
} from '../constants';
|
||||
|
@ -21,7 +20,7 @@ describe('Debug', () => {
|
|||
cy.intercept('GET', '/rest/executions/*').as('getExecution');
|
||||
cy.intercept('POST', '/rest/workflows/**/run').as('postWorkflowRun');
|
||||
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
cy.signinAsOwner();
|
||||
|
||||
workflowPage.actions.visit();
|
||||
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
import { TemplatesPage } from '../pages/templates';
|
||||
import { WorkflowPage } from '../pages/workflow';
|
||||
import { WorkflowsPage } from '../pages/workflows';
|
||||
import { MainSidebar } from '../pages/sidebar/main-sidebar';
|
||||
import OnboardingWorkflow from '../fixtures/Onboarding_workflow.json';
|
||||
import WorkflowTemplate from '../fixtures/Workflow_template_write_http_query.json';
|
||||
|
||||
const templatesPage = new TemplatesPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const mainSidebar = new MainSidebar();
|
||||
|
||||
describe('Workflow templates', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', '**/rest/settings', (req) => {
|
||||
// Disable cache
|
||||
delete req.headers['if-none-match'];
|
||||
req.reply((res) => {
|
||||
if (res.body.data) {
|
||||
// Disable custom templates host if it has been overridden by another intercept
|
||||
res.body.data.templates = { enabled: true, host: 'https://api.n8n.io/api/' };
|
||||
}
|
||||
const mockTemplateHost = (host: string) => {
|
||||
cy.overrideSettings({
|
||||
templates: { enabled: true, host },
|
||||
});
|
||||
}).as('settingsRequest');
|
||||
};
|
||||
|
||||
describe('For api.n8n.io', () => {
|
||||
beforeEach(() => {
|
||||
mockTemplateHost('https://api.n8n.io/api/');
|
||||
});
|
||||
|
||||
it('Opens website when clicking templates sidebar link', () => {
|
||||
|
@ -47,9 +49,198 @@ describe('Workflow templates', () => {
|
|||
});
|
||||
|
||||
it('Redirects to website when visiting templates page directly', () => {
|
||||
cy.intercept(
|
||||
{
|
||||
hostname: 'n8n.io',
|
||||
pathname: '/workflows',
|
||||
},
|
||||
'Mock Template Page',
|
||||
).as('templatesPage');
|
||||
|
||||
cy.visit(templatesPage.url);
|
||||
cy.origin('https://n8n.io', () => {
|
||||
cy.url().should('include', 'https://n8n.io/workflows');
|
||||
|
||||
cy.wait('@templatesPage');
|
||||
});
|
||||
});
|
||||
|
||||
describe('For a custom template host', () => {
|
||||
const hostname = 'random.domain';
|
||||
const categories = [
|
||||
{ id: 1, name: 'Engineering' },
|
||||
{ id: 2, name: 'Finance' },
|
||||
{ id: 3, name: 'Sales' },
|
||||
];
|
||||
const collections = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test Collection',
|
||||
workflows: [{ id: 1 }],
|
||||
nodes: [],
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
cy.intercept({ hostname, pathname: '/api/health' }, { status: 'OK' });
|
||||
cy.intercept({ hostname, pathname: '/api/templates/categories' }, { categories });
|
||||
cy.intercept(
|
||||
{ hostname, pathname: '/api/templates/collections', query: { category: '**' } },
|
||||
(req) => {
|
||||
req.reply({ collections: req.query['category[]'] === '3' ? [] : collections });
|
||||
},
|
||||
);
|
||||
cy.intercept(
|
||||
{ hostname, pathname: '/api/templates/search', query: { category: '**' } },
|
||||
(req) => {
|
||||
const fixture =
|
||||
req.query.category === 'Sales'
|
||||
? 'templates_search/sales_templates_search_response.json'
|
||||
: 'templates_search/all_templates_search_response.json';
|
||||
req.reply({ statusCode: 200, fixture });
|
||||
},
|
||||
);
|
||||
|
||||
cy.intercept(
|
||||
{ hostname, pathname: '/api/workflows/templates/1' },
|
||||
{
|
||||
statusCode: 200,
|
||||
body: {
|
||||
id: 1,
|
||||
name: OnboardingWorkflow.name,
|
||||
workflow: OnboardingWorkflow,
|
||||
},
|
||||
},
|
||||
).as('getTemplate');
|
||||
|
||||
cy.intercept(
|
||||
{ hostname, pathname: '/api/templates/workflows/1' },
|
||||
{
|
||||
statusCode: 200,
|
||||
body: WorkflowTemplate,
|
||||
},
|
||||
).as('getTemplatePreview');
|
||||
|
||||
mockTemplateHost(`https://${hostname}/api`);
|
||||
});
|
||||
|
||||
it('can open onboarding flow', () => {
|
||||
templatesPage.actions.openOnboardingFlow();
|
||||
cy.url().should('match', /.*\/workflow\/.*?onboardingId=1$/);
|
||||
|
||||
workflowPage.actions.shouldHaveWorkflowName('Demo: ' + OnboardingWorkflow.name);
|
||||
workflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
workflowPage.getters.canvasNodes().first().should('have.descendants', '.node-pin-data-icon');
|
||||
});
|
||||
|
||||
it('can import template', () => {
|
||||
templatesPage.actions.importTemplate();
|
||||
cy.url().should('include', '/workflow/new?templateId=1');
|
||||
|
||||
workflowPage.getters.canvasNodes().should('have.length', 4);
|
||||
workflowPage.getters.stickies().should('have.length', 1);
|
||||
workflowPage.actions.shouldHaveWorkflowName(OnboardingWorkflow.name);
|
||||
});
|
||||
|
||||
it('should save template id with the workflow', () => {
|
||||
templatesPage.actions.importTemplate();
|
||||
|
||||
cy.visit(templatesPage.url);
|
||||
cy.get('.el-skeleton.n8n-loading').should('not.exist');
|
||||
templatesPage.getters.firstTemplateCard().should('exist');
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
templatesPage.getters.firstTemplateCard().click();
|
||||
cy.url().should('include', '/templates/1');
|
||||
cy.wait('@getTemplatePreview');
|
||||
|
||||
templatesPage.getters.useTemplateButton().click();
|
||||
cy.url().should('include', '/workflow/new');
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
workflowPage.actions.hitSelectAll();
|
||||
workflowPage.actions.hitCopy();
|
||||
|
||||
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
|
||||
// Check workflow JSON by copying it to clipboard
|
||||
cy.readClipboard().then((workflowJSON) => {
|
||||
expect(workflowJSON).to.contain('"templateId": "1"');
|
||||
});
|
||||
});
|
||||
|
||||
it('can open template with images and hides workflow screenshots', () => {
|
||||
cy.visit(`${templatesPage.url}/1`);
|
||||
cy.wait('@getTemplatePreview');
|
||||
|
||||
templatesPage.getters.description().find('img').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('renders search elements correctly', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
templatesPage.getters.searchInput().should('exist');
|
||||
templatesPage.getters.allCategoriesFilter().should('exist');
|
||||
templatesPage.getters.categoryFilters().should('have.length.greaterThan', 1);
|
||||
templatesPage.getters.templateCards().should('have.length.greaterThan', 0);
|
||||
});
|
||||
|
||||
it('can filter templates by category', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
templatesPage.getters.categoryFilter('sales').should('exist');
|
||||
let initialTemplateCount = 0;
|
||||
let initialCollectionCount = 0;
|
||||
|
||||
templatesPage.getters.templateCountLabel().then(($el) => {
|
||||
initialTemplateCount = parseInt($el.text().replace(/\D/g, ''), 10);
|
||||
templatesPage.getters.collectionCountLabel().then(($el1) => {
|
||||
initialCollectionCount = parseInt($el1.text().replace(/\D/g, ''), 10);
|
||||
|
||||
templatesPage.getters.categoryFilter('sales').click();
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
|
||||
// Should have less templates and collections after selecting a category
|
||||
templatesPage.getters.templateCountLabel().should(($el2) => {
|
||||
expect(parseInt($el2.text().replace(/\D/g, ''), 10)).to.be.lessThan(
|
||||
initialTemplateCount,
|
||||
);
|
||||
});
|
||||
templatesPage.getters.collectionCountLabel().should(($el2) => {
|
||||
expect(parseInt($el2.text().replace(/\D/g, ''), 10)).to.be.lessThan(
|
||||
initialCollectionCount,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve search query in URL', () => {
|
||||
cy.visit(templatesPage.url);
|
||||
templatesPage.getters.templatesLoadingContainer().should('not.exist');
|
||||
templatesPage.getters.categoryFilter('sales').should('exist');
|
||||
templatesPage.getters.categoryFilter('sales').click();
|
||||
templatesPage.getters.searchInput().type('auto');
|
||||
|
||||
cy.url().should('include', '?categories=');
|
||||
cy.url().should('include', '&search=');
|
||||
|
||||
cy.reload();
|
||||
|
||||
// Should preserve search query in URL
|
||||
cy.url().should('include', '?categories=');
|
||||
cy.url().should('include', '&search=');
|
||||
|
||||
// Sales category should still be selected
|
||||
templatesPage.getters
|
||||
.categoryFilter('sales')
|
||||
.find('label')
|
||||
.should('have.class', 'is-checked');
|
||||
// Search input should still have the search query
|
||||
templatesPage.getters.searchInput().should('have.value', 'auto');
|
||||
// Sales checkbox should be pushed to the top
|
||||
templatesPage.getters
|
||||
.categoryFilters()
|
||||
.eq(1)
|
||||
.then(($el) => {
|
||||
expect($el.text()).to.equal('Sales');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,6 @@ import {
|
|||
CODE_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
IF_NODE_NAME,
|
||||
INSTANCE_OWNER,
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
} from '../constants';
|
||||
import {
|
||||
|
@ -125,7 +124,7 @@ describe('Editor actions should work', () => {
|
|||
beforeEach(() => {
|
||||
cy.enableFeature('debugInEditor');
|
||||
cy.enableFeature('workflowHistory');
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
cy.signinAsOwner();
|
||||
createNewWorkflowAndActivate();
|
||||
});
|
||||
|
||||
|
@ -186,7 +185,7 @@ describe('Editor zoom should work after route changes', () => {
|
|||
beforeEach(() => {
|
||||
cy.enableFeature('debugInEditor');
|
||||
cy.enableFeature('workflowHistory');
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
cy.signinAsOwner();
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Lots_of_nodes.json', 'Lots of nodes');
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants';
|
||||
import { WorkerViewPage } from '../pages';
|
||||
|
||||
const workerViewPage = new WorkerViewPage();
|
||||
|
@ -10,13 +9,13 @@ describe('Worker View (unlicensed)', () => {
|
|||
});
|
||||
|
||||
it('should not show up in the menu sidebar', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[0]);
|
||||
cy.signinAsMember(0);
|
||||
cy.visit(workerViewPage.url);
|
||||
workerViewPage.getters.menuItem().should('not.exist');
|
||||
});
|
||||
|
||||
it('should show action box', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[0]);
|
||||
cy.signinAsMember(0);
|
||||
cy.visit(workerViewPage.url);
|
||||
workerViewPage.getters.workerViewUnlicensed().should('exist');
|
||||
});
|
||||
|
@ -29,14 +28,14 @@ describe('Worker View (licensed)', () => {
|
|||
});
|
||||
|
||||
it('should show up in the menu sidebar', () => {
|
||||
cy.signin(INSTANCE_OWNER);
|
||||
cy.signinAsOwner();
|
||||
cy.enableQueueMode();
|
||||
cy.visit(workerViewPage.url);
|
||||
workerViewPage.getters.menuItem().should('exist');
|
||||
});
|
||||
|
||||
it('should show worker list view', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[0]);
|
||||
cy.signinAsMember(0);
|
||||
cy.visit(workerViewPage.url);
|
||||
workerViewPage.getters.workerViewLicensed().should('exist');
|
||||
});
|
||||
|
|
|
@ -8,10 +8,19 @@ import { WorkflowPage } from '../pages/workflow';
|
|||
import * as formStep from '../composables/setup-template-form-step';
|
||||
import { getSetupWorkflowCredentialsButton } from '../composables/setup-workflow-credentials-button';
|
||||
import * as setupCredsModal from '../composables/modals/workflow-credential-setup-modal';
|
||||
import TestTemplate1 from '../fixtures/Test_Template_1.json';
|
||||
import TestTemplate2 from '../fixtures/Test_Template_2.json';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
const testTemplate = templateCredentialsSetupPage.testData.simpleTemplate;
|
||||
const testTemplate = {
|
||||
id: 1205,
|
||||
data: TestTemplate1,
|
||||
};
|
||||
const templateWithoutCredentials = {
|
||||
id: 1344,
|
||||
data: TestTemplate2,
|
||||
};
|
||||
|
||||
// NodeView uses beforeunload listener that will show a browser
|
||||
// native popup, which will block cypress from continuing / exiting.
|
||||
|
@ -29,19 +38,19 @@ Cypress.on('window:before:load', (win) => {
|
|||
|
||||
describe('Template credentials setup', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', `https://api.n8n.io/api/templates/workflows/${testTemplate.id}`, {
|
||||
fixture: testTemplate.fixture,
|
||||
cy.intercept(
|
||||
'GET',
|
||||
`https://api.n8n.io/api/templates/workflows/${testTemplate.id}`,
|
||||
testTemplate.data,
|
||||
).as('getTemplatePreview');
|
||||
cy.intercept(
|
||||
'GET',
|
||||
`https://api.n8n.io/api/workflows/templates/${testTemplate.id}`,
|
||||
testTemplate.data.workflow,
|
||||
).as('getTemplate');
|
||||
cy.overrideSettings({
|
||||
templates: { enabled: true, host: 'https://api.n8n.io/api/' },
|
||||
});
|
||||
cy.intercept('GET', '**/rest/settings', (req) => {
|
||||
// Disable cache
|
||||
delete req.headers['if-none-match'];
|
||||
req.reply((res) => {
|
||||
if (res.body.data) {
|
||||
// Disable custom templates host if it has been overridden by another intercept
|
||||
res.body.data.templates = { enabled: true, host: 'https://api.n8n.io/api/' };
|
||||
}
|
||||
});
|
||||
}).as('settingsRequest');
|
||||
});
|
||||
|
||||
it('can be opened from template collection page', () => {
|
||||
|
@ -108,7 +117,7 @@ describe('Template credentials setup', () => {
|
|||
|
||||
// Focus the canvas so the copy to clipboard works
|
||||
workflowPage.getters.canvasNodes().eq(0).realClick();
|
||||
workflowPage.actions.selectAll();
|
||||
workflowPage.actions.hitSelectAll();
|
||||
workflowPage.actions.hitCopy();
|
||||
|
||||
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
|
||||
|
@ -125,11 +134,9 @@ describe('Template credentials setup', () => {
|
|||
});
|
||||
|
||||
it('should work with a template that has no credentials (ADO-1603)', () => {
|
||||
const templateWithoutCreds = templateCredentialsSetupPage.testData.templateWithoutCredentials;
|
||||
cy.intercept('GET', `https://api.n8n.io/api/templates/workflows/${templateWithoutCreds.id}`, {
|
||||
fixture: templateWithoutCreds.fixture,
|
||||
});
|
||||
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(templateWithoutCreds.id);
|
||||
const { id, data } = templateWithoutCredentials;
|
||||
cy.intercept('GET', `https://api.n8n.io/api/templates/workflows/${id}`, data);
|
||||
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(id);
|
||||
|
||||
const expectedAppNames = ['1. Email (IMAP)', '2. Nextcloud'];
|
||||
const expectedAppDescriptions = [
|
||||
|
@ -152,7 +159,7 @@ describe('Template credentials setup', () => {
|
|||
workflowPage.getters.canvasNodes().should('have.length', 3);
|
||||
});
|
||||
|
||||
describe('Credential setup from workflow editor', () => {
|
||||
describe('Credential setup from workflow editor', { disableAutoLogin: true }, () => {
|
||||
beforeEach(() => {
|
||||
cy.resetDatabase();
|
||||
cy.signinAsOwner();
|
||||
|
@ -190,7 +197,7 @@ describe('Template credentials setup', () => {
|
|||
|
||||
// Focus the canvas so the copy to clipboard works
|
||||
workflowPage.getters.canvasNodes().eq(0).realClick();
|
||||
workflowPage.actions.selectAll();
|
||||
workflowPage.actions.hitSelectAll();
|
||||
workflowPage.actions.hitCopy();
|
||||
|
||||
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { INSTANCE_ADMIN, INSTANCE_OWNER } from '../constants';
|
||||
import { SettingsPage } from '../pages/settings';
|
||||
|
||||
const settingsPage = new SettingsPage();
|
||||
|
||||
describe('Admin user', { disableAutoLogin: true }, () => {
|
||||
it('should see same Settings sub menu items as instance owner', () => {
|
||||
cy.signin(INSTANCE_OWNER);
|
||||
cy.signinAsOwner();
|
||||
cy.visit(settingsPage.url);
|
||||
|
||||
let ownerMenuItems = 0;
|
||||
|
@ -15,7 +14,7 @@ describe('Admin user', { disableAutoLogin: true }, () => {
|
|||
});
|
||||
|
||||
cy.signout();
|
||||
cy.signin(INSTANCE_ADMIN);
|
||||
cy.signinAsAdmin();
|
||||
cy.visit(settingsPage.url);
|
||||
|
||||
settingsPage.getters.menuItems().should('have.length', ownerMenuItems);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { INSTANCE_OWNER } from '../constants';
|
||||
import { WorkflowsPage } from '../pages/workflows';
|
||||
import {
|
||||
closeVersionUpdatesPanel,
|
||||
|
@ -11,11 +10,7 @@ const workflowsPage = new WorkflowsPage();
|
|||
|
||||
describe('Versions', () => {
|
||||
it('should open updates panel', () => {
|
||||
cy.intercept('GET', '/rest/settings', (req) => {
|
||||
req.continue((res) => {
|
||||
if (res.body.hasOwnProperty('data')) {
|
||||
res.body.data = {
|
||||
...res.body.data,
|
||||
cy.overrideSettings({
|
||||
releaseChannel: 'stable',
|
||||
versionCli: '1.0.0',
|
||||
versionNotifications: {
|
||||
|
@ -23,40 +18,10 @@ describe('Versions', () => {
|
|||
endpoint: 'https://api.n8n.io/api/versions/',
|
||||
infoUrl: 'https://docs.n8n.io/getting-started/installation/updating.html',
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
}).as('settings');
|
||||
|
||||
cy.intercept('GET', 'https://api.n8n.io/api/versions/1.0.0', [
|
||||
{
|
||||
name: '1.3.1',
|
||||
createdAt: '2023-08-18T11:53:12.857Z',
|
||||
hasSecurityIssue: null,
|
||||
hasSecurityFix: null,
|
||||
securityIssueFixVersion: null,
|
||||
hasBreakingChange: null,
|
||||
documentationUrl: 'https://docs.n8n.io/release-notes/#n8n131',
|
||||
nodes: [],
|
||||
description: 'Includes <strong>bug fixes</strong>',
|
||||
},
|
||||
{
|
||||
name: '1.0.5',
|
||||
createdAt: '2023-07-24T10:54:56.097Z',
|
||||
hasSecurityIssue: false,
|
||||
hasSecurityFix: null,
|
||||
securityIssueFixVersion: null,
|
||||
hasBreakingChange: true,
|
||||
documentationUrl: 'https://docs.n8n.io/release-notes/#n8n104',
|
||||
nodes: [],
|
||||
description: 'Includes <strong>core functionality</strong> and <strong>bug fixes</strong>',
|
||||
},
|
||||
]);
|
||||
|
||||
cy.signin(INSTANCE_OWNER);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
cy.wait('@settings');
|
||||
cy.wait('@loadSettings');
|
||||
|
||||
getVersionUpdatesPanelOpenButton().should('contain', '2 updates');
|
||||
openVersionUpdatesPanel();
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import {
|
||||
INSTANCE_ADMIN,
|
||||
INSTANCE_MEMBERS,
|
||||
INSTANCE_OWNER,
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
NOTION_NODE_NAME,
|
||||
} from '../constants';
|
||||
import { INSTANCE_MEMBERS, MANUAL_TRIGGER_NODE_NAME, NOTION_NODE_NAME } from '../constants';
|
||||
import {
|
||||
WorkflowsPage,
|
||||
WorkflowPage,
|
||||
|
@ -23,7 +17,7 @@ const credentialsModal = new CredentialsModal();
|
|||
const executionsTab = new WorkflowExecutionsTab();
|
||||
const ndv = new NDV();
|
||||
|
||||
describe('Projects', () => {
|
||||
describe('Projects', { disableAutoLogin: true }, () => {
|
||||
before(() => {
|
||||
cy.resetDatabase();
|
||||
cy.enableFeature('sharing');
|
||||
|
@ -34,7 +28,7 @@ describe('Projects', () => {
|
|||
});
|
||||
|
||||
it('should handle workflows and credentials and menu items', () => {
|
||||
cy.signin(INSTANCE_ADMIN);
|
||||
cy.signinAsAdmin();
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('not.have.length');
|
||||
|
||||
|
@ -230,8 +224,7 @@ describe('Projects', () => {
|
|||
});
|
||||
|
||||
it('should not show project add button and projects to a member if not invited to any project', () => {
|
||||
cy.signout();
|
||||
cy.signin(INSTANCE_MEMBERS[1]);
|
||||
cy.signinAsMember(1);
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
projects.getAddProjectButton().should('not.exist');
|
||||
|
@ -249,7 +242,7 @@ describe('Projects', () => {
|
|||
});
|
||||
|
||||
it('should filter credentials by project ID when creating new workflow or hard reloading an opened workflow', () => {
|
||||
cy.signin(INSTANCE_OWNER);
|
||||
cy.signinAsOwner();
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
// Create a project and add a credential to it
|
||||
|
|
|
@ -345,7 +345,7 @@ describe('NDV', () => {
|
|||
ndv.getters.parameterInput('remoteOptions').click();
|
||||
getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3);
|
||||
|
||||
ndv.actions.setInvalidExpression({ fieldName: 'fieldId', delay: 200 });
|
||||
ndv.actions.setInvalidExpression({ fieldName: 'fieldId' });
|
||||
|
||||
ndv.getters.inputPanel().click(); // remove focus from input, hide expression preview
|
||||
|
||||
|
@ -363,7 +363,7 @@ describe('NDV', () => {
|
|||
getVisibleSelect().find('.el-select-dropdown__item').should('have.length', 3);
|
||||
ndv.getters.parameterInput('remoteOptions').click();
|
||||
|
||||
ndv.actions.setInvalidExpression({ fieldName: 'otherField', delay: 50 });
|
||||
ndv.actions.setInvalidExpression({ fieldName: 'otherField' });
|
||||
|
||||
ndv.getters.nodeParameters().click(); // remove focus from input, hide expression preview
|
||||
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import {
|
||||
CODE_NODE_NAME,
|
||||
MANUAL_TRIGGER_NODE_NAME,
|
||||
META_KEY,
|
||||
SCHEDULE_TRIGGER_NODE_NAME,
|
||||
EDIT_FIELDS_SET_NODE_NAME,
|
||||
INSTANCE_MEMBERS,
|
||||
INSTANCE_OWNER,
|
||||
NOTION_NODE_NAME,
|
||||
} from '../constants';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
|
@ -136,13 +133,13 @@ describe('Workflow Actions', () => {
|
|||
);
|
||||
cy.reload();
|
||||
cy.get('.el-loading-mask').should('exist');
|
||||
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||
WorkflowPage.actions.hitSaveWorkflow();
|
||||
WorkflowPage.actions.hitSaveWorkflow();
|
||||
WorkflowPage.actions.hitSaveWorkflow();
|
||||
cy.wrap(null).then(() => expect(interceptCalledCount).to.eq(0));
|
||||
cy.waitForLoad();
|
||||
WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||
WorkflowPage.actions.hitSaveWorkflow();
|
||||
cy.wait('@saveWorkflow');
|
||||
cy.wrap(null).then(() => expect(interceptCalledCount).to.eq(1));
|
||||
});
|
||||
|
@ -172,9 +169,10 @@ describe('Workflow Actions', () => {
|
|||
WorkflowPage.getters.canvasNodes().should('have.have.length', 2);
|
||||
|
||||
cy.get('#node-creator').should('not.exist');
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('a');
|
||||
|
||||
WorkflowPage.actions.hitSelectAll();
|
||||
cy.get('.jtk-drag-selected').should('have.length', 2);
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('c');
|
||||
WorkflowPage.actions.hitCopy();
|
||||
successToast().should('exist');
|
||||
});
|
||||
|
||||
|
@ -338,33 +336,32 @@ describe('Workflow Actions', () => {
|
|||
it('should run workflow using keyboard shortcut', () => {
|
||||
WorkflowPage.actions.addInitialNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
|
||||
WorkflowPage.actions.saveWorkflowOnButtonClick();
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('{enter}');
|
||||
WorkflowPage.actions.hitExecuteWorkflow();
|
||||
successToast().should('contain.text', 'Workflow executed successfully');
|
||||
});
|
||||
|
||||
it('should not run empty workflows', () => {
|
||||
// Clear the canvas
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('a');
|
||||
cy.get('body').type('{backspace}');
|
||||
WorkflowPage.actions.hitDeleteAllNodes();
|
||||
WorkflowPage.getters.canvasNodes().should('have.length', 0);
|
||||
// Button should be disabled
|
||||
WorkflowPage.getters.executeWorkflowButton().should('be.disabled');
|
||||
// Keyboard shortcut should not work
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('{enter}');
|
||||
WorkflowPage.actions.hitExecuteWorkflow();
|
||||
successToast().should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Menu entry Push To Git', () => {
|
||||
it('should not show up in the menu for members', () => {
|
||||
cy.signin(INSTANCE_MEMBERS[0]);
|
||||
cy.signinAsMember(0);
|
||||
cy.visit(WorkflowPages.url);
|
||||
WorkflowPage.actions.visit();
|
||||
WorkflowPage.getters.workflowMenuItemGitPush().should('not.exist');
|
||||
});
|
||||
|
||||
it('should show up for owners', () => {
|
||||
cy.signin(INSTANCE_OWNER);
|
||||
cy.signinAsOwner();
|
||||
cy.visit(WorkflowPages.url);
|
||||
WorkflowPage.actions.visit();
|
||||
WorkflowPage.getters.workflowMenuItemGitPush().should('exist');
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"totalWorkflows": 506,
|
||||
"workflows": [
|
||||
{
|
||||
"id": 60,
|
||||
"id": 1,
|
||||
"name": "test1 test1",
|
||||
"totalViews": 120000000,
|
||||
"recentViews": 0,
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"id": 60,
|
||||
"name": "test1 test1",
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Start",
|
||||
"type": "n8n-nodes-base.start",
|
||||
"position": [
|
||||
250,
|
||||
300
|
||||
],
|
||||
"parameters": {},
|
||||
"typeVersion": 1
|
||||
}
|
||||
],
|
||||
"connections": {}
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
{
|
||||
"workflow": {
|
||||
"id": 60,
|
||||
"name": "test1 test1",
|
||||
"views": 120000000,
|
||||
"recentViews": 0,
|
||||
"totalViews": 120000000,
|
||||
"createdAt": "2019-08-30T16:39:31.362Z",
|
||||
"description": "here is a description. here is a description. here is a description. \n\n![Screenshot from 20190806 091433.png](fileId:88)",
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Start",
|
||||
"type": "n8n-nodes-base.start",
|
||||
"position": [
|
||||
250,
|
||||
300
|
||||
],
|
||||
"parameters": {},
|
||||
"typeVersion": 1
|
||||
}
|
||||
],
|
||||
"connections": {}
|
||||
},
|
||||
"lastUpdatedBy": null,
|
||||
"workflowInfo": {
|
||||
"nodeCount": 1,
|
||||
"nodeTypes": {
|
||||
"n8n-nodes-base.start": {
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"username": "admin"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": 11,
|
||||
"icon": "file:amqp.png",
|
||||
"name": "n8n-nodes-base.amqpTrigger",
|
||||
"defaults": {
|
||||
"name": "AMQP Trigger"
|
||||
},
|
||||
"iconData": {
|
||||
"type": "file",
|
||||
"fileBuffer": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAB7UlEQVRoge2W4W3CMBCFj26QjkBHSEdIR4AR6Ah0BBgBRqAjhBFgBBghHaEVlV29PN0lDr+o9D7JEjhn+975bJ8JIYQQQgghhBBCCCGEEA9CY2bf0NaBW2uyu7UN2XSOzTyY60J2BzNbObbsH7eTmS2mhHJHE1wmCD7A93ngEAquHaHc2omCcysSXQW74g32BHfwfTEiuCoQm9vuDsEndPYpELxKjjBj0foCEXX6XdM3by3c7aOZPZvZzMzeaBzbIh9pzIuZXaG/RqNIMAq7Ur8XCHQ2kx3LC56DMQ39X4LI23zbAd88ruRHD09wTVF5p+/eBZI5g7O8w5FgXOvsZAI7PxRwS4HGIPbm8wRjBL/Sgp/QNyQYHWySmOxgJBgFeGnPfZHgDVyufET+YMEVCdo7gziCTBbGmRKlGQpCMXOnj+1L6B0JFsxndO3cjjZyjo6OnZeqGb5gqhTQS3qKeK1SwbesfB3IrF/awqu+g8Dgs5SLE37SciHiPUv8rLVp7k2wdl63tDDqgTs8lqpINWGXbSTKe9rlJgXME7C9I6V7oGAWsEzv2gzeN2TstkbCZyIJWBYKWUwtF4foKGU9TpRGdZDSdVDpDNXSVVBLt5TeucS9K6X/E3USX3rshBBCCCGEEEIIIYQQ4tExsx8PuuPnwhCIbgAAAABJRU5ErkJggg=="
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Development"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Communication"
|
||||
}
|
||||
],
|
||||
"displayName": "AMQP Trigger",
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"icon": "file:autopilot.svg",
|
||||
"name": "n8n-nodes-base.autopilot",
|
||||
"defaults": {
|
||||
"name": "Autopilot"
|
||||
},
|
||||
"iconData": {
|
||||
"type": "file",
|
||||
"fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjM4IDI2IDM1IDM1Ij48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMThkNGIyIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9IiMxOGQ0YjIiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDUuNCA0Mi42aDE5LjlsMy40LTQuOEg0MmwzLjQgNC44em0zLjEgOC4zaDEzLjFsMy40LTQuOEg0NS40bDMuMSA0Ljh6bTU0LS43Ii8+PC9zdmc+"
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Marketing"
|
||||
}
|
||||
],
|
||||
"displayName": "Autopilot",
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"icon": "file:lambda.svg",
|
||||
"name": "n8n-nodes-base.awsLambda",
|
||||
"defaults": {
|
||||
"name": "AWS Lambda"
|
||||
},
|
||||
"iconData": {
|
||||
"type": "file",
|
||||
"fileBuffer": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9Ii0zLjAyMyAtMC4yMiA0MjAuOTIzIDQzMy41NCIgd2lkdGg9IjI0NDMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIwOC40NSAyMjcuODljLTEuNTkgMi4yNi0yLjkzIDQuMTItNC4yMiA2cS0zMC44NiA0NS40Mi02MS43IDkwLjgzLTI4LjY5IDQyLjI0LTU3LjQ0IDg0LjQzYTMuODggMy44OCAwIDAxLTIuNzMgMS41OXEtNDAuNTktLjM1LTgxLjE2LS44OGMtLjMgMC0uNjEtLjA5LTEuMi0uMThhMTQuNDQgMTQuNDQgMCAwMS43Ni0xLjY1cTI4LjMxLTQzLjg5IDU2LjYyLTg3Ljc2IDI1LjExLTM4Ljg4IDUwLjI1LTc3Ljc0IDI3Ljg2LTQzLjE4IDU1LjY5LTg2LjQyYzIuNzQtNC4yNSA1LjU5LTguNDIgOC4xOS0xMi43NWE1LjI2IDUuMjYgMCAwMC41Ni0zLjgzYy01LTE1Ljk0LTEwLjEtMzEuODQtMTUuMTktNDcuNzQtMi4xOC02LjgxLTQuNDYtMTMuNTgtNi41LTIwLjQzLS42Ni0yLjItMS43NS0yLjg3LTQtMi44Ni0xNyAuMDctMzMuOS4wNS01MC44NS4wNS0zLjIyIDAtMy4yMyAwLTMuMjMtMy4xOCAwLTIwLjg0IDAtNDEuNjgtLjA2LTYyLjUyIDAtMi4zMi43Ni0yLjg0IDIuOTQtMi44NHE1MS4xOS4wOSAxMDIuNCAwYTMuMjkgMy4yOSAwIDAxMy42IDIuNDNxMjcgNjcuOTEgNTQgMTM1Ljc3IDMxLjUgNzkuMTQgNjMgMTU4LjNjNi41MiAxNi4zOCAxMy4wOSAzMi43NSAxOS41NCA0OS4xNy43NyAyIDEuNTcgMi4zOCAzLjU5IDEuNzYgMTcuODktNS41MyAzNS44Mi0xMC45MSA1My43LTE2LjQ1IDIuMjUtLjcgMy4wNy0uMjMgMy43NyAyIDYuMSAxOS4xNyAxMi4zMiAzOC4zIDE4LjUgNTcuNDUuMjEuNjYuMzcgMS4zMy42MiAyLjI1LTEuMjguNDctMi40OCAxLTMuNzEgMS4zNHEtNjEgMTkuMzMtMTIxLjkzIDM4LjY4Yy0xLjk0LjYxLTIuNTItLjA1LTMuMTctMS42OHEtMTguNjEtNDcuMTYtMzcuMzEtOTQuMjgtMTguMjktNDYuMTQtMzYuNi05Mi4yOGMtMS44My00LjYyLTMuNjMtOS4yNi01LjQ2LTEzLjg4LS4yOS0uNzktLjY5LTEuNDgtMS4yNy0yLjd6IiBmaWxsPSIjZmE3ZTE0Ii8+PC9zdmc+"
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Development"
|
||||
}
|
||||
],
|
||||
"displayName": "AWS Lambda",
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"icon": "file:clearbit.svg",
|
||||
"name": "n8n-nodes-base.clearbit",
|
||||
"defaults": {
|
||||
"name": "Clearbit"
|
||||
},
|
||||
"iconData": {
|
||||
"type": "file",
|
||||
"fileBuffer": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNTAlIiB4Mj0iMTAwJSIgeTE9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI0RFRjJGRSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI0RCRjFGRSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJiIiB4MT0iMCUiIHgyPSI1MCUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM1N0JDRkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM1MUI1RkQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYyIgeDE9IjM3LjUlIiB4Mj0iNjIuNSUiIHkxPSIwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMxQ0E3RkQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMxNDhDRkMiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGZpbGw9InVybCgjYSkiIGQ9Ik03MiAzNnYxNi43N2wtLjAwNC44NjhjLS4wNiA2LjAzNS0uNzUgOC4zNTMtMiAxMC42ODhhMTMuNjMgMTMuNjMgMCAwMS01LjY3IDUuNjdsLS4zMjYuMTcxQzYxLjY1OCA3MS4zNjQgNTkuMTYgNzIgNTIuNzcgNzJIMzZWMzZoMzZ6Ii8+PHBhdGggZmlsbD0idXJsKCNiKSIgZD0iTTY0LjMyNiAyLjAwM2ExMy42MyAxMy42MyAwIDAxNS42NyA1LjY3bC4xNzEuMzI3QzcxLjM2NCAxMC4zNDIgNzIgMTIuODQgNzIgMTkuMjNWMzZIMzZWMGgxNi43N2M2LjY4NyAwIDkuMTEyLjY5NiAxMS41NTYgMi4wMDN6Ii8+PHBhdGggZmlsbD0idXJsKCNjKSIgZD0iTTM2IDB2NzJIMTkuMjNsLS44NjgtLjAwNGMtNi4wMzUtLjA2LTguMzUzLS43NS0xMC42ODgtMmExMy42MyAxMy42MyAwIDAxLTUuNjctNS42N0wxLjgzMiA2NEMuNjM2IDYxLjY1OCAwIDU5LjE2IDAgNTIuNzdWMTkuMjNjMC02LjY4Ny42OTYtOS4xMTIgMi4wMDMtMTEuNTU2YTEzLjYzIDEzLjYzIDAgMDE1LjY3LTUuNjdMOCAxLjgzMkMxMC4zNDIuNjM2IDEyLjg0IDAgMTkuMjMgMEgzNnoiLz48L2c+PC9zdmc+"
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Sales"
|
||||
}
|
||||
],
|
||||
"displayName": "Clearbit",
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"icon": "file:convertKit.svg",
|
||||
"name": "n8n-nodes-base.convertKitTrigger",
|
||||
"defaults": {
|
||||
"name": "ConvertKit Trigger"
|
||||
},
|
||||
"iconData": {
|
||||
"type": "file",
|
||||
"fileBuffer": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTcyIiBoZWlnaHQ9IjE2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNODIuNzIgMTI2LjMxNmMyOS43NyAwIDUyLjc4LTIyLjYyMiA1Mi43OC01MC41MjYgMC0yNi4xNDMtMjEuNjE3LTQyLjEwNi0zNS45MzUtNDIuMTA2LTE5Ljk0NSAwLTM1LjkzIDE0LjA4NC0zOC4xOTggMzQuOTg4LS40MTggMy44NTYtMy40NzYgNy4wOS03LjM1NSA3LjA2MS02LjQyMy0uMDQ2LTE1Ljc0Ni0uMS0yMS42NTgtLjA4LTIuNTU1LjAwOC00LjY2OS0yLjA2NS00LjU0My00LjYxOC44OS0xOC4xMjMgNi45MTQtMzUuMDcgMTguNDAyLTQ4LjA4N0M1OC45NzYgOC40ODggNzcuNTYxIDAgOTkuNTY1IDBjMzYuOTY5IDAgNzEuODY5IDMzLjc4NiA3MS44NjkgNzUuNzkgMCA0Ni41MDgtMzguMzEyIDg0LjIxLTg3LjkyNyA4NC4yMS0zNS4zODQgMC03MS4wMjEtMjMuMjU4LTgzLjQ2NC01NS43NzVhLjcwMi43MDIgMCAwMS0uMDMtLjM3N2MuMTY1LS45NjIuNDk0LTEuODQxLjgxOC0yLjcwNy40NzEtMS4yNTguOTMxLTIuNDg4Ljg2NC0zLjkwNmwtLjIxNS00LjUyOWE1LjUyMyA1LjUyMyAwIDAxMy4xOC01LjI2M2wxLjc5OC0uODQyYTYuOTgyIDYuOTgyIDAgMDAzLjkxMi01LjA3NSA2Ljk5MyA2Ljk5MyAwIDAxNi44ODctNS43MzZjNS4yODIgMCA5Ljg3NSAzLjUxNSAxMS41OSA4LjUxMiA4LjMwNyAyNC4yMTIgMjEuNTExIDQyLjAxNCA1My44NzMgNDIuMDE0eiIgZmlsbD0iI0ZCNjk3MCIvPjwvc3ZnPg=="
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Marketing"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Sales"
|
||||
}
|
||||
],
|
||||
"displayName": "ConvertKit Trigger",
|
||||
"typeVersion": 1
|
||||
}
|
||||
],
|
||||
"categories": [],
|
||||
"image": []
|
||||
}
|
||||
}
|
|
@ -10,19 +10,23 @@
|
|||
"format": "prettier --write . --ignore-path ../.prettierignore",
|
||||
"lint": "eslint . --quiet",
|
||||
"lintfix": "eslint . --fix",
|
||||
"develop": "cd ..; pnpm dev",
|
||||
"start": "cd ..; pnpm start"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.195",
|
||||
"@types/uuid": "^8.3.2",
|
||||
"eslint-plugin-cypress": "^3.3.0",
|
||||
"n8n-workflow": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ngneat/falso": "^6.4.0",
|
||||
"@ngneat/falso": "^7.2.0",
|
||||
"@sinonjs/fake-timers": "^11.2.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^13.6.2",
|
||||
"cypress": "^13.11.0",
|
||||
"cypress-otp": "^1.0.3",
|
||||
"cypress-real-events": "^1.11.0",
|
||||
"cypress-real-events": "^1.12.0",
|
||||
"lodash": "4.17.21",
|
||||
"start-server-and-test": "^2.0.3",
|
||||
"uuid": "8.3.2"
|
||||
}
|
||||
|
|
|
@ -158,9 +158,7 @@ export class NDV extends BasePage {
|
|||
this.getters.pinnedDataEditor().click();
|
||||
this.getters
|
||||
.pinnedDataEditor()
|
||||
.type(`{selectall}{backspace}${pinnedData.replace(new RegExp('{', 'g'), '{{}')}`, {
|
||||
delay: 0,
|
||||
});
|
||||
.type(`{selectall}{backspace}${pinnedData.replace(new RegExp('{', 'g'), '{{}')}`);
|
||||
|
||||
this.actions.savePinnedData();
|
||||
},
|
||||
|
@ -168,10 +166,7 @@ export class NDV extends BasePage {
|
|||
this.getters.editPinnedDataButton().click();
|
||||
|
||||
this.getters.pinnedDataEditor().click();
|
||||
this.getters
|
||||
.pinnedDataEditor()
|
||||
.type('{selectall}{backspace}', { delay: 0 })
|
||||
.paste(JSON.stringify(data));
|
||||
this.getters.pinnedDataEditor().type('{selectall}{backspace}').paste(JSON.stringify(data));
|
||||
|
||||
this.actions.savePinnedData();
|
||||
},
|
||||
|
@ -181,7 +176,7 @@ export class NDV extends BasePage {
|
|||
typeIntoParameterInput: (
|
||||
parameterName: string,
|
||||
content: string,
|
||||
opts?: { parseSpecialCharSequences: boolean; delay?: number },
|
||||
opts?: { parseSpecialCharSequences: boolean },
|
||||
) => {
|
||||
this.getters.parameterInput(parameterName).type(content, opts);
|
||||
},
|
||||
|
@ -272,16 +267,13 @@ export class NDV extends BasePage {
|
|||
setInvalidExpression: ({
|
||||
fieldName,
|
||||
invalidExpression,
|
||||
delay,
|
||||
}: {
|
||||
fieldName: string;
|
||||
invalidExpression?: string;
|
||||
delay?: number;
|
||||
}) => {
|
||||
this.actions.typeIntoParameterInput(fieldName, '=');
|
||||
this.actions.typeIntoParameterInput(fieldName, invalidExpression ?? "{{ $('unknown')", {
|
||||
parseSpecialCharSequences: false,
|
||||
delay,
|
||||
});
|
||||
this.actions.validateExpressionPreview(fieldName, "node doesn't exist");
|
||||
},
|
||||
|
|
|
@ -30,6 +30,7 @@ export class PersonalSettingsPage extends BasePage {
|
|||
this.getters.themeSelector().click();
|
||||
this.getters.selectOptionsVisible().should('have.length', 3);
|
||||
this.getters.selectOptionsVisible().contains(theme).click();
|
||||
this.getters.saveSettingsButton().realClick();
|
||||
},
|
||||
loginAndVisit: (email: string, password: string) => {
|
||||
cy.signin({ email, password });
|
||||
|
|
|
@ -2,22 +2,6 @@ import * as formStep from '../composables/setup-template-form-step';
|
|||
import { overrideFeatureFlag } from '../composables/featureFlags';
|
||||
import { CredentialsModal, MessageBox } from './modals';
|
||||
|
||||
export type TemplateTestData = {
|
||||
id: number;
|
||||
fixture: string;
|
||||
};
|
||||
|
||||
export const testData = {
|
||||
simpleTemplate: {
|
||||
id: 1205,
|
||||
fixture: 'Test_Template_1.json',
|
||||
},
|
||||
templateWithoutCredentials: {
|
||||
id: 1344,
|
||||
fixture: 'Test_Template_2.json',
|
||||
},
|
||||
};
|
||||
|
||||
const credentialsModal = new CredentialsModal();
|
||||
const messageBox = new MessageBox();
|
||||
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
import { BasePage } from './base';
|
||||
|
||||
export class TemplateWorkflowPage extends BasePage {
|
||||
url = '/templates';
|
||||
|
||||
getters = {
|
||||
useTemplateButton: () => cy.get('[data-test-id="use-template-button"]'),
|
||||
description: () => cy.get('[data-test-id="template-description"]'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
visit: (templateId: number) => {
|
||||
cy.visit(`${this.url}/${templateId}`);
|
||||
},
|
||||
|
||||
clickUseThisWorkflowButton: () => {
|
||||
this.getters.useTemplateButton().click();
|
||||
},
|
||||
|
||||
openTemplate: (
|
||||
template: {
|
||||
workflow: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
user: { username: string };
|
||||
image: Array<{ id: number; url: string }>;
|
||||
};
|
||||
},
|
||||
templateHost: string,
|
||||
) => {
|
||||
cy.intercept('GET', `${templateHost}/api/templates/workflows/${template.workflow.id}`, {
|
||||
statusCode: 200,
|
||||
body: template,
|
||||
}).as('getTemplate');
|
||||
|
||||
this.actions.visit(template.workflow.id);
|
||||
cy.wait('@getTemplate');
|
||||
},
|
||||
};
|
||||
}
|
|
@ -5,6 +5,7 @@ export class TemplatesPage extends BasePage {
|
|||
|
||||
getters = {
|
||||
useTemplateButton: () => cy.getByTestId('use-template-button'),
|
||||
description: () => cy.getByTestId('template-description'),
|
||||
templateCards: () => cy.getByTestId('template-card'),
|
||||
firstTemplateCard: () => this.getters.templateCards().first(),
|
||||
allCategoriesFilter: () => cy.getByTestId('template-filter-all-categories'),
|
||||
|
@ -14,50 +15,30 @@ export class TemplatesPage extends BasePage {
|
|||
collectionCountLabel: () => cy.getByTestId('collection-count-label'),
|
||||
templateCountLabel: () => cy.getByTestId('template-count-label'),
|
||||
templatesLoadingContainer: () => cy.getByTestId('templates-loading-container'),
|
||||
expandCategoriesButton: () => cy.getByTestId('expand-categories-button'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
openSingleTemplateView: (templateId: number) => {
|
||||
cy.visit(`${this.url}/${templateId}`);
|
||||
cy.waitForLoad();
|
||||
},
|
||||
|
||||
openOnboardingFlow: (id: number, name: string, workflow: object, templatesHost: string) => {
|
||||
const apiResponse = {
|
||||
id,
|
||||
name,
|
||||
workflow,
|
||||
};
|
||||
openOnboardingFlow: () => {
|
||||
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
|
||||
cy.intercept('GET', `${templatesHost}/api/workflows/templates/${id}`, {
|
||||
statusCode: 200,
|
||||
body: apiResponse,
|
||||
}).as('getTemplate');
|
||||
cy.intercept('GET', 'rest/workflows/**').as('getWorkflow');
|
||||
|
||||
cy.visit(`/workflows/onboarding/${id}`);
|
||||
cy.visit('/workflows/onboarding/1');
|
||||
cy.window().then((win) => {
|
||||
win.preventNodeViewBeforeUnload = true;
|
||||
});
|
||||
|
||||
cy.wait('@getTemplate');
|
||||
cy.wait(['@createWorkflow', '@getWorkflow']);
|
||||
cy.wait(['@getTemplate', '@createWorkflow', '@getWorkflow']);
|
||||
},
|
||||
|
||||
importTemplate: (id: number, name: string, workflow: object, templatesHost: string) => {
|
||||
const apiResponse = {
|
||||
id,
|
||||
name,
|
||||
workflow,
|
||||
};
|
||||
cy.intercept('GET', `${templatesHost}/api/workflows/templates/${id}`, {
|
||||
statusCode: 200,
|
||||
body: apiResponse,
|
||||
}).as('getTemplate');
|
||||
importTemplate: () => {
|
||||
cy.intercept('GET', 'rest/workflows/**').as('getWorkflow');
|
||||
|
||||
cy.visit(`/workflows/templates/${id}`);
|
||||
cy.visit('/workflows/templates/1');
|
||||
cy.window().then((win) => {
|
||||
win.preventNodeViewBeforeUnload = true;
|
||||
});
|
||||
|
||||
cy.wait('@getTemplate');
|
||||
cy.wait('@getWorkflow');
|
||||
cy.wait(['@getTemplate', '@getWorkflow']);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -283,7 +283,7 @@ export class WorkflowPage extends BasePage {
|
|||
},
|
||||
saveWorkflowUsingKeyboardShortcut: () => {
|
||||
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
|
||||
cy.get('body').type(META_KEY, { release: false }).type('s');
|
||||
this.actions.hitSaveWorkflow();
|
||||
},
|
||||
deleteNode: (name: string) => {
|
||||
this.getters.canvasNodeByName(name).first().click();
|
||||
|
@ -339,35 +339,43 @@ export class WorkflowPage extends BasePage {
|
|||
});
|
||||
});
|
||||
},
|
||||
/** Certain keyboard shortcuts are not possible on Cypress via a simple `.type`, and some delays are needed to emulate these events */
|
||||
hitComboShortcut: (modifier: string, key: string) => {
|
||||
cy.get('body').wait(100).type(modifier, { delay: 100, release: false }).type(key);
|
||||
},
|
||||
hitUndo: () => {
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('z');
|
||||
this.actions.hitComboShortcut(`{${META_KEY}}`, 'z');
|
||||
},
|
||||
hitRedo: () => {
|
||||
cy.get('body')
|
||||
.type(META_KEY, { delay: 500, release: false })
|
||||
.type('{shift}', { release: false })
|
||||
.type('z');
|
||||
cy.get('body').type(`{${META_KEY}+shift+z}`);
|
||||
},
|
||||
selectAll: () => {
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('a');
|
||||
hitSelectAll: () => {
|
||||
this.actions.hitComboShortcut(`{${META_KEY}}`, 'a');
|
||||
},
|
||||
hitDeleteAllNodes: () => {
|
||||
this.actions.hitSelectAll();
|
||||
cy.get('body').type('{backspace}');
|
||||
},
|
||||
hitDisableNodeShortcut: () => {
|
||||
cy.get('body').type('d');
|
||||
},
|
||||
hitCopy: () => {
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('c');
|
||||
this.actions.hitComboShortcut(`{${META_KEY}}`, 'c');
|
||||
},
|
||||
hitPinNodeShortcut: () => {
|
||||
cy.get('body').type('p');
|
||||
},
|
||||
hitExecuteWorkflowShortcut: () => {
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('{enter}');
|
||||
hitSaveWorkflow: () => {
|
||||
cy.get('body').type(`{${META_KEY}+s}`);
|
||||
},
|
||||
hitDuplicateNodeShortcut: () => {
|
||||
cy.get('body').type(META_KEY, { delay: 500, release: false }).type('d');
|
||||
hitExecuteWorkflow: () => {
|
||||
cy.get('body').type(`{${META_KEY}+enter}`);
|
||||
},
|
||||
hitAddStickyShortcut: () => {
|
||||
cy.get('body').type('{shift}', { delay: 500, release: false }).type('S');
|
||||
hitDuplicateNode: () => {
|
||||
cy.get('body').type(`{${META_KEY}+d}`);
|
||||
},
|
||||
hitAddSticky: () => {
|
||||
cy.get('body').type('{shift+S}');
|
||||
},
|
||||
executeWorkflow: () => {
|
||||
this.getters.executeWorkflowButton().click();
|
||||
|
|
|
@ -50,7 +50,7 @@ switch (scenario) {
|
|||
break;
|
||||
case 'dev':
|
||||
runTests({
|
||||
startCommand: 'dev',
|
||||
startCommand: 'develop',
|
||||
url: 'http://localhost:8080/favicon.ico',
|
||||
testCommand: 'cypress open',
|
||||
customEnv: {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'cypress-real-events';
|
||||
import FakeTimers from '@sinonjs/fake-timers';
|
||||
import type { IN8nUISettings } from 'n8n-workflow';
|
||||
import { WorkflowPage } from '../pages';
|
||||
import {
|
||||
BACKEND_BASE_URL,
|
||||
|
@ -66,9 +67,9 @@ Cypress.Commands.add('signin', ({ email, password }) => {
|
|||
);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('signinAsOwner', () => {
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
});
|
||||
Cypress.Commands.add('signinAsOwner', () => cy.signin(INSTANCE_OWNER));
|
||||
Cypress.Commands.add('signinAsAdmin', () => cy.signin(INSTANCE_ADMIN));
|
||||
Cypress.Commands.add('signinAsMember', (index = 0) => cy.signin(INSTANCE_MEMBERS[index]));
|
||||
|
||||
Cypress.Commands.add('signout', () => {
|
||||
cy.request({
|
||||
|
@ -79,8 +80,9 @@ Cypress.Commands.add('signout', () => {
|
|||
cy.getCookie(N8N_AUTH_COOKIE).should('not.exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('interceptREST', (method, url) => {
|
||||
cy.intercept(method, `${BACKEND_BASE_URL}/rest${url}`);
|
||||
export let settings: Partial<IN8nUISettings>;
|
||||
Cypress.Commands.add('overrideSettings', (value: Partial<IN8nUISettings>) => {
|
||||
settings = value;
|
||||
});
|
||||
|
||||
const setFeature = (feature: string, enabled: boolean) =>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { INSTANCE_OWNER } from '../constants';
|
||||
import './commands';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import merge from 'lodash/merge';
|
||||
import { settings } from './commands';
|
||||
|
||||
before(() => {
|
||||
cy.resetDatabase();
|
||||
|
@ -11,21 +12,54 @@ before(() => {
|
|||
|
||||
beforeEach(() => {
|
||||
if (!cy.config('disableAutoLogin')) {
|
||||
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||
cy.signinAsOwner();
|
||||
}
|
||||
|
||||
cy.window().then((win): void => {
|
||||
win.localStorage.setItem('N8N_THEME', 'light');
|
||||
});
|
||||
|
||||
cy.intercept('GET', '/rest/settings').as('loadSettings');
|
||||
cy.intercept('GET', '/rest/settings', (req) => {
|
||||
// Disable cache
|
||||
delete req.headers['if-none-match'];
|
||||
req.on('response', (res) => {
|
||||
const defaultSettings = res.body.data;
|
||||
res.send({ data: merge(cloneDeep(defaultSettings), settings) });
|
||||
});
|
||||
}).as('loadSettings');
|
||||
|
||||
cy.intercept('GET', '/types/nodes.json').as('loadNodeTypes');
|
||||
|
||||
// Always intercept the request to test credentials and return a success
|
||||
cy.intercept('POST', '/rest/credentials/test', {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
data: { status: 'success', message: 'Tested successfully' },
|
||||
}).as('credentialTest');
|
||||
|
||||
cy.intercept('POST', '/rest/license/renew', {});
|
||||
|
||||
cy.intercept({ pathname: '/api/health' }, { status: 'OK' }).as('healthCheck');
|
||||
cy.intercept({ pathname: '/api/versions/*' }, [
|
||||
{
|
||||
name: '1.45.1',
|
||||
createdAt: '2023-08-18T11:53:12.857Z',
|
||||
hasSecurityIssue: null,
|
||||
hasSecurityFix: null,
|
||||
securityIssueFixVersion: null,
|
||||
hasBreakingChange: null,
|
||||
documentationUrl: 'https://docs.n8n.io/release-notes/#n8n131',
|
||||
nodes: [],
|
||||
description: 'Includes <strong>bug fixes</strong>',
|
||||
},
|
||||
});
|
||||
{
|
||||
name: '1.0.5',
|
||||
createdAt: '2023-07-24T10:54:56.097Z',
|
||||
hasSecurityIssue: false,
|
||||
hasSecurityFix: null,
|
||||
securityIssueFixVersion: null,
|
||||
hasBreakingChange: true,
|
||||
documentationUrl: 'https://docs.n8n.io/release-notes/#n8n104',
|
||||
nodes: [],
|
||||
description: 'Includes <strong>core functionality</strong> and <strong>bug fixes</strong>',
|
||||
},
|
||||
]).as('getVersions');
|
||||
});
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
// Load type definitions that come with Cypress module
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import type { Interception } from 'cypress/types/net-stubbing';
|
||||
import type { IN8nUISettings } from 'n8n-workflow';
|
||||
|
||||
Cypress.Keyboard.defaults({
|
||||
keystrokeDelay: 0,
|
||||
});
|
||||
|
||||
interface SigninPayload {
|
||||
email: string;
|
||||
|
@ -22,10 +26,13 @@ declare global {
|
|||
): Chainable<JQuery<HTMLElement>>;
|
||||
findChildByTestId(childTestId: string): Chainable<JQuery<HTMLElement>>;
|
||||
createFixtureWorkflow(fixtureKey: string, workflowName: string): void;
|
||||
/** @deprecated */
|
||||
signin(payload: SigninPayload): void;
|
||||
signinAsOwner(): void;
|
||||
signinAsAdmin(): void;
|
||||
signinAsMember(index?: number): void;
|
||||
signout(): void;
|
||||
interceptREST(method: string, url: string): Chainable<Interception>;
|
||||
overrideSettings(value: Partial<IN8nUISettings>): void;
|
||||
enableFeature(feature: string): void;
|
||||
disableFeature(feature: string): void;
|
||||
enableQueueMode(): void;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"sourceMap": false,
|
||||
"declaration": false,
|
||||
"lib": ["esnext", "dom"],
|
||||
"types": ["cypress", "node"]
|
||||
"types": ["cypress", "node", "cypress-real-events"]
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["**/dist/**/*", "**/node_modules/**/*"],
|
||||
|
|
|
@ -467,7 +467,7 @@ const config = (module.exports = {
|
|||
|
||||
overrides: [
|
||||
{
|
||||
files: ['test/**/*.ts', '**/__tests__/*.ts'],
|
||||
files: ['test/**/*.ts', '**/__tests__/*.ts', '**/*.cy.ts'],
|
||||
rules: {
|
||||
'n8n-local-rules/no-plain-errors': 'off',
|
||||
'n8n-local-rules/no-skipped-tests':
|
||||
|
|
|
@ -104,8 +104,6 @@ const iconSource = computed<NodeIconSource>(() => {
|
|||
// Otherwise, extract it from icon prop
|
||||
if (nodeType.icon) {
|
||||
const icon = getNodeIcon(nodeType, uiStore.appliedTheme);
|
||||
console.log(nodeType.icon, icon);
|
||||
|
||||
if (icon) {
|
||||
const [type, path] = icon.split(':');
|
||||
if (type === 'file') {
|
||||
|
|
|
@ -123,8 +123,8 @@ importers:
|
|||
cypress:
|
||||
dependencies:
|
||||
'@ngneat/falso':
|
||||
specifier: ^6.4.0
|
||||
version: 6.4.0
|
||||
specifier: ^7.2.0
|
||||
version: 7.2.0
|
||||
'@sinonjs/fake-timers':
|
||||
specifier: ^11.2.2
|
||||
version: 11.2.2
|
||||
|
@ -132,14 +132,17 @@ importers:
|
|||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
cypress:
|
||||
specifier: ^13.6.2
|
||||
version: 13.6.2
|
||||
specifier: ^13.11.0
|
||||
version: 13.11.0
|
||||
cypress-otp:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
cypress-real-events:
|
||||
specifier: ^1.11.0
|
||||
version: 1.11.0(cypress@13.6.2)
|
||||
specifier: ^1.12.0
|
||||
version: 1.12.0(cypress@13.11.0)
|
||||
lodash:
|
||||
specifier: 4.17.21
|
||||
version: 4.17.21
|
||||
start-server-and-test:
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3
|
||||
|
@ -147,9 +150,15 @@ importers:
|
|||
specifier: 8.3.2
|
||||
version: 8.3.2
|
||||
devDependencies:
|
||||
'@types/lodash':
|
||||
specifier: ^4.14.195
|
||||
version: 4.14.195
|
||||
'@types/uuid':
|
||||
specifier: ^8.3.2
|
||||
version: 8.3.4
|
||||
eslint-plugin-cypress:
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0(eslint@8.57.0)
|
||||
n8n-workflow:
|
||||
specifier: workspace:*
|
||||
version: link:../packages/workflow
|
||||
|
@ -4118,8 +4127,8 @@ packages:
|
|||
'@ndelangen/get-tarball@3.0.7':
|
||||
resolution: {integrity: sha512-NqGfTZIZpRFef1GoVaShSSRwDC3vde3ThtTeqFdcYd6ipKqnfEVhjK2hUeHjCQUcptyZr2TONqcloFXM+5QBrQ==}
|
||||
|
||||
'@ngneat/falso@6.4.0':
|
||||
resolution: {integrity: sha512-f6r036h2fX/AoHw1eV2t8+qWQwrbSrozs3zXMhhwoO7SJBc+DGMxRWEhFeYIinfwx0uhUH8ggx5+PDLzYESLOA==}
|
||||
'@ngneat/falso@7.2.0':
|
||||
resolution: {integrity: sha512-283EXBFd05kCbGuGSXgmvhCsQYEYzvD/eJaE7lxd05qRB0tgREvZX7TRlJ1KSp8nHxoK6Ws029G1Y30mt4IVAA==}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
|
@ -7296,13 +7305,13 @@ packages:
|
|||
cypress-otp@1.0.3:
|
||||
resolution: {integrity: sha512-o7LssfI0HRHa+TkaOE5/Aukv6M9vsoZAtYESr9m7Ky2i+HRNb2p/IRelE7Z0wJ/UK2f+nXAGZIfXqraf9EPDqw==}
|
||||
|
||||
cypress-real-events@1.11.0:
|
||||
resolution: {integrity: sha512-4LXVRsyq+xBh5TmlEyO1ojtBXtN7xw720Pwb9rEE9rkJuXmeH3VyoR1GGayMGr+Itqf11eEjfDewtDmcx6PWPQ==}
|
||||
cypress-real-events@1.12.0:
|
||||
resolution: {integrity: sha512-oiy+4kGKkzc2PT36k3GGQqkGxNiVypheWjMtfyi89iIk6bYmTzeqxapaLHS3pnhZOX1IEbTDUVxh8T4Nhs1tyQ==}
|
||||
peerDependencies:
|
||||
cypress: ^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x || ^13.x
|
||||
|
||||
cypress@13.6.2:
|
||||
resolution: {integrity: sha512-TW3bGdPU4BrfvMQYv1z3oMqj71YI4AlgJgnrycicmPZAXtvywVFZW9DAToshO65D97rCWfG/kqMFsYB6Kp91gQ==}
|
||||
cypress@13.11.0:
|
||||
resolution: {integrity: sha512-NXXogbAxVlVje4XHX+Cx5eMFZv4Dho/2rIcdBHg9CNPFUGZdM4cRdgIgM7USmNYsC12XY0bZENEQ+KBk72fl+A==}
|
||||
engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
|
||||
|
@ -7900,6 +7909,11 @@ packages:
|
|||
eslint-import-resolver-webpack:
|
||||
optional: true
|
||||
|
||||
eslint-plugin-cypress@3.3.0:
|
||||
resolution: {integrity: sha512-HPHMPzYBIshzJM8wqgKSKHG2p/8R0Gbg4Pb3tcdC9WrmkuqxiKxSKbjunUrajhV5l7gCIFrh1P7C7GuBqH6YuQ==}
|
||||
peerDependencies:
|
||||
eslint: '>=7'
|
||||
|
||||
eslint-plugin-import@2.29.1:
|
||||
resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -17147,7 +17161,7 @@ snapshots:
|
|||
pump: 3.0.0
|
||||
tar-fs: 2.1.1
|
||||
|
||||
'@ngneat/falso@6.4.0':
|
||||
'@ngneat/falso@7.2.0':
|
||||
dependencies:
|
||||
seedrandom: 3.0.5
|
||||
uuid: 8.3.2
|
||||
|
@ -21458,15 +21472,14 @@ snapshots:
|
|||
dependencies:
|
||||
otplib: 12.0.1
|
||||
|
||||
cypress-real-events@1.11.0(cypress@13.6.2):
|
||||
cypress-real-events@1.12.0(cypress@13.11.0):
|
||||
dependencies:
|
||||
cypress: 13.6.2
|
||||
cypress: 13.11.0
|
||||
|
||||
cypress@13.6.2:
|
||||
cypress@13.11.0:
|
||||
dependencies:
|
||||
'@cypress/request': 3.0.1
|
||||
'@cypress/xvfb': 1.2.4(supports-color@8.1.1)
|
||||
'@types/node': 18.16.16
|
||||
'@types/sinonjs__fake-timers': 8.1.1
|
||||
'@types/sizzle': 2.3.3
|
||||
arch: 2.2.0
|
||||
|
@ -22223,6 +22236,11 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-cypress@3.3.0(eslint@8.57.0):
|
||||
dependencies:
|
||||
eslint: 8.57.0
|
||||
globals: 13.20.0
|
||||
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
|
||||
dependencies:
|
||||
array-includes: 3.1.7
|
||||
|
|
Loading…
Reference in a new issue