diff --git a/packages/e2e-tests/.env-example b/packages/e2e-tests/.env-example
index 0cf8775e..446683be 100644
--- a/packages/e2e-tests/.env-example
+++ b/packages/e2e-tests/.env-example
@@ -3,4 +3,9 @@ POSTGRES_USER=automatisch_user
POSTGRES_PASSWORD=automatisch_password
POSTGRES_PORT=5432
POSTGRES_HOST=localhost
-BACKEND_APP_URL=http://localhost:3000
\ No newline at end of file
+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
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/admin/users-page.js b/packages/e2e-tests/fixtures/admin/users-page.js
index 9b707669..301ad47d 100644
--- a/packages/e2e-tests/fixtures/admin/users-page.js
+++ b/packages/e2e-tests/fixtures/admin/users-page.js
@@ -24,6 +24,16 @@ export class AdminUsersPage extends AuthenticatedPage {
}
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 Promise.all([
this.page.waitForResponse(
diff --git a/packages/e2e-tests/fixtures/flow-editor-page.js b/packages/e2e-tests/fixtures/flow-editor-page.js
index dd2f32ae..eed41923 100644
--- a/packages/e2e-tests/fixtures/flow-editor-page.js
+++ b/packages/e2e-tests/fixtures/flow-editor-page.js
@@ -39,6 +39,7 @@ export class FlowEditorPage extends AuthenticatedPage {
this.goBackButton = this.page.getByTestId('editor-go-back-button');
this.exportFlowButton = page.getByTestId('export-flow-button');
this.stepName = page.getByTestId('step-name');
+ this.folderName = page.getByTestId('folder-name');
}
async createWebhookTrigger(workSynchronously) {
diff --git a/packages/e2e-tests/fixtures/flows-page.js b/packages/e2e-tests/fixtures/flows-page.js
index 14c8cb97..87cba5c5 100644
--- a/packages/e2e-tests/fixtures/flows-page.js
+++ b/packages/e2e-tests/fixtures/flows-page.js
@@ -4,7 +4,24 @@ export class FlowsPage extends AuthenticatedPage {
constructor(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.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');
}
}
diff --git a/packages/e2e-tests/fixtures/folder/create-folder-dialog.js b/packages/e2e-tests/fixtures/folder/create-folder-dialog.js
new file mode 100644
index 00000000..f1530f6e
--- /dev/null
+++ b/packages/e2e-tests/fixtures/folder/create-folder-dialog.js
@@ -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');
+ }
+}
diff --git a/packages/e2e-tests/fixtures/folder/delete-folder-dialog.js b/packages/e2e-tests/fixtures/folder/delete-folder-dialog.js
new file mode 100644
index 00000000..194ff4d5
--- /dev/null
+++ b/packages/e2e-tests/fixtures/folder/delete-folder-dialog.js
@@ -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'
+ );
+ }
+}
diff --git a/packages/e2e-tests/fixtures/folder/move-flow-dialog.js b/packages/e2e-tests/fixtures/folder/move-flow-dialog.js
new file mode 100644
index 00000000..4b0e5c71
--- /dev/null
+++ b/packages/e2e-tests/fixtures/folder/move-flow-dialog.js
@@ -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');
+ }
+}
diff --git a/packages/e2e-tests/fixtures/folder/update-folder-dialog.js b/packages/e2e-tests/fixtures/folder/update-folder-dialog.js
new file mode 100644
index 00000000..f085aa05
--- /dev/null
+++ b/packages/e2e-tests/fixtures/folder/update-folder-dialog.js
@@ -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');
+ }
+}
diff --git a/packages/e2e-tests/helpers/auth-api-helper.js b/packages/e2e-tests/helpers/auth-api-helper.js
index a5401e85..5f078ee0 100644
--- a/packages/e2e-tests/helpers/auth-api-helper.js
+++ b/packages/e2e-tests/helpers/auth-api-helper.js
@@ -1,12 +1,16 @@
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(
`${process.env.BACKEND_APP_URL}/internal/api/v1/access-tokens`,
{
data: {
- email: process.env.LOGIN_EMAIL,
- password: process.env.LOGIN_PASSWORD,
+ email: email,
+ password: password,
},
}
);
diff --git a/packages/e2e-tests/helpers/folder-api-helper.js b/packages/e2e-tests/helpers/folder-api-helper.js
new file mode 100644
index 00000000..c68d9ca9
--- /dev/null
+++ b/packages/e2e-tests/helpers/folder-api-helper.js
@@ -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();
+};
diff --git a/packages/e2e-tests/tests/admin/manage-roles.spec.js b/packages/e2e-tests/tests/admin/manage-roles.spec.js
index 2419ee84..194fb110 100644
--- a/packages/e2e-tests/tests/admin/manage-roles.spec.js
+++ b/packages/e2e-tests/tests/admin/manage-roles.spec.js
@@ -195,7 +195,7 @@ test.describe('Role management page', () => {
});
await test.step('Change the role the user has', async () => {
- await adminUsersPage.navigateTo();
+ await adminUsersPage.navigateToAndWaitForUsers();
await adminUsersPage.usersLoader.waitFor({
state: 'detached',
});
@@ -264,7 +264,7 @@ test.describe('Role management page', () => {
});
await test.step('Delete this user', async () => {
- await adminUsersPage.navigateTo();
+ await adminUsersPage.navigateToAndWaitForUsers();
const row = await adminUsersPage.findUserPageWithEmail(
'user-delete-role-test@automatisch.io'
);
diff --git a/packages/e2e-tests/tests/admin/manage-users.spec.js b/packages/e2e-tests/tests/admin/manage-users.spec.js
index 2887412c..274852d4 100644
--- a/packages/e2e-tests/tests/admin/manage-users.spec.js
+++ b/packages/e2e-tests/tests/admin/manage-users.spec.js
@@ -100,7 +100,7 @@ test.describe('User management page', () => {
});
await test.step('Delete the created user', async () => {
- await adminUsersPage.navigateTo();
+ await adminUsersPage.navigateToAndWaitForUsers();
await adminUsersPage.findUserPageWithEmail(testUser.email);
const userRow = await adminUsersPage.getUserRowByEmail(testUser.email);
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 adminUsersPage.navigateTo();
+ await adminUsersPage.navigateToAndWaitForUsers();
await adminUsersPage.findUserPageWithEmail(user2.email);
let userRow = await adminUsersPage.getUserRowByEmail(user2.email);
await adminUsersPage.clickEditUser(userRow);
diff --git a/packages/e2e-tests/tests/flow-folders/flow-folder.spec.js b/packages/e2e-tests/tests/flow-folders/flow-folder.spec.js
new file mode 100644
index 00000000..4d2de36f
--- /dev/null
+++ b/packages/e2e-tests/tests/flow-folders/flow-folder.spec.js
@@ -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();
+ });
+ });
+});
diff --git a/packages/web/src/components/CreateFolderDialog/index.jsx b/packages/web/src/components/CreateFolderDialog/index.jsx
index 73b4f688..ee32dee1 100644
--- a/packages/web/src/components/CreateFolderDialog/index.jsx
+++ b/packages/web/src/components/CreateFolderDialog/index.jsx
@@ -44,6 +44,7 @@ export default function CreateFolderDialog(props) {
{formatMessage('createFolderDialog.title')}
{formatMessage('editFolderDialog.title')}
{(allowed) => (
-