mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
feat: Add workflow and credential sharing access e2e tests (#5463)
feat: add workflow and credential sharing access e2e tests
This commit is contained in:
parent
b25c10a0e1
commit
246189f6da
168
cypress/e2e/17-sharing.cy.ts
Normal file
168
cypress/e2e/17-sharing.cy.ts
Normal file
|
@ -0,0 +1,168 @@
|
|||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||
import {
|
||||
CredentialsModal,
|
||||
CredentialsPage,
|
||||
NDV,
|
||||
WorkflowPage,
|
||||
WorkflowSharingModal,
|
||||
WorkflowsPage,
|
||||
} from '../pages';
|
||||
|
||||
/**
|
||||
* User U1 - Instance owner
|
||||
* User U2 - User, owns C1, W1, W2
|
||||
* User U3 - User, owns C2
|
||||
*
|
||||
* W1 - Workflow owned by User U2, shared with User U3
|
||||
* W2 - Workflow owned by User U2
|
||||
*
|
||||
* C1 - Credential owned by User U2
|
||||
* C2 - Credential owned by User U3, shared with User U1 and User U2
|
||||
*/
|
||||
|
||||
const credentialsPage = new CredentialsPage();
|
||||
const credentialsModal = new CredentialsModal();
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const workflowSharingModal = new WorkflowSharingModal();
|
||||
const ndv = new NDV();
|
||||
|
||||
const instanceOwner = {
|
||||
email: `${DEFAULT_USER_EMAIL}one`,
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: 'User',
|
||||
lastName: 'U1',
|
||||
};
|
||||
|
||||
const users = [
|
||||
{
|
||||
email: `${DEFAULT_USER_EMAIL}two`,
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: 'User',
|
||||
lastName: 'U2',
|
||||
},
|
||||
{
|
||||
email: `${DEFAULT_USER_EMAIL}three`,
|
||||
password: DEFAULT_USER_PASSWORD,
|
||||
firstName: 'User',
|
||||
lastName: 'U3',
|
||||
},
|
||||
];
|
||||
|
||||
describe('Sharing', () => {
|
||||
before(() => {
|
||||
cy.resetAll();
|
||||
cy.setupOwner(instanceOwner);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.on('uncaught:exception', (err, runnable) => {
|
||||
expect(err.message).to.include('Not logged in');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
it('should invite User U2 and User U3 to instance', () => {
|
||||
cy.inviteUsers({ instanceOwner, users });
|
||||
});
|
||||
|
||||
let workflowW2Url = '';
|
||||
it('should create C1, W1, W2, share W1 with U3, as U2', () => {
|
||||
cy.signin(users[0]);
|
||||
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
credentialsModal.getters.newCredentialTypeOption('Notion API').click();
|
||||
credentialsModal.getters.newCredentialTypeButton().click();
|
||||
credentialsModal.getters.connectionParameter('API Key').type('1234567890');
|
||||
credentialsModal.actions.setName('Credential C1');
|
||||
credentialsModal.actions.save();
|
||||
credentialsModal.actions.close();
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
workflowPage.actions.setWorkflowName('Workflow W1');
|
||||
workflowPage.actions.addInitialNodeToCanvas('Manual Trigger');
|
||||
workflowPage.actions.addNodeToCanvas('Notion', true, true);
|
||||
ndv.getters.credentialInput().should('contain', 'Credential C1');
|
||||
ndv.actions.close();
|
||||
|
||||
workflowPage.actions.openShareModal();
|
||||
workflowSharingModal.actions.addUser(users[1].email);
|
||||
workflowSharingModal.actions.save();
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.createWorkflowButton().click();
|
||||
cy.createFixtureWorkflow('Test_workflow_1.json', 'Workflow W2');
|
||||
cy.url().then((url) => {
|
||||
workflowW2Url = url;
|
||||
});
|
||||
});
|
||||
|
||||
it('should create C2, share C2 with U1 and U2, as U3', () => {
|
||||
cy.signin(users[1]);
|
||||
|
||||
cy.visit(credentialsPage.url);
|
||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||
credentialsModal.getters.newCredentialTypeOption('Airtable API').click();
|
||||
credentialsModal.getters.newCredentialTypeButton().click();
|
||||
credentialsModal.getters.connectionParameter('API Key').type('1234567890');
|
||||
credentialsModal.actions.setName('Credential C2');
|
||||
credentialsModal.actions.changeTab('Sharing');
|
||||
credentialsModal.actions.addUser(instanceOwner.email);
|
||||
credentialsModal.actions.addUser(users[0].email);
|
||||
credentialsModal.actions.save();
|
||||
credentialsModal.actions.close();
|
||||
});
|
||||
|
||||
it('should open W1, add node using C2 as U3', () => {
|
||||
cy.signin(users[1]);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||
workflowsPage.getters.workflowCard('Workflow W1').click();
|
||||
workflowPage.actions.addNodeToCanvas('Airtable', true, true);
|
||||
ndv.getters.credentialInput().should('contain', 'Credential C2');
|
||||
ndv.actions.close();
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
|
||||
workflowPage.actions.openNode('Notion');
|
||||
ndv.getters
|
||||
.credentialInput()
|
||||
.find('input')
|
||||
.should('have.value', 'Credential C1')
|
||||
.should('be.disabled');
|
||||
ndv.actions.close();
|
||||
});
|
||||
|
||||
it('should not have access to W2, as U3', () => {
|
||||
cy.signin(users[1]);
|
||||
|
||||
cy.visit(workflowW2Url);
|
||||
cy.waitForLoad();
|
||||
cy.wait(1000);
|
||||
cy.get('.el-notification').contains('Could not find workflow').should('be.visible');
|
||||
});
|
||||
|
||||
it('should have access to W1, W2, as U1', () => {
|
||||
cy.signin(instanceOwner);
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCards().should('have.length', 2);
|
||||
workflowsPage.getters.workflowCard('Workflow W1').click();
|
||||
workflowPage.actions.openNode('Notion');
|
||||
ndv.getters
|
||||
.credentialInput()
|
||||
.find('input')
|
||||
.should('have.value', 'Credential C1')
|
||||
.should('be.disabled');
|
||||
ndv.actions.close();
|
||||
|
||||
cy.waitForLoad();
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.workflowCard('Workflow W2').click();
|
||||
workflowPage.actions.executeWorkflow();
|
||||
});
|
||||
});
|
|
@ -26,8 +26,19 @@ export class CredentialsModal extends BasePage {
|
|||
credentialAuthTypeRadioButtons: () =>
|
||||
this.getters.credentialsAuthTypeSelector().find('label[role=radio]'),
|
||||
credentialInputs: () => cy.getByTestId('credential-connection-parameter'),
|
||||
menu: () => this.getters.editCredentialModal().get('.menu-container'),
|
||||
menuItem: (name: string) => this.getters.menu().get('.n8n-menu-item').contains(name),
|
||||
usersSelect: () => cy.getByTestId('credential-sharing-modal-users-select'),
|
||||
};
|
||||
actions = {
|
||||
addUser: (email: string) => {
|
||||
this.getters.usersSelect().click();
|
||||
this.getters
|
||||
.usersSelect()
|
||||
.get('.el-select-dropdown__item')
|
||||
.contains(email.toLowerCase())
|
||||
.click();
|
||||
},
|
||||
setName: (name: string) => {
|
||||
this.getters.name().click();
|
||||
this.getters.nameInput().clear().type(name);
|
||||
|
@ -64,5 +75,8 @@ export class CredentialsModal extends BasePage {
|
|||
this.getters.nameInput().type(newName);
|
||||
this.getters.nameInput().type('{enter}');
|
||||
},
|
||||
changeTab: (tabName: string) => {
|
||||
this.getters.menuItem(tabName).click();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './credentials-modal';
|
||||
export * from './message-box';
|
||||
export * from './workflow-sharing-modal';
|
||||
|
|
26
cypress/pages/modals/workflow-sharing-modal.ts
Normal file
26
cypress/pages/modals/workflow-sharing-modal.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { BasePage } from '../base';
|
||||
|
||||
export class WorkflowSharingModal extends BasePage {
|
||||
getters = {
|
||||
modal: () => cy.getByTestId('workflowShare-modal', { timeout: 5000 }),
|
||||
usersSelect: () => cy.getByTestId('workflow-sharing-modal-users-select'),
|
||||
saveButton: () => cy.getByTestId('workflow-sharing-modal-save-button'),
|
||||
closeButton: () => this.getters.modal().find('.el-dialog__close').first(),
|
||||
};
|
||||
actions = {
|
||||
addUser: (email: string) => {
|
||||
this.getters.usersSelect().click();
|
||||
this.getters
|
||||
.usersSelect()
|
||||
.get('.el-select-dropdown__item')
|
||||
.contains(email.toLowerCase())
|
||||
.click();
|
||||
},
|
||||
save: () => {
|
||||
this.getters.saveButton().click();
|
||||
},
|
||||
closeModal: () => {
|
||||
this.getters.closeButton().click();
|
||||
},
|
||||
};
|
||||
}
|
|
@ -5,6 +5,7 @@ export class NDV extends BasePage {
|
|||
container: () => cy.getByTestId('ndv'),
|
||||
backToCanvas: () => cy.getByTestId('back-to-canvas'),
|
||||
copyInput: () => cy.getByTestId('copy-input'),
|
||||
credentialInput: (eq = 0) => cy.getByTestId('node-credentials-select').eq(eq),
|
||||
nodeExecuteButton: () => cy.getByTestId('node-execute-button'),
|
||||
inputSelect: () => cy.getByTestId('ndv-input-select'),
|
||||
inputOption: () => cy.getByTestId('ndv-input-option'),
|
||||
|
@ -24,15 +25,22 @@ export class NDV extends BasePage {
|
|||
outputTableRows: () => this.getters.outputDataContainer().find('table tr'),
|
||||
outputTableHeaders: () => this.getters.outputDataContainer().find('table thead th'),
|
||||
outputTableRow: (row: number) => this.getters.outputTableRows().eq(row),
|
||||
outputTbodyCell: (row: number, col: number) => this.getters.outputTableRow(row).find('td').eq(col),
|
||||
outputTbodyCell: (row: number, col: number) =>
|
||||
this.getters.outputTableRow(row).find('td').eq(col),
|
||||
inputTableRows: () => this.getters.inputDataContainer().find('table tr'),
|
||||
inputTableHeaders: () => this.getters.inputDataContainer().find('table thead th'),
|
||||
inputTableRow: (row: number) => this.getters.inputTableRows().eq(row),
|
||||
inputTbodyCell: (row: number, col: number) => this.getters.inputTableRow(row).find('td').eq(col),
|
||||
inputTbodyCell: (row: number, col: number) =>
|
||||
this.getters.inputTableRow(row).find('td').eq(col),
|
||||
inlineExpressionEditorInput: () => cy.getByTestId('inline-expression-editor-input'),
|
||||
nodeParameters: () => cy.getByTestId('node-parameters'),
|
||||
parameterInput: (parameterName: string) => cy.getByTestId(`parameter-input-${parameterName}`),
|
||||
parameterExpressionPreview: (parameterName: string) => this.getters.nodeParameters().find(`[data-test-id="parameter-input-${parameterName}"] + [data-test-id="parameter-expression-preview"]`),
|
||||
parameterExpressionPreview: (parameterName: string) =>
|
||||
this.getters
|
||||
.nodeParameters()
|
||||
.find(
|
||||
`[data-test-id="parameter-input-${parameterName}"] + [data-test-id="parameter-expression-preview"]`,
|
||||
),
|
||||
nodeNameContainer: () => cy.getByTestId('node-title-container'),
|
||||
nodeRenameInput: () => cy.getByTestId('node-rename-input'),
|
||||
executePrevious: () => cy.getByTestId('execute-previous-node'),
|
||||
|
@ -77,18 +85,11 @@ export class NDV extends BasePage {
|
|||
this.getters.parameterInput(parameterName).type(content);
|
||||
},
|
||||
selectOptionInParameterDropdown: (parameterName: string, content: string) => {
|
||||
this.getters
|
||||
.parameterInput(parameterName)
|
||||
.find('.option-headline')
|
||||
.contains(content)
|
||||
.click();
|
||||
this.getters.parameterInput(parameterName).find('.option-headline').contains(content).click();
|
||||
},
|
||||
rename: (newName: string) => {
|
||||
this.getters.nodeNameContainer().click();
|
||||
this.getters.nodeRenameInput()
|
||||
.should('be.visible')
|
||||
.type('{selectall}')
|
||||
.type(newName);
|
||||
this.getters.nodeRenameInput().should('be.visible').type('{selectall}').type(newName);
|
||||
cy.get('body').type('{enter}');
|
||||
},
|
||||
executePrevious: () => {
|
||||
|
@ -104,10 +105,10 @@ export class NDV extends BasePage {
|
|||
cy.draganddrop('', droppable);
|
||||
},
|
||||
switchInputMode: (type: 'Schema' | 'Table' | 'JSON' | 'Binary') => {
|
||||
this.getters.inputDisplayMode().find('label').contains(type).click({force: true});
|
||||
this.getters.inputDisplayMode().find('label').contains(type).click({ force: true });
|
||||
},
|
||||
switchOutputMode: (type: 'Schema' | 'Table' | 'JSON' | 'Binary') => {
|
||||
this.getters.outputDisplayMode().find('label').contains(type).click({force: true});
|
||||
this.getters.outputDisplayMode().find('label').contains(type).click({ force: true });
|
||||
},
|
||||
selectInputNode: (nodeName: string) => {
|
||||
this.getters.inputSelect().find('.el-select').click();
|
||||
|
|
|
@ -26,7 +26,7 @@ export class WorkflowPage extends BasePage {
|
|||
canvasNodeByName: (nodeName: string) =>
|
||||
this.getters.canvasNodes().filter(`:contains("${nodeName}")`),
|
||||
getEndpointSelector: (type: 'input' | 'output' | 'plus', nodeName: string, index = 0) => {
|
||||
return `[data-endpoint-name='${nodeName}'][data-endpoint-type='${type}'][data-input-index='${index}']`
|
||||
return `[data-endpoint-name='${nodeName}'][data-endpoint-type='${type}'][data-input-index='${index}']`;
|
||||
},
|
||||
canvasNodeInputEndpointByName: (nodeName: string, index = 0) => {
|
||||
return cy.get(this.getters.getEndpointSelector('input', nodeName, index));
|
||||
|
@ -79,7 +79,7 @@ export class WorkflowPage extends BasePage {
|
|||
workflowSettingsSaveButton: () =>
|
||||
cy.getByTestId('workflow-settings-save-button').find('button'),
|
||||
|
||||
shareButton: () => cy.getByTestId('workflow-share-button').find('button'),
|
||||
shareButton: () => cy.getByTestId('workflow-share-button'),
|
||||
|
||||
duplicateWorkflowModal: () => cy.getByTestId('duplicate-modal'),
|
||||
nodeViewBackground: () => cy.getByTestId('node-view-background'),
|
||||
|
@ -155,11 +155,17 @@ export class WorkflowPage extends BasePage {
|
|||
saveWorkflowOnButtonClick: () => {
|
||||
this.getters.saveButton().should('contain', 'Save');
|
||||
this.getters.saveButton().click();
|
||||
this.getters.saveButton().should('contain', 'Saved')
|
||||
this.getters.saveButton().should('contain', 'Saved');
|
||||
},
|
||||
saveWorkflowUsingKeyboardShortcut: () => {
|
||||
cy.get('body').type('{meta}', { release: false }).type('s');
|
||||
},
|
||||
setWorkflowName: (name: string) => {
|
||||
this.getters.workflowNameInput().should('be.disabled');
|
||||
this.getters.workflowNameInput().parent().click();
|
||||
this.getters.workflowNameInput().should('be.enabled');
|
||||
this.getters.workflowNameInput().clear().type(name).type('{enter}');
|
||||
},
|
||||
activateWorkflow: () => {
|
||||
this.getters.activatorSwitch().find('input').first().should('be.enabled');
|
||||
this.getters.activatorSwitch().click();
|
||||
|
|
|
@ -40,10 +40,7 @@ Cypress.Commands.add('createFixtureWorkflow', (fixtureKey, workflowName) => {
|
|||
WorkflowPage.getters
|
||||
.workflowImportInput()
|
||||
.selectFile(`cypress/fixtures/${fixtureKey}`, { force: true });
|
||||
WorkflowPage.getters.workflowNameInput().should('be.disabled');
|
||||
WorkflowPage.getters.workflowNameInput().parent().click();
|
||||
WorkflowPage.getters.workflowNameInput().should('be.enabled');
|
||||
WorkflowPage.getters.workflowNameInput().clear().type(workflowName).type('{enter}');
|
||||
WorkflowPage.actions.setWorkflowName(workflowName);
|
||||
|
||||
WorkflowPage.getters.saveButton().should('contain', 'Saved');
|
||||
});
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
:users="usersList"
|
||||
:currentUserId="usersStore.currentUser.id"
|
||||
:placeholder="$locale.baseText('credentialEdit.credentialSharing.select.placeholder')"
|
||||
data-test-id="credential-sharing-modal-users-select"
|
||||
@input="onAddSharee"
|
||||
>
|
||||
<template #prefix>
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
:value="getSelectedName(credentialTypeDescription.name)"
|
||||
disabled
|
||||
size="small"
|
||||
data-test-id="node-credentials-select"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
:users="usersList"
|
||||
:currentUserId="currentUser.id"
|
||||
:placeholder="$locale.baseText('workflows.shareModal.select.placeholder')"
|
||||
data-test-id="workflow-sharing-modal-users-select"
|
||||
@input="onAddSharee"
|
||||
>
|
||||
<template #prefix>
|
||||
|
@ -108,10 +109,11 @@
|
|||
</n8n-text>
|
||||
<n8n-button
|
||||
v-show="workflowPermissions.updateSharing"
|
||||
@click="onSave"
|
||||
:loading="loading"
|
||||
:disabled="!isDirty"
|
||||
size="medium"
|
||||
data-test-id="workflow-sharing-modal-save-button"
|
||||
@click="onSave"
|
||||
>
|
||||
{{ $locale.baseText('workflows.shareModal.save') }}
|
||||
</n8n-button>
|
||||
|
|
Loading…
Reference in a new issue