mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 21:37:32 -08:00
test: Add e2e tests for cred setup on workflow editor (no-changelog) (#8245)
## Summary Follow-up to https://github.com/n8n-io/n8n/pull/8240 Adds e2e tests for the template credential setup in workflow editor ## Related tickets and issues https://linear.app/n8n/issue/ADO-1463/feature-enable-users-to-close-and-re-open-the-setup
This commit is contained in:
parent
3cf6704dbb
commit
008fd5a917
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Getters
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getWorkflowCredentialsModal = () => cy.getByTestId('setup-workflow-credentials-modal');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const closeModal = () =>
|
||||||
|
getWorkflowCredentialsModal().find("button[aria-label='Close this dialog']").click();
|
14
cypress/composables/setup-template-form-step.ts
Normal file
14
cypress/composables/setup-template-form-step.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Getters
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getFormStep = () => cy.getByTestId('setup-credentials-form-step');
|
||||||
|
|
||||||
|
export const getStepHeading = ($el: JQuery<HTMLElement>) =>
|
||||||
|
cy.wrap($el).findChildByTestId('credential-step-heading');
|
||||||
|
|
||||||
|
export const getStepDescription = ($el: JQuery<HTMLElement>) =>
|
||||||
|
cy.wrap($el).findChildByTestId('credential-step-description');
|
||||||
|
|
||||||
|
export const getCreateAppCredentialsButton = (appName: string) =>
|
||||||
|
cy.get(`button:contains("Create new ${appName} credential")`);
|
5
cypress/composables/setup-workflow-credentials-button.ts
Normal file
5
cypress/composables/setup-workflow-credentials-button.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/**
|
||||||
|
* Getters
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getSetupWorkflowCredentialsButton = () => cy.get(`button:contains("Set up Template")`);
|
|
@ -6,6 +6,9 @@ import {
|
||||||
import * as templateCredentialsSetupPage from '../pages/template-credential-setup';
|
import * as templateCredentialsSetupPage from '../pages/template-credential-setup';
|
||||||
import { TemplateWorkflowPage } from '../pages/template-workflow';
|
import { TemplateWorkflowPage } from '../pages/template-workflow';
|
||||||
import { WorkflowPage } from '../pages/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 templateWorkflowPage = new TemplateWorkflowPage();
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
|
@ -69,13 +72,9 @@ describe('Template credentials setup', () => {
|
||||||
'The credential you select will be used in the Telegram node of the workflow template.',
|
'The credential you select will be used in the Telegram node of the workflow template.',
|
||||||
];
|
];
|
||||||
|
|
||||||
templateCredentialsSetupPage.getters.appCredentialSteps().each(($el, index) => {
|
formStep.getFormStep().each(($el, index) => {
|
||||||
templateCredentialsSetupPage.getters
|
formStep.getStepHeading($el).should('have.text', expectedAppNames[index]);
|
||||||
.stepHeading($el)
|
formStep.getStepDescription($el).should('have.text', expectedAppDescriptions[index]);
|
||||||
.should('have.text', expectedAppNames[index]);
|
|
||||||
templateCredentialsSetupPage.getters
|
|
||||||
.stepDescription($el)
|
|
||||||
.should('have.text', expectedAppDescriptions[index]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -100,10 +99,7 @@ describe('Template credentials setup', () => {
|
||||||
templateCredentialsSetupPage.fillInDummyCredentialsForAppWithConfirm('X (Formerly Twitter)');
|
templateCredentialsSetupPage.fillInDummyCredentialsForAppWithConfirm('X (Formerly Twitter)');
|
||||||
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Telegram');
|
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Telegram');
|
||||||
|
|
||||||
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
|
templateCredentialsSetupPage.finishCredentialSetup();
|
||||||
templateCredentialsSetupPage.getters.continueButton().should('be.enabled');
|
|
||||||
templateCredentialsSetupPage.getters.continueButton().click();
|
|
||||||
cy.wait('@createWorkflow');
|
|
||||||
|
|
||||||
workflowPage.getters.canvasNodes().should('have.length', 3);
|
workflowPage.getters.canvasNodes().should('have.length', 3);
|
||||||
|
|
||||||
|
@ -137,13 +133,9 @@ describe('Template credentials setup', () => {
|
||||||
'The credential you select will be used in the Nextcloud node of the workflow template.',
|
'The credential you select will be used in the Nextcloud node of the workflow template.',
|
||||||
];
|
];
|
||||||
|
|
||||||
templateCredentialsSetupPage.getters.appCredentialSteps().each(($el, index) => {
|
formStep.getFormStep().each(($el, index) => {
|
||||||
templateCredentialsSetupPage.getters
|
formStep.getStepHeading($el).should('have.text', expectedAppNames[index]);
|
||||||
.stepHeading($el)
|
formStep.getStepDescription($el).should('have.text', expectedAppDescriptions[index]);
|
||||||
.should('have.text', expectedAppNames[index]);
|
|
||||||
templateCredentialsSetupPage.getters
|
|
||||||
.stepDescription($el)
|
|
||||||
.should('have.text', expectedAppDescriptions[index]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
templateCredentialsSetupPage.getters.continueButton().should('be.disabled');
|
templateCredentialsSetupPage.getters.continueButton().should('be.disabled');
|
||||||
|
@ -151,11 +143,68 @@ describe('Template credentials setup', () => {
|
||||||
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Email (IMAP)');
|
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Email (IMAP)');
|
||||||
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Nextcloud');
|
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Nextcloud');
|
||||||
|
|
||||||
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
|
templateCredentialsSetupPage.finishCredentialSetup();
|
||||||
templateCredentialsSetupPage.getters.continueButton().should('be.enabled');
|
|
||||||
templateCredentialsSetupPage.getters.continueButton().click();
|
|
||||||
cy.wait('@createWorkflow');
|
|
||||||
|
|
||||||
workflowPage.getters.canvasNodes().should('have.length', 3);
|
workflowPage.getters.canvasNodes().should('have.length', 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Credential setup from workflow editor', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.resetDatabase();
|
||||||
|
cy.signinAsOwner();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow credential setup from workflow editor if user skips it during template setup', () => {
|
||||||
|
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);
|
||||||
|
templateCredentialsSetupPage.getters.skipLink().click();
|
||||||
|
|
||||||
|
getSetupWorkflowCredentialsButton().should('be.visible');
|
||||||
|
|
||||||
|
// We need to save the workflow or otherwise a browser native popup
|
||||||
|
// will block cypress from continuing
|
||||||
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow credential setup from workflow editor if user fills in credentials partially during template setup', () => {
|
||||||
|
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);
|
||||||
|
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Shopify');
|
||||||
|
|
||||||
|
templateCredentialsSetupPage.finishCredentialSetup();
|
||||||
|
|
||||||
|
getSetupWorkflowCredentialsButton().should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fill credentials from workflow editor', () => {
|
||||||
|
templateCredentialsSetupPage.visitTemplateCredentialSetupPage(testTemplate.id);
|
||||||
|
templateCredentialsSetupPage.getters.skipLink().click();
|
||||||
|
|
||||||
|
getSetupWorkflowCredentialsButton().click();
|
||||||
|
setupCredsModal.getWorkflowCredentialsModal().should('be.visible');
|
||||||
|
|
||||||
|
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Shopify');
|
||||||
|
templateCredentialsSetupPage.fillInDummyCredentialsForAppWithConfirm('X (Formerly Twitter)');
|
||||||
|
templateCredentialsSetupPage.fillInDummyCredentialsForApp('Telegram');
|
||||||
|
|
||||||
|
setupCredsModal.closeModal();
|
||||||
|
|
||||||
|
// Focus the canvas so the copy to clipboard works
|
||||||
|
workflowPage.getters.canvasNodes().eq(0).realClick();
|
||||||
|
workflowPage.actions.selectAll();
|
||||||
|
workflowPage.actions.hitCopy();
|
||||||
|
|
||||||
|
cy.grantBrowserPermissions('clipboardReadWrite', 'clipboardSanitizedWrite');
|
||||||
|
// Check workflow JSON by copying it to clipboard
|
||||||
|
cy.readClipboard().then((workflowJSON) => {
|
||||||
|
const workflow = JSON.parse(workflowJSON);
|
||||||
|
|
||||||
|
workflow.nodes.forEach((node: any) => {
|
||||||
|
expect(Object.keys(node.credentials ?? {})).to.have.lengthOf(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// We need to save the workflow or otherwise a browser native popup
|
||||||
|
// will block cypress from continuing
|
||||||
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { CredentialsModal, MessageBox } from './modals';
|
import { CredentialsModal, MessageBox } from './modals';
|
||||||
|
import * as formStep from '../composables/setup-template-form-step';
|
||||||
|
|
||||||
export type TemplateTestData = {
|
export type TemplateTestData = {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -24,17 +25,6 @@ export const getters = {
|
||||||
skipLink: () => cy.get('a:contains("Skip")'),
|
skipLink: () => cy.get('a:contains("Skip")'),
|
||||||
title: (title: string) => cy.get(`h1:contains(${title})`),
|
title: (title: string) => cy.get(`h1:contains(${title})`),
|
||||||
infoCallout: () => cy.getByTestId('info-callout'),
|
infoCallout: () => cy.getByTestId('info-callout'),
|
||||||
createAppCredentialsButton: (appName: string) =>
|
|
||||||
cy.get(`button:contains("Create new ${appName} credential")`),
|
|
||||||
appCredentialSteps: () => cy.getByTestId('setup-credentials-form-step'),
|
|
||||||
stepHeading: ($el: JQuery<HTMLElement>) =>
|
|
||||||
cy.wrap($el).findChildByTestId('credential-step-heading'),
|
|
||||||
stepDescription: ($el: JQuery<HTMLElement>) =>
|
|
||||||
cy.wrap($el).findChildByTestId('credential-step-description'),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const visitTemplateCredentialSetupPage = (templateId: number) => {
|
|
||||||
cy.visit(`/templates/${templateId}/setup`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const enableTemplateCredentialSetupFeatureFlag = () => {
|
export const enableTemplateCredentialSetupFeatureFlag = () => {
|
||||||
|
@ -43,11 +33,17 @@ export const enableTemplateCredentialSetupFeatureFlag = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const visitTemplateCredentialSetupPage = (templateId: number) => {
|
||||||
|
cy.visit(`/templates/${templateId}/setup`);
|
||||||
|
formStep.getFormStep().eq(0).should('be.visible');
|
||||||
|
enableTemplateCredentialSetupFeatureFlag();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills in dummy credentials for the given app name.
|
* Fills in dummy credentials for the given app name.
|
||||||
*/
|
*/
|
||||||
export const fillInDummyCredentialsForApp = (appName: string) => {
|
export const fillInDummyCredentialsForApp = (appName: string) => {
|
||||||
getters.createAppCredentialsButton(appName).click();
|
formStep.getCreateAppCredentialsButton(appName).click();
|
||||||
credentialsModal.getters.editCredentialModal().find('input:first()').type('test');
|
credentialsModal.getters.editCredentialModal().find('input:first()').type('test');
|
||||||
credentialsModal.actions.save(false);
|
credentialsModal.actions.save(false);
|
||||||
credentialsModal.actions.close();
|
credentialsModal.actions.close();
|
||||||
|
@ -62,3 +58,13 @@ export const fillInDummyCredentialsForAppWithConfirm = (appName: string) => {
|
||||||
fillInDummyCredentialsForApp(appName);
|
fillInDummyCredentialsForApp(appName);
|
||||||
messageBox.actions.cancel();
|
messageBox.actions.cancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finishes the credential setup by clicking the continue button.
|
||||||
|
*/
|
||||||
|
export const finishCredentialSetup = () => {
|
||||||
|
cy.intercept('POST', '/rest/workflows').as('createWorkflow');
|
||||||
|
getters.continueButton().should('be.enabled');
|
||||||
|
getters.continueButton().click();
|
||||||
|
cy.wait('@createWorkflow');
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import 'cypress-real-events';
|
import 'cypress-real-events';
|
||||||
import { WorkflowPage } from '../pages';
|
import { WorkflowPage } from '../pages';
|
||||||
import { BACKEND_BASE_URL, INSTANCE_MEMBERS, INSTANCE_OWNER, N8N_AUTH_COOKIE } from '../constants';
|
import {
|
||||||
|
BACKEND_BASE_URL,
|
||||||
|
INSTANCE_ADMIN,
|
||||||
|
INSTANCE_MEMBERS,
|
||||||
|
INSTANCE_OWNER,
|
||||||
|
N8N_AUTH_COOKIE,
|
||||||
|
} from '../constants';
|
||||||
|
|
||||||
Cypress.Commands.add('getByTestId', (selector, ...args) => {
|
Cypress.Commands.add('getByTestId', (selector, ...args) => {
|
||||||
return cy.get(`[data-test-id="${selector}"]`, ...args);
|
return cy.get(`[data-test-id="${selector}"]`, ...args);
|
||||||
|
@ -51,6 +57,10 @@ Cypress.Commands.add('signin', ({ email, password }) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('signinAsOwner', () => {
|
||||||
|
cy.signin({ email: INSTANCE_OWNER.email, password: INSTANCE_OWNER.password });
|
||||||
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('signout', () => {
|
Cypress.Commands.add('signout', () => {
|
||||||
cy.request('POST', `${BACKEND_BASE_URL}/rest/logout`);
|
cy.request('POST', `${BACKEND_BASE_URL}/rest/logout`);
|
||||||
cy.getCookie(N8N_AUTH_COOKIE).should('not.exist');
|
cy.getCookie(N8N_AUTH_COOKIE).should('not.exist');
|
||||||
|
@ -183,3 +193,11 @@ Cypress.Commands.add('shouldNotHaveConsoleErrors', () => {
|
||||||
cy.wrap(spy).should('not.have.been.called');
|
cy.wrap(spy).should('not.have.been.called');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('resetDatabase', () => {
|
||||||
|
cy.request('POST', `${BACKEND_BASE_URL}/rest/e2e/reset`, {
|
||||||
|
owner: INSTANCE_OWNER,
|
||||||
|
members: INSTANCE_MEMBERS,
|
||||||
|
admin: INSTANCE_ADMIN,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import { BACKEND_BASE_URL, INSTANCE_ADMIN, INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants';
|
import { INSTANCE_OWNER } from '../constants';
|
||||||
import './commands';
|
import './commands';
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.request('POST', `${BACKEND_BASE_URL}/rest/e2e/reset`, {
|
cy.resetDatabase();
|
||||||
owner: INSTANCE_OWNER,
|
|
||||||
members: INSTANCE_MEMBERS,
|
|
||||||
admin: INSTANCE_ADMIN,
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.on('uncaught:exception', (err) => {
|
Cypress.on('uncaught:exception', (err) => {
|
||||||
return !err.message.includes('ResizeObserver');
|
return !err.message.includes('ResizeObserver');
|
||||||
|
|
|
@ -23,6 +23,7 @@ declare global {
|
||||||
findChildByTestId(childTestId: string): Chainable<JQuery<HTMLElement>>;
|
findChildByTestId(childTestId: string): Chainable<JQuery<HTMLElement>>;
|
||||||
createFixtureWorkflow(fixtureKey: string, workflowName: string): void;
|
createFixtureWorkflow(fixtureKey: string, workflowName: string): void;
|
||||||
signin(payload: SigninPayload): void;
|
signin(payload: SigninPayload): void;
|
||||||
|
signinAsOwner(): void;
|
||||||
signout(): void;
|
signout(): void;
|
||||||
interceptREST(method: string, url: string): Chainable<Interception>;
|
interceptREST(method: string, url: string): Chainable<Interception>;
|
||||||
enableFeature(feature: string): void;
|
enableFeature(feature: string): void;
|
||||||
|
@ -48,6 +49,7 @@ declare global {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
resetDatabase(): void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,7 @@
|
||||||
<ModalRoot :name="SETUP_CREDENTIALS_MODAL_KEY">
|
<ModalRoot :name="SETUP_CREDENTIALS_MODAL_KEY">
|
||||||
<template #default="{ modalName, data }">
|
<template #default="{ modalName, data }">
|
||||||
<SetupWorkflowCredentialsModal
|
<SetupWorkflowCredentialsModal
|
||||||
data-test-id="suggested-templates-preview-modal"
|
data-test-id="setup-workflow-credentials-modal"
|
||||||
:modal-name="modalName"
|
:modal-name="modalName"
|
||||||
:data="data"
|
:data="data"
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in a new issue