Skip to content

Commit

Permalink
feat(editor): Use website as the main templates repository (#8591)
Browse files Browse the repository at this point in the history
  • Loading branch information
MiloradFilipovic authored Feb 9, 2024
1 parent 5ab34fe commit 79b09fd
Show file tree
Hide file tree
Showing 12 changed files with 278 additions and 151 deletions.
153 changes: 29 additions & 124 deletions cypress/e2e/29-templates.cy.ts
Original file line number Diff line number Diff line change
@@ -1,139 +1,44 @@
import { TemplatesPage } from '../pages/templates';
import { WorkflowPage } from '../pages/workflow';

import OnboardingWorkflow from '../fixtures/Onboarding_workflow.json';
import WorkflowTemplate from '../fixtures/Workflow_template_write_http_query.json';
import { TemplateWorkflowPage } from '../pages/template-workflow';
import { WorkflowsPage } from '../pages/workflows';
import { MainSidebar } from '../pages/sidebar/main-sidebar';

const templatesPage = new TemplatesPage();
const workflowPage = new WorkflowPage();
const templateWorkflowPage = new TemplateWorkflowPage();
const workflowsPage = new WorkflowsPage();
const mainSidebar = new MainSidebar();

describe('Templates', () => {
describe('Workflow templates', () => {
beforeEach(() => {
cy.intercept('GET', '**/api/templates/search?page=1&rows=20&category=&search=', { fixture: 'templates_search/all_templates_search_response.json' }).as('searchRequest');
cy.intercept('GET', '**/api/templates/search?page=1&rows=20&category=Sales*', { fixture: 'templates_search/sales_templates_search_response.json' }).as('categorySearchRequest');
cy.intercept('GET', '**/api/templates/workflows/*', { fixture: 'templates_search/test_template_preview.json' }).as('singleTemplateRequest');
cy.intercept('GET', '**/api/workflows/templates/*', { fixture: 'templates_search/test_template_import.json' }).as('singleTemplateRequest');
});

it('can open onboarding flow', () => {
templatesPage.actions.openOnboardingFlow(1234, OnboardingWorkflow.name, OnboardingWorkflow);
cy.url().then(($url) => {
expect($url).to.match(/.*\/workflow\/.*?onboardingId=1234$/);
})

workflowPage.actions.shouldHaveWorkflowName(`Demo: ${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(1234, OnboardingWorkflow.name, OnboardingWorkflow);

cy.url().then(($url) => {
expect($url).to.include('/workflow/new?templateId=1234');
});

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', () => {
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/');

cy.url().then(($url) => {
const templateId = $url.split('/').pop();

templatesPage.getters.useTemplateButton().click();
cy.url().should('include', '/workflow/new');
workflowPage.actions.saveWorkflowOnButtonClick();

workflowPage.actions.selectAll();
workflowPage.actions.hitCopy();

cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
// Check workflow JSON by copying it to clipboard
cy.readClipboard().then((workflowJSON) => {
expect(workflowJSON).to.contain(`"templateId": "${templateId}"`);
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 open template with images and hides workflow screenshots', () => {
templateWorkflowPage.actions.openTemplate(WorkflowTemplate);

templateWorkflowPage.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('Opens website when clicking templates sidebar link', () => {
cy.visit(workflowsPage.url);
mainSidebar.getters.menuItem('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');
mainSidebar.getters.templates().parent('a').should('have.attr', 'target', '_blank');
});

it('can filter templates by category', () => {
it('Redirects to website when visiting templates page directly', () => {
cy.visit(templatesPage.url);
templatesPage.getters.templatesLoadingContainer().should('not.exist');
templatesPage.getters.expandCategoriesButton().click();
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(($el) => {
initialCollectionCount = parseInt($el.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(($el) => {
expect(parseInt($el.text().replace(/\D/g, ''), 10)).to.be.lessThan(initialTemplateCount);
});
templatesPage.getters.collectionCountLabel().should(($el) => {
expect(parseInt($el.text().replace(/\D/g, ''), 10)).to.be.lessThan(initialCollectionCount);
});
});
});
cy.origin('https://n8n.io', () => {
cy.url().should('include', 'https://n8n.io/workflows');
})
});

it('should preserve search query in URL', () => {
cy.visit(templatesPage.url);
templatesPage.getters.templatesLoadingContainer().should('not.exist');
templatesPage.getters.expandCategoriesButton().click();
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');
});
it('Redirects to website when visiting template by id page directly', () => {
cy.visit(`${templatesPage.url}/1`);
cy.origin('https://n8n.io', () => {
cy.url().should('include', 'https://n8n.io/workflows/1');
})
});
});
24 changes: 10 additions & 14 deletions cypress/e2e/34-template-credentials-setup.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import {
testData,
} from '../pages/template-collection';
import * as templateCredentialsSetupPage from '../pages/template-credential-setup';
import { TemplateWorkflowPage } from '../pages/template-workflow';
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';

const templateWorkflowPage = new TemplateWorkflowPage();
const workflowPage = new WorkflowPage();

const testTemplate = templateCredentialsSetupPage.testData.simpleTemplate;
Expand All @@ -34,18 +32,16 @@ describe('Template credentials setup', () => {
cy.intercept('GET', `https://api.n8n.io/api/templates/workflows/${testTemplate.id}`, {
fixture: testTemplate.fixture,
});
});

it('can be opened from template workflow page', () => {
templateWorkflowPage.actions.visit(testTemplate.id);
templateCredentialsSetupPage.enableTemplateCredentialSetupFeatureFlag();
templateWorkflowPage.getters.useTemplateButton().should('be.visible');
templateCredentialsSetupPage.enableTemplateCredentialSetupFeatureFlag();
templateWorkflowPage.actions.clickUseThisWorkflowButton();

templateCredentialsSetupPage.getters
.title(`Set up 'Promote new Shopify products on Twitter and Telegram' template`)
.should('be.visible');
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', () => {
Expand Down
147 changes: 147 additions & 0 deletions cypress/e2e/38-custom-template-repository.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { TemplatesPage } from '../pages/templates';
import { WorkflowPage } from '../pages/workflow';
import { TemplateWorkflowPage } from '../pages/template-workflow';
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 templateWorkflowPage = new TemplateWorkflowPage();


describe('In-app templates repository', () => {
beforeEach(() => {
cy.intercept('GET', '**/api/templates/search?page=1&rows=20&category=&search=', { fixture: 'templates_search/all_templates_search_response.json' }).as('searchRequest');
cy.intercept('GET', '**/api/templates/search?page=1&rows=20&category=Sales*', { fixture: 'templates_search/sales_templates_search_response.json' }).as('categorySearchRequest');
cy.intercept('GET', '**/api/templates/workflows/*', { fixture: 'templates_search/test_template_preview.json' }).as('singleTemplateRequest');
cy.intercept('GET', '**/api/workflows/templates/*', { fixture: 'templates_search/test_template_import.json' }).as('singleTemplateRequest');
cy.intercept('GET', '**/rest/settings', (req) => {
// Disable cache
delete req.headers['if-none-match']
req.reply((res) => {
if (res.body.data) {
// Enable in-app templates by setting a custom host
res.body.data.templates = { enabled: true, host: 'https://api-staging.n8n.io/api/' };
}
});
}).as('settingsRequest');
});

it('can open onboarding flow', () => {
templatesPage.actions.openOnboardingFlow(1, OnboardingWorkflow.name, OnboardingWorkflow, 'https://api-staging.n8n.io');
cy.url().then(($url) => {
expect($url).to.match(/.*\/workflow\/.*?onboardingId=1$/);
})

workflowPage.actions.shouldHaveWorkflowName(`Demo: ${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(1, OnboardingWorkflow.name, OnboardingWorkflow, 'https://api-staging.n8n.io');

cy.url().then(($url) => {
expect($url).to.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', () => {
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/');

cy.url().then(($url) => {
const templateId = $url.split('/').pop();

templatesPage.getters.useTemplateButton().click();
cy.url().should('include', '/workflow/new');
workflowPage.actions.saveWorkflowOnButtonClick();

workflowPage.actions.selectAll();
workflowPage.actions.hitCopy();

cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
// Check workflow JSON by copying it to clipboard
cy.readClipboard().then((workflowJSON) => {
expect(workflowJSON).to.contain(`"templateId": "${templateId}"`);
});
});
});

it('can open template with images and hides workflow screenshots', () => {
templateWorkflowPage.actions.openTemplate(WorkflowTemplate, 'https://api-staging.n8n.io');

templateWorkflowPage.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(($el) => {
initialCollectionCount = parseInt($el.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(($el) => {
expect(parseInt($el.text().replace(/\D/g, ''), 10)).to.be.lessThan(initialTemplateCount);
});
templatesPage.getters.collectionCountLabel().should(($el) => {
expect(parseInt($el.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');
});
});
});
4 changes: 2 additions & 2 deletions cypress/fixtures/Workflow_template_write_http_query.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"workflow": {
"id": 3,
"id": 1,
"name": "Write HTTP query string on image",
"views": 116,
"recentViews": 9766,
Expand Down Expand Up @@ -185,4 +185,4 @@
}
]
}
}
}
4 changes: 2 additions & 2 deletions cypress/pages/template-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export class TemplateWorkflowPage extends BasePage {
user: { username: string };
image: { id: number; url: string }[];
};
}) => {
cy.intercept('GET', `https://api.n8n.io/api/templates/workflows/${template.workflow.id}`, {
}, templateHost: string) => {
cy.intercept('GET', `${templateHost}/api/templates/workflows/${template.workflow.id}`, {
statusCode: 200,
body: template,
}).as('getTemplate');
Expand Down
Loading

0 comments on commit 79b09fd

Please sign in to comment.