diff --git a/packages/e2e-tests/fixtures/admin/create-role-page.js b/packages/e2e-tests/fixtures/admin/create-role-page.js
new file mode 100644
index 00000000..bd6ff23c
--- /dev/null
+++ b/packages/e2e-tests/fixtures/admin/create-role-page.js
@@ -0,0 +1,106 @@
+const { AuthenticatedPage } = require('../authenticated-page');
+const { RoleConditionsModal } = require('./role-conditions-modal');
+
+export class AdminCreateRolePage extends AuthenticatedPage {
+ screenshotPath = '/admin/create-role'
+
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ constructor (page) {
+ super(page);
+ this.nameInput = page.getByTestId('name-input');
+ this.descriptionInput = page.getByTestId('description-input');
+ this.createButton = page.getByTestId('create-button');
+ this.connectionRow = page.getByTestId('Connection-permission-row');
+ this.executionRow = page.getByTestId('Execution-permission-row');
+ this.flowRow = page.getByTestId('Flow-permission-row');
+ }
+
+ /**
+ * @param {('Connection'|'Execution'|'Flow')} subject
+ */
+ getRoleConditionsModal (subject) {
+ return new RoleConditionsModal(this.page, subject);
+ }
+
+ async getPermissionConfigs () {
+ const subjects = ['Connection', 'Flow', 'Execution'];
+ const permissionConfigs = [];
+ for (let subject of subjects) {
+ const row = this.getSubjectRow(subject);
+ const actionInputs = await this.getRowInputs(row);
+ Object.keys(actionInputs).forEach(action => {
+ permissionConfigs.push({
+ action,
+ locator: actionInputs[action],
+ subject,
+ row
+ });
+ });
+ }
+ return permissionConfigs;
+ }
+
+ /**
+ *
+ * @param {(
+ * 'Connection' | 'Flow' | 'Execution'
+ * )} subject
+ */
+ getSubjectRow (subject) {
+ const k = `${subject.toLowerCase()}Row`
+ if (this[k]) {
+ return this[k]
+ } else {
+ throw 'Unknown row'
+ }
+ }
+
+ /**
+ * @param {import('@playwright/test').Locator} row
+ */
+ async getRowInputs (row) {
+ const inputs = {
+ // settingsButton: row.getByTestId('permission-settings-button')
+ }
+ for (let input of ['create', 'read', 'update', 'delete', 'publish']) {
+ const testId = `${input}-checkbox`
+ if (await row.getByTestId(testId).count() > 0) {
+ inputs[input] = row.getByTestId(testId).locator('input');
+ }
+ }
+ return inputs
+ }
+
+ /**
+ * @param {import('@playwright/test').Locator} row
+ */
+ async clickPermissionSettings (row) {
+ await row.getByTestId('permission-settings-button').click();
+ }
+
+ /**
+ *
+ * @param {string} subject
+ * @param {'create'|'read'|'update'|'delete'|'publish'} action
+ * @param {boolean} val
+ */
+ async updateAction (subject, action, val) {
+ const row = await this.getSubjectRow(subject);
+ const inputs = await this.getRowInputs(row);
+ if (inputs[action]) {
+ if (await inputs[action].isChecked()) {
+ if (!val) {
+ await inputs[action].click();
+ }
+ } else {
+ if (val) {
+ await inputs[action].click();
+ }
+ }
+ } else {
+ throw new Error(`${subject} does not have action ${action}`)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/admin/delete-role-modal.js b/packages/e2e-tests/fixtures/admin/delete-role-modal.js
new file mode 100644
index 00000000..e456f6bd
--- /dev/null
+++ b/packages/e2e-tests/fixtures/admin/delete-role-modal.js
@@ -0,0 +1,19 @@
+export class DeleteRoleModal {
+ screenshotPath = '/admin/delete-role-modal';
+
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ constructor (page) {
+ this.page = page;
+ this.modal = page.getByTestId('delete-role-modal');
+ this.cancelButton = this.modal.getByTestId('confirmation-cancel-button');
+ this.deleteButton = this.modal.getByTestId('confirmation-confirm-button');
+ }
+
+ async close () {
+ await this.page.click('body', {
+ position: { x: 10, y: 10 }
+ });
+ }
+}
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/admin/edit-role-page.js b/packages/e2e-tests/fixtures/admin/edit-role-page.js
new file mode 100644
index 00000000..679597ec
--- /dev/null
+++ b/packages/e2e-tests/fixtures/admin/edit-role-page.js
@@ -0,0 +1,9 @@
+const { AdminCreateRolePage } = require('./create-role-page')
+
+export class AdminEditRolePage extends AdminCreateRolePage {
+ constructor (page) {
+ super(page);
+ delete this.createButton;
+ this.updateButton = page.getByTestId('update-button');
+ }
+}
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/admin/edit-user-page.js b/packages/e2e-tests/fixtures/admin/edit-user-page.js
index 69755a61..0297796a 100644
--- a/packages/e2e-tests/fixtures/admin/edit-user-page.js
+++ b/packages/e2e-tests/fixtures/admin/edit-user-page.js
@@ -13,6 +13,7 @@ export class AdminEditUserPage extends AuthenticatedPage {
super(page);
this.fullNameInput = page.getByTestId('full-name-input');
this.emailInput = page.getByTestId('email-input');
+ this.roleInput = page.getByTestId('role.id-autocomplete');
this.updateButton = page.getByTestId('update-button');
}
diff --git a/packages/e2e-tests/fixtures/admin/index.js b/packages/e2e-tests/fixtures/admin/index.js
index 191546f5..8c25fd7c 100644
--- a/packages/e2e-tests/fixtures/admin/index.js
+++ b/packages/e2e-tests/fixtures/admin/index.js
@@ -2,6 +2,10 @@ const { AdminCreateUserPage } = require('./create-user-page');
const { AdminEditUserPage } = require('./edit-user-page');
const { AdminUsersPage } = require('./users-page');
+const { AdminRolesPage } = require('./roles-page');
+const { AdminCreateRolePage } = require('./create-role-page');
+const { AdminEditRolePage } = require('./edit-role-page');
+
export const adminFixtures = {
adminUsersPage: async ({ page }, use) => {
await use(new AdminUsersPage(page));
@@ -11,5 +15,15 @@ export const adminFixtures = {
},
adminEditUserPage: async ({page}, use) => {
await use(new AdminEditUserPage(page));
- }
-}
\ No newline at end of file
+ },
+ adminRolesPage: async ({ page}, use) => {
+ await use(new AdminRolesPage(page));
+ },
+ adminEditRolePage: async ({ page}, use) => {
+ await use(new AdminEditRolePage(page));
+ },
+ adminCreateRolePage: async ({ page}, use) => {
+ await use(new AdminCreateRolePage(page));
+ },
+}
+
diff --git a/packages/e2e-tests/fixtures/admin/role-conditions-modal.js b/packages/e2e-tests/fixtures/admin/role-conditions-modal.js
new file mode 100644
index 00000000..4e2c64fe
--- /dev/null
+++ b/packages/e2e-tests/fixtures/admin/role-conditions-modal.js
@@ -0,0 +1,47 @@
+export class RoleConditionsModal {
+
+ /**
+ * @param {import('@playwright/test').Page} page
+ * @param {('Connection'|'Execution'|'Flow')} subject
+ */
+ constructor (page, subject) {
+ this.page = page;
+ this.modal = page.getByTestId(`${subject}-role-conditions-modal`);
+ this.modalBody = this.modal.getByTestId('role-conditions-modal-body');
+ this.createCheckbox = this.modal.getByTestId(
+ 'isCreator-create-checkbox'
+ ).locator('input');
+ this.readCheckbox = this.modal.getByTestId(
+ 'isCreator-read-checkbox'
+ ).locator('input');
+ this.updateCheckbox = this.modal.getByTestId(
+ 'isCreator-update-checkbox'
+ ).locator('input');
+ this.deleteCheckbox = this.modal.getByTestId(
+ 'isCreator-delete-checkbox'
+ ).locator('input');
+ this.publishCheckbox = this.modal.getByTestId(
+ 'isCreator-publish-checkbox'
+ ).locator('input');
+ this.applyButton = this.modal.getByTestId('confirmation-confirm-button');
+ this.cancelButton = this.modal.getByTestId('confirmation-cancel-button');
+ }
+
+ async getAvailableConditions () {
+ let conditions = {};
+ const actions = ['create', 'read', 'update', 'delete', 'publish'];
+ for (let action of actions) {
+ const locator = this[`${action}Checkbox`];
+ if (locator && await locator.count() > 0) {
+ conditions[action] = locator;
+ }
+ }
+ return conditions;
+ }
+
+ async close () {
+ await this.page.click('body', {
+ position: { x: 10, y: 10 }
+ });
+ }
+}
\ No newline at end of file
diff --git a/packages/e2e-tests/fixtures/admin/roles-page.js b/packages/e2e-tests/fixtures/admin/roles-page.js
new file mode 100644
index 00000000..c051b8ee
--- /dev/null
+++ b/packages/e2e-tests/fixtures/admin/roles-page.js
@@ -0,0 +1,79 @@
+const { AuthenticatedPage } = require('../authenticated-page');
+const { DeleteRoleModal } = require('./delete-role-modal')
+
+export class AdminRolesPage extends AuthenticatedPage {
+ screenshotPath = '/admin-roles';
+
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ constructor (page) {
+ super(page);
+ this.roleDrawerLink = page.getByTestId('roles-drawer-link');
+ this.createRoleButton = page.getByTestId('create-role');
+ this.deleteRoleModal = new DeleteRoleModal(page);
+ this.roleRow = page.getByTestId('role-row');
+ this.rolesLoader = page.getByTestId('roles-list-loader');
+ }
+
+ /**
+ *
+ * @param {boolean} isMobile - navigation on smaller devices requires the
+ * user to open up the drawer menu
+ */
+ async navigateTo (isMobile=false) {
+ await this.profileMenuButton.click();
+ await this.adminMenuItem.click();
+ if (isMobile) {
+ await this.drawerMenuButton.click();
+ }
+ await this.roleDrawerLink.click();
+ }
+
+ /**
+ * @param {string} name
+ */
+ async getRoleRowByName (name) {
+ return this.roleRow.filter({
+ has: this.page.getByTestId('role-name').filter({
+ hasText: name
+ })
+ });
+ }
+
+ /**
+ * @param {import('@playwright/test').Locator} row
+ */
+ async getRowData (row) {
+ return {
+ role: await row.getByTestId('role-name').textContent(),
+ description: await row.getByTestId('role-description').textContent(),
+ canEdit: await row.getByTestId(
+ 'role-edit'
+ ).isEnabled(),
+ canDelete: await row.getByTestId(
+ 'role-delete'
+ ).isEnabled()
+ }
+ }
+
+ /**
+ * @param {import('@playwright/test').Locator} row
+ */
+ async clickEditRole (row) {
+ await row.getByTestId('role-edit').click();
+ }
+
+ /**
+ * @param {import('@playwright/test').Locator} row
+ */
+ async clickDeleteRole (row) {
+ await row.getByTestId('role-delete').click();
+ return this.deleteRoleModal;
+ }
+
+ async editRole (subject) {
+ const row = await this.getRoleRowByName(subject);
+ await this.clickEditRole(row);
+ }
+}
\ 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 88641df2..4696fada 100644
--- a/packages/e2e-tests/fixtures/admin/users-page.js
+++ b/packages/e2e-tests/fixtures/admin/users-page.js
@@ -25,6 +25,11 @@ export class AdminUsersPage extends AuthenticatedPage {
async navigateTo () {
await this.profileMenuButton.click();
await this.adminMenuItem.click();
+ if (await this.usersLoader.isVisible()) {
+ await this.usersLoader.waitFor({
+ state: 'detached'
+ });
+ }
}
/**
@@ -66,8 +71,14 @@ export class AdminUsersPage extends AuthenticatedPage {
/**
* @param {string} email
+ * @returns {import('@playwright/test').Locator | null}
*/
async findUserPageWithEmail (email) {
+ if (await this.usersLoader.isVisible()) {
+ await this.usersLoader.waitFor({
+ state: 'detached'
+ });
+ }
// start at the first page
const firstPageDisabled = await this.firstPageButton.isDisabled();
if (!firstPageDisabled) {
@@ -75,6 +86,11 @@ export class AdminUsersPage extends AuthenticatedPage {
}
while (true) {
+ if (await this.usersLoader.isVisible()) {
+ await this.usersLoader.waitFor({
+ state: 'detached'
+ });
+ }
const rowLocator = await this.getUserRowByEmail(email);
if ((await rowLocator.count()) === 1) {
return rowLocator;
diff --git a/packages/e2e-tests/fixtures/authenticated-page.js b/packages/e2e-tests/fixtures/authenticated-page.js
index 58fc4d44..3bfc1584 100644
--- a/packages/e2e-tests/fixtures/authenticated-page.js
+++ b/packages/e2e-tests/fixtures/authenticated-page.js
@@ -14,6 +14,7 @@ export class AuthenticatedPage extends BasePage {
this.adminMenuItem = this.page.getByRole('menuitem', { name: 'Admin' });
this.userInterfaceDrawerItem = this.page.getByTestId('user-interface-drawer-link');
this.appBar = this.page.getByTestId('app-bar');
+ this.drawerMenuButton = this.page.getByTestId('drawer-menu-button');
this.goToDashboardButton = this.page.getByTestId('go-back-drawer-link');
this.typographyLogo = this.page.getByTestId('typography-logo');
this.customLogo = this.page.getByTestId('custom-logo');
diff --git a/packages/e2e-tests/fixtures/login-page.js b/packages/e2e-tests/fixtures/login-page.js
index cfef7c0d..ad588dd3 100644
--- a/packages/e2e-tests/fixtures/login-page.js
+++ b/packages/e2e-tests/fixtures/login-page.js
@@ -1,9 +1,19 @@
-const path = require('node:path');
-const { expect } = require('@playwright/test');
const { BasePage } = require('./base-page');
export class LoginPage extends BasePage {
path = '/login';
+ static defaultEmail = process.env.LOGIN_EMAIL;
+ static defaultPassword = process.env.LOGIN_PASSWORD;
+
+ static setDefaultLogin (email, password) {
+ this.defaultEmail = email;
+ this.defaultPassword = password;
+ }
+
+ static resetDefaultLogin () {
+ this.defaultEmail = process.env.LOGIN_EMAIL;
+ this.defaultPassword = process.env.LOGIN_PASSWORD;
+ }
/**
* @param {import('@playwright/test').Page} page
@@ -22,8 +32,8 @@ export class LoginPage extends BasePage {
}
async login(
- email = process.env.LOGIN_EMAIL,
- password = process.env.LOGIN_PASSWORD
+ email = LoginPage.defaultEmail,
+ password = LoginPage.defaultPassword
) {
await this.page.goto(this.path);
await this.emailTextField.fill(email);
diff --git a/packages/e2e-tests/tests/admin/manage-roles.spec.js b/packages/e2e-tests/tests/admin/manage-roles.spec.js
new file mode 100644
index 00000000..36f7aa08
--- /dev/null
+++ b/packages/e2e-tests/tests/admin/manage-roles.spec.js
@@ -0,0 +1,483 @@
+const { test, expect } = require('../../fixtures/index');
+const { LoginPage } = require('../../fixtures/login-page');
+
+test.describe('Role management page', () => {
+
+ test('Admin role is not deletable', async ({ adminRolesPage }) => {
+ await adminRolesPage.navigateTo();
+ const adminRow = await adminRolesPage.getRoleRowByName('Admin');
+ const rowCount = await adminRow.count();
+ await expect(rowCount).toBe(1);
+ const data = await adminRolesPage.getRowData(adminRow);
+ await expect(data.role).toBe('Admin');
+ await expect(data.canEdit).toBe(true);
+ await expect(data.canDelete).toBe(false);
+ });
+
+ test(
+ 'Can create, edit, and delete a role',
+ async ({
+ adminCreateRolePage, adminEditRolePage, adminRolesPage, page
+ }) => {
+ await test.step('Create a new role', async () => {
+ await adminRolesPage.navigateTo();
+ await adminRolesPage.createRoleButton.click();
+ await adminCreateRolePage.nameInput.fill('Create Edit Test');
+ await adminCreateRolePage.descriptionInput.fill('Test description');
+ await adminCreateRolePage.createButton.click();
+ await adminCreateRolePage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminCreateRolePage.getSnackbarData(
+ 'snackbar-create-role-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminCreateRolePage.closeSnackbar();
+ });
+
+ let roleRow = await test.step(
+ 'Make sure role data is correct',
+ async () => {
+ const roleRow = await adminRolesPage.getRoleRowByName(
+ 'Create Edit Test'
+ );
+ const rowCount = await roleRow.count();
+ await expect(rowCount).toBe(1);
+ const roleData = await adminRolesPage.getRowData(roleRow);
+ await expect(roleData.role).toBe('Create Edit Test');
+ await expect(roleData.description).toBe('Test description');
+ await expect(roleData.canEdit).toBe(true);
+ await expect(roleData.canDelete).toBe(true);
+ return roleRow
+ }
+ );
+
+ await test.step('Edit the role', async () => {
+ await adminRolesPage.clickEditRole(roleRow);
+ await adminEditRolePage.nameInput.fill('Create Update Test');
+ await adminEditRolePage.descriptionInput.fill(
+ 'Update test description'
+ );
+ await adminEditRolePage.updateButton.click();
+ await adminEditRolePage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminEditRolePage.getSnackbarData(
+ 'snackbar-edit-role-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminEditRolePage.closeSnackbar();
+ });
+
+ roleRow = await test.step(
+ 'Make sure changes reflected on roles page',
+ async () => {
+ const roleRow = await adminRolesPage.getRoleRowByName(
+ 'Create Update Test'
+ );
+ const rowCount = await roleRow.count();
+ await expect(rowCount).toBe(1);
+ const roleData = await adminRolesPage.getRowData(roleRow);
+ await expect(roleData.role).toBe('Create Update Test');
+ await expect(roleData.description).toBe('Update test description');
+ await expect(roleData.canEdit).toBe(true);
+ await expect(roleData.canDelete).toBe(true);
+ return roleRow;
+ }
+ );
+
+ await test.step('Delete the role', async () => {
+ await adminRolesPage.clickDeleteRole(roleRow);
+ const deleteModal = adminRolesPage.deleteRoleModal;
+ await deleteModal.modal.waitFor({
+ state: 'attached'
+ });
+ await deleteModal.deleteButton.click();
+ await adminRolesPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminRolesPage.getSnackbarData(
+ 'snackbar-delete-role-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminRolesPage.closeSnackbar();
+ await deleteModal.modal.waitFor({
+ state: 'detached'
+ });
+ const rowCount = await roleRow.count();
+ await expect(rowCount).toBe(0);
+ });
+ }
+ );
+
+ // This test breaks right now
+ test.skip(
+ 'Make sure create/edit role page is scrollable',
+ async ({ adminRolesPage, page }) => {
+ const initViewportSize = page.viewportSize;
+ await page.setViewportSize({
+ width: 800,
+ height: 400
+ });
+
+ await test.step('Ensure create role page is scrollable', async () => {
+ await adminRolesPage.navigateTo(true);
+ await adminRolesPage.createRoleButton.click();
+
+ const initScrollTop = await page.evaluate(() => {
+ return document.documentElement.scrollTop;
+ });
+ await page.mouse.move(400, 100);
+ await page.mouse.click(400, 100);
+ await page.mouse.wheel(200, 0);
+ const updatedScrollTop = await page.evaluate(() => {
+ return document.documentElement.scrollTop;
+ });
+ await expect(initScrollTop).not.toBe(updatedScrollTop);
+ });
+
+ await test.step('Ensure edit role page is scrollable', async () => {
+ await adminRolesPage.navigateTo(true);
+ const adminRow = await adminRolesPage.getRoleRowByName('Admin');
+ await adminRolesPage.clickEditRole(adminRow);
+
+ const initScrollTop = await page.evaluate(() => {
+ return document.documentElement.scrollTop;
+ });
+ await page.mouse.move(400, 100);
+ await page.mouse.wheel(200, 0);
+ const updatedScrollTop = await page.evaluate(() => {
+ return document.documentElement.scrollTop;
+ });
+ await expect(initScrollTop).not.toBe(updatedScrollTop);
+ });
+
+ await test.step('Reset viewport', async () => {
+ await page.setViewportSize(initViewportSize);
+ });
+ }
+ );
+
+ test(
+ 'Cannot delete a role with a user attached to it',
+ async ({
+ adminCreateRolePage, adminRolesPage,
+ adminUsersPage, adminCreateUserPage, adminEditUserPage,
+ page
+ }) => {
+ await adminRolesPage.navigateTo();
+ await test.step('Create a new role', async () => {
+ await adminRolesPage.createRoleButton.click();
+ await adminCreateRolePage.nameInput.fill('Delete Role');
+ await adminCreateRolePage.createButton.click();
+ await adminCreateRolePage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminCreateRolePage.getSnackbarData(
+ 'snackbar-create-role-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminCreateRolePage.closeSnackbar();
+ });
+ await test.step(
+ 'Create a new user with the "Delete Role" role',
+ async () => {
+ await adminUsersPage.navigateTo();
+ await adminUsersPage.createUserButton.click();
+ await adminCreateUserPage.fullNameInput.fill('User Role Test');
+ await adminCreateUserPage.emailInput.fill('user-role-test@automatisch.io');
+ await adminCreateUserPage.passwordInput.fill('sample');
+ await adminCreateUserPage.roleInput.click();
+ await adminCreateUserPage.page.getByRole(
+ 'option', { name: 'Delete Role' }
+ ).click();
+ await adminCreateUserPage.createButton.click();
+ await adminUsersPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminUsersPage.getSnackbarData(
+ 'snackbar-create-user-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminUsersPage.closeSnackbar();
+ }
+ );
+ await test.step(
+ 'Try to delete "Delete Role" role when new user has it',
+ async () => {
+ await adminRolesPage.navigateTo();
+ const row = await adminRolesPage.getRoleRowByName('Delete Role');
+ const modal = await adminRolesPage.clickDeleteRole(row);
+ await modal.deleteButton.click();
+ await adminRolesPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminRolesPage.getSnackbarData(
+ 'snackbar-error'
+ );
+ await expect(snackbar.variant).toBe('error');
+ await adminRolesPage.closeSnackbar();
+ await modal.close();
+ }
+ );
+ await test.step(
+ 'Change the role the user has',
+ async () => {
+ await adminUsersPage.navigateTo();
+ await adminUsersPage.usersLoader.waitFor({
+ state: 'detached'
+ });
+ const row = await adminUsersPage.findUserPageWithEmail(
+ 'user-role-test@automatisch.io'
+ );
+ await adminUsersPage.clickEditUser(row);
+ await adminEditUserPage.roleInput.click();
+ await adminEditUserPage.page.getByRole(
+ 'option', { name: 'Admin' }
+ ).click();
+ await adminEditUserPage.updateButton.click();
+ await adminEditUserPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminEditUserPage.getSnackbarData(
+ 'snackbar-edit-user-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminEditUserPage.closeSnackbar();
+ }
+ );
+ await test.step(
+ 'Delete the original role',
+ async () => {
+ await adminRolesPage.navigateTo();
+ const row = await adminRolesPage.getRoleRowByName('Delete Role');
+ const modal = await adminRolesPage.clickDeleteRole(row);
+ await expect(modal.modal).toBeVisible();
+ await modal.deleteButton.click();
+ await adminRolesPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminRolesPage.getSnackbarData(
+ 'snackbar-delete-role-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminRolesPage.closeSnackbar();
+ }
+ );
+ }
+ );
+
+ test(
+ 'Deleting a role after deleting a user with that role',
+ async ({
+ adminCreateRolePage, adminRolesPage,
+ adminUsersPage, adminCreateUserPage,
+ page
+ }) => {
+ await adminRolesPage.navigateTo();
+ await test.step('Create a new role', async () => {
+ await adminRolesPage.createRoleButton.click();
+ await adminCreateRolePage.nameInput.fill('Cannot Delete Role');
+ await adminCreateRolePage.createButton.click();
+ await adminCreateRolePage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminCreateRolePage.getSnackbarData(
+ 'snackbar-create-role-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminCreateRolePage.closeSnackbar();
+ });
+ await test.step('Create a new user with this role', async () => {
+ await adminUsersPage.navigateTo();
+ await adminUsersPage.createUserButton.click();
+ await adminCreateUserPage.fullNameInput.fill('User Delete Role Test');
+ await adminCreateUserPage.emailInput.fill(
+ 'user-delete-role-test@automatisch.io'
+ );
+ await adminCreateUserPage.passwordInput.fill('sample');
+ await adminCreateUserPage.roleInput.click();
+ await adminCreateUserPage.page.getByRole(
+ 'option', { name: 'Cannot Delete Role' }
+ ).click();
+ await adminCreateUserPage.createButton.click();
+ await adminCreateUserPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminCreateUserPage.getSnackbarData(
+ 'snackbar-create-user-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminCreateUserPage.closeSnackbar();
+ });
+ await test.step('Delete this user', async () => {
+ await adminUsersPage.navigateTo();
+ const row = await adminUsersPage.findUserPageWithEmail(
+ 'user-delete-role-test@automatisch.io'
+ );
+ const modal = await adminUsersPage.clickDeleteUser(row);
+ await modal.deleteButton.click();
+ await adminUsersPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminUsersPage.getSnackbarData(
+ 'snackbar-delete-user-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminUsersPage.closeSnackbar();
+ });
+ await test.step('Try deleting this role', async () => {
+ await adminRolesPage.navigateTo();
+ const row = await adminRolesPage.getRoleRowByName(
+ 'Cannot Delete Role'
+ );
+ const modal = await adminRolesPage.clickDeleteRole(row);
+ await modal.deleteButton.click();
+ await adminRolesPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ /*
+ * TODO: await snackbar - make assertions based on product
+ * decisions
+ const snackbar = await adminRolesPage.getSnackbarData();
+ await expect(snackbar.variant).toBe('...');
+ */
+ await adminRolesPage.closeSnackbar();
+ });
+ }
+ );
+});
+
+test(
+ 'Accessibility of role management page',
+ async ({
+ page,
+ adminUsersPage, adminCreateUserPage, adminEditUserPage,
+ adminRolesPage, adminCreateRolePage,
+ }) => {
+ test.slow();
+ await test.step('Create the basic test role', async () => {
+ await adminRolesPage.navigateTo();
+ await adminRolesPage.createRoleButton.click();
+ await adminCreateRolePage.nameInput.fill('Basic Test');
+ await adminCreateRolePage.createButton.click();
+ await adminCreateRolePage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminCreateRolePage.getSnackbarData(
+ 'snackbar-create-role-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminCreateRolePage.closeSnackbar();
+ });
+
+ await test.step('Create a new user with the basic role', async () => {
+ await adminUsersPage.navigateTo();
+ await adminUsersPage.createUserButton.click();
+ await adminCreateUserPage.fullNameInput.fill('Role Test');
+ await adminCreateUserPage.emailInput.fill('basic-role-test@automatisch.io');
+ await adminCreateUserPage.passwordInput.fill('sample');
+ await adminCreateUserPage.roleInput.click();
+ await adminCreateUserPage.page.getByRole(
+ 'option', { name: 'Basic Test' }
+ ).click();
+ await adminCreateUserPage.createButton.click();
+ await adminCreateUserPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminCreateUserPage.getSnackbarData(
+ 'snackbar-create-user-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminCreateRolePage.closeSnackbar();
+ });
+
+ await test.step('Logout and login to the basic role user', async () => {
+ await page.getByTestId('profile-menu-button').click();
+ await page.getByTestId('logout-item').click();
+ // await page.reload({ waitUntil: 'networkidle' });
+ const loginPage = new LoginPage(page);
+ await loginPage.login('basic-role-test@automatisch.io', 'sample');
+ await expect(loginPage.loginButton).not.toBeVisible();
+ await expect(page).toHaveURL('/flows');
+ });
+
+ await test.step(
+ 'Navigate to the admin settings page and make sure it is blank',
+ async () => {
+ const pageUrl = new URL(page.url());
+ const url = `${pageUrl.origin}/admin-settings/users`;
+ await page.goto(url);
+ await page.waitForTimeout(750);
+ const isUnmounted = await page.evaluate(() => {
+ const root = document.querySelector('#root');
+ if (root) {
+ return root.children.length === 0;
+ }
+ return false;
+ });
+ await expect(isUnmounted).toBe(true);
+ }
+ );
+
+ await test.step(
+ 'Log back into the admin account',
+ async () => {
+ await page.goto('/');
+ await page.getByTestId('profile-menu-button').click();
+ await page.getByTestId('logout-item').click();
+ const loginPage = new LoginPage(page);
+ await loginPage.login();
+ }
+ );
+
+ await test.step(
+ 'Move the user off the role',
+ async () => {
+ await adminUsersPage.navigateTo();
+ const row = await adminUsersPage.findUserPageWithEmail(
+ 'basic-role-test@automatisch.io'
+ );
+ await adminUsersPage.clickEditUser(row);
+ await adminEditUserPage.roleInput.click();
+ await adminEditUserPage.page.getByRole(
+ 'option', { name: 'Admin' }
+ ).click();
+ await adminEditUserPage.updateButton.click();
+ await adminEditUserPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ await adminEditUserPage.closeSnackbar();
+ }
+ );
+
+ await test.step(
+ 'Delete the role',
+ async () => {
+ await adminRolesPage.navigateTo();
+ const roleRow = await adminRolesPage.getRoleRowByName(
+ 'Basic Test'
+ );
+ await adminRolesPage.clickDeleteRole(roleRow);
+ const deleteModal = adminRolesPage.deleteRoleModal;
+ await deleteModal.modal.waitFor({
+ state: 'attached'
+ });
+ await deleteModal.deleteButton.click();
+ await adminRolesPage.snackbar.waitFor({
+ state: 'attached'
+ });
+ const snackbar = await adminRolesPage.getSnackbarData(
+ 'snackbar-delete-role-success'
+ );
+ await expect(snackbar.variant).toBe('success');
+ await adminRolesPage.closeSnackbar();
+ await deleteModal.modal.waitFor({
+ state: 'detached'
+ });
+ const rowCount = await roleRow.count();
+ await expect(rowCount).toBe(0);
+ }
+ );
+ }
+);
\ No newline at end of file
diff --git a/packages/e2e-tests/tests/admin/role-conditions.spec.js b/packages/e2e-tests/tests/admin/role-conditions.spec.js
new file mode 100644
index 00000000..6f69ad58
--- /dev/null
+++ b/packages/e2e-tests/tests/admin/role-conditions.spec.js
@@ -0,0 +1,69 @@
+const { test, expect } = require('../../fixtures/index');
+
+test(
+ 'Role permissions conform with role conditions ',
+ async({ adminRolesPage, adminCreateRolePage }) => {
+ await adminRolesPage.navigateTo();
+ await adminRolesPage.createRoleButton.click();
+
+ /*
+ example config: {
+ action: 'read',
+ subject: 'connection',
+ row: page.getByTestId('connection-permission-row'),
+ locator: row.getByTestId('read-checkbox')
+ }
+ */
+ const permissionConfigs =
+ await adminCreateRolePage.getPermissionConfigs();
+
+ await test.step(
+ 'Iterate over each permission config and make sure role conditions conform',
+ async () => {
+ for (let config of permissionConfigs) {
+ await config.locator.click();
+ await adminCreateRolePage.clickPermissionSettings(config.row);
+ const modal = adminCreateRolePage.getRoleConditionsModal(
+ config.subject
+ );
+ await expect(modal.modal).toBeVisible();
+ const conditions = await modal.getAvailableConditions();
+ for (let conditionAction of Object.keys(conditions)) {
+ if (conditionAction === config.action) {
+ await expect(conditions[conditionAction]).not.toBeDisabled();
+ } else {
+ await expect(conditions[conditionAction]).toBeDisabled();
+ }
+ }
+ await modal.close();
+ await config.locator.click();
+ }
+ }
+ );
+ }
+);
+
+test(
+ 'Default role permissions conforms with role conditions',
+ async({ adminRolesPage, adminCreateRolePage }) => {
+ await adminRolesPage.navigateTo();
+ await adminRolesPage.createRoleButton.click();
+
+ const subjects = ['Connection', 'Execution', 'Flow'];
+ for (let subject of subjects) {
+ const row = adminCreateRolePage.getSubjectRow(subject)
+ const modal = adminCreateRolePage.getRoleConditionsModal(subject);
+ await adminCreateRolePage.clickPermissionSettings(row);
+ await expect(modal.modal).toBeVisible();
+ const availableConditions = await modal.getAvailableConditions();
+ const conditions = ['create', 'read', 'update', 'delete', 'publish'];
+ for (let condition of conditions) {
+ if (availableConditions[condition]) {
+ await expect(availableConditions[condition]).toBeDisabled();
+ }
+ }
+ await modal.close();
+ }
+
+ }
+);
\ No newline at end of file
diff --git a/packages/web/src/components/AppBar/index.tsx b/packages/web/src/components/AppBar/index.tsx
index abadd456..60bea460 100644
--- a/packages/web/src/components/AppBar/index.tsx
+++ b/packages/web/src/components/AppBar/index.tsx
@@ -56,6 +56,7 @@ export default function AppBar(props: AppBarProps): React.ReactElement {
aria-label="open drawer"
onClick={drawerOpen ? onDrawerClose : onDrawerOpen}
sx={{ mr: 2 }}
+ data-test="drawer-menu-button"
>
{drawerOpen && matchSmallScreens ? : }
diff --git a/packages/web/src/components/ConditionalIconButton/index.tsx b/packages/web/src/components/ConditionalIconButton/index.tsx
index ec981391..ddcff1c9 100644
--- a/packages/web/src/components/ConditionalIconButton/index.tsx
+++ b/packages/web/src/components/ConditionalIconButton/index.tsx
@@ -21,6 +21,7 @@ export default function ConditionalIconButton(props: any): React.ReactElement {
component={buttonProps.component}
to={buttonProps.to}
disabled={buttonProps.disabled}
+ data-test={buttonProps['data-test']}
>
{icon}
diff --git a/packages/web/src/components/ControlledCheckbox/index.tsx b/packages/web/src/components/ControlledCheckbox/index.tsx
index ea8f38ac..1b8b269c 100644
--- a/packages/web/src/components/ControlledCheckbox/index.tsx
+++ b/packages/web/src/components/ControlledCheckbox/index.tsx
@@ -5,6 +5,7 @@ import Checkbox, { CheckboxProps } from '@mui/material/Checkbox';
type ControlledCheckboxProps = {
name: string;
defaultValue?: boolean;
+ dataTest?: string;
} & Omit;
export default function ControlledCheckbox(
@@ -18,6 +19,7 @@ export default function ControlledCheckbox(
disabled = false,
onBlur,
onChange,
+ dataTest,
...checkboxProps
} = props;
@@ -53,6 +55,7 @@ export default function ControlledCheckbox(
onBlur?.(...args);
}}
inputRef={ref}
+ data-test={dataTest}
/>
);
}}
diff --git a/packages/web/src/components/DeleteRoleButton/index.ee.tsx b/packages/web/src/components/DeleteRoleButton/index.ee.tsx
index 2a07fac6..9eeb520e 100644
--- a/packages/web/src/components/DeleteRoleButton/index.ee.tsx
+++ b/packages/web/src/components/DeleteRoleButton/index.ee.tsx
@@ -48,6 +48,7 @@ export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
disabled={!allowed || disabled}
onClick={() => setShowConfirmation(true)}
size="small"
+ data-test="role-delete"
>
@@ -62,6 +63,7 @@ export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
onConfirm={handleConfirm}
cancelButtonChildren={formatMessage('deleteRoleButton.cancel')}
confirmButtionChildren={formatMessage('deleteRoleButton.confirm')}
+ data-test="delete-role-modal"
/>
>
);
diff --git a/packages/web/src/components/PermissionCatalogField/PermissionSettings.ee.tsx b/packages/web/src/components/PermissionCatalogField/PermissionSettings.ee.tsx
index 6b9f9e04..0592d3de 100644
--- a/packages/web/src/components/PermissionCatalogField/PermissionSettings.ee.tsx
+++ b/packages/web/src/components/PermissionCatalogField/PermissionSettings.ee.tsx
@@ -66,10 +66,15 @@ export default function PermissionSettings(props: PermissionSettingsProps) {
};
return (
-