Skip to content

Add integration tests for manage permissions #1216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions apps/jetstream-e2e/src/fixtures/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
AuthenticationPage,
LoadSingleObjectPage,
LoadWithoutFilePage,
ManagePermissionPage,
OrganizationsPage,
PlatformEventPage,
PlaywrightPage,
Expand Down Expand Up @@ -40,6 +41,7 @@ type MyFixtures = {
newUser: Awaited<ReturnType<AuthenticationPage['signUpAndVerifyEmail']>>;
queryPage: QueryPage;
loadSingleObjectPage: LoadSingleObjectPage;
managePermissionPage: ManagePermissionPage;
organizationsPage: OrganizationsPage;
loadWithoutFilePage: LoadWithoutFilePage;
platformEventPage: PlatformEventPage;
Expand All @@ -62,9 +64,9 @@ export const test = base.extend<MyFixtures>({
newUser: async ({ authenticationPage }, use) => {
await use(await authenticationPage.signUpAndVerifyEmail());
},
queryPage: async ({ page, apiRequestUtils, playwrightPage }, use) => {
queryPage: async ({ page, apiRequestUtils }, use) => {
await apiRequestUtils.selectDefaultOrg();
await use(new QueryPage(page, apiRequestUtils, playwrightPage));
await use(new QueryPage(page, apiRequestUtils));
},
loadSingleObjectPage: async ({ page, apiRequestUtils, playwrightPage }, use) => {
await apiRequestUtils.selectDefaultOrg();
Expand All @@ -74,6 +76,10 @@ export const test = base.extend<MyFixtures>({
await apiRequestUtils.selectDefaultOrg();
await use(new LoadWithoutFilePage(page, apiRequestUtils, playwrightPage));
},
managePermissionPage: async ({ page, apiRequestUtils }, use) => {
await apiRequestUtils.selectDefaultOrg();
await use(new ManagePermissionPage(page, apiRequestUtils));
},
organizationsPage: async ({ page }, use) => {
await use(new OrganizationsPage(page));
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* eslint-disable playwright/no-conditional-expect */
/* eslint-disable playwright/no-conditional-in-test */
import { expect, test } from '../../fixtures/fixtures';

test.beforeEach(async ({ page }) => {
await page.goto('/app');
});

test.describe.configure({ mode: 'parallel' });

test.describe('MANAGE PERMISSIONS', () => {
const continueButtonTestCases = [
{
profileNames: ['Standard User'],
permissionSetNames: [],
sobjectNames: [],
enabled: false,
},
{
profileNames: ['Standard User'],
permissionSetNames: ['Jetstream'],
sobjectNames: [],
enabled: false,
},
{
profileNames: [],
permissionSetNames: [],
sobjectNames: ['Account'],
enabled: false,
},
{
profileNames: ['Standard User'],
permissionSetNames: [],
sobjectNames: ['Account'],
enabled: true,
},
{
profileNames: [],
permissionSetNames: ['Jetstream'],
sobjectNames: ['Account'],
enabled: true,
},
];

for (const testCase of continueButtonTestCases) {
test(`Should test the continue button for profiles: ${testCase.profileNames.join()}, permSets: ${testCase.permissionSetNames.join(
','
)}, objects:${testCase.sobjectNames}`, async ({ page, managePermissionPage }) => {
await managePermissionPage.goto();
await managePermissionPage.selectProfilesPermissionSetsAndObjects(testCase);
if (testCase.enabled) {
await expect(managePermissionPage.continueButton).toBeVisible();
} else {
await expect(managePermissionPage.continueButtonDisabled).toBeVisible();
await expect(managePermissionPage.continueButtonDisabled).toBeDisabled();
}
});
}

test('Should allow selecting profiles, permission sets and objects', async ({ page, managePermissionPage }) => {
await managePermissionPage.goto();
await managePermissionPage.selectProfilesPermissionSetsAndObjects({
profileNames: ['Standard User'],
permissionSetNames: ['Jetstream'],
sobjectNames: ['Account', 'Contact', 'Opportunity'],
});

await expect(managePermissionPage.continueButton).toBeVisible();

await managePermissionPage.gotoEditor();

await test.step('Validate field dependencies work', async () => {
const read = managePermissionPage.getCheckboxLocator('Account', 'AccountNumber', 'profile', 'read');
const edit = managePermissionPage.getCheckboxLocator('Account', 'AccountNumber', 'profile', 'edit');

await read.uncheck();
await expect(read).toBeChecked({ checked: false });
await expect(edit).toBeChecked({ checked: false });

await read.check();
await expect(read).toBeChecked();
await expect(edit).toBeChecked({ checked: false });

await read.uncheck();
await edit.check();
await expect(read).toBeChecked();
await expect(edit).toBeChecked();
});

await test.step('bulk edit row with dependencies', async () => {
await page.getByPlaceholder('Filter...').click();
await page.getByPlaceholder('Filter...').fill('level__c');
await page.getByTestId('row-action-button-Contact.Level__c').click();

await page.getByTestId('row-action-popover').locator('label').filter({ hasText: 'Edit' }).check();
await expect(page.getByTestId('row-action-popover').locator('label').filter({ hasText: 'Read' })).toBeChecked();

await page.getByRole('button', { name: 'Apply to Row' }).click();
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000003RK82OAG-read"]')).toBeChecked();
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000003RK82OAG-edit"]')).toBeChecked();
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000001M6CFOA0-read"]')).toBeChecked();
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000001M6CFOA0-edit"]')).toBeChecked();

await page.getByTestId('row-action-popover').locator('label').filter({ hasText: 'Read' }).uncheck();
await expect(page.getByTestId('row-action-popover').locator('label').filter({ hasText: 'Edit' })).toBeChecked({ checked: false });

await page.getByRole('button', { name: 'Apply to Row' }).click();
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000003RK82OAG-read"]')).toBeChecked({ checked: false });
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000003RK82OAG-edit"]')).toBeChecked({ checked: false });
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000001M6CFOA0-read"]')).toBeChecked({ checked: false });
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000001M6CFOA0-edit"]')).toBeChecked({ checked: false });

await page.getByTestId('row-action-popover').locator('label').filter({ hasText: 'Read' }).check();
await expect(page.getByTestId('row-action-popover').locator('label').filter({ hasText: 'Edit' })).toBeChecked({ checked: false });

await page.getByRole('button', { name: 'Apply to Row' }).click();
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000003RK82OAG-read"]')).toBeChecked();
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000003RK82OAG-edit"]')).toBeChecked({ checked: false });
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000001M6CFOA0-read"]')).toBeChecked();
await expect(page.locator('[id="Contact\\.Level__c-0PSDn000001M6CFOA0-edit"]')).toBeChecked({ checked: false });
await page.getByRole('button', { name: 'Close dialog' }).click();
});

await test.step('save', async () => {
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('button', { name: 'Save Changes' }).click();
// TODO: validate saved changes
});
});

// TODO: reset changes

// TODO: tab visibility

// TODO: object permissions
});
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export const ManagePermissionsSelection: FunctionComponent<ManagePermissionsSele
>
<div className="slds-p-horizontal_x-small">
<ListWithFilterMultiSelect
testId="profiles-list"
labels={{
listHeading: 'Profiles',
filter: 'Filter Profiles',
Expand All @@ -157,6 +158,7 @@ export const ManagePermissionsSelection: FunctionComponent<ManagePermissionsSele
</div>
<div className="slds-p-horizontal_x-small">
<ListWithFilterMultiSelect
testId="permission-sets-list"
labels={{
listHeading: 'Permission Sets',
filter: 'Filter Permission Sets',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,7 @@ export const RowActionRenderer = ({
return (
<Popover
ref={popoverRef}
testId="row-action-popover"
size={type === 'object' ? 'large' : 'medium'}
placement="bottom"
onChange={handlePopoverChange}
Expand Down Expand Up @@ -1404,6 +1405,7 @@ export const RowActionRenderer = ({
buttonProps={{
className: 'slds-button slds-button_stretch',
onChange: handleOpen,
'data-testid': `row-action-button-${row.sobject}.${row.apiName}`,
}}
buttonStyle={{ lineHeight: '1rem' }}
>
Expand Down
1 change: 1 addition & 0 deletions libs/test/e2e-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './lib/e2e-database-validation.utils';
export * from './lib/pageObjectModels/AuthenticationPage.model';
export * from './lib/pageObjectModels/LoadSingleObjectPage.model';
export * from './lib/pageObjectModels/LoadWithoutFilePage.model';
export * from './lib/pageObjectModels/ManagePermissionPage.model';
export * from './lib/pageObjectModels/OrganizationsPage';
export * from './lib/pageObjectModels/PlatformEventPage.model';
export * from './lib/pageObjectModels/PlaywrightPage.model';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { formatNumber } from '@jetstream/shared/ui-utils';
import { pluralizeFromNumber } from '@jetstream/shared/utils';
import { Locator, Page, expect } from '@playwright/test';
import { ApiRequestUtils } from '../ApiRequestUtils';

function getSelectionText(length: number, identifier: string) {
return `${formatNumber(length)} ${pluralizeFromNumber(identifier, length)} selected`;
}

export class ManagePermissionPage {
readonly apiRequestUtils: ApiRequestUtils;
readonly page: Page;

readonly profileList: Locator;
readonly permissionSetList: Locator;
readonly sobjectList: Locator;

readonly continueButton: Locator;
readonly continueButtonDisabled: Locator;

readonly standardUserProfileId = '0PSDn000003RK82OAG';
readonly jetstreamPermSetId = '0PSDn000001M6CFOA0';

constructor(page: Page, apiRequestUtils: ApiRequestUtils) {
this.apiRequestUtils = apiRequestUtils;
this.page = page;

this.profileList = page.getByTestId('profiles-list');
this.permissionSetList = page.getByTestId('permission-sets-list');
this.sobjectList = page.getByTestId('sobject-list-multi-select');
this.continueButton = page.getByRole('link', { name: 'Continue' });
this.continueButtonDisabled = page.getByRole('button', { name: 'Continue' });
}

getCheckboxLocator(entity: string, field: string, type: 'profile' | 'permissionSet', which: 'read' | 'edit') {
// await page.locator('[id="Account\\.AccountNumber-0PSDn000003RK82OAG-edit"]').uncheck();
return this.page.locator(
`[id="${entity}\\.${field}-${type === 'profile' ? this.standardUserProfileId : this.jetstreamPermSetId}-${which}"]`
);
}

isChecked(entity: string, field: string, type: 'profile' | 'permissionSet', which: 'read' | 'edit') {
return this.getCheckboxLocator(entity, field, type, which).isChecked();
}

async validateFieldDependency(entity: string, field: string, type: 'profile' | 'permissionSet') {
const read = this.getCheckboxLocator(entity, field, type, 'read');
const edit = this.getCheckboxLocator(entity, field, type, 'edit');
await edit.check();
await expect(read).toBeChecked();
await expect(edit).toBeChecked();

await read.uncheck();
await expect(read).toBeChecked({ checked: false });
await expect(edit).toBeChecked({ checked: false });
}

async modifyValue(entity: string, field: string, type: 'profile' | 'permissionSet') {
const read = this.getCheckboxLocator(entity, field, type, 'read');
const edit = this.getCheckboxLocator(entity, field, type, 'edit');
await edit.check();
await expect(read).toBeChecked();
await expect(edit).toBeChecked();

await read.uncheck();
await expect(read).toBeChecked({ checked: false });
await expect(edit).toBeChecked({ checked: false });
}

async goto() {
await this.page.getByRole('menuitem', { name: 'Manage Permissions' }).click();
await this.page.waitForURL('**/permissions-manager');
}

async gotoEditor() {
await this.continueButton.click();
await this.page.waitForURL('**/permissions-manager/editor');
}

async selectProfilesPermissionSetsAndObjects({
profileNames,
permissionSetNames,
sobjectNames,
}: {
sobjectNames: string[];
profileNames: string[];
permissionSetNames: string[];
}) {
for (const name of profileNames) {
await this.profileList.getByText(name, { exact: true }).first().click();
}
if (profileNames.length > 0) {
await expect(this.profileList.getByRole('button', { name: getSelectionText(profileNames.length, 'item') })).toBeVisible();
}

for (const name of permissionSetNames) {
await this.permissionSetList.getByText(name, { exact: true }).first().click();
}
if (permissionSetNames.length > 0) {
await expect(this.permissionSetList.getByRole('button', { name: getSelectionText(permissionSetNames.length, 'item') })).toBeVisible();
}

for (const name of sobjectNames) {
await this.sobjectList.getByText(name, { exact: true }).first().click();
}
if (sobjectNames.length > 0) {
await expect(this.sobjectList.getByRole('button', { name: getSelectionText(sobjectNames.length, 'object') })).toBeVisible();
}
}
}
4 changes: 3 additions & 1 deletion libs/ui/src/lib/list/ListWithFilterMultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Tooltip from '../widgets/Tooltip';
import List from './List';

export interface ListWithFilterMultiSelectProps {
testId?: string;
labels: {
listHeading?: string;
filter: string; // {Filter Items}
Expand All @@ -43,6 +44,7 @@ export interface ListWithFilterMultiSelectProps {
* This will extend to the full page height
*/
export const ListWithFilterMultiSelect: FunctionComponent<ListWithFilterMultiSelectProps> = ({
testId,
labels,
items,
selectedItems = [],
Expand Down Expand Up @@ -150,7 +152,7 @@ export const ListWithFilterMultiSelect: FunctionComponent<ListWithFilterMultiSel
<Spinner />
</div>
)}
<div>
<div data-testid={testId}>
{hasError && (
<p className="slds-p-around_medium slds-text-align_center">
<span className="slds-text-color_error">There was an error loading {labels.descriptorPlural} for the selected org.</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DescribeGlobalSObjectResult, Maybe, RecentHistoryItemType, SalesforceOr
import { recentHistoryItemsDb } from '@jetstream/ui/db';
import { formatRelative } from 'date-fns/formatRelative';
import { useLiveQuery } from 'dexie-react-hooks';
import { Fragment, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import Grid from '../grid/Grid';
import Icon from '../widgets/Icon';
import Tooltip from '../widgets/Tooltip';
Expand Down Expand Up @@ -132,7 +132,7 @@ export const ConnectedSobjectListMultiSelect = forwardRef<any, ConnectedSobjectL
}

return (
<Fragment>
<div data-testid="sobject-list-multi-select">
<Grid>
<h2 className="slds-text-heading_medium slds-grow slds-text-align_center">{label}</h2>
<div>
Expand All @@ -154,7 +154,7 @@ export const ConnectedSobjectListMultiSelect = forwardRef<any, ConnectedSobjectL
onSelected={onSelectedSObjects}
errorReattempt={() => setErrorMessage(null)}
/>
</Fragment>
</div>
);
}
);
Expand Down
Loading