@@ -3,4 +3,9 @@ POSTGRES_USER=automatisch_user
|
|||||||
POSTGRES_PASSWORD=automatisch_password
|
POSTGRES_PASSWORD=automatisch_password
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
POSTGRES_HOST=localhost
|
POSTGRES_HOST=localhost
|
||||||
BACKEND_APP_URL=http://localhost:3000
|
BACKEND_APP_URL=http://localhost:3000
|
||||||
|
LOGIN_EMAIL=user@automatisch.io
|
||||||
|
LOGIN_PASSWORD=sample
|
||||||
|
ENCRYPTION_KEY=sample_encryption_key
|
||||||
|
WEBHOOK_SECRET_KEY=sample_webhook_secret_key
|
||||||
|
LICENSE_KEY=dummy_license_key
|
||||||
@@ -24,6 +24,16 @@ export class AdminUsersPage extends AuthenticatedPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async navigateTo() {
|
async navigateTo() {
|
||||||
|
await this.profileMenuButton.click();
|
||||||
|
await Promise.all([this.adminMenuItem.click(), this.isMounted()]);
|
||||||
|
if (await this.usersLoader.isVisible()) {
|
||||||
|
await this.usersLoader.waitFor({
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async navigateToAndWaitForUsers() {
|
||||||
await this.profileMenuButton.click();
|
await this.profileMenuButton.click();
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.page.waitForResponse(
|
this.page.waitForResponse(
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export class FlowEditorPage extends AuthenticatedPage {
|
|||||||
this.goBackButton = this.page.getByTestId('editor-go-back-button');
|
this.goBackButton = this.page.getByTestId('editor-go-back-button');
|
||||||
this.exportFlowButton = page.getByTestId('export-flow-button');
|
this.exportFlowButton = page.getByTestId('export-flow-button');
|
||||||
this.stepName = page.getByTestId('step-name');
|
this.stepName = page.getByTestId('step-name');
|
||||||
|
this.folderName = page.getByTestId('folder-name');
|
||||||
}
|
}
|
||||||
|
|
||||||
async createWebhookTrigger(workSynchronously) {
|
async createWebhookTrigger(workSynchronously) {
|
||||||
|
|||||||
@@ -4,7 +4,24 @@ export class FlowsPage extends AuthenticatedPage {
|
|||||||
constructor(page) {
|
constructor(page) {
|
||||||
super(page);
|
super(page);
|
||||||
|
|
||||||
this.flowRow = this.page.getByTestId('flow-row');
|
this.searchInput = page.locator('#search-input');
|
||||||
|
this.flowRow = page.getByTestId('flow-row');
|
||||||
this.importFlowButton = page.getByTestId('import-flow-button');
|
this.importFlowButton = page.getByTestId('import-flow-button');
|
||||||
|
this.createFlowButton = page.getByTestId('create-flow-button');
|
||||||
|
this.uncategorizedFlowsFolder = page.getByTestId(
|
||||||
|
'uncategorized-flows-folder'
|
||||||
|
);
|
||||||
|
this.allFlowsFolder = page.getByTestId('all-flows-folder');
|
||||||
|
this.addNewFolderButton = page.getByTestId('add-folder-button');
|
||||||
|
this.userFolders = page.getByTestId('user-folders');
|
||||||
|
this.editFolder = page.getByTestId('edit-folder');
|
||||||
|
this.deleteFolder = page.getByTestId('delete-folder');
|
||||||
|
this.deleteFolderSuccessAlert = page.getByTestId(
|
||||||
|
'snackbar-delete-folder-success'
|
||||||
|
);
|
||||||
|
|
||||||
|
//flow actions
|
||||||
|
this.moveTo = page.getByTestId('move-to');
|
||||||
|
this.delete = page.getByTestId('delete-flow');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
packages/e2e-tests/fixtures/folder/create-folder-dialog.js
Normal file
15
packages/e2e-tests/fixtures/folder/create-folder-dialog.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
const { AuthenticatedPage } = require('../authenticated-page');
|
||||||
|
|
||||||
|
export class CreateFolderDialog extends AuthenticatedPage {
|
||||||
|
constructor(page) {
|
||||||
|
super(page);
|
||||||
|
|
||||||
|
this.folderNameInput = page.getByTestId('new-folder-name').locator('input');
|
||||||
|
this.createButton = page.getByTestId('create-folder-dialog-create-button');
|
||||||
|
this.successAlert = page.getByTestId('create-folder-dialog-success-alert');
|
||||||
|
this.errorAlert = page.getByTestId(
|
||||||
|
'create-folder-dialog-generic-error-alert'
|
||||||
|
);
|
||||||
|
this.closeDialog = page.getByTestId('close-dialog');
|
||||||
|
}
|
||||||
|
}
|
||||||
14
packages/e2e-tests/fixtures/folder/delete-folder-dialog.js
Normal file
14
packages/e2e-tests/fixtures/folder/delete-folder-dialog.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const { AuthenticatedPage } = require('../authenticated-page');
|
||||||
|
|
||||||
|
export class DeleteFolderDialog extends AuthenticatedPage {
|
||||||
|
constructor(page) {
|
||||||
|
super(page);
|
||||||
|
|
||||||
|
this.modal = page.getByTestId('delete-folder-modal');
|
||||||
|
this.cancelButton = this.modal.getByTestId('confirmation-cancel-button');
|
||||||
|
this.deleteButton = this.modal.getByTestId('confirmation-confirm-button');
|
||||||
|
this.deleteAlert = this.modal.getByTestId(
|
||||||
|
'confirmation-dialog-error-alert'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
packages/e2e-tests/fixtures/folder/move-flow-dialog.js
Normal file
18
packages/e2e-tests/fixtures/folder/move-flow-dialog.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const { AuthenticatedPage } = require('../authenticated-page');
|
||||||
|
|
||||||
|
export class MoveFolderDialog extends AuthenticatedPage {
|
||||||
|
constructor(page) {
|
||||||
|
super(page);
|
||||||
|
|
||||||
|
this.folderNameAutocomplete = page
|
||||||
|
.getByTestId('move-to-folder-name')
|
||||||
|
.locator('input');
|
||||||
|
this.moveButton = page.getByTestId(
|
||||||
|
'flow-folder-change-dialog-confirm-button'
|
||||||
|
);
|
||||||
|
this.successAlert = page.getByTestId(
|
||||||
|
'flow-folder-change-dialog-success-alert'
|
||||||
|
);
|
||||||
|
this.closeDialog = page.getByTestId('close-dialog');
|
||||||
|
}
|
||||||
|
}
|
||||||
12
packages/e2e-tests/fixtures/folder/update-folder-dialog.js
Normal file
12
packages/e2e-tests/fixtures/folder/update-folder-dialog.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const { AuthenticatedPage } = require('../authenticated-page');
|
||||||
|
|
||||||
|
export class UpdateFolderDialog extends AuthenticatedPage {
|
||||||
|
constructor(page) {
|
||||||
|
super(page);
|
||||||
|
|
||||||
|
this.folderNameInput = page.getByTestId('new-folder-name').locator('input');
|
||||||
|
this.updateButton = page.getByTestId('edit-folder-dialog-update-button');
|
||||||
|
this.successAlert = page.getByTestId('edit-folder-dialog-success-alert');
|
||||||
|
this.closeDialog = page.getByTestId('close-dialog');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
const { expect } = require('../fixtures/index');
|
const { expect } = require('../fixtures/index');
|
||||||
|
|
||||||
export const getToken = async (apiRequest) => {
|
export const getToken = async (
|
||||||
|
apiRequest,
|
||||||
|
email = process.env.LOGIN_EMAIL,
|
||||||
|
password = process.env.LOGIN_PASSWORD
|
||||||
|
) => {
|
||||||
const tokenResponse = await apiRequest.post(
|
const tokenResponse = await apiRequest.post(
|
||||||
`${process.env.BACKEND_APP_URL}/internal/api/v1/access-tokens`,
|
`${process.env.BACKEND_APP_URL}/internal/api/v1/access-tokens`,
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
email: process.env.LOGIN_EMAIL,
|
email: email,
|
||||||
password: process.env.LOGIN_PASSWORD,
|
password: password,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
14
packages/e2e-tests/helpers/folder-api-helper.js
Normal file
14
packages/e2e-tests/helpers/folder-api-helper.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const { expect } = require('../fixtures/index');
|
||||||
|
|
||||||
|
export const addFolder = async (apiRequest, token, folderName) => {
|
||||||
|
const addFolderResponse = await apiRequest.post(
|
||||||
|
`${process.env.BACKEND_APP_URL}/internal/api/v1/folders`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: token },
|
||||||
|
data: { name: folderName },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(addFolderResponse.status()).toBe(201);
|
||||||
|
|
||||||
|
return await addFolderResponse.json();
|
||||||
|
};
|
||||||
@@ -195,7 +195,7 @@ test.describe('Role management page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Change the role the user has', async () => {
|
await test.step('Change the role the user has', async () => {
|
||||||
await adminUsersPage.navigateTo();
|
await adminUsersPage.navigateToAndWaitForUsers();
|
||||||
await adminUsersPage.usersLoader.waitFor({
|
await adminUsersPage.usersLoader.waitFor({
|
||||||
state: 'detached',
|
state: 'detached',
|
||||||
});
|
});
|
||||||
@@ -264,7 +264,7 @@ test.describe('Role management page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Delete this user', async () => {
|
await test.step('Delete this user', async () => {
|
||||||
await adminUsersPage.navigateTo();
|
await adminUsersPage.navigateToAndWaitForUsers();
|
||||||
const row = await adminUsersPage.findUserPageWithEmail(
|
const row = await adminUsersPage.findUserPageWithEmail(
|
||||||
'user-delete-role-test@automatisch.io'
|
'user-delete-role-test@automatisch.io'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ test.describe('User management page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Delete the created user', async () => {
|
await test.step('Delete the created user', async () => {
|
||||||
await adminUsersPage.navigateTo();
|
await adminUsersPage.navigateToAndWaitForUsers();
|
||||||
await adminUsersPage.findUserPageWithEmail(testUser.email);
|
await adminUsersPage.findUserPageWithEmail(testUser.email);
|
||||||
const userRow = await adminUsersPage.getUserRowByEmail(testUser.email);
|
const userRow = await adminUsersPage.getUserRowByEmail(testUser.email);
|
||||||
await adminUsersPage.clickDeleteUser(userRow);
|
await adminUsersPage.clickDeleteUser(userRow);
|
||||||
@@ -198,7 +198,7 @@ test.describe('User management page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Try editing the second user to have the email of the first user', async () => {
|
await test.step('Try editing the second user to have the email of the first user', async () => {
|
||||||
await adminUsersPage.navigateTo();
|
await adminUsersPage.navigateToAndWaitForUsers();
|
||||||
await adminUsersPage.findUserPageWithEmail(user2.email);
|
await adminUsersPage.findUserPageWithEmail(user2.email);
|
||||||
let userRow = await adminUsersPage.getUserRowByEmail(user2.email);
|
let userRow = await adminUsersPage.getUserRowByEmail(user2.email);
|
||||||
await adminUsersPage.clickEditUser(userRow);
|
await adminUsersPage.clickEditUser(userRow);
|
||||||
|
|||||||
479
packages/e2e-tests/tests/flow-folders/flow-folder.spec.js
Normal file
479
packages/e2e-tests/tests/flow-folders/flow-folder.spec.js
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
const { request } = require('@playwright/test');
|
||||||
|
|
||||||
|
const {
|
||||||
|
CreateFolderDialog,
|
||||||
|
} = require('../../fixtures/folder/create-folder-dialog');
|
||||||
|
const {
|
||||||
|
UpdateFolderDialog,
|
||||||
|
} = require('../../fixtures/folder/update-folder-dialog');
|
||||||
|
const { MoveFolderDialog } = require('../../fixtures/folder/move-flow-dialog');
|
||||||
|
const {
|
||||||
|
DeleteFolderDialog,
|
||||||
|
} = require('../../fixtures/folder/delete-folder-dialog');
|
||||||
|
const { getToken } = require('../../helpers/auth-api-helper');
|
||||||
|
const { addUser, acceptInvitation } = require('../../helpers/user-api-helper');
|
||||||
|
const { addFolder } = require('../../helpers/folder-api-helper');
|
||||||
|
const { createFlow, updateFlowName } = require('../../helpers/flow-api-helper');
|
||||||
|
import Crypto from 'crypto';
|
||||||
|
|
||||||
|
test.describe('Folders', () => {
|
||||||
|
test('owner folder lifecycle', async ({
|
||||||
|
page,
|
||||||
|
flowsPage,
|
||||||
|
flowEditorPage,
|
||||||
|
}) => {
|
||||||
|
const createFolderDialog = new CreateFolderDialog(page);
|
||||||
|
const updateFolderDialog = new UpdateFolderDialog(page);
|
||||||
|
const folderMoveToDialog = new MoveFolderDialog(page);
|
||||||
|
const deleteFolderDialog = new DeleteFolderDialog(page);
|
||||||
|
const flowName = Crypto.randomUUID();
|
||||||
|
|
||||||
|
await test.step('new flow should be in uncategorized folder', async () => {
|
||||||
|
await flowsPage.createFlowButton.click();
|
||||||
|
await flowEditorPage.flowName.click();
|
||||||
|
await flowEditorPage.flowNameInput.fill(flowName);
|
||||||
|
await flowEditorPage.flowStep.first().click();
|
||||||
|
await flowEditorPage.goBackButton.click();
|
||||||
|
await expect(page).toHaveURL('/flows');
|
||||||
|
await flowsPage.searchInput.fill(flowName);
|
||||||
|
await expect(
|
||||||
|
flowsPage.flowRow.filter({
|
||||||
|
hasText: flowName,
|
||||||
|
})
|
||||||
|
).toHaveCount(1);
|
||||||
|
await flowsPage.uncategorizedFlowsFolder.click();
|
||||||
|
await flowsPage.searchInput.fill(flowName);
|
||||||
|
await expect(
|
||||||
|
flowsPage.flowRow.filter({
|
||||||
|
hasText: flowName,
|
||||||
|
})
|
||||||
|
).toHaveCount(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('folder name should be at least 1 character long', async () => {
|
||||||
|
await flowsPage.addNewFolderButton.click();
|
||||||
|
await createFolderDialog.createButton.click();
|
||||||
|
await expect(createFolderDialog.errorAlert).toHaveCount(1);
|
||||||
|
await createFolderDialog.closeDialog.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('should be able to add a folder', async () => {
|
||||||
|
await flowsPage.addNewFolderButton.click();
|
||||||
|
await createFolderDialog.folderNameInput.fill('newFolder');
|
||||||
|
await createFolderDialog.createButton.click();
|
||||||
|
await expect(createFolderDialog.successAlert).toHaveCount(1);
|
||||||
|
await createFolderDialog.closeDialog.click();
|
||||||
|
await expect(flowsPage.userFolders.getByText('newFolder')).toHaveCount(1);
|
||||||
|
await flowsPage.userFolders.getByText('newFolder').click();
|
||||||
|
await expect(flowsPage.flowRow).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('should be possible to move flow to the new folder', async () => {
|
||||||
|
await flowsPage.allFlowsFolder.click();
|
||||||
|
await flowsPage.flowRow
|
||||||
|
.filter({
|
||||||
|
hasText: flowName,
|
||||||
|
})
|
||||||
|
.getByRole('button')
|
||||||
|
.click();
|
||||||
|
await flowsPage.moveTo.click();
|
||||||
|
|
||||||
|
await folderMoveToDialog.folderNameAutocomplete.click();
|
||||||
|
await page.getByRole('option', { name: 'newFolder' }).last().click();
|
||||||
|
await folderMoveToDialog.moveButton.click();
|
||||||
|
await expect(folderMoveToDialog.successAlert).toHaveCount(1);
|
||||||
|
await folderMoveToDialog.closeDialog.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('flow should be visible in the new folder and in all flows folder', async () => {
|
||||||
|
await flowsPage.userFolders.getByText('newFolder').click();
|
||||||
|
await flowsPage.searchInput.fill(flowName);
|
||||||
|
await expect(
|
||||||
|
flowsPage.flowRow.filter({
|
||||||
|
hasText: flowName,
|
||||||
|
})
|
||||||
|
).toHaveCount(1);
|
||||||
|
await flowsPage.flowRow
|
||||||
|
.filter({
|
||||||
|
hasText: flowName,
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
await expect(page).toHaveURL(
|
||||||
|
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
|
||||||
|
);
|
||||||
|
await expect(flowEditorPage.folderName).toContainText('newFolder');
|
||||||
|
await flowEditorPage.goBackButton.click();
|
||||||
|
|
||||||
|
await flowsPage.allFlowsFolder.click();
|
||||||
|
await flowsPage.searchInput.fill(flowName);
|
||||||
|
await expect(
|
||||||
|
flowsPage.flowRow.filter({
|
||||||
|
hasText: flowName,
|
||||||
|
})
|
||||||
|
).toHaveCount(1);
|
||||||
|
await flowsPage.uncategorizedFlowsFolder.click();
|
||||||
|
await flowsPage.searchInput.fill(flowName);
|
||||||
|
await expect(
|
||||||
|
flowsPage.flowRow.filter({
|
||||||
|
hasText: flowName,
|
||||||
|
})
|
||||||
|
).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('should be able to update a folder', async () => {
|
||||||
|
await expect(
|
||||||
|
flowsPage.userFolders.getByText('updatedFolderName')
|
||||||
|
).toHaveCount(0);
|
||||||
|
await flowsPage.userFolders.getByText('newFolder').first().click();
|
||||||
|
await flowsPage.editFolder.click();
|
||||||
|
await updateFolderDialog.folderNameInput.fill('updatedFolderName');
|
||||||
|
await updateFolderDialog.updateButton.click();
|
||||||
|
await expect(updateFolderDialog.successAlert).toHaveCount(1);
|
||||||
|
await updateFolderDialog.closeDialog.click();
|
||||||
|
await expect(
|
||||||
|
flowsPage.userFolders.getByText('updatedFolderName')
|
||||||
|
).toHaveCount(1);
|
||||||
|
await flowsPage.flowRow
|
||||||
|
.filter({
|
||||||
|
hasText: flowName,
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
await expect(page).toHaveURL(
|
||||||
|
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
|
||||||
|
);
|
||||||
|
await page.reload();
|
||||||
|
await expect(flowEditorPage.folderName).toContainText(
|
||||||
|
'updatedFolderName'
|
||||||
|
);
|
||||||
|
await flowEditorPage.goBackButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('should be able to delete a folder with a flow', async () => {
|
||||||
|
await expect(
|
||||||
|
flowsPage.userFolders.getByText('updatedFolderName')
|
||||||
|
).toHaveCount(1);
|
||||||
|
|
||||||
|
await flowsPage.deleteFolder.click();
|
||||||
|
await deleteFolderDialog.cancelButton.click();
|
||||||
|
await flowsPage.deleteFolder.click();
|
||||||
|
await deleteFolderDialog.deleteButton.click(),
|
||||||
|
await expect(
|
||||||
|
flowsPage.userFolders.getByText('updatedFolderName')
|
||||||
|
).toHaveCount(0);
|
||||||
|
await expect(flowsPage.deleteFolderSuccessAlert).toHaveCount(1);
|
||||||
|
await flowsPage.deleteFolderSuccessAlert.click();
|
||||||
|
await expect(flowsPage.deleteFolderSuccessAlert).toHaveCount(0);
|
||||||
|
await expect(page).toHaveURL(
|
||||||
|
/\/flows\?flowName=[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
|
||||||
|
);
|
||||||
|
|
||||||
|
await flowsPage.flowRow
|
||||||
|
.filter({
|
||||||
|
hasText: flowName,
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
await expect(page).toHaveURL(
|
||||||
|
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
|
||||||
|
);
|
||||||
|
await expect(flowEditorPage.folderName).toContainText('Uncategorized');
|
||||||
|
await flowEditorPage.goBackButton.click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// removal of the empty folder is buggy
|
||||||
|
test.skip('should be able to delete empty folder', async ({
|
||||||
|
page,
|
||||||
|
flowsPage,
|
||||||
|
flowEditorPage,
|
||||||
|
}) => {
|
||||||
|
// add flow
|
||||||
|
await flowsPage.createFlowButton.click();
|
||||||
|
await flowEditorPage.flowName.click();
|
||||||
|
await flowEditorPage.flowNameInput.fill('deleteFlow');
|
||||||
|
await flowEditorPage.flowStep.first().click();
|
||||||
|
await flowEditorPage.goBackButton.click();
|
||||||
|
await expect(page).toHaveURL('/flows');
|
||||||
|
// add folder
|
||||||
|
await flowsPage.addNewFolderButton.click();
|
||||||
|
await createFolderDialog.folderNameInput.fill('deleteFolder');
|
||||||
|
await createFolderDialog.createButton.click();
|
||||||
|
await expect(createFolderDialog.successAlert).toContainText(
|
||||||
|
'The folder has been successfully created!'
|
||||||
|
);
|
||||||
|
await createFolderDialog.closeDialog.click();
|
||||||
|
await expect(flowsPage.userFolders.getByText('deleteFolder')).toHaveCount(
|
||||||
|
1
|
||||||
|
);
|
||||||
|
await flowsPage.userFolders.getByText('deleteFolder').click();
|
||||||
|
// move flow
|
||||||
|
await flowsPage.allFlowsFolder.click();
|
||||||
|
await flowsPage.flowRow
|
||||||
|
.filter({
|
||||||
|
hasText: 'deleteFlow',
|
||||||
|
})
|
||||||
|
.getByRole('button')
|
||||||
|
.click();
|
||||||
|
await flowsPage.moveTo.click();
|
||||||
|
|
||||||
|
await folderMoveToDialog.folderNameAutocomplete.click();
|
||||||
|
await page.getByRole('option', { name: 'deleteFolder' }).last().click();
|
||||||
|
await folderMoveToDialog.moveButton.click();
|
||||||
|
await expect(folderMoveToDialog.successAlert).toHaveCount(1);
|
||||||
|
await folderMoveToDialog.closeDialog.click();
|
||||||
|
// remove flow
|
||||||
|
await flowsPage.allFlowsFolder.click();
|
||||||
|
await flowsPage.flowRow
|
||||||
|
.filter({
|
||||||
|
hasText: 'deleteFlow',
|
||||||
|
})
|
||||||
|
.getByRole('button')
|
||||||
|
.click();
|
||||||
|
await flowsPage.delete.click();
|
||||||
|
// remove folder
|
||||||
|
await flowsPage.userFolders.getByText('deleteFolder').click();
|
||||||
|
await flowsPage.deleteFolder.click();
|
||||||
|
await deleteFolderDialog.deleteButton.click();
|
||||||
|
await expect(flowsPage.userFolders.getByText('deleteFolder')).toHaveCount(
|
||||||
|
0
|
||||||
|
);
|
||||||
|
await expect(flowsPage.deleteFolderSuccessAlert).toHaveCount(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// folder name is not correct (still uncategorized)
|
||||||
|
test.skip('should be possible to add flow in the new folder', async ({
|
||||||
|
flowsPage,
|
||||||
|
flowEditorPage,
|
||||||
|
}) => {
|
||||||
|
await expect(flowsPage.userFolders.getByText('newFolder')).toHaveCount(1);
|
||||||
|
await flowsPage.userFolders.getByText('newFolder').click();
|
||||||
|
await flowsPage.createFlowButton.click();
|
||||||
|
await expect(flowEditorPage.folderName).toContainText('newFolder');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to add a folder with the same name', async ({
|
||||||
|
flowsPage,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const createFolderDialog = new CreateFolderDialog(page);
|
||||||
|
|
||||||
|
await flowsPage.addNewFolderButton.click();
|
||||||
|
await createFolderDialog.folderNameInput.fill('sameNameFolder');
|
||||||
|
await createFolderDialog.createButton.click();
|
||||||
|
await expect(createFolderDialog.successAlert).toHaveCount(1);
|
||||||
|
await createFolderDialog.closeDialog.click();
|
||||||
|
await expect(flowsPage.userFolders.getByText('sameNameFolder')).toHaveCount(
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
await flowsPage.addNewFolderButton.click();
|
||||||
|
await createFolderDialog.folderNameInput.fill('sameNameFolder');
|
||||||
|
await createFolderDialog.createButton.click();
|
||||||
|
await expect(createFolderDialog.successAlert).toHaveCount(1);
|
||||||
|
await createFolderDialog.closeDialog.click();
|
||||||
|
await expect(flowsPage.userFolders.getByText('sameNameFolder')).toHaveCount(
|
||||||
|
2
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('non-owner folder visibility', async ({
|
||||||
|
page,
|
||||||
|
flowsPage,
|
||||||
|
flowEditorPage,
|
||||||
|
adminCreateUserPage,
|
||||||
|
}) => {
|
||||||
|
const apiRequest = await request.newContext();
|
||||||
|
const tokenJsonResponse = await getToken(apiRequest);
|
||||||
|
let userTokenJsonResponse;
|
||||||
|
let flowId;
|
||||||
|
|
||||||
|
await test.step('add another user', async () => {
|
||||||
|
adminCreateUserPage.seed(
|
||||||
|
Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)
|
||||||
|
);
|
||||||
|
const testUser = adminCreateUserPage.generateUser();
|
||||||
|
|
||||||
|
const addUserResponse = await addUser(
|
||||||
|
apiRequest,
|
||||||
|
tokenJsonResponse.data.token,
|
||||||
|
{
|
||||||
|
fullName: testUser.fullName,
|
||||||
|
email: testUser.email,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const acceptToken =
|
||||||
|
addUserResponse.data.acceptInvitationUrl.split('=')[1];
|
||||||
|
await acceptInvitation(apiRequest, {
|
||||||
|
token: acceptToken,
|
||||||
|
password: 'alamakota',
|
||||||
|
});
|
||||||
|
userTokenJsonResponse = await getToken(
|
||||||
|
apiRequest,
|
||||||
|
testUser.email,
|
||||||
|
'alamakota'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('add folder as an another user', async () => {
|
||||||
|
await addFolder(
|
||||||
|
apiRequest,
|
||||||
|
userTokenJsonResponse.data.token,
|
||||||
|
'anotherUserFolder'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('add folder as a main user', async () => {
|
||||||
|
await addFolder(
|
||||||
|
apiRequest,
|
||||||
|
tokenJsonResponse.data.token,
|
||||||
|
'mainUserFolder'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('add flow as an another user', async () => {
|
||||||
|
const flow = await createFlow(
|
||||||
|
apiRequest,
|
||||||
|
userTokenJsonResponse.data.token
|
||||||
|
);
|
||||||
|
flowId = flow.data.id;
|
||||||
|
await updateFlowName(
|
||||||
|
apiRequest,
|
||||||
|
userTokenJsonResponse.data.token,
|
||||||
|
flowId
|
||||||
|
);
|
||||||
|
await page.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('should not see folders of different user on the list', async () => {
|
||||||
|
await expect(
|
||||||
|
flowsPage.userFolders.getByText('anotherUserFolder')
|
||||||
|
).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('should not see folders of different user in the flow details', async () => {
|
||||||
|
await flowsPage.flowRow
|
||||||
|
.filter({
|
||||||
|
hasText: flowId,
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
await expect(page).toHaveURL(
|
||||||
|
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
|
||||||
|
);
|
||||||
|
await expect(flowEditorPage.folderName).toContainText('Uncategorized');
|
||||||
|
await flowEditorPage.goBackButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("another user's flow should be uncategorized and in all flows folder", async () => {
|
||||||
|
await flowsPage.allFlowsFolder.click();
|
||||||
|
await flowsPage.searchInput.fill(flowId);
|
||||||
|
await expect(
|
||||||
|
flowsPage.flowRow.filter({
|
||||||
|
hasText: flowId,
|
||||||
|
})
|
||||||
|
).toHaveCount(1);
|
||||||
|
await flowsPage.uncategorizedFlowsFolder.click();
|
||||||
|
await flowsPage.searchInput.fill(flowId);
|
||||||
|
await expect(
|
||||||
|
flowsPage.flowRow.filter({
|
||||||
|
hasText: flowId,
|
||||||
|
})
|
||||||
|
).toHaveCount(1);
|
||||||
|
await flowsPage.userFolders.getByText('mainUserFolder').click();
|
||||||
|
await flowsPage.searchInput.fill(flowId);
|
||||||
|
await expect(
|
||||||
|
flowsPage.flowRow.filter({
|
||||||
|
hasText: flowId,
|
||||||
|
})
|
||||||
|
).toHaveCount(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('non-owner should not reassign folder of different user', async ({
|
||||||
|
page,
|
||||||
|
flowsPage,
|
||||||
|
adminCreateUserPage,
|
||||||
|
}) => {
|
||||||
|
const folderMoveToDialog = new MoveFolderDialog(page);
|
||||||
|
const apiRequest = await request.newContext();
|
||||||
|
const tokenJsonResponse = await getToken(apiRequest);
|
||||||
|
let userTokenJsonResponse;
|
||||||
|
let flowId;
|
||||||
|
|
||||||
|
await test.step('add another user', async () => {
|
||||||
|
adminCreateUserPage.seed(
|
||||||
|
Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)
|
||||||
|
);
|
||||||
|
const testUser = adminCreateUserPage.generateUser();
|
||||||
|
|
||||||
|
const addUserResponse = await addUser(
|
||||||
|
apiRequest,
|
||||||
|
tokenJsonResponse.data.token,
|
||||||
|
{
|
||||||
|
fullName: testUser.fullName,
|
||||||
|
email: testUser.email,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const acceptToken =
|
||||||
|
addUserResponse.data.acceptInvitationUrl.split('=')[1];
|
||||||
|
await acceptInvitation(apiRequest, {
|
||||||
|
token: acceptToken,
|
||||||
|
password: 'alamakota',
|
||||||
|
});
|
||||||
|
userTokenJsonResponse = await getToken(
|
||||||
|
apiRequest,
|
||||||
|
testUser.email,
|
||||||
|
'alamakota'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('add folder as an another user', async () => {
|
||||||
|
await addFolder(
|
||||||
|
apiRequest,
|
||||||
|
userTokenJsonResponse.data.token,
|
||||||
|
'anotherUserFolder'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('add flow as an another user', async () => {
|
||||||
|
const flow = await createFlow(
|
||||||
|
apiRequest,
|
||||||
|
userTokenJsonResponse.data.token
|
||||||
|
);
|
||||||
|
flowId = flow.data.id;
|
||||||
|
await updateFlowName(
|
||||||
|
apiRequest,
|
||||||
|
userTokenJsonResponse.data.token,
|
||||||
|
flowId
|
||||||
|
);
|
||||||
|
await page.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("should not be able to move another user's flow", async () => {
|
||||||
|
await flowsPage.allFlowsFolder.click();
|
||||||
|
await flowsPage.searchInput.fill(flowId);
|
||||||
|
await expect(
|
||||||
|
flowsPage.flowRow.filter({
|
||||||
|
hasText: flowId,
|
||||||
|
})
|
||||||
|
).toHaveCount(1);
|
||||||
|
|
||||||
|
await flowsPage.flowRow
|
||||||
|
.filter({
|
||||||
|
hasText: flowId,
|
||||||
|
})
|
||||||
|
.getByRole('button')
|
||||||
|
.click();
|
||||||
|
await flowsPage.moveTo.click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByText(
|
||||||
|
'A flow can only be moved to a folder by the flow owner.'
|
||||||
|
)
|
||||||
|
).toHaveCount(1);
|
||||||
|
|
||||||
|
await expect(folderMoveToDialog.moveButton).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -44,6 +44,7 @@ export default function CreateFolderDialog(props) {
|
|||||||
<DialogTitle>{formatMessage('createFolderDialog.title')}</DialogTitle>
|
<DialogTitle>{formatMessage('createFolderDialog.title')}</DialogTitle>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
|
data-test="close-dialog"
|
||||||
aria-label="close"
|
aria-label="close"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
sx={{
|
sx={{
|
||||||
@@ -61,6 +62,7 @@ export default function CreateFolderDialog(props) {
|
|||||||
{formatMessage('createFolderDialog.description')}
|
{formatMessage('createFolderDialog.description')}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<TextField
|
<TextField
|
||||||
|
data-test="new-folder-name"
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
value={folderName}
|
value={folderName}
|
||||||
onKeyDown={handleTextFieldKeyDown}
|
onKeyDown={handleTextFieldKeyDown}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export default function EditFolderDialog(props) {
|
|||||||
<DialogTitle>{formatMessage('editFolderDialog.title')}</DialogTitle>
|
<DialogTitle>{formatMessage('editFolderDialog.title')}</DialogTitle>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
|
data-test="close-dialog"
|
||||||
aria-label="close"
|
aria-label="close"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
sx={{
|
sx={{
|
||||||
@@ -64,6 +65,7 @@ export default function EditFolderDialog(props) {
|
|||||||
{formatMessage('editFolderDialog.description')}
|
{formatMessage('editFolderDialog.description')}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<TextField
|
<TextField
|
||||||
|
data-test="new-folder-name"
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
value={folderName}
|
value={folderName}
|
||||||
onKeyDown={handleTextFieldKeyDown}
|
onKeyDown={handleTextFieldKeyDown}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export default function FlowFolder(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography
|
<Typography
|
||||||
|
data-test="folder-name"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={URLS.FOLDER_FLOWS(id)}
|
to={URLS.FOLDER_FLOWS(id)}
|
||||||
variant="body1"
|
variant="body1"
|
||||||
|
|||||||
@@ -150,7 +150,11 @@ function ContextMenu(props) {
|
|||||||
|
|
||||||
<Can I="manage" a="Flow" passThrough>
|
<Can I="manage" a="Flow" passThrough>
|
||||||
{(allowed) => (
|
{(allowed) => (
|
||||||
<MenuItem disabled={!allowed} onClick={onFlowFolderUpdate}>
|
<MenuItem
|
||||||
|
data-test="move-to"
|
||||||
|
disabled={!allowed}
|
||||||
|
onClick={onFlowFolderUpdate}
|
||||||
|
>
|
||||||
{formatMessage('flow.moveTo')}
|
{formatMessage('flow.moveTo')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
@@ -166,7 +170,11 @@ function ContextMenu(props) {
|
|||||||
|
|
||||||
<Can I="manage" a="Flow" passThrough>
|
<Can I="manage" a="Flow" passThrough>
|
||||||
{(allowed) => (
|
{(allowed) => (
|
||||||
<MenuItem disabled={!allowed} onClick={onFlowDelete}>
|
<MenuItem
|
||||||
|
data-test="delete-flow"
|
||||||
|
disabled={!allowed}
|
||||||
|
onClick={onFlowDelete}
|
||||||
|
>
|
||||||
{formatMessage('flow.delete')}
|
{formatMessage('flow.delete')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ function FlowFolderChangeDialog(props) {
|
|||||||
<DialogTitle>{formatMessage('flowFolderChangeDialog.title')}</DialogTitle>
|
<DialogTitle>{formatMessage('flowFolderChangeDialog.title')}</DialogTitle>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
|
data-test="close-dialog"
|
||||||
aria-label="close"
|
aria-label="close"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
sx={{
|
sx={{
|
||||||
@@ -81,6 +82,7 @@ function FlowFolderChangeDialog(props) {
|
|||||||
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
|
data-test="move-to-folder-name"
|
||||||
value={
|
value={
|
||||||
folders?.data.find((folder) => folder.id === selectedFolder) ||
|
folders?.data.find((folder) => folder.id === selectedFolder) ||
|
||||||
uncategorizedFolder
|
uncategorizedFolder
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ export default function Folders() {
|
|||||||
secondaryAction={
|
secondaryAction={
|
||||||
<Stack direction="row" gap={1}>
|
<Stack direction="row" gap={1}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
data-test="edit-folder"
|
||||||
edge="end"
|
edge="end"
|
||||||
aria-label="edit"
|
aria-label="edit"
|
||||||
onClick={() => setShowEditFolderDialog(true)}
|
onClick={() => setShowEditFolderDialog(true)}
|
||||||
@@ -115,6 +116,7 @@ export default function Folders() {
|
|||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
data-test="delete-folder"
|
||||||
edge="end"
|
edge="end"
|
||||||
aria-label="delete"
|
aria-label="delete"
|
||||||
onClick={() => setShowDeleteFolderDialog(true)}
|
onClick={() => setShowDeleteFolderDialog(true)}
|
||||||
@@ -152,6 +154,7 @@ export default function Folders() {
|
|||||||
<Box component={Card}>
|
<Box component={Card}>
|
||||||
<List component="nav" aria-label="static folders">
|
<List component="nav" aria-label="static folders">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
|
data-test="all-flows-folder"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={{ search: allFlowsFolder }}
|
to={{ search: allFlowsFolder }}
|
||||||
selected={allFlowsFolderSelected}
|
selected={allFlowsFolderSelected}
|
||||||
@@ -164,6 +167,7 @@ export default function Folders() {
|
|||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
|
data-test="uncategorized-flows-folder"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={{ search: unassignedFlowsFolder }}
|
to={{ search: unassignedFlowsFolder }}
|
||||||
selected={unassignedFlowsFolderSelected}
|
selected={unassignedFlowsFolderSelected}
|
||||||
@@ -178,10 +182,17 @@ export default function Folders() {
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<List component="nav" aria-label="user folders">
|
<List
|
||||||
|
component="nav"
|
||||||
|
aria-label="user folders"
|
||||||
|
data-test="user-folders"
|
||||||
|
>
|
||||||
{folders?.data?.map((folder) => generateFolderItem(folder))}
|
{folders?.data?.map((folder) => generateFolderItem(folder))}
|
||||||
|
|
||||||
<ListItemButton onClick={() => setShowCreateFolderDialog(true)}>
|
<ListItemButton
|
||||||
|
data-test="add-folder-button"
|
||||||
|
onClick={() => setShowCreateFolderDialog(true)}
|
||||||
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
@@ -210,6 +221,7 @@ export default function Folders() {
|
|||||||
onConfirm={handleDeleteFolderConfirmation}
|
onConfirm={handleDeleteFolderConfirmation}
|
||||||
cancelButtonChildren={formatMessage('deleteFolderDialog.cancel')}
|
cancelButtonChildren={formatMessage('deleteFolderDialog.cancel')}
|
||||||
confirmButtonChildren={formatMessage('deleteFolderDialog.confirm')}
|
confirmButtonChildren={formatMessage('deleteFolderDialog.confirm')}
|
||||||
|
data-test="delete-folder-modal"
|
||||||
errorMessage={generalErrorMessage}
|
errorMessage={generalErrorMessage}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user