test: write tests for role management (#1396)

This commit is contained in:
QAComet
2023-11-06 02:35:20 -07:00
committed by GitHub
parent 5fb48ed54b
commit 1581b5ac0a
21 changed files with 899 additions and 13 deletions

View File

@@ -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}`)
}
}
}

View File

@@ -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 }
});
}
}

View File

@@ -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');
}
}

View File

@@ -13,6 +13,7 @@ export class AdminEditUserPage extends AuthenticatedPage {
super(page); super(page);
this.fullNameInput = page.getByTestId('full-name-input'); this.fullNameInput = page.getByTestId('full-name-input');
this.emailInput = page.getByTestId('email-input'); this.emailInput = page.getByTestId('email-input');
this.roleInput = page.getByTestId('role.id-autocomplete');
this.updateButton = page.getByTestId('update-button'); this.updateButton = page.getByTestId('update-button');
} }

View File

@@ -2,6 +2,10 @@ const { AdminCreateUserPage } = require('./create-user-page');
const { AdminEditUserPage } = require('./edit-user-page'); const { AdminEditUserPage } = require('./edit-user-page');
const { AdminUsersPage } = require('./users-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 = { export const adminFixtures = {
adminUsersPage: async ({ page }, use) => { adminUsersPage: async ({ page }, use) => {
await use(new AdminUsersPage(page)); await use(new AdminUsersPage(page));
@@ -11,5 +15,15 @@ export const adminFixtures = {
}, },
adminEditUserPage: async ({page}, use) => { adminEditUserPage: async ({page}, use) => {
await use(new AdminEditUserPage(page)); await use(new AdminEditUserPage(page));
} },
} 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));
},
}

View File

@@ -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 }
});
}
}

View File

@@ -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);
}
}

View File

@@ -25,6 +25,11 @@ export class AdminUsersPage extends AuthenticatedPage {
async navigateTo () { async navigateTo () {
await this.profileMenuButton.click(); await this.profileMenuButton.click();
await this.adminMenuItem.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 * @param {string} email
* @returns {import('@playwright/test').Locator | null}
*/ */
async findUserPageWithEmail (email) { async findUserPageWithEmail (email) {
if (await this.usersLoader.isVisible()) {
await this.usersLoader.waitFor({
state: 'detached'
});
}
// start at the first page // start at the first page
const firstPageDisabled = await this.firstPageButton.isDisabled(); const firstPageDisabled = await this.firstPageButton.isDisabled();
if (!firstPageDisabled) { if (!firstPageDisabled) {
@@ -75,6 +86,11 @@ export class AdminUsersPage extends AuthenticatedPage {
} }
while (true) { while (true) {
if (await this.usersLoader.isVisible()) {
await this.usersLoader.waitFor({
state: 'detached'
});
}
const rowLocator = await this.getUserRowByEmail(email); const rowLocator = await this.getUserRowByEmail(email);
if ((await rowLocator.count()) === 1) { if ((await rowLocator.count()) === 1) {
return rowLocator; return rowLocator;

View File

@@ -14,6 +14,7 @@ export class AuthenticatedPage extends BasePage {
this.adminMenuItem = this.page.getByRole('menuitem', { name: 'Admin' }); this.adminMenuItem = this.page.getByRole('menuitem', { name: 'Admin' });
this.userInterfaceDrawerItem = this.page.getByTestId('user-interface-drawer-link'); this.userInterfaceDrawerItem = this.page.getByTestId('user-interface-drawer-link');
this.appBar = this.page.getByTestId('app-bar'); 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.goToDashboardButton = this.page.getByTestId('go-back-drawer-link');
this.typographyLogo = this.page.getByTestId('typography-logo'); this.typographyLogo = this.page.getByTestId('typography-logo');
this.customLogo = this.page.getByTestId('custom-logo'); this.customLogo = this.page.getByTestId('custom-logo');

View File

@@ -1,9 +1,19 @@
const path = require('node:path');
const { expect } = require('@playwright/test');
const { BasePage } = require('./base-page'); const { BasePage } = require('./base-page');
export class LoginPage extends BasePage { export class LoginPage extends BasePage {
path = '/login'; 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 * @param {import('@playwright/test').Page} page
@@ -22,8 +32,8 @@ export class LoginPage extends BasePage {
} }
async login( async login(
email = process.env.LOGIN_EMAIL, email = LoginPage.defaultEmail,
password = process.env.LOGIN_PASSWORD password = LoginPage.defaultPassword
) { ) {
await this.page.goto(this.path); await this.page.goto(this.path);
await this.emailTextField.fill(email); await this.emailTextField.fill(email);

View File

@@ -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);
}
);
}
);

View File

@@ -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();
}
}
);

View File

@@ -56,6 +56,7 @@ export default function AppBar(props: AppBarProps): React.ReactElement {
aria-label="open drawer" aria-label="open drawer"
onClick={drawerOpen ? onDrawerClose : onDrawerOpen} onClick={drawerOpen ? onDrawerClose : onDrawerOpen}
sx={{ mr: 2 }} sx={{ mr: 2 }}
data-test="drawer-menu-button"
> >
{drawerOpen && matchSmallScreens ? <MenuOpenIcon /> : <MenuIcon />} {drawerOpen && matchSmallScreens ? <MenuOpenIcon /> : <MenuIcon />}
</IconButton> </IconButton>

View File

@@ -21,6 +21,7 @@ export default function ConditionalIconButton(props: any): React.ReactElement {
component={buttonProps.component} component={buttonProps.component}
to={buttonProps.to} to={buttonProps.to}
disabled={buttonProps.disabled} disabled={buttonProps.disabled}
data-test={buttonProps['data-test']}
> >
{icon} {icon}
</IconButton> </IconButton>

View File

@@ -5,6 +5,7 @@ import Checkbox, { CheckboxProps } from '@mui/material/Checkbox';
type ControlledCheckboxProps = { type ControlledCheckboxProps = {
name: string; name: string;
defaultValue?: boolean; defaultValue?: boolean;
dataTest?: string;
} & Omit<CheckboxProps, 'defaultValue'>; } & Omit<CheckboxProps, 'defaultValue'>;
export default function ControlledCheckbox( export default function ControlledCheckbox(
@@ -18,6 +19,7 @@ export default function ControlledCheckbox(
disabled = false, disabled = false,
onBlur, onBlur,
onChange, onChange,
dataTest,
...checkboxProps ...checkboxProps
} = props; } = props;
@@ -53,6 +55,7 @@ export default function ControlledCheckbox(
onBlur?.(...args); onBlur?.(...args);
}} }}
inputRef={ref} inputRef={ref}
data-test={dataTest}
/> />
); );
}} }}

View File

@@ -48,6 +48,7 @@ export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
disabled={!allowed || disabled} disabled={!allowed || disabled}
onClick={() => setShowConfirmation(true)} onClick={() => setShowConfirmation(true)}
size="small" size="small"
data-test="role-delete"
> >
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
@@ -62,6 +63,7 @@ export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
onConfirm={handleConfirm} onConfirm={handleConfirm}
cancelButtonChildren={formatMessage('deleteRoleButton.cancel')} cancelButtonChildren={formatMessage('deleteRoleButton.cancel')}
confirmButtionChildren={formatMessage('deleteRoleButton.confirm')} confirmButtionChildren={formatMessage('deleteRoleButton.confirm')}
data-test="delete-role-modal"
/> />
</> </>
); );

View File

@@ -66,10 +66,15 @@ export default function PermissionSettings(props: PermissionSettingsProps) {
}; };
return ( return (
<Dialog open onClose={cancel} sx={{ display: open ? 'block' : 'none' }}> <Dialog
open
onClose={cancel}
sx={{ display: open ? 'block' : 'none' }}
data-test={`${subject}-role-conditions-modal`}
>
<DialogTitle>{formatMessage('permissionSettings.title')}</DialogTitle> <DialogTitle>{formatMessage('permissionSettings.title')}</DialogTitle>
<DialogContent> <DialogContent data-test="role-conditions-modal-body">
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table> <Table>
<TableHead> <TableHead>
@@ -113,6 +118,7 @@ export default function PermissionSettings(props: PermissionSettingsProps) {
{action.subjects.includes(subject) && ( {action.subjects.includes(subject) && (
<ControlledCheckbox <ControlledCheckbox
name={`${fieldPrefix}.${action.key}.conditions.${condition.key}`} name={`${fieldPrefix}.${action.key}.conditions.${condition.key}`}
dataTest={`${condition.key}-${action.key.toLowerCase()}-checkbox`}
defaultValue={defaultChecked} defaultValue={defaultChecked}
disabled={ disabled={
getValues( getValues(

View File

@@ -62,6 +62,7 @@ const PermissionCatalogField = ({
<TableRow <TableRow
key={subject.key} key={subject.key}
sx={{ '&:last-child td': { border: 0 } }} sx={{ '&:last-child td': { border: 0 } }}
data-test={`${subject.key}-permission-row`}
> >
<TableCell scope="row"> <TableCell scope="row">
<Typography variant="subtitle2">{subject.label}</Typography> <Typography variant="subtitle2">{subject.label}</Typography>
@@ -74,6 +75,7 @@ const PermissionCatalogField = ({
<ControlledCheckbox <ControlledCheckbox
disabled={disabled} disabled={disabled}
name={`${name}.${subject.key}.${action.key}.value`} name={`${name}.${subject.key}.${action.key}.value`}
dataTest={`${action.key.toLowerCase()}-checkbox`}
/> />
)} )}
@@ -89,6 +91,7 @@ const PermissionCatalogField = ({
size="small" size="small"
onClick={() => setDialogName(subject.key)} onClick={() => setDialogName(subject.key)}
disabled={disabled} disabled={disabled}
data-test="permission-settings-button"
> >
<SettingsIcon /> <SettingsIcon />
</IconButton> </IconButton>

View File

@@ -49,21 +49,29 @@ export default function RoleList(): React.ReactElement {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{loading && <ListLoader rowsNumber={3} columnsNumber={2} />} {loading && <ListLoader
rowsNumber={3}
columnsNumber={2}
data-test="roles-list-loader" />}
{!loading && {!loading &&
roles.map((role) => ( roles.map((role) => (
<TableRow <TableRow
key={role.id} key={role.id}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
data-test="role-row"
> >
<TableCell scope="row"> <TableCell scope="row">
<Typography variant="subtitle2">{role.name}</Typography> <Typography
variant="subtitle2"
data-test="role-name"
>{role.name}</Typography>
</TableCell> </TableCell>
<TableCell scope="row"> <TableCell scope="row">
<Typography variant="subtitle2"> <Typography
{role.description} variant="subtitle2"
</Typography> data-test="role-description"
>{role.description}</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
@@ -72,6 +80,7 @@ export default function RoleList(): React.ReactElement {
size="small" size="small"
component={Link} component={Link}
to={URLS.ROLE(role.id)} to={URLS.ROLE(role.id)}
data-test="role-edit"
> >
<EditIcon /> <EditIcon />
</IconButton> </IconButton>
@@ -79,6 +88,7 @@ export default function RoleList(): React.ReactElement {
<DeleteRoleButton <DeleteRoleButton
disabled={role.isAdmin} disabled={role.isAdmin}
roleId={role.id} roleId={role.id}
data-test="role-delete"
/> />
</Stack> </Stack>
</TableCell> </TableCell>

View File

@@ -69,12 +69,14 @@ export default function CreateRole(): React.ReactElement {
name="name" name="name"
label={formatMessage('roleForm.name')} label={formatMessage('roleForm.name')}
fullWidth fullWidth
data-test="name-input"
/> />
<TextField <TextField
name="description" name="description"
label={formatMessage('roleForm.description')} label={formatMessage('roleForm.description')}
fullWidth fullWidth
data-test="description-input"
/> />
<PermissionCatalogField <PermissionCatalogField
@@ -88,6 +90,7 @@ export default function CreateRole(): React.ReactElement {
color="primary" color="primary"
sx={{ boxShadow: 2 }} sx={{ boxShadow: 2 }}
loading={loading} loading={loading}
data-test="create-button"
> >
{formatMessage('createRole.submit')} {formatMessage('createRole.submit')}
</LoadingButton> </LoadingButton>

View File

@@ -92,6 +92,7 @@ export default function EditRole(): React.ReactElement {
required={true} required={true}
name="name" name="name"
label={formatMessage('roleForm.name')} label={formatMessage('roleForm.name')}
data-test="name-input"
fullWidth fullWidth
/> />
@@ -99,6 +100,7 @@ export default function EditRole(): React.ReactElement {
disabled={role.isAdmin} disabled={role.isAdmin}
name="description" name="description"
label={formatMessage('roleForm.description')} label={formatMessage('roleForm.description')}
data-test="description-input"
fullWidth fullWidth
/> />
</> </>
@@ -116,6 +118,7 @@ export default function EditRole(): React.ReactElement {
sx={{ boxShadow: 2 }} sx={{ boxShadow: 2 }}
loading={loading} loading={loading}
disabled={role?.isAdmin || roleLoading} disabled={role?.isAdmin || roleLoading}
data-test="update-button"
> >
{formatMessage('editRole.submit')} {formatMessage('editRole.submit')}
</LoadingButton> </LoadingButton>