ci: Refactor e2e tests to be less flaky (no-changelog) (#9695)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2024-06-11 14:45:15 +02:00
committed by GitHub
parent bc35e8c33d
commit 3d0393c739
39 changed files with 485 additions and 539 deletions

View File

@@ -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();

View File

@@ -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);

View File

@@ -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);
});

View File

@@ -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');

View File

@@ -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);

View File

@@ -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

View File

@@ -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,
deployment: { type: 'cloud' },
n8nMetadata: { userId: 1 },
},
});
});
}).as('loadSettings');
cy.overrideSettings({
deployment: { type: 'cloud' },
n8nMetadata: { userId: '1' },
});
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']);

View File

@@ -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: [],

View File

@@ -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();

View File

@@ -1,55 +1,246 @@
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/' };
}
});
}).as('settingsRequest');
const mockTemplateHost = (host: string) => {
cy.overrideSettings({
templates: { enabled: true, host },
});
};
describe('For api.n8n.io', () => {
beforeEach(() => {
mockTemplateHost('https://api.n8n.io/api/');
});
it('Opens website when clicking templates sidebar link', () => {
cy.visit(workflowsPage.url);
mainSidebar.getters.templates().should('be.visible');
// Templates should be a link to the website
mainSidebar.getters
.templates()
.parent('a')
.should('have.attr', 'href')
.and('include', 'https://n8n.io/workflows');
// Link should contain instance address and n8n version
mainSidebar.getters
.templates()
.parent('a')
.then(($a) => {
const href = $a.attr('href');
const params = new URLSearchParams(href);
// Link should have all mandatory parameters expected on the website
expect(decodeURIComponent(`${params.get('utm_instance')}`)).to.include(
window.location.origin,
);
expect(params.get('utm_n8n_version')).to.match(/[0-9]+\.[0-9]+\.[0-9]+/);
expect(params.get('utm_awc')).to.match(/[0-9]+/);
});
mainSidebar.getters.templates().parent('a').should('have.attr', 'target', '_blank');
});
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.wait('@templatesPage');
});
});
it('Opens website when clicking templates sidebar link', () => {
cy.visit(workflowsPage.url);
mainSidebar.getters.templates().should('be.visible');
// Templates should be a link to the website
mainSidebar.getters
.templates()
.parent('a')
.should('have.attr', 'href')
.and('include', 'https://n8n.io/workflows');
// Link should contain instance address and n8n version
mainSidebar.getters
.templates()
.parent('a')
.then(($a) => {
const href = $a.attr('href');
const params = new URLSearchParams(href);
// Link should have all mandatory parameters expected on the website
expect(decodeURIComponent(`${params.get('utm_instance')}`)).to.include(
window.location.origin,
);
expect(params.get('utm_n8n_version')).to.match(/[0-9]+\.[0-9]+\.[0-9]+/);
expect(params.get('utm_awc')).to.match(/[0-9]+/);
});
mainSidebar.getters.templates().parent('a').should('have.attr', 'target', '_blank');
});
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: [],
},
];
it('Redirects to website when visiting templates page directly', () => {
cy.visit(templatesPage.url);
cy.origin('https://n8n.io', () => {
cy.url().should('include', 'https://n8n.io/workflows');
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');
});
});
});
});

View File

@@ -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();

View File

@@ -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');
});

View File

@@ -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');

View File

@@ -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);

View File

@@ -1,4 +1,3 @@
import { INSTANCE_OWNER } from '../constants';
import { WorkflowsPage } from '../pages/workflows';
import {
closeVersionUpdatesPanel,
@@ -11,52 +10,18 @@ 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,
releaseChannel: 'stable',
versionCli: '1.0.0',
versionNotifications: {
enabled: true,
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>',
cy.overrideSettings({
releaseChannel: 'stable',
versionCli: '1.0.0',
versionNotifications: {
enabled: true,
endpoint: 'https://api.n8n.io/api/versions/',
infoUrl: 'https://docs.n8n.io/getting-started/installation/updating.html',
},
{
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();

View File

@@ -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

View File

@@ -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

View File

@@ -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');