first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
import { test, expect } from '../../../../fixtures/base';
|
||||
import type { TestRequirements } from '../../../../Types';
|
||||
import { resolveFromRoot } from '../../../../utils/path-helper';
|
||||
|
||||
test.use({ capability: { env: { TEST_ISOLATION: 'template-credentials-setup' } } });
|
||||
|
||||
const TEMPLATE_HOST = 'https://api.n8n.io/api/';
|
||||
const TEMPLATE_ID = 1205;
|
||||
const TEMPLATE_WITHOUT_CREDS_ID = 1344;
|
||||
const COLLECTION_ID = 1;
|
||||
|
||||
const testTemplate = JSON.parse(
|
||||
readFileSync(resolveFromRoot('workflows', 'Test_Template_1.json'), 'utf8'),
|
||||
);
|
||||
|
||||
const templateWithoutCredentials = JSON.parse(
|
||||
readFileSync(resolveFromRoot('workflows', 'Test_Template_2.json'), 'utf8'),
|
||||
);
|
||||
|
||||
const ecommerceCollection = JSON.parse(
|
||||
readFileSync(
|
||||
resolveFromRoot('workflows', 'Ecommerce_starter_pack_template_collection.json'),
|
||||
'utf8',
|
||||
),
|
||||
);
|
||||
|
||||
function createTemplateRequirements(): TestRequirements {
|
||||
return {
|
||||
storage: {
|
||||
N8N_EXPERIMENT_OVERRIDES: JSON.stringify({
|
||||
'055_template_setup_experience': 'control',
|
||||
'069_setup_panel': 'control',
|
||||
}),
|
||||
},
|
||||
config: {
|
||||
settings: {
|
||||
templates: {
|
||||
enabled: true,
|
||||
host: TEMPLATE_HOST,
|
||||
},
|
||||
},
|
||||
},
|
||||
intercepts: {
|
||||
getTemplatePreview: {
|
||||
url: `${TEMPLATE_HOST}templates/workflows/${TEMPLATE_ID}`,
|
||||
response: testTemplate,
|
||||
},
|
||||
getTemplate: {
|
||||
url: `${TEMPLATE_HOST}workflows/templates/${TEMPLATE_ID}`,
|
||||
response: testTemplate.workflow,
|
||||
},
|
||||
getTemplateCollection: {
|
||||
url: `${TEMPLATE_HOST}templates/collections/${COLLECTION_ID}`,
|
||||
response: ecommerceCollection,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test.describe(
|
||||
'Template credentials setup @db:reset',
|
||||
{
|
||||
annotation: [{ type: 'owner', description: 'Adore' }],
|
||||
},
|
||||
() => {
|
||||
test.beforeEach(async ({ setupRequirements }) => {
|
||||
await setupRequirements(createTemplateRequirements());
|
||||
});
|
||||
|
||||
test('can be opened from template collection page', async ({ n8n }) => {
|
||||
await n8n.navigate.toTemplateCollection(COLLECTION_ID);
|
||||
await n8n.templates.clickUseWorkflowButton('Promote new Shopify products');
|
||||
|
||||
await expect(
|
||||
n8n.templateCredentialSetup.getTitle("Set up 'Promote new Shopify products' template"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('has all the elements on page', async ({ n8n }) => {
|
||||
await n8n.navigate.toTemplateCredentialSetup(TEMPLATE_ID);
|
||||
|
||||
await expect(
|
||||
n8n.templateCredentialSetup.getTitle("Set up 'Promote new Shopify products' template"),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(n8n.templateCredentialSetup.getInfoCallout()).toContainText(
|
||||
'You need 1x Shopify, 1x X (Formerly Twitter) and 1x Telegram account to setup this template',
|
||||
);
|
||||
|
||||
const expectedAppNames = ['1. Shopify', '2. X (Formerly Twitter)', '3. Telegram'];
|
||||
const expectedAppDescriptions = [
|
||||
'The credential you select will be used in the product created node of the workflow template.',
|
||||
'The credential you select will be used in the Twitter node of the workflow template.',
|
||||
'The credential you select will be used in the Telegram node of the workflow template.',
|
||||
];
|
||||
|
||||
const formSteps = n8n.templateCredentialSetup.getFormSteps();
|
||||
const count = await formSteps.count();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const step = formSteps.nth(i);
|
||||
await expect(n8n.templateCredentialSetup.getStepHeading(step)).toHaveText(
|
||||
expectedAppNames[i],
|
||||
);
|
||||
await expect(n8n.templateCredentialSetup.getStepDescription(step)).toHaveText(
|
||||
expectedAppDescriptions[i],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('can skip template creation', async ({ n8n }) => {
|
||||
await n8n.navigate.toTemplateCredentialSetup(TEMPLATE_ID);
|
||||
|
||||
await n8n.templateCredentialSetup.getSkipLink().click();
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
|
||||
});
|
||||
|
||||
test('can create credentials and workflow from the template', async ({ n8n }) => {
|
||||
await n8n.navigate.toTemplateCredentialSetup(TEMPLATE_ID);
|
||||
|
||||
await expect(n8n.templateCredentialSetup.getContinueButton()).toBeDisabled();
|
||||
|
||||
await n8n.templatesComposer.fillDummyCredentialForApp('Shopify', {
|
||||
fields: { shopSubdomain: 'test-shop', apiKey: 'test-token', password: 'test-key' },
|
||||
});
|
||||
|
||||
await expect(n8n.templateCredentialSetup.getContinueButton()).toBeEnabled();
|
||||
|
||||
await n8n.templatesComposer.fillDummyCredentialForOAuthApp('X (Formerly Twitter)', {
|
||||
fields: { consumerKey: 'consumer-key', consumerSecret: 'consumer-secret' },
|
||||
});
|
||||
await n8n.templatesComposer.fillDummyCredentialForApp('Telegram', {
|
||||
fields: { accessToken: 'bot-token' },
|
||||
});
|
||||
|
||||
await n8n.notifications.quickCloseAll();
|
||||
|
||||
const workflowCreatePromise = n8n.page.waitForResponse('**/rest/workflows');
|
||||
await n8n.templateCredentialSetup.getContinueButton().click();
|
||||
await workflowCreatePromise;
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
|
||||
|
||||
const workflow = await n8n.canvasComposer.getWorkflowFromClipboard();
|
||||
|
||||
expect(workflow.meta).toHaveProperty('templateId', TEMPLATE_ID.toString());
|
||||
expect(workflow.meta).not.toHaveProperty('templateCredsSetupCompleted');
|
||||
workflow.nodes.forEach((node: { credentials?: Record<string, unknown> }) => {
|
||||
expect(Object.keys(node.credentials ?? {})).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
test('should work with a template that has no credentials (ADO-1603)', async ({
|
||||
n8n,
|
||||
setupRequirements,
|
||||
}) => {
|
||||
await setupRequirements({
|
||||
storage: {
|
||||
N8N_EXPERIMENT_OVERRIDES: JSON.stringify({
|
||||
'055_template_setup_experience': 'control',
|
||||
'069_setup_panel': 'control',
|
||||
}),
|
||||
},
|
||||
config: {
|
||||
settings: {
|
||||
templates: {
|
||||
enabled: true,
|
||||
host: TEMPLATE_HOST,
|
||||
},
|
||||
},
|
||||
},
|
||||
intercepts: {
|
||||
getTemplatePreview: {
|
||||
url: `${TEMPLATE_HOST}templates/workflows/${TEMPLATE_WITHOUT_CREDS_ID}`,
|
||||
response: templateWithoutCredentials,
|
||||
},
|
||||
getTemplate: {
|
||||
url: `${TEMPLATE_HOST}workflows/templates/${TEMPLATE_WITHOUT_CREDS_ID}`,
|
||||
response: templateWithoutCredentials.workflow,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await n8n.navigate.toTemplateCredentialSetup(TEMPLATE_WITHOUT_CREDS_ID);
|
||||
|
||||
const expectedAppNames = ['1. Email (IMAP)', '2. Nextcloud'];
|
||||
const expectedAppDescriptions = [
|
||||
'The credential you select will be used in the IMAP Email node of the workflow template.',
|
||||
'The credential you select will be used in the Nextcloud node of the workflow template.',
|
||||
];
|
||||
|
||||
const formSteps = n8n.templateCredentialSetup.getFormSteps();
|
||||
const count = await formSteps.count();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const step = formSteps.nth(i);
|
||||
await expect(n8n.templateCredentialSetup.getStepHeading(step)).toHaveText(
|
||||
expectedAppNames[i],
|
||||
);
|
||||
await expect(n8n.templateCredentialSetup.getStepDescription(step)).toHaveText(
|
||||
expectedAppDescriptions[i],
|
||||
);
|
||||
}
|
||||
|
||||
await expect(n8n.templateCredentialSetup.getContinueButton()).toBeDisabled();
|
||||
|
||||
await n8n.templatesComposer.fillDummyCredentialForApp('Email (IMAP)');
|
||||
await n8n.templatesComposer.fillDummyCredentialForApp('Nextcloud');
|
||||
|
||||
await n8n.notifications.quickCloseAll();
|
||||
|
||||
const workflowCreatePromise = n8n.page.waitForResponse('**/rest/workflows');
|
||||
await n8n.templateCredentialSetup.getContinueButton().click();
|
||||
await workflowCreatePromise;
|
||||
|
||||
await expect(n8n.canvas.getCanvasNodes()).toHaveCount(3);
|
||||
});
|
||||
|
||||
test.describe('Credential setup from workflow editor', () => {
|
||||
test('should allow credential setup from workflow editor if user skips it during template setup', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.navigate.toTemplateCredentialSetup(TEMPLATE_ID);
|
||||
await n8n.templateCredentialSetup.getSkipLink().click();
|
||||
|
||||
await expect(n8n.canvas.getSetupWorkflowCredentialsButton()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow credential setup from workflow editor if user fills in credentials partially during template setup', async ({
|
||||
n8n,
|
||||
}) => {
|
||||
await n8n.navigate.toTemplateCredentialSetup(TEMPLATE_ID);
|
||||
await n8n.templatesComposer.fillDummyCredentialForApp('Shopify', {
|
||||
fields: { shopSubdomain: 'test-shop', apiKey: 'test-token', password: 'test-key' },
|
||||
});
|
||||
|
||||
await n8n.notifications.quickCloseAll();
|
||||
|
||||
const workflowCreatePromise = n8n.page.waitForResponse('**/rest/workflows');
|
||||
await n8n.templateCredentialSetup.getContinueButton().click();
|
||||
await workflowCreatePromise;
|
||||
|
||||
await expect(n8n.canvas.getSetupWorkflowCredentialsButton()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should fill credentials from workflow editor', async ({ n8n }) => {
|
||||
await n8n.navigate.toTemplateCredentialSetup(TEMPLATE_ID);
|
||||
await n8n.templateCredentialSetup.getSkipLink().click();
|
||||
|
||||
await n8n.canvas.getSetupWorkflowCredentialsButton().click();
|
||||
await expect(n8n.workflowCredentialSetupModal.getModal()).toBeVisible();
|
||||
|
||||
await n8n.templatesComposer.fillDummyCredentialForApp('Shopify', {
|
||||
fields: { shopSubdomain: 'test-shop', apiKey: 'test-token', password: 'test-key' },
|
||||
});
|
||||
await n8n.templatesComposer.fillDummyCredentialForOAuthApp('X (Formerly Twitter)', {
|
||||
fields: { consumerKey: 'consumer-key', consumerSecret: 'consumer-secret' },
|
||||
});
|
||||
await n8n.templatesComposer.fillDummyCredentialForApp('Telegram', {
|
||||
fields: { accessToken: 'bot-token' },
|
||||
});
|
||||
|
||||
await n8n.notifications.quickCloseAll();
|
||||
|
||||
await n8n.workflowCredentialSetupModal.clickContinue();
|
||||
await expect(n8n.workflowCredentialSetupModal.getModal()).toBeHidden();
|
||||
|
||||
const workflow = await n8n.canvasComposer.getWorkflowFromClipboard();
|
||||
|
||||
workflow.nodes.forEach((node: { credentials?: Record<string, unknown> }) => {
|
||||
expect(Object.keys(node.credentials ?? {})).toHaveLength(1);
|
||||
});
|
||||
|
||||
await expect(n8n.canvas.getSetupWorkflowCredentialsButton()).toBeHidden();
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user