(test): restructure system console page-object-model (#35185)

Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
sabril 2026-02-12 08:53:59 +08:00 committed by GitHub
parent d09ab7173e
commit e4bd8398ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 3205 additions and 1336 deletions

View file

@ -51,14 +51,7 @@ export {
ScheduledDraftModal,
ScheduledPost,
SendMessageNowModal,
SystemConsoleSidebar,
SystemConsoleNavbar,
SystemUsers,
SystemUsersFilterPopover,
SystemUsersFilterMenu,
SystemUsersColumnToggleMenu,
SystemConsoleFeatureDiscovery,
SystemConsoleMobileSecurity,
MessagePriority,
UserProfilePopover,
UserAccountMenu,

View file

@ -1,157 +1,189 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import ChannelsHeader from './channels/header';
import ChannelsAppBar from './channels/app_bar';
import ChannelsPostCreate from './channels/post_create';
import ChannelsPost from './channels/post';
import ChannelsCenterView from './channels/center_view';
import ChannelsSidebarLeft from './channels/sidebar_left';
import ChannelsSidebarRight from './channels/sidebar_right';
import ChannelSettingsModal from './channels/channel_settings/channel_settings_modal';
import DeletePostModal from './channels/delete_post_modal';
import FindChannelsModal from './channels/find_channels_modal';
import InvitePeopleModal from './channels/invite_people_modal';
import SettingsModal from './channels/settings/settings_modal';
// Shared / Global Components
import Footer from './footer';
import GlobalHeader from './global_header';
import SearchBox from './channels/search_box';
import MainHeader from './main_header';
import PostDotMenu from './channels/post_dot_menu';
import PostReminderMenu from './channels/post_reminder_menu';
import PostMenu from './channels/post_menu';
import ThreadFooter from './channels/thread_footer';
import EmojiGifPicker from './channels/emoji_gif_picker';
import GenericConfirmModal from './channels/generic_confirm_modal';
import MessagePriority from './channels/message_priority';
import ScheduleMessageMenu from './channels/schedule_message_menu';
import ScheduleMessageModal from './channels/schedule_message_modal';
import ScheduledPostIndicator from './channels/scheduled_post_indicator';
import ScheduledDraftModal from './channels/scheduled_draft_modal';
import UserAccountMenu from './user_account_menu';
import TeamMenu from './channels/team_menu';
import TeamSettingsModal from './channels/team_settings/team_settings_modal';
import ProfileModal from './channels/profile_modal';
import UserProfilePopover from './channels/user_profile_popover';
import SystemConsoleSidebar from './system_console/sidebar';
import SystemConsoleNavbar from './system_console/navbar';
import SystemUsers from './system_console/sections/system_users/system_users';
import SystemUsersFilterPopover from './system_console/sections/system_users/filter_popover';
import SystemUsersFilterMenu from './system_console/sections/system_users/filter_menu';
import SystemUsersColumnToggleMenu from './system_console/sections/system_users/column_toggle_menu';
// Channels Components
import ChannelsAppBar from './channels/app_bar';
import ChannelsCenterView from './channels/center_view';
import ChannelsHeader from './channels/header';
import ChannelsPost from './channels/post';
import ChannelsPostCreate from './channels/post_create';
import ChannelsPostEdit from './channels/post_edit';
import ChannelSettingsModal from './channels/channel_settings/channel_settings_modal';
import ChannelsSidebarLeft from './channels/sidebar_left';
import ChannelsSidebarRight from './channels/sidebar_right';
import DeletePostConfirmationDialog from './channels/delete_post_confirmation_dialog';
import RestorePostConfirmationDialog from './channels/restore_post_confirmation_dialog';
import SystemConsoleFeatureDiscovery from './system_console/sections/system_users/feature_discovery';
import SystemConsoleMobileSecurity from './system_console/sections/system_users/mobile_security';
import SystemConsoleNotifications from './system_console/sections/site_configuration/notifications';
import ScheduledPost from './channels/scheduled_post';
import SendMessageNowModal from './channels/send_message_now_modal';
import DeletePostModal from './channels/delete_post_modal';
import DeleteScheduledPostModal from './channels/delete_scheduled_post_modal';
import DraftPost from './channels/draft_post';
import EmojiGifPicker from './channels/emoji_gif_picker';
import FindChannelsModal from './channels/find_channels_modal';
import FlagPostConfirmationDialog from './channels/flag_post_confirmation_dialog';
import GenericConfirmModal from './channels/generic_confirm_modal';
import InvitePeopleModal from './channels/invite_people_modal';
import MessagePriority from './channels/message_priority';
import PostDotMenu from './channels/post_dot_menu';
import PostMenu from './channels/post_menu';
import PostReminderMenu from './channels/post_reminder_menu';
import ProfileModal from './channels/profile_modal';
import RestorePostConfirmationDialog from './channels/restore_post_confirmation_dialog';
import ScheduledDraftModal from './channels/scheduled_draft_modal';
import ScheduledPost from './channels/scheduled_post';
import ScheduledPostIndicator from './channels/scheduled_post_indicator';
import ScheduleMessageMenu from './channels/schedule_message_menu';
import ScheduleMessageModal from './channels/schedule_message_modal';
import SearchBox from './channels/search_box';
import SendMessageNowModal from './channels/send_message_now_modal';
import SettingsModal from './channels/settings/settings_modal';
import TeamMenu from './channels/team_menu';
import TeamSettingsModal from './channels/team_settings/team_settings_modal';
import ThreadFooter from './channels/thread_footer';
import UserProfilePopover from './channels/user_profile_popover';
// System Console Components
import {AdminSectionPanel, DropdownSetting, RadioSetting, TextInputSetting} from './system_console/base_components';
import DelegatedGranularAdministration from './system_console/sections/user_management/delegated_granular_administration';
import UserDetail from './system_console/sections/user_management/user_detail';
import EditionAndLicense from './system_console/sections/about/edition_and_license';
import MobileSecurity from './system_console/sections/environment/mobile_security';
import Notifications from './system_console/sections/site_configuration/notifications';
import SystemConsoleFeatureDiscovery from './system_console/sections/system_users/feature_discovery';
import SystemConsoleHeader from './system_console/header';
import SystemConsoleNavbar from './system_console/navbar';
import SystemConsoleSidebar from './system_console/sidebar';
import SystemConsoleSidebarHeader from './system_console/sidebar_header';
import TeamStatistics from './system_console/sections/reporting/team_statistics';
import Users from './system_console/sections/user_management/users';
const components = {
// Shared / Global
Footer,
GlobalHeader,
SearchBox,
ChannelsCenterView,
ChannelsSidebarLeft,
ChannelsSidebarRight,
MainHeader,
UserAccountMenu,
// Channels
ChannelsAppBar,
ChannelsCenterView,
ChannelsHeader,
ChannelsPost,
ChannelsPostCreate,
ChannelsPostEdit,
ChannelsPost,
ChannelSettingsModal,
DraftPost,
FindChannelsModal,
FlagPostConfirmationDialog,
ChannelsSidebarLeft,
ChannelsSidebarRight,
DeletePostConfirmationDialog,
DeletePostModal,
DeleteScheduledPostModal,
DraftPost,
EmojiGifPicker,
FindChannelsModal,
FlagPostConfirmationDialog,
GenericConfirmModal,
InvitePeopleModal,
SettingsModal,
MessagePriority,
PostDotMenu,
PostMenu,
ThreadFooter,
Footer,
MainHeader,
PostReminderMenu,
EmojiGifPicker,
GenericConfirmModal,
ScheduleMessageMenu,
ScheduleMessageModal,
ScheduledPostIndicator,
ProfileModal,
RestorePostConfirmationDialog,
ScheduledDraftModal,
ScheduledPost,
ScheduledPostIndicator,
ScheduleMessageMenu,
ScheduleMessageModal,
SearchBox,
SendMessageNowModal,
SystemConsoleSidebar,
SystemConsoleNavbar,
SystemUsers,
SystemUsersFilterPopover,
SystemUsersFilterMenu,
SystemUsersColumnToggleMenu,
SystemConsoleFeatureDiscovery,
SystemConsoleMobileSecurity,
SystemConsoleNotifications,
MessagePriority,
UserProfilePopover,
UserAccountMenu,
SettingsModal,
TeamMenu,
TeamSettingsModal,
DeletePostConfirmationDialog,
RestorePostConfirmationDialog,
ProfileModal,
ThreadFooter,
UserProfilePopover,
// System Console
AdminSectionPanel,
DelegatedGranularAdministration,
DropdownSetting,
EditionAndLicense,
MobileSecurity,
Notifications,
RadioSetting,
SystemConsoleFeatureDiscovery,
SystemConsoleHeader,
SystemConsoleNavbar,
SystemConsoleSidebar,
SystemConsoleSidebarHeader,
TeamStatistics,
TextInputSetting,
UserDetail,
Users,
};
export {
components,
// Shared / Global
Footer,
GlobalHeader,
SearchBox,
ChannelsCenterView,
ChannelsSidebarLeft,
ChannelsSidebarRight,
MainHeader,
UserAccountMenu,
// Channels Page
ChannelsAppBar,
ChannelsCenterView,
ChannelsHeader,
ChannelsPost,
ChannelsPostCreate,
ChannelsPostEdit,
ChannelsPost,
ChannelSettingsModal,
DraftPost,
FindChannelsModal,
FlagPostConfirmationDialog,
ChannelsSidebarLeft,
ChannelsSidebarRight,
DeletePostConfirmationDialog,
DeletePostModal,
DeleteScheduledPostModal,
DraftPost,
EmojiGifPicker,
FindChannelsModal,
FlagPostConfirmationDialog,
GenericConfirmModal,
InvitePeopleModal,
SettingsModal,
MessagePriority,
PostDotMenu,
PostMenu,
ThreadFooter,
Footer,
MainHeader,
PostReminderMenu,
EmojiGifPicker,
GenericConfirmModal,
ScheduleMessageMenu,
ScheduleMessageModal,
ScheduledPostIndicator,
ProfileModal,
RestorePostConfirmationDialog,
ScheduledDraftModal,
ScheduledPost,
ScheduledPostIndicator,
ScheduleMessageMenu,
ScheduleMessageModal,
SearchBox,
SendMessageNowModal,
SystemConsoleSidebar,
SystemConsoleNavbar,
SystemUsers,
SystemUsersFilterPopover,
SystemUsersFilterMenu,
SystemUsersColumnToggleMenu,
SystemConsoleFeatureDiscovery,
SystemConsoleMobileSecurity,
SystemConsoleNotifications,
MessagePriority,
UserProfilePopover,
UserAccountMenu,
SettingsModal,
TeamMenu,
TeamSettingsModal,
DeletePostConfirmationDialog,
RestorePostConfirmationDialog,
ProfileModal,
ThreadFooter,
UserProfilePopover,
// System Console
AdminSectionPanel,
DelegatedGranularAdministration,
DropdownSetting,
EditionAndLicense,
MobileSecurity,
Notifications,
RadioSetting,
SystemConsoleFeatureDiscovery,
SystemConsoleHeader,
SystemConsoleNavbar,
SystemConsoleSidebar,
SystemConsoleSidebarHeader,
TeamStatistics,
TextInputSetting,
UserDetail,
Users,
};

View file

@ -0,0 +1,145 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* Radio Setting - represents a true/false radio button group
* Uses getByRole for radio buttons, getByText for help text
*
* Usage:
* await setting.selectTrue();
* await setting.toBeTrue();
* await setting.toBeFalse();
*/
export class RadioSetting {
readonly container: Locator;
readonly trueOption: Locator;
readonly falseOption: Locator;
readonly helpText: Locator;
constructor(container: Locator) {
this.container = container;
this.trueOption = container.getByRole('radio', {name: 'True'});
this.falseOption = container.getByRole('radio', {name: 'False'});
this.helpText = container.locator('.help-text');
}
/**
* Select the True option
*/
async selectTrue() {
await this.trueOption.check();
}
/**
* Select the False option
*/
async selectFalse() {
await this.falseOption.check();
}
/**
* Assert that the setting is visible
*/
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Assert that the True option is selected
*/
async toBeTrue() {
await expect(this.trueOption).toBeChecked();
}
/**
* Assert that the False option is selected (True is not checked)
*/
async toBeFalse() {
await expect(this.falseOption).toBeChecked();
}
}
/**
* Text Input Setting - represents a text input field
*/
export class TextInputSetting {
readonly container: Locator;
readonly label: Locator;
readonly input: Locator;
readonly helpText: Locator;
constructor(container: Locator, labelText: string) {
this.container = container;
this.label = container.getByText(labelText);
this.input = container.getByRole('textbox');
this.helpText = container.locator('.help-text');
}
async fill(value: string) {
await this.input.fill(value);
}
async getValue(): Promise<string> {
return (await this.input.inputValue()) ?? '';
}
async clear() {
await this.input.clear();
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
/**
* Dropdown Setting - represents a select dropdown
*/
export class DropdownSetting {
readonly container: Locator;
readonly label: Locator;
readonly dropdown: Locator;
readonly helpText: Locator;
constructor(container: Locator, labelText: string) {
this.container = container;
this.label = container.getByText(labelText);
this.dropdown = container.getByRole('combobox');
this.helpText = container.locator('.help-text');
}
async select(option: string) {
await this.dropdown.selectOption(option);
}
async getSelectedValue(): Promise<string> {
return (await this.dropdown.inputValue()) ?? '';
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
/**
* Admin Section Panel - represents a collapsible section panel
*/
export class AdminSectionPanel {
readonly container: Locator;
readonly title: Locator;
readonly description: Locator;
readonly body: Locator;
constructor(container: Locator, titleText: string) {
this.container = container;
this.title = container.getByRole('heading', {name: titleText});
this.description = container.locator('.AdminSectionPanel__description');
this.body = container.locator('.AdminSectionPanel__body');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}

View file

@ -0,0 +1,57 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* Base modal component for System Console modals.
* Can be extended or used directly for various modal dialogs.
*/
export default class BaseModal {
readonly container: Locator;
readonly title: Locator;
readonly closeButton: Locator;
readonly cancelButton: Locator;
constructor(container: Locator) {
this.container = container;
this.title = container.locator('.modal-title');
this.closeButton = container.getByRole('button', {name: 'Close'});
this.cancelButton = container.getByRole('button', {name: 'Cancel'});
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async close() {
await this.closeButton.click();
await expect(this.container).not.toBeVisible();
}
async cancel() {
await this.cancelButton.click();
await expect(this.container).not.toBeVisible();
}
async clickButton(name: string) {
await this.container.getByRole('button', {name}).click();
}
}
/**
* Confirm modal with specific confirm button ID (#confirmModalButton)
*/
export class ConfirmModal extends BaseModal {
readonly confirmButton: Locator;
constructor(container: Locator) {
super(container);
this.confirmButton = container.locator('#confirmModalButton');
}
async confirm() {
await this.confirmButton.click();
await expect(this.container).not.toBeVisible();
}
}

View file

@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* System Console section header component
* Represents the header area that displays the current section title
*/
export default class SystemConsoleHeader {
readonly container: Locator;
readonly title: Locator;
constructor(container: Locator) {
this.container = container;
this.title = container.locator('.admin-console__header');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async getTitle(): Promise<string> {
return (await this.title.textContent()) ?? '';
}
async toHaveTitle(expectedTitle: string) {
await expect(this.title).toContainText(expectedTitle);
}
}

View file

@ -3,14 +3,27 @@
import {Locator, expect} from '@playwright/test';
/**
* System Console Navbar component
*/
export default class SystemConsoleNavbar {
readonly container: Locator;
readonly backLink: Locator;
constructor(container: Locator) {
this.container = container;
this.backLink = container.getByRole('link', {name: /Back/});
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.backLink).toBeVisible();
}
/**
* Click the back link to return to the team
*/
async clickBack() {
await this.backLink.click();
}
}

View file

@ -0,0 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* System Console -> About -> Edition and License
*/
export default class EditionAndLicense {
readonly container: Locator;
readonly header: Locator;
constructor(container: Locator) {
this.container = container;
this.header = container.getByText('Edition and License', {exact: true});
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.header).toBeVisible();
}
}

View file

@ -0,0 +1,132 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
import {RadioSetting, TextInputSetting, DropdownSetting, AdminSectionPanel} from '../../base_components';
/**
* System Console -> Environment -> Mobile Security
*/
export default class MobileSecurity {
readonly container: Locator;
// Header
readonly header: Locator;
// Panels
readonly generalMobileSecurity: GeneralMobileSecurityPanel;
readonly microsoftIntune: MicrosoftIntunePanel;
// Save section
readonly saveButton: Locator;
readonly errorMessage: Locator;
constructor(container: Locator) {
this.container = container;
this.header = container.getByText('Mobile Security', {exact: true});
this.generalMobileSecurity = new GeneralMobileSecurityPanel(
container.locator('.AdminSectionPanel').filter({hasText: 'General Mobile Security'}),
);
this.microsoftIntune = new MicrosoftIntunePanel(
container.locator('.AdminSectionPanel').filter({hasText: 'Microsoft Intune'}),
);
this.saveButton = container.getByRole('button', {name: 'Save'});
this.errorMessage = container.locator('.error-message');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.header).toBeVisible();
}
async save() {
await this.saveButton.click();
}
// Convenience shortcuts for General Mobile Security settings
get enableBiometricAuthentication() {
return this.generalMobileSecurity.enableBiometricAuthentication;
}
get preventScreenCapture() {
return this.generalMobileSecurity.preventScreenCapture;
}
get enableJailbreakProtection() {
return this.generalMobileSecurity.enableJailbreakProtection;
}
get enableSecureFilePreviewMode() {
return this.generalMobileSecurity.enableSecureFilePreviewMode;
}
get allowPdfLinkNavigation() {
return this.generalMobileSecurity.allowPdfLinkNavigation;
}
// Convenience shortcuts for Microsoft Intune settings
get enableIntuneMAM() {
return this.microsoftIntune.enableIntuneMAM;
}
get authProvider() {
return this.microsoftIntune.authProvider;
}
get tenantId() {
return this.microsoftIntune.tenantId;
}
get clientId() {
return this.microsoftIntune.clientId;
}
}
class GeneralMobileSecurityPanel extends AdminSectionPanel {
readonly enableBiometricAuthentication: RadioSetting;
readonly preventScreenCapture: RadioSetting;
readonly enableJailbreakProtection: RadioSetting;
readonly enableSecureFilePreviewMode: RadioSetting;
readonly allowPdfLinkNavigation: RadioSetting;
constructor(container: Locator) {
super(container, 'General Mobile Security');
this.enableBiometricAuthentication = new RadioSetting(
this.body.getByRole('group', {name: /Enable Biometric Authentication/}),
);
this.preventScreenCapture = new RadioSetting(this.body.getByRole('group', {name: /Prevent Screen Capture/}));
this.enableJailbreakProtection = new RadioSetting(
this.body.getByRole('group', {name: /Enable Jailbreak\/Root Protection/}),
);
this.enableSecureFilePreviewMode = new RadioSetting(
this.body.getByRole('group', {name: /Enable Secure File Preview Mode/}),
);
this.allowPdfLinkNavigation = new RadioSetting(
this.body.getByRole('group', {name: /Allow Link Navigation in Secure PDFs/}),
);
}
}
class MicrosoftIntunePanel extends AdminSectionPanel {
readonly enableIntuneMAM: RadioSetting;
readonly authProvider: DropdownSetting;
readonly tenantId: TextInputSetting;
readonly clientId: TextInputSetting;
constructor(container: Locator) {
super(container, 'Microsoft Intune');
this.enableIntuneMAM = new RadioSetting(this.body.getByRole('group', {name: /Enable Microsoft Intune MAM/}));
this.authProvider = new DropdownSetting(
this.body.locator('.form-group').filter({hasText: 'Auth Provider:'}),
'Auth Provider:',
);
this.tenantId = new TextInputSetting(
this.body.locator('.form-group').filter({hasText: 'Tenant ID:'}),
'Tenant ID:',
);
this.clientId = new TextInputSetting(
this.body.locator('.form-group').filter({hasText: 'Application (Client) ID:'}),
'Application (Client) ID:',
);
}
}

View file

@ -0,0 +1,155 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* System Console -> Reporting -> Team Statistics
*/
export default class TeamStatistics {
readonly container: Locator;
readonly header: Locator;
// Team filter
readonly teamFilterDropdown: Locator;
// Banner
readonly banner: Locator;
// Statistics cards
readonly totalActivatedUsers: StatCard;
readonly publicChannels: StatCard;
readonly privateChannels: StatCard;
readonly totalPosts: StatCard;
// Charts
readonly totalPostsChart: ChartSection;
readonly activeUsersWithPostsChart: ChartSection;
// Tables
readonly recentActiveUsers: TableSection;
readonly newlyCreatedUsers: TableSection;
constructor(container: Locator) {
this.container = container;
this.header = container.locator('.team-statistics__header');
this.teamFilterDropdown = container.getByTestId('teamFilter');
this.banner = container.locator('.banner');
const gridStatistics = container.locator('.grid-statistics');
this.totalActivatedUsers = new StatCard(
gridStatistics.locator('.grid-statistics__card').filter({hasText: 'Total Activated Users'}),
);
this.publicChannels = new StatCard(
gridStatistics.locator('.grid-statistics__card').filter({hasText: 'Public Channels'}),
);
this.privateChannels = new StatCard(
gridStatistics.locator('.grid-statistics__card').filter({hasText: 'Private Channels'}),
);
this.totalPosts = new StatCard(
gridStatistics.locator('.grid-statistics__card').filter({hasText: 'Total Posts'}),
);
this.totalPostsChart = new ChartSection(
container.locator('.total-count.by-day').filter({hasText: 'Total Posts'}),
);
this.activeUsersWithPostsChart = new ChartSection(
container.locator('.total-count.by-day').filter({hasText: 'Active Users With Posts'}),
);
this.recentActiveUsers = new TableSection(
container.locator('.recent-active-users').filter({hasText: 'Recent Active Users'}),
);
this.newlyCreatedUsers = new TableSection(
container.locator('.recent-active-users').filter({hasText: 'Newly Created Users'}),
);
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.header).toBeVisible();
}
async selectTeam(teamName: string) {
// Wait for the dropdown to be enabled (it may be disabled while loading)
await expect(this.teamFilterDropdown).toBeEnabled();
await this.teamFilterDropdown.selectOption({label: teamName});
}
async selectTeamById(teamId: string) {
// Wait for the dropdown to be enabled (it may be disabled while loading)
await expect(this.teamFilterDropdown).toBeEnabled();
await this.teamFilterDropdown.selectOption({value: teamId});
}
async getSelectedTeam(): Promise<string> {
return (await this.teamFilterDropdown.inputValue()) ?? '';
}
/**
* Verify the team statistics header shows the expected team name
*/
async toHaveTeamHeader(teamDisplayName: string) {
const heading = this.container.getByText(`Team Statistics for ${teamDisplayName}`, {exact: true});
await expect(heading).toBeVisible();
}
}
class StatCard {
readonly container: Locator;
readonly title: Locator;
readonly value: Locator;
constructor(container: Locator) {
this.container = container;
this.title = container.locator('.title');
this.value = container.locator('.content');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async getValue(): Promise<string> {
return (await this.value.textContent()) ?? '';
}
}
class ChartSection {
readonly container: Locator;
readonly title: Locator;
readonly content: Locator;
constructor(container: Locator) {
this.container = container;
this.title = container.locator('.title');
this.content = container.locator('.content');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async hasNoData(): Promise<boolean> {
const text = await this.content.textContent();
return text?.includes('Not enough data') ?? false;
}
}
class TableSection {
readonly container: Locator;
readonly title: Locator;
readonly table: Locator;
constructor(container: Locator) {
this.container = container;
this.title = container.locator('.title');
this.table = container.locator('table');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}

View file

@ -3,75 +3,98 @@
import {Locator, expect} from '@playwright/test';
import {RadioSetting, TextInputSetting, DropdownSetting} from '../../base_components';
/**
* System Console -> Site Configuration -> Notifications
*/
export default class SystemConsoleNotifications {
export default class Notifications {
readonly container: Locator;
// header
// Header
readonly header: Locator;
// Notification Display Name
readonly notificationDisplayName: Locator;
readonly notificationDisplayNameInput: Locator;
readonly notificationDisplayNameHelpText: Locator;
// Radio Settings
readonly showMentionConfirmDialog: RadioSetting;
readonly enableEmailNotifications: RadioSetting;
readonly enablePreviewModeBanner: RadioSetting;
readonly enableEmailBatching: RadioSetting;
readonly enableNotificationMonitoring: RadioSetting;
// Notification From Address
readonly notificationFromAddress: Locator;
readonly notificationFromAddressInput: Locator;
readonly notificationFromAddressHelpText: Locator;
// Dropdown Settings
readonly emailNotificationContents: DropdownSetting;
readonly pushNotificationContents: DropdownSetting;
// Support Email Address
readonly supportEmailAddress: Locator;
readonly supportEmailAddressInput: Locator;
readonly supportEmailHelpText: Locator;
// Text Input Settings
readonly notificationDisplayName: TextInputSetting;
readonly notificationFromAddress: TextInputSetting;
readonly supportEmailAddress: TextInputSetting;
readonly notificationReplyToAddress: TextInputSetting;
readonly notificationFooterMailingAddress: TextInputSetting;
// Push Notification Contents
readonly pushNotificationContents: Locator;
readonly pushNotificationContentsDropdown: Locator;
readonly pushNotificationContentsHelpText: Locator;
// Save button
// Save section
readonly saveButton: Locator;
readonly errorMessage: Locator;
constructor(container: Locator) {
this.container = container;
// header
this.header = this.container.locator('.admin-console__header').getByText('Notifications');
this.header = container.getByText('Notifications', {exact: true});
// Notification Display Name
this.notificationDisplayName = this.container.getByTestId('EmailSettings.FeedbackNameinput');
this.notificationDisplayNameInput = this.container.getByTestId('EmailSettings.FeedbackNameinput');
this.notificationDisplayNameHelpText = this.container.getByTestId('EmailSettings.FeedbackNamehelp-text');
// Notification From Address
this.notificationFromAddress = this.container.getByLabel('Notification From Address:');
this.notificationFromAddressInput = this.container.getByTestId('EmailSettings.FeedbackEmailinput');
this.notificationFromAddressHelpText = this.container.getByTestId('EmailSettings.FeedbackEmailhelp-text');
// Support Email Address
this.supportEmailAddress = this.container.getByLabel('Support Email Address:');
this.supportEmailAddressInput = this.container.getByTestId('SupportSettings.SupportEmailinput');
this.supportEmailHelpText = this.container.getByTestId('SupportSettings.SupportEmailhelp-text');
// Push Notification Contents
this.pushNotificationContents = this.container.getByTestId('EmailSettings.PushNotificationContents');
this.pushNotificationContentsDropdown = this.container.getByTestId(
'EmailSettings.PushNotificationContentsdropdown',
this.showMentionConfirmDialog = new RadioSetting(
container.getByRole('group', {name: /Show @channel, @all, @here and group mention confirmation dialog/}),
);
this.pushNotificationContentsHelpText = this.container.getByTestId(
'EmailSettings.PushNotificationContentshelp-text',
this.enableEmailNotifications = new RadioSetting(
container.getByRole('group', {name: /Enable Email Notifications/}),
);
this.enablePreviewModeBanner = new RadioSetting(
container.getByRole('group', {name: /Enable Preview Mode Banner/}),
);
this.enableEmailBatching = new RadioSetting(container.getByRole('group', {name: /Enable Email Batching/}));
this.enableNotificationMonitoring = new RadioSetting(
container.getByRole('group', {name: /Enable Notification Monitoring/}),
);
// Save button and error message
this.saveButton = this.container.getByTestId('saveSetting');
this.errorMessage = this.container.locator('.has-error');
this.emailNotificationContents = new DropdownSetting(
container.locator('.form-group').filter({hasText: 'Email Notification Contents:'}),
'Email Notification Contents:',
);
this.pushNotificationContents = new DropdownSetting(
container.locator('.form-group').filter({hasText: 'Push Notification Contents:'}),
'Push Notification Contents:',
);
this.notificationDisplayName = new TextInputSetting(
container.locator('.form-group').filter({hasText: 'Notification Display Name:'}),
'Notification Display Name:',
);
this.notificationFromAddress = new TextInputSetting(
container.locator('.form-group').filter({hasText: 'Notification From Address:'}),
'Notification From Address:',
);
this.supportEmailAddress = new TextInputSetting(
container.locator('.form-group').filter({hasText: 'Support Email Address:'}),
'Support Email Address:',
);
this.notificationReplyToAddress = new TextInputSetting(
container.locator('.form-group').filter({hasText: 'Notification Reply-To Address:'}),
'Notification Reply-To Address:',
);
this.notificationFooterMailingAddress = new TextInputSetting(
container.locator('.form-group').filter({hasText: 'Notification Footer Mailing Address:'}),
'Notification Footer Mailing Address:',
);
this.saveButton = container.getByRole('button', {name: 'Save'});
this.errorMessage = container.locator('.has-error');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.header).toBeVisible();
}
async save() {
await this.saveButton.click();
}
}

View file

@ -1,50 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
export default class SystemUsersColumnToggleMenu {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Return the locator for the menu item with the given name.
*/
async getMenuItem(menuItem: string) {
const menuItemLocator = this.container.getByRole('menuitemcheckbox').filter({hasText: menuItem});
await menuItemLocator.waitFor();
return menuItemLocator;
}
/**
* Returns the list of locators for all the menu items.
*/
async getAllMenuItems() {
const menuItemLocators = this.container.getByRole('menuitemcheckbox');
return menuItemLocators;
}
/**
* Pass in the item name to check/uncheck the menu item.
*/
async clickMenuItem(menuItem: string) {
const menuItemLocator = await this.getMenuItem(menuItem);
await menuItemLocator.click();
}
/**
* Close column toggle menu.
*/
async close() {
await this.container.press('Escape');
await expect(this.container).not.toBeVisible();
}
}

View file

@ -1,44 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* The dropdown menu which appears for both Role and Status filter.
*/
export default class SystemUsersFilterMenu {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Return the locator for the menu item with the given name.
*/
async getMenuItem(menuItem: string) {
const menuItemLocator = this.container.getByText(menuItem);
await menuItemLocator.waitFor();
return menuItemLocator;
}
/**
* Clicks on the menu item with the given name.
*/
async clickMenuItem(menuItem: string) {
const menuItemLocator = await this.getMenuItem(menuItem);
await menuItemLocator.click();
}
/**
* Close the menu.
*/
async close() {
await this.container.press('Escape');
}
}

View file

@ -1,68 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
export default class SystemUsersFilterPopover {
readonly container: Locator;
readonly teamMenuInput: Locator;
readonly roleMenuButton: Locator;
readonly statusMenuButton: Locator;
readonly applyButton: Locator;
constructor(container: Locator) {
this.container = container;
this.teamMenuInput = this.container.locator('#asyncTeamSelectInput');
this.roleMenuButton = this.container.locator('#DropdownInput_filterRole');
this.statusMenuButton = this.container.locator('#DropdownInput_filterStatus');
this.applyButton = this.container.getByText('Apply');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.applyButton).toBeVisible();
}
/**
* Save the filter settings.
*/
async save() {
await this.applyButton.click();
}
/**
* Allows to type in the team filter for searching.
*/
async searchInTeamMenu(teamDisplayName: string) {
expect(this.teamMenuInput).toBeVisible();
await this.teamMenuInput.fill(teamDisplayName);
}
/**
* Opens the role filter menu.
*/
async openRoleMenu() {
expect(this.roleMenuButton).toBeVisible();
await this.roleMenuButton.click();
}
/**
* Opens the status filter menu.
*/
async openStatusMenu() {
expect(this.statusMenuButton).toBeVisible();
await this.statusMenuButton.click();
}
/**
* Closes the filter popover.
*/
async close() {
await this.container.press('Escape');
await expect(this.container).not.toBeVisible();
}
}

View file

@ -1,185 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator, Page} from '@playwright/test';
/**
* System Console -> Environment -> Mobile Security
*/
export default class MobileSecurity {
readonly page: Page;
readonly container: Locator;
readonly enableBiometricAuthenticationToggleTrue: Locator;
readonly enableBiometricAuthenticationToggleFalse: Locator;
readonly preventScreenCaptureToggleTrue: Locator;
readonly preventScreenCaptureToggleFalse: Locator;
readonly jailbreakProtectionToggleTrue: Locator;
readonly jailbreakProtectionToggleFalse: Locator;
readonly enableSecureFilePreviewToggleTrue: Locator;
readonly enableSecureFilePreviewToggleFalse: Locator;
readonly allowPdfLinkNavigationToggleTrue: Locator;
readonly allowPdfLinkNavigationToggleFalse: Locator;
readonly enableIntuneMAMToggleTrue: Locator;
readonly enableIntuneMAMToggleFalse: Locator;
// New IntuneSettings fields
readonly enableIntuneToggleTrue: Locator;
readonly enableIntuneToggleFalse: Locator;
readonly intuneAuthServiceDropdown: Locator;
readonly intuneTenantIdInput: Locator;
readonly intuneClientIdInput: Locator;
readonly intuneTenantIdRequiredError: Locator;
readonly saveButton: Locator;
constructor(container: Locator, page: Page) {
this.container = container;
this.page = page;
this.enableBiometricAuthenticationToggleTrue = this.container.getByTestId(
'NativeAppSettings.MobileEnableBiometricstrue',
);
this.enableBiometricAuthenticationToggleFalse = this.container.getByTestId(
'NativeAppSettings.MobileEnableBiometricsfalse',
);
this.preventScreenCaptureToggleTrue = this.container.getByTestId(
'NativeAppSettings.MobilePreventScreenCapturetrue',
);
this.preventScreenCaptureToggleFalse = this.container.getByTestId(
'NativeAppSettings.MobilePreventScreenCapturefalse',
);
this.jailbreakProtectionToggleTrue = this.container.getByTestId(
'NativeAppSettings.MobileJailbreakProtectiontrue',
);
this.jailbreakProtectionToggleFalse = this.container.getByTestId(
'NativeAppSettings.MobileJailbreakProtectionfalse',
);
this.jailbreakProtectionToggleTrue = this.container.getByTestId(
'NativeAppSettings.MobileJailbreakProtectiontrue',
);
this.jailbreakProtectionToggleFalse = this.container.getByTestId(
'NativeAppSettings.MobileJailbreakProtectionfalse',
);
this.enableSecureFilePreviewToggleTrue = this.container.getByTestId(
'NativeAppSettings.MobileEnableSecureFilePreviewtrue',
);
this.enableSecureFilePreviewToggleFalse = this.container.getByTestId(
'NativeAppSettings.MobileEnableSecureFilePreviewfalse',
);
this.allowPdfLinkNavigationToggleTrue = this.container.getByTestId(
'NativeAppSettings.MobileAllowPdfLinkNavigationtrue',
);
this.allowPdfLinkNavigationToggleFalse = this.container.getByTestId(
'NativeAppSettings.MobileAllowPdfLinkNavigationfalse',
);
// Legacy Intune toggle (will be removed in Phase 6)
this.enableIntuneMAMToggleTrue = this.container.getByTestId('IntuneSettings.Enabletrue');
this.enableIntuneMAMToggleFalse = this.container.getByTestId('IntuneSettings.Enablefalse');
// New IntuneSettings fields
this.enableIntuneToggleTrue = this.container.getByTestId('IntuneSettings.Enabletrue');
this.enableIntuneToggleFalse = this.container.getByTestId('IntuneSettings.Enablefalse');
this.intuneAuthServiceDropdown = this.container.getByTestId('IntuneSettings.AuthServicedropdown');
this.intuneTenantIdInput = this.container.getByTestId('IntuneSettings.TenantIdinput');
this.intuneClientIdInput = this.container.getByTestId('IntuneSettings.ClientIdinput');
this.intuneTenantIdRequiredError = this.container.getByTestId('errorMessage');
this.saveButton = this.container.getByRole('button', {name: 'Save'});
}
async discardChanges() {
this.page.getByRole('button', {name: 'Yes, Discard'}).click();
}
async intuneTenantIdRequiredErrorToBeVisible() {
await expect(this.intuneTenantIdRequiredError).toBeVisible();
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async clickEnableBiometricAuthenticationToggleTrue() {
await this.enableBiometricAuthenticationToggleTrue.click();
}
async clickEnableBiometricAuthenticationToggleFalse() {
await this.enableBiometricAuthenticationToggleFalse.click();
}
async clickPreventScreenCaptureToggleTrue() {
await this.preventScreenCaptureToggleTrue.click();
}
async clickPreventScreenCaptureToggleFalse() {
await this.preventScreenCaptureToggleFalse.click();
}
async clickJailbreakProtectionToggleTrue() {
await this.jailbreakProtectionToggleTrue.click();
}
async clickJailbreakProtectionToggleFalse() {
await this.jailbreakProtectionToggleFalse.click();
}
async clickEnableSecureFilePreviewToggleTrue() {
await this.enableSecureFilePreviewToggleTrue.click();
}
async clickEnableSecureFilePreviewToggleFalse() {
await this.enableSecureFilePreviewToggleFalse.click();
}
async clickAllowPdfLinkNavigationToggleTrue() {
await this.allowPdfLinkNavigationToggleTrue.click();
}
async clickAllowPdfLinkNavigationToggleFalse() {
await this.allowPdfLinkNavigationToggleFalse.click();
}
async clickEnableIntuneMAMToggleTrue() {
await this.enableIntuneMAMToggleTrue.click();
}
async selectAuthProvider(value: 'office365' | 'saml') {
await this.intuneAuthServiceDropdown.selectOption(value);
}
async clickEnableIntuneMAMToggleFalse() {
await this.enableIntuneMAMToggleFalse.click();
}
// New IntuneSettings methods
async clickEnableIntuneToggleTrue() {
await this.enableIntuneToggleTrue.click();
}
async clickEnableIntuneToggleFalse() {
await this.enableIntuneToggleFalse.click();
}
async selectIntuneAuthService(value: 'office365' | 'saml') {
await this.intuneAuthServiceDropdown.selectOption(value);
}
async fillIntuneTenantId(value: string) {
await this.intuneTenantIdInput.fill(value);
}
async fillIntuneClientId(value: string) {
await this.intuneClientIdInput.fill(value);
}
async clickSaveButton() {
await this.saveButton.click();
}
}

View file

@ -1,132 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* System Console -> User Management -> Users
*/
export default class SystemUsers {
readonly container: Locator;
readonly searchInput: Locator;
readonly saveRoleChange: Locator;
readonly columnToggleMenuButton: Locator;
readonly dateRangeSelectorMenuButton: Locator;
readonly exportButton: Locator;
readonly filterPopoverButton: Locator;
readonly actionMenuButtons: Locator[];
readonly loadingSpinner: Locator;
constructor(container: Locator) {
this.container = container;
this.searchInput = this.container.getByLabel('Search users');
this.saveRoleChange = this.container.locator('button.btn-primary:has-text("Save")');
this.columnToggleMenuButton = this.container.locator('#systemUsersColumnTogglerMenuButton');
this.dateRangeSelectorMenuButton = this.container.locator('#systemUsersDateRangeSelectorMenuButton');
this.exportButton = this.container.getByText('Export');
this.filterPopoverButton = this.container.getByText(/Filters \(\d+\)/);
this.actionMenuButtons = Array.from(Array(10).keys()).map((index) =>
this.container.locator(`#actionMenuButton-systemUsersTable-${index}`),
);
this.loadingSpinner = this.container.getByText('Loading');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async isLoadingComplete() {
await expect(this.loadingSpinner).toHaveCount(0);
}
/**
* Returns the locator for the header of the given column.
*/
async getColumnHeader(columnName: string) {
const columnHeader = this.container.getByRole('columnheader').filter({hasText: columnName});
return columnHeader;
}
/**
* Checks if given column exists in the table. By searching for the column header.
*/
async doesColumnExist(columnName: string) {
const columnHeader = await this.getColumnHeader(columnName);
return await columnHeader.isVisible();
}
/**
* Clicks on the column header of the given column for sorting.
*/
async clickSortOnColumn(columnName: string) {
const columnHeader = await this.getColumnHeader(columnName);
await columnHeader.waitFor();
await columnHeader.click();
}
/**
* Return the locator for the given row number. If '0' is passed, it will return the header row.
*/
async getNthRow(rowNumber: number) {
const row = this.container.getByRole('row').nth(rowNumber);
await row.waitFor();
return row;
}
/**
* Opens the Filter popover
*/
async openFilterPopover() {
expect(this.filterPopoverButton).toBeVisible();
await this.filterPopoverButton.click();
}
/**
* Open the column toggle menu
*/
async openColumnToggleMenu() {
expect(this.columnToggleMenuButton).toBeVisible();
await this.columnToggleMenuButton.click();
}
/**
* Open the date range selector menu
*/
async openDateRangeSelectorMenu() {
expect(this.dateRangeSelectorMenuButton).toBeVisible();
await this.dateRangeSelectorMenuButton.click();
}
/**
* Enter the given search term in the search input
*/
async enterSearchText(searchText: string) {
expect(this.searchInput).toBeVisible();
await this.searchInput.fill(`${searchText}`);
await this.isLoadingComplete();
}
/**
* Searches and verifies that the row with given text is found
*/
async verifyRowWithTextIsFound(text: string) {
const foundUser = this.container.getByText(text);
await foundUser.waitFor();
await expect(foundUser).toBeVisible();
}
/**
* Searches and verifies that the row with given text is not found
*/
async verifyRowWithTextIsNotFound(text: string) {
const foundUser = this.container.getByText(text);
await expect(foundUser).not.toBeVisible();
}
}

View file

@ -0,0 +1,154 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
import SystemRoles from './system_roles';
/**
* System Console -> User Management -> Delegated Granular Administration
*/
export default class DelegatedGranularAdministration {
readonly container: Locator;
readonly header: Locator;
// Admin Roles Panel
readonly adminRolesPanel: AdminRolesPanel;
// System Roles page (accessed by clicking Edit on a role row)
readonly systemRoles: SystemRoles;
constructor(container: Locator) {
this.container = container;
this.header = container.getByText('Delegated Granular Administration', {exact: true});
this.adminRolesPanel = new AdminRolesPanel(container.locator('#SystemRoles'));
// System Roles page (click Edit on a role to navigate here)
this.systemRoles = new SystemRoles(container);
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.header).toBeVisible();
}
}
class AdminRolesPanel {
readonly container: Locator;
readonly title: Locator;
readonly description: Locator;
private readonly dataGrid: DataGrid;
constructor(container: Locator) {
this.container = container;
this.title = container.getByRole('heading', {name: 'Admin Roles'});
this.description = container.getByText('Manage different levels of access to the system console.');
this.dataGrid = new DataGrid(container.locator('.DataGrid'));
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.title).toBeVisible();
}
// Shortcuts to role rows
get systemAdmin() {
return this.dataGrid.systemAdmin;
}
get systemManager() {
return this.dataGrid.systemManager;
}
get userManager() {
return this.dataGrid.userManager;
}
get customGroupManager() {
return this.dataGrid.customGroupManager;
}
get viewer() {
return this.dataGrid.viewer;
}
}
class DataGrid {
readonly container: Locator;
readonly header: Locator;
readonly rows: Locator;
// Role rows
readonly systemAdmin: RoleRow;
readonly systemManager: RoleRow;
readonly userManager: RoleRow;
readonly customGroupManager: RoleRow;
readonly viewer: RoleRow;
constructor(container: Locator) {
this.container = container;
this.header = container.locator('.DataGrid_header');
this.rows = container.locator('.DataGrid_rows');
// Individual role rows
this.systemAdmin = new RoleRow(
this.rows.locator('.DataGrid_row').filter({hasText: 'System Admin'}),
'system_admin_edit',
);
this.systemManager = new RoleRow(
this.rows.locator('.DataGrid_row').filter({hasText: 'System Manager'}),
'system_manager_edit',
);
this.userManager = new RoleRow(
this.rows.locator('.DataGrid_row').filter({hasText: 'User Manager'}),
'system_user_manager_edit',
);
this.customGroupManager = new RoleRow(
this.rows.locator('.DataGrid_row').filter({hasText: 'Custom Group Manager'}),
'system_custom_group_admin_edit',
);
this.viewer = new RoleRow(
this.rows.locator('.DataGrid_row').filter({hasText: 'Viewer'}),
'system_read_only_admin_edit',
);
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
class RoleRow {
readonly container: Locator;
readonly roleName: Locator;
readonly description: Locator;
readonly type: Locator;
readonly editLink: Locator;
constructor(container: Locator, editTestId: string) {
this.container = container;
const cells = container.locator('.DataGrid_cell');
this.roleName = cells.nth(0);
this.description = cells.nth(1);
this.type = cells.nth(2);
this.editLink = container.getByTestId(editTestId).getByRole('link', {name: 'Edit'});
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async clickEdit() {
await this.editLink.click();
}
async getRoleName(): Promise<string> {
return (await this.roleName.textContent()) ?? '';
}
async getDescription(): Promise<string> {
return (await this.description.textContent()) ?? '';
}
async getType(): Promise<string> {
return (await this.type.textContent()) ?? '';
}
}

View file

@ -0,0 +1,392 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* System Console -> User Management -> Delegated Granular Administration -> [Role] Edit
* This page is shown when editing a specific role (e.g., System Manager, User Manager, etc.)
*/
export default class SystemRoles {
readonly container: Locator;
// Header
readonly backLink: Locator;
readonly roleName: Locator;
// Privileges Panel
readonly privilegesPanel: PrivilegesPanel;
// Assigned People Panel
readonly assignedPeoplePanel: AssignedPeoplePanel;
// Save section
readonly saveButton: Locator;
readonly cancelButton: Locator;
readonly errorMessage: Locator;
constructor(container: Locator) {
this.container = container;
this.backLink = container.locator('.admin-console__header .back');
this.roleName = container.locator('.admin-console__header span').last();
this.privilegesPanel = new PrivilegesPanel(container.locator('#SystemRolePermissions'));
this.assignedPeoplePanel = new AssignedPeoplePanel(container.locator('#SystemRoleUsers'));
this.saveButton = container.getByTestId('saveSetting');
this.cancelButton = container.getByRole('link', {name: 'Cancel'});
this.errorMessage = container.locator('.error-message');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.roleName).toBeVisible();
}
async goBack() {
await this.backLink.click();
}
async save() {
await this.saveButton.click();
}
async cancel() {
await this.cancelButton.click();
}
async getRoleName(): Promise<string> {
return (await this.roleName.textContent()) ?? '';
}
}
class PrivilegesPanel {
readonly container: Locator;
readonly title: Locator;
readonly description: Locator;
// Permission sections
readonly about: PermissionSection;
readonly reporting: PermissionSection;
readonly userManagement: PermissionSection;
readonly environment: PermissionSection;
readonly siteConfiguration: PermissionSection;
readonly authentication: PermissionSection;
readonly plugins: PermissionSection;
readonly integrations: PermissionSection;
readonly compliance: PermissionSection;
readonly experimental: PermissionSection;
constructor(container: Locator) {
this.container = container;
this.title = container.getByRole('heading', {name: 'Privileges'});
this.description = container.getByText('Level of access to the system console.');
// Permission sections
this.about = new PermissionSection(container, 'permission_section_about');
this.reporting = new PermissionSection(container, 'permission_section_reporting');
this.userManagement = new PermissionSection(container, 'permission_section_user_management');
this.environment = new PermissionSection(container, 'permission_section_environment');
this.siteConfiguration = new PermissionSection(container, 'permission_section_site');
this.authentication = new PermissionSection(container, 'permission_section_authentication');
this.plugins = new PermissionSection(container, 'permission_section_plugins');
this.integrations = new PermissionSection(container, 'permission_section_integrations');
this.compliance = new PermissionSection(container, 'permission_section_compliance');
this.experimental = new PermissionSection(container, 'permission_section_experimental');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.title).toBeVisible();
}
}
class PermissionSection {
readonly container: Locator;
readonly row: Locator;
readonly title: Locator;
readonly description: Locator;
readonly subsectionsToggle: Locator;
readonly dropdownButton: Locator;
readonly subsectionsContainer: Locator;
private readonly panelContainer: Locator;
private readonly testId: string;
private readonly sectionName: string;
constructor(panelContainer: Locator, testId: string) {
this.panelContainer = panelContainer;
this.testId = testId;
// Extract section name from testId (e.g., 'permission_section_user_management' -> 'user_management')
this.sectionName = testId.replace('permission_section_', '');
this.container = panelContainer.getByTestId(testId);
// Use CSS :has() selector to find the row containing this section
this.row = panelContainer.locator(`.PermissionRow:has([data-testid="${testId}"])`);
this.title = this.container.locator('.PermissionSectionText_title');
this.description = this.container.locator('.PermissionSection_description');
this.subsectionsToggle = this.container.locator('.PermissionSubsectionsToggle button');
// Use the dropdown button ID which is more reliable
this.dropdownButton = panelContainer.page().locator(`#systemRolePermissionDropdown${this.sectionName}`);
this.subsectionsContainer = this.row.locator('.PermissionSubsections');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Get the current permission value (e.g., "Can edit", "Read only", "No access", "Mixed access")
*/
async getPermissionValue(): Promise<string> {
return (await this.dropdownButton.locator('.PermissionSectionDropdownButton_text').textContent()) ?? '';
}
/**
* Set permission for this section
* @param permission - "Can edit", "Read only", or "No access"
*/
async setPermission(permission: 'Can edit' | 'Read only' | 'No access') {
await expect(this.dropdownButton).toBeVisible();
await this.dropdownButton.click();
// Wait for the MenuWrapper to have --open class which indicates menu is open
const menuWrapper = this.dropdownButton.locator('xpath=ancestor::div[contains(@class, "MenuWrapper")]');
await expect(menuWrapper).toHaveClass(/MenuWrapper--open/);
// Find the menu items and click the one matching the permission
const menuItem = menuWrapper.locator('.Menu__content li').filter({hasText: permission});
await expect(menuItem).toBeVisible();
await menuItem.click();
// Wait for menu to close
await expect(menuWrapper).not.toHaveClass(/MenuWrapper--open/);
}
/**
* Expand subsections if they are collapsed
*/
async expandSubsections() {
const hasToggle = await this.subsectionsToggle.isVisible();
if (!hasToggle) {
return;
}
const buttonText = await this.subsectionsToggle.textContent();
if (buttonText?.includes('Show')) {
await this.subsectionsToggle.click();
// Wait for subsections to be visible
await expect(this.subsectionsContainer).toBeVisible();
}
}
/**
* Collapse subsections if they are expanded
*/
async collapseSubsections() {
const hasToggle = await this.subsectionsToggle.isVisible();
if (!hasToggle) {
return;
}
const buttonText = await this.subsectionsToggle.textContent();
if (buttonText?.includes('Hide')) {
await this.subsectionsToggle.click();
}
}
/**
* Check if subsections are visible
*/
async hasSubsections(): Promise<boolean> {
return this.subsectionsToggle.isVisible();
}
/**
* Get a subsection by its testId suffix
* @param subsectionName - The suffix of the subsection testId (e.g., "team_statistics" for "permission_section_reporting_team_statistics")
*/
getSubsection(subsectionName: string): PermissionSubsection {
const subsectionTestId = `${this.testId}_${subsectionName}`;
return new PermissionSubsection(this.panelContainer, subsectionTestId);
}
// Reporting subsections shortcuts
get siteStatistics() {
return this.getSubsection('site_statistics');
}
get teamStatistics() {
return this.getSubsection('team_statistics');
}
get serverLogs() {
return this.getSubsection('server_logs');
}
// User Management subsections shortcuts
get users() {
return this.getSubsection('users');
}
get groups() {
return this.getSubsection('groups');
}
get teams() {
return this.getSubsection('teams');
}
get channels() {
return this.getSubsection('channels');
}
get permissions() {
return this.getSubsection('permissions');
}
get systemRoles() {
return this.getSubsection('system_roles');
}
}
class PermissionSubsection {
readonly container: Locator;
readonly title: Locator;
readonly description: Locator;
readonly dropdownButton: Locator;
private readonly sectionName: string;
constructor(panelContainer: Locator, testId: string) {
this.container = panelContainer.getByTestId(testId);
this.title = this.container.locator('.PermissionSectionText_title');
this.description = this.container.locator('.PermissionSection_description');
// Extract section name from testId (e.g., 'permission_section_user_management_teams' -> 'user_management_teams')
this.sectionName = testId.replace('permission_section_', '');
// Use the dropdown button ID which is more reliable
this.dropdownButton = panelContainer.page().locator(`#systemRolePermissionDropdown${this.sectionName}`);
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Get the current permission value (e.g., "Can edit", "Read only", "No access")
*/
async getPermissionValue(): Promise<string> {
return (await this.dropdownButton.locator('.PermissionSectionDropdownButton_text').textContent()) ?? '';
}
/**
* Set permission for this subsection
* @param permission - "Can edit", "Read only", or "No access"
*/
async setPermission(permission: 'Can edit' | 'Read only' | 'No access') {
// Wait for subsection to be visible
await this.toBeVisible();
// Click the dropdown button to open the menu
await expect(this.dropdownButton).toBeVisible();
await this.dropdownButton.click();
// Wait for the MenuWrapper to have --open class which indicates menu is open
const menuWrapper = this.dropdownButton.locator('xpath=ancestor::div[contains(@class, "MenuWrapper")]');
await expect(menuWrapper).toHaveClass(/MenuWrapper--open/);
// Find the menu items and click the one matching the permission
const menuItem = menuWrapper.locator('.Menu__content li').filter({hasText: permission});
await expect(menuItem).toBeVisible();
await menuItem.click();
// Wait for menu to close
await expect(menuWrapper).not.toHaveClass(/MenuWrapper--open/);
}
}
class AssignedPeoplePanel {
readonly container: Locator;
readonly title: Locator;
readonly description: Locator;
readonly addPeopleButton: Locator;
readonly searchInput: Locator;
readonly userRows: Locator;
readonly paginationInfo: Locator;
readonly previousPageButton: Locator;
readonly nextPageButton: Locator;
constructor(container: Locator) {
this.container = container;
this.title = container.getByRole('heading', {name: 'Assigned People'});
this.description = container.getByText('List of people assigned to this system role.');
this.addPeopleButton = container.getByRole('button', {name: 'Add People'});
this.searchInput = container.getByTestId('searchInput');
this.userRows = container.locator('.DataGrid_rows .DataGrid_row');
this.paginationInfo = container.locator('.DataGrid_footer span');
this.previousPageButton = container.locator('.DataGrid_footer .prev');
this.nextPageButton = container.locator('.DataGrid_footer .next');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.title).toBeVisible();
}
async clickAddPeople() {
await this.addPeopleButton.click();
}
async searchUsers(searchTerm: string) {
await this.searchInput.fill(searchTerm);
}
async clearSearch() {
await this.searchInput.clear();
}
async getUserCount(): Promise<number> {
return this.userRows.count();
}
/**
* Get a user row by index (0-based)
*/
getUserRowByIndex(index: number): AssignedUserRow {
return new AssignedUserRow(this.userRows.nth(index));
}
/**
* Get a user row by username
*/
getUserRowByUsername(username: string): AssignedUserRow {
const row = this.userRows.filter({hasText: username});
return new AssignedUserRow(row);
}
}
class AssignedUserRow {
readonly container: Locator;
readonly avatar: Locator;
readonly name: Locator;
readonly email: Locator;
readonly removeLink: Locator;
constructor(container: Locator) {
this.container = container;
this.avatar = container.locator('.Avatar');
this.name = container.locator('.UserGrid_name span').first();
this.email = container.locator('.ug-email');
this.removeLink = container.getByRole('link', {name: 'Remove'});
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async getName(): Promise<string> {
return (await this.name.textContent()) ?? '';
}
async getEmail(): Promise<string> {
return (await this.email.textContent()) ?? '';
}
async remove() {
await this.removeLink.click();
}
}

View file

@ -0,0 +1,214 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* System Console -> User Management -> Users -> User Detail
* Accessed by clicking on a user row in the Users list
*/
export default class UserDetail {
readonly container: Locator;
// Header
readonly backLink: Locator;
readonly header: Locator;
// User Card
readonly userCard: AdminUserCard;
// Team Membership Panel
readonly teamMembershipPanel: TeamMembershipPanel;
// Save section
readonly saveButton: Locator;
readonly cancelButton: Locator;
readonly errorMessage: Locator;
constructor(container: Locator) {
this.container = container.locator('.SystemUserDetail');
// Header
this.backLink = this.container.locator('.admin-console__header .back');
this.header = this.container.getByText('User Configuration', {exact: true});
// User Card
this.userCard = new AdminUserCard(this.container.locator('.AdminUserCard'));
// Team Membership Panel
this.teamMembershipPanel = new TeamMembershipPanel(
this.container.locator('.AdminPanel').filter({hasText: 'Team Membership'}),
);
// Save section
this.saveButton = this.container.getByTestId('saveSetting');
this.cancelButton = this.container.getByRole('button', {name: 'Cancel'});
this.errorMessage = this.container.locator('.error-message');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.header).toBeVisible();
}
async goBack() {
await this.backLink.click();
}
async save() {
await expect(this.saveButton).toBeEnabled();
await this.saveButton.click();
}
async cancel() {
await expect(this.cancelButton).toBeVisible();
await this.cancelButton.click();
}
async waitForSaveComplete() {
await expect(this.saveButton).toBeDisabled();
}
}
class AdminUserCard {
readonly container: Locator;
// Header section
readonly profileImage: Locator;
readonly displayName: Locator;
readonly nickname: Locator;
readonly userId: Locator;
// Body section (two-column layout with fields)
readonly body: Locator;
readonly twoColumnLayout: Locator;
readonly fieldRows: Locator;
// Footer section
readonly resetPasswordButton: Locator;
readonly deactivateButton: Locator;
readonly manageUserSettingsButton: Locator;
constructor(container: Locator) {
this.container = container;
// Header
const header = container.locator('.AdminUserCard__header');
this.profileImage = header.locator('.Avatar');
this.displayName = header.locator('.AdminUserCard__user-info span').first();
this.nickname = header.locator('.AdminUserCard__user-nickname');
this.userId = header.locator('.AdminUserCard__user-id');
// Body
this.body = container.locator('.AdminUserCard__body');
this.twoColumnLayout = this.body.locator('.two-column-layout');
this.fieldRows = this.body.locator('.field-row');
// Footer
const footer = container.locator('.AdminUserCard__footer');
this.resetPasswordButton = footer.getByRole('button', {name: 'Reset Password'});
this.deactivateButton = footer.getByRole('button', {name: 'Deactivate'});
this.manageUserSettingsButton = footer.getByRole('button', {name: 'Manage User Settings'});
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
getFieldByLabel(labelText: string): Locator {
return this.body.getByLabel(labelText);
}
getSelectByLabel(labelText: string): Locator {
return this.body.getByLabel(labelText);
}
async fillField(labelText: string, value: string) {
const input = this.getFieldByLabel(labelText);
await input.clear();
await input.fill(value);
}
async getFieldValue(labelText: string): Promise<string> {
const input = this.getFieldByLabel(labelText);
return await input.inputValue();
}
async getUserId(): Promise<string> {
const text = (await this.userId.textContent()) ?? '';
return text.replace('User ID: ', '');
}
}
class TeamMembershipPanel {
readonly container: Locator;
readonly title: Locator;
readonly description: Locator;
readonly addTeamButton: Locator;
readonly teamRows: Locator;
constructor(container: Locator) {
this.container = container;
this.title = container.getByRole('heading', {name: 'Team Membership'});
this.description = container.getByText('Teams to which this user belongs');
this.addTeamButton = container.getByRole('button', {name: 'Add Team'});
this.teamRows = container.locator('.TeamRow');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.title).toBeVisible();
}
async clickAddTeam() {
await this.addTeamButton.click();
}
async getTeamCount(): Promise<number> {
return this.teamRows.count();
}
getTeamRowByIndex(index: number): TeamRow {
return new TeamRow(this.teamRows.nth(index));
}
getTeamRowByName(teamName: string): TeamRow {
return new TeamRow(this.teamRows.filter({hasText: teamName}));
}
}
class TeamRow {
readonly container: Locator;
readonly teamName: Locator;
readonly teamType: Locator;
readonly role: Locator;
readonly actionMenuButton: Locator;
constructor(container: Locator) {
this.container = container;
this.teamName = container.locator('.TeamRow__team-name b');
this.teamType = container.locator('.TeamRow__description').first();
this.role = container.locator('.TeamRow__description').last();
this.actionMenuButton = container.locator('.TeamRow__actions button');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async getTeamName(): Promise<string> {
return (await this.teamName.textContent()) ?? '';
}
async getTeamType(): Promise<string> {
return (await this.teamType.textContent()) ?? '';
}
async getRole(): Promise<string> {
return (await this.role.textContent()) ?? '';
}
async openActionMenu() {
await this.actionMenuButton.click();
}
}

View file

@ -0,0 +1,213 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
import UserDetail from '../user_detail';
import {ColumnToggleMenu, DateRangeMenu, FilterMenu, FilterPopover} from './menus';
import {ManageRolesModal, ResetPasswordModal, UpdateEmailModal} from './modals';
import {UsersTable} from './users_table';
import {ConfirmModal} from '@/ui/components/system_console/base_modal';
// Re-export sub-components for external use
export {ColumnToggleMenu, DateRangeMenu, FilterMenu, FilterPopover} from './menus';
export {ManageRolesModal, ResetPasswordModal, UpdateEmailModal} from './modals';
export {UserActionMenu} from './user_action_menu';
export {UserRow, UsersTable} from './users_table';
/**
* System Console -> User Management -> Users
*/
export default class Users {
readonly container: Locator;
private readonly page;
// User Detail page (accessed by clicking on a user row)
readonly userDetail: UserDetail;
// Modals (opened from user actions)
readonly confirmModal: ConfirmModal;
readonly manageRolesModal: ManageRolesModal;
readonly resetPasswordModal: ResetPasswordModal;
readonly updateEmailModal: UpdateEmailModal;
// Header
readonly header: Locator;
readonly revokeAllSessionsButton: Locator;
// Filters section
readonly searchInput: Locator;
readonly filtersButton: Locator;
readonly columnToggleMenuButton: Locator;
readonly dateRangeSelectorMenuButton: Locator;
readonly exportButton: Locator;
// Loading indicator
readonly loadingSpinner: Locator;
// Table
readonly usersTable: UsersTable;
// Menus and Popovers (populated from page-level locators)
readonly columnToggleMenu: ColumnToggleMenu;
readonly filterPopover: FilterPopover;
readonly roleFilterMenu: FilterMenu;
readonly statusFilterMenu: FilterMenu;
readonly dateRangeMenu: DateRangeMenu;
// Pagination
readonly paginationInfo: Locator;
readonly previousPageButton: Locator;
readonly nextPageButton: Locator;
readonly rowsPerPageSelector: Locator;
constructor(container: Locator) {
this.container = container;
this.page = container.page();
this.userDetail = new UserDetail(container);
// Modals
this.confirmModal = new ConfirmModal(this.page.locator('#confirmModal'));
this.manageRolesModal = new ManageRolesModal(this.page.locator('.manage-teams'));
this.resetPasswordModal = new ResetPasswordModal(this.page.locator('#resetPasswordModal'));
this.updateEmailModal = new UpdateEmailModal(this.page.locator('#resetEmailModal'));
this.header = container.getByText('Mattermost Users', {exact: true});
this.revokeAllSessionsButton = container.getByRole('button', {name: 'Revoke All Sessions'});
this.searchInput = container.getByRole('textbox', {name: 'Search users'});
this.filtersButton = container.getByRole('button', {name: /Filters/});
this.columnToggleMenuButton = container.locator('#systemUsersColumnTogglerMenuButton');
this.dateRangeSelectorMenuButton = container.locator('#systemUsersDateRangeSelectorMenuButton');
this.exportButton = container.getByText('Export');
this.loadingSpinner = container.getByText('Loading');
this.usersTable = new UsersTable(container.locator('#systemUsersTable'));
this.columnToggleMenu = new ColumnToggleMenu(this.page.locator('#systemUsersColumnTogglerMenu'));
this.filterPopover = new FilterPopover(this.page.locator('#systemUsersFilterPopover'));
this.roleFilterMenu = new FilterMenu(this.page.locator('.DropDown__menu'));
this.statusFilterMenu = new FilterMenu(this.page.locator('.DropDown__menu'));
this.dateRangeMenu = new DateRangeMenu(this.page.locator('#systemUsersDateRangeSelectorMenu'));
const footer = container.locator('.adminConsoleListTabletOptionalFoot');
this.paginationInfo = footer.locator('span').first();
this.previousPageButton = footer.getByRole('button', {name: 'Go to previous page'});
this.nextPageButton = footer.getByRole('button', {name: 'Go to next page'});
this.rowsPerPageSelector = footer.locator('.adminConsoleListTablePageSize .react-select');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.header).toBeVisible();
}
/**
* Wait for loading to complete
*/
async isLoadingComplete() {
await expect(this.loadingSpinner).toHaveCount(0);
}
/**
* Search for users by typing in the search input
*/
async searchUsers(searchTerm: string) {
await this.searchInput.fill(searchTerm);
await this.isLoadingComplete();
}
/**
* Clear the search input
*/
async clearSearch() {
await this.searchInput.clear();
}
/**
* Get the current filter count from the Filters button
*/
async getFilterCount(): Promise<number> {
const buttonText = await this.filtersButton.textContent();
const match = buttonText?.match(/Filters \((\d+)\)/);
return match ? parseInt(match[1], 10) : 0;
}
/**
* Open the column toggle menu
*/
async openColumnToggleMenu(): Promise<ColumnToggleMenu> {
await expect(this.columnToggleMenuButton).toBeVisible();
await this.columnToggleMenuButton.click();
await this.columnToggleMenu.toBeVisible();
return this.columnToggleMenu;
}
/**
* Open the filter popover
*/
async openFilterPopover(): Promise<FilterPopover> {
await expect(this.filtersButton).toBeVisible();
await this.filtersButton.click();
await this.filterPopover.toBeVisible();
return this.filterPopover;
}
/**
* Open the date range selector menu
*/
async openDateRangeSelectorMenu(): Promise<DateRangeMenu> {
await expect(this.dateRangeSelectorMenuButton).toBeVisible();
await this.dateRangeSelectorMenuButton.click();
await this.dateRangeMenu.toBeVisible();
return this.dateRangeMenu;
}
/**
* Click the Export button
*/
async clickExport() {
await this.exportButton.click();
}
/**
* Click Revoke All Sessions button
*/
async clickRevokeAllSessions() {
await this.revokeAllSessionsButton.click();
}
/**
* Go to next page
*/
async goToNextPage() {
await this.nextPageButton.click();
}
/**
* Go to previous page
*/
async goToPreviousPage() {
await this.previousPageButton.click();
}
/**
* Get the pagination info text (e.g., "Showing 1 - 10 of 212 users")
*/
async getPaginationInfo(): Promise<string> {
return (await this.paginationInfo.textContent()) ?? '';
}
/**
* Get the total user count from pagination info
*/
async getTotalUserCount(): Promise<number> {
const text = await this.getPaginationInfo();
const match = text.match(/of (\d+) users/);
return match ? parseInt(match[1], 10) : 0;
}
}

View file

@ -0,0 +1,196 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* Column toggle menu that appears when clicking the Columns button
*/
export class ColumnToggleMenu {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Get a menu item by text
*/
async getMenuItem(menuItem: string): Promise<Locator> {
const menuItemLocator = this.container.getByRole('menuitemcheckbox').filter({hasText: menuItem});
await menuItemLocator.waitFor();
return menuItemLocator;
}
/**
* Get all menu items
*/
getAllMenuItems(): Locator {
return this.container.getByRole('menuitemcheckbox');
}
/**
* Click a menu item to toggle it
*/
async clickMenuItem(menuItem: string) {
const item = await this.getMenuItem(menuItem);
await item.click();
}
/**
* Close the menu
*/
async close() {
await this.container.press('Escape');
await expect(this.container).not.toBeVisible();
}
}
/**
* Filter popover that appears when clicking the Filters button
*/
export class FilterPopover {
readonly container: Locator;
readonly teamMenuInput: Locator;
readonly roleMenuButton: Locator;
readonly statusMenuButton: Locator;
readonly applyButton: Locator;
constructor(container: Locator) {
this.container = container;
this.teamMenuInput = container.locator('#asyncTeamSelectInput');
this.roleMenuButton = container.locator('#DropdownInput_filterRole');
this.statusMenuButton = container.locator('#DropdownInput_filterStatus');
this.applyButton = container.getByText('Apply');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.applyButton).toBeVisible();
}
/**
* Save/apply the filter settings and wait for popover to close
*/
async save() {
await this.applyButton.click();
// Apply button closes the popover, wait for it to close
await expect(this.container).not.toBeVisible({timeout: 5000});
}
/**
* Search in the team filter and wait for dropdown options
*/
async searchInTeamMenu(teamDisplayName: string) {
await expect(this.teamMenuInput).toBeVisible();
await this.teamMenuInput.fill(teamDisplayName);
// Wait a bit for async search results to appear
await this.container.page().waitForTimeout(500);
}
/**
* Open the role filter menu
*/
async openRoleMenu() {
await expect(this.roleMenuButton).toBeVisible();
await this.roleMenuButton.click();
}
/**
* Open the status filter menu
*/
async openStatusMenu() {
await expect(this.statusMenuButton).toBeVisible();
await this.statusMenuButton.click();
}
/**
* Close the popover (if still open)
*/
async close() {
const isVisible = await this.container.isVisible();
if (isVisible) {
await this.container.press('Escape');
await expect(this.container).not.toBeVisible();
}
}
}
/**
* Generic filter menu for role/status dropdowns (react-select dropdown)
*/
export class FilterMenu {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Get a menu item by text
*/
async getMenuItem(menuItem: string): Promise<Locator> {
const menuItemLocator = this.container.getByText(menuItem);
await menuItemLocator.waitFor();
return menuItemLocator;
}
/**
* Click a menu item (this also closes the dropdown automatically)
*/
async clickMenuItem(menuItem: string) {
const item = await this.getMenuItem(menuItem);
await item.click();
// Dropdown closes automatically after selection, wait for it
await expect(this.container).not.toBeVisible({timeout: 5000});
}
/**
* Close the menu (if still open)
*/
async close() {
const isVisible = await this.container.isVisible();
if (isVisible) {
await this.container.press('Escape');
}
}
}
/**
* Date range selector menu
*/
export class DateRangeMenu {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Click a menu item
*/
async clickMenuItem(menuItem: string) {
const item = this.container.getByText(menuItem);
await item.waitFor();
await item.click();
}
/**
* Close the menu
*/
async close() {
await this.container.press('Escape');
}
}

View file

@ -0,0 +1,69 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
import BaseModal from '@/ui/components/system_console/base_modal';
/**
* Manage Roles modal (System Console -> Users -> Action -> Manage roles)
*/
export class ManageRolesModal extends BaseModal {
readonly saveButton: Locator;
constructor(container: Locator) {
super(container);
this.saveButton = container.getByRole('button', {name: 'Save'});
}
async save() {
await this.saveButton.click();
await expect(this.container).not.toBeVisible();
}
}
/**
* Reset Password modal (System Console -> Users -> Action -> Reset password)
*/
export class ResetPasswordModal extends BaseModal {
readonly resetButton: Locator;
readonly passwordInput: Locator;
constructor(container: Locator) {
super(container);
this.resetButton = container.getByRole('button', {name: 'Reset'});
this.passwordInput = container.locator('input[type="password"]');
}
async reset() {
await this.resetButton.click();
await expect(this.container).not.toBeVisible();
}
async fillPassword(password: string) {
await this.passwordInput.fill(password);
}
}
/**
* Update Email modal (System Console -> Users -> Action -> Update email)
*/
export class UpdateEmailModal extends BaseModal {
readonly updateButton: Locator;
readonly emailInput: Locator;
constructor(container: Locator) {
super(container);
this.updateButton = container.getByRole('button', {name: 'Update'});
this.emailInput = container.locator('input[type="email"]');
}
async update() {
await this.updateButton.click();
await expect(this.container).not.toBeVisible();
}
async fillEmail(email: string) {
await this.emailInput.fill(email);
}
}

View file

@ -0,0 +1,62 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* User action menu that appears when clicking the action button on a user row
*/
export class UserActionMenu {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Get a menu item by text
*/
getMenuItem(text: string): Locator {
return this.container.getByText(text, {exact: true});
}
/**
* Click a menu item by text
*/
async clickMenuItem(text: string) {
const item = this.getMenuItem(text);
await item.click();
}
async clickDeactivate() {
await this.clickMenuItem('Deactivate');
}
async clickActivate() {
await this.clickMenuItem('Activate');
}
async clickManageRoles() {
await this.clickMenuItem('Manage roles');
}
async clickManageTeams() {
await this.clickMenuItem('Manage teams');
}
async clickResetPassword() {
await this.clickMenuItem('Reset password');
}
async clickUpdateEmail() {
await this.clickMenuItem('Update email');
}
async clickRevokeSessions() {
await this.clickMenuItem('Revoke sessions');
}
}

View file

@ -0,0 +1,209 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
import {UserActionMenu} from './user_action_menu';
/**
* Users table component
*/
export class UsersTable {
readonly container: Locator;
readonly headerRow: Locator;
readonly bodyRows: Locator;
// Column headers
readonly userDetailsHeader: Locator;
readonly emailHeader: Locator;
readonly memberSinceHeader: Locator;
readonly lastLoginHeader: Locator;
readonly lastActivityHeader: Locator;
readonly lastPostHeader: Locator;
readonly daysActiveHeader: Locator;
readonly messagesPostedHeader: Locator;
readonly actionsHeader: Locator;
constructor(container: Locator) {
this.container = container;
this.headerRow = container.locator('thead tr');
this.bodyRows = container.locator('tbody tr');
// Column headers
this.userDetailsHeader = container.locator('#systemUsersTable-header-usernameColumn');
this.emailHeader = container.locator('#systemUsersTable-header-emailColumn');
this.memberSinceHeader = container.locator('#systemUsersTable-header-createAtColumn');
this.lastLoginHeader = container.locator('#systemUsersTable-header-lastLoginColumn');
this.lastActivityHeader = container.locator('#systemUsersTable-header-lastStatusAtColumn');
this.lastPostHeader = container.locator('#systemUsersTable-header-lastPostDateColumn');
this.daysActiveHeader = container.locator('#systemUsersTable-header-daysActiveColumn');
this.messagesPostedHeader = container.locator('#systemUsersTable-header-totalPostsColumn');
this.actionsHeader = container.locator('#systemUsersTable-header-actionsColumn');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Get a user row by index (0-based)
*/
getRowByIndex(index: number): UserRow {
return new UserRow(this.bodyRows.nth(index), index);
}
/**
* Get a column header by display name
*/
getColumnHeader(columnName: string): Locator {
const headerMap: Record<string, Locator> = {
'User details': this.userDetailsHeader,
Email: this.emailHeader,
'Member since': this.memberSinceHeader,
'Last login': this.lastLoginHeader,
'Last activity': this.lastActivityHeader,
'Last post': this.lastPostHeader,
'Days active': this.daysActiveHeader,
'Messages posted': this.messagesPostedHeader,
Actions: this.actionsHeader,
};
const header = headerMap[columnName];
if (!header) {
throw new Error(`Unknown column: ${columnName}`);
}
return header;
}
/**
* Click on a column header to sort by that column
*/
async clickSortOnColumn(columnName: string) {
const header = this.getColumnHeader(columnName);
await header.click();
}
/**
* Click on a sortable column header and wait for sort to complete
* @param columnName - The display name of the column
* @returns The new sort direction after clicking
*/
async sortByColumn(columnName: string): Promise<'ascending' | 'descending' | 'none'> {
const header = this.getColumnHeader(columnName);
// Get current sort direction
const currentSort = await header.getAttribute('aria-sort');
// Click to sort
await header.click();
// Wait for sort direction to change (or for it to be set if it wasn't before)
if (currentSort) {
// Wait for the attribute to change
await expect(header).not.toHaveAttribute('aria-sort', currentSort);
} else {
// Wait for the attribute to be set
await expect(header).toHaveAttribute('aria-sort');
}
// Wait for table to stabilize
await this.waitForLoadingComplete();
// Return the new sort direction
const newSort = await header.getAttribute('aria-sort');
return (newSort as 'ascending' | 'descending' | 'none') ?? 'none';
}
/**
* Wait for the table to finish loading (spinner to disappear)
*/
async waitForLoadingComplete() {
// Wait for any loading spinners to disappear
const loadingSpinner = this.container.locator('.loading-screen, .LoadingSpinner');
await loadingSpinner.waitFor({state: 'detached', timeout: 10000}).catch(() => {
// Spinner may not appear for fast loads, ignore timeout
});
// Also wait for at least one row to be visible
await this.bodyRows.first().waitFor({state: 'visible'});
}
}
/**
* A single row in the users table
*/
export class UserRow {
readonly container: Locator;
readonly index: number;
// Cells
readonly userDetailsCell: Locator;
readonly emailCell: Locator;
readonly memberSinceCell: Locator;
readonly lastLoginCell: Locator;
readonly lastActivityCell: Locator;
readonly lastPostCell: Locator;
readonly daysActiveCell: Locator;
readonly messagesPostedCell: Locator;
readonly actionsCell: Locator;
// User details components
readonly profilePicture: Locator;
readonly displayName: Locator;
readonly userName: Locator;
// Action menu button
readonly actionMenuButton: Locator;
// Action menu (populated after opening)
private readonly actionMenu: UserActionMenu;
constructor(container: Locator, index: number) {
this.container = container;
this.index = index;
this.userDetailsCell = container.locator('.usernameColumn');
this.emailCell = container.locator('.emailColumn');
this.memberSinceCell = container.locator('.createAtColumn');
this.lastLoginCell = container.locator('.lastLoginColumn');
this.lastActivityCell = container.locator('.lastStatusAtColumn');
this.lastPostCell = container.locator('.lastPostDateColumn');
this.daysActiveCell = container.locator('.daysActiveColumn');
this.messagesPostedCell = container.locator('.totalPostsColumn');
this.actionsCell = container.locator('.actionsColumn');
this.profilePicture = this.userDetailsCell.locator('.profilePicture');
this.displayName = this.userDetailsCell.locator('.displayName');
this.userName = this.userDetailsCell.locator('.userName');
this.actionMenuButton = this.actionsCell.getByRole('button');
this.actionMenu = new UserActionMenu(container.page().locator(`#actionMenu-systemUsersTable-${index}`));
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
/**
* Click on the row to view user details
*/
async click() {
await this.container.click();
}
/**
* Get the email
*/
async getEmail(): Promise<string> {
return (await this.emailCell.textContent()) ?? '';
}
/**
* Click the action menu button to open the actions dropdown
* Returns the action menu for further interactions
*/
async openActionMenu(): Promise<UserActionMenu> {
await this.actionMenuButton.click();
await this.actionMenu.toBeVisible();
return this.actionMenu;
}
}

View file

@ -3,37 +3,342 @@
import {Locator, expect} from '@playwright/test';
import SystemConsoleSidebarHeader from './sidebar_header';
export default class SystemConsoleSidebar {
readonly container: Locator;
readonly header: SystemConsoleSidebarHeader;
readonly searchInput: Locator;
readonly about: AboutCategory;
readonly reporting: ReportingCategory;
readonly userManagement: UserManagementCategory;
readonly systemAttributes: SystemAttributesCategory;
readonly environment: EnvironmentCategory;
readonly siteConfiguration: SiteConfigurationCategory;
readonly authentication: AuthenticationCategory;
readonly plugins: PluginsCategory;
readonly integrations: IntegrationsCategory;
readonly compliance: ComplianceCategory;
readonly experimental: ExperimentalCategory;
constructor(container: Locator) {
this.container = container;
this.header = new SystemConsoleSidebarHeader(container.locator('.AdminSidebarHeader'));
this.searchInput = container.getByPlaceholder('Find settings');
this.about = new AboutCategory(container.getByTestId('about'));
this.reporting = new ReportingCategory(container.getByTestId('reporting'));
this.userManagement = new UserManagementCategory(container.getByTestId('user_management'));
this.systemAttributes = new SystemAttributesCategory(container.getByTestId('system_attributes'));
this.environment = new EnvironmentCategory(container.getByTestId('environment'));
this.siteConfiguration = new SiteConfigurationCategory(container.getByTestId('site'));
this.authentication = new AuthenticationCategory(container.getByTestId('authentication'));
this.plugins = new PluginsCategory(container.getByTestId('plugins'));
this.integrations = new IntegrationsCategory(container.getByTestId('integrations'));
this.compliance = new ComplianceCategory(container.getByTestId('compliance'));
this.experimental = new ExperimentalCategory(container.getByTestId('experimental'));
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await this.header.toBeVisible();
await expect(this.searchInput).toBeVisible();
}
/**
* Clicks on the sidebar section link with the given name. Pass the exact name of the section.
* @param sectionName
*/
async goToItem(sectionName: string) {
const section = this.container.getByText(sectionName, {exact: true});
await section.waitFor();
await section.click();
async search(text: string) {
await this.searchInput.fill(text);
}
/**
* Searches for the given item in the sidebar search input.
* @param itemName
*/
async searchForItem(itemName: string) {
await this.searchInput.fill(itemName);
async clearSearch() {
await this.searchInput.clear();
}
// Convenience shortcuts
get editionAndLicense() {
return this.about.editionAndLicense;
}
get users() {
return this.userManagement.users;
}
get groups() {
return this.userManagement.groups;
}
get teams() {
return this.userManagement.teams;
}
get channels() {
return this.userManagement.channels;
}
get permissions() {
return this.userManagement.permissions;
}
get delegatedGranularAdministration() {
return this.userManagement.delegatedGranularAdministration;
}
get mobileSecurity() {
return this.environment.mobileSecurity;
}
get notifications() {
return this.siteConfiguration.notifications;
}
get pluginManagement() {
return this.plugins.pluginManagement;
}
}
class SidebarSection {
readonly container: Locator;
readonly link: Locator;
constructor(container: Locator, link: Locator) {
this.container = container;
this.link = link;
}
async click() {
await this.link.click();
}
async isActive(): Promise<boolean> {
const classAttr = await this.link.getAttribute('class');
return classAttr?.includes('sidebar-section-title--active') ?? false;
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
class SidebarCategory {
readonly container: Locator;
readonly title: Locator;
readonly sections: Locator;
constructor(container: Locator) {
this.container = container;
this.title = container.locator('.category-title');
this.sections = container.locator('ul.sections');
}
protected section(name: string): SidebarSection {
const link = this.sections.getByRole('link', {name, exact: true});
return new SidebarSection(this.sections, link);
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}
class AboutCategory extends SidebarCategory {
readonly editionAndLicense: SidebarSection;
constructor(container: Locator) {
super(container);
this.editionAndLicense = this.section('Edition and License');
}
}
class ReportingCategory extends SidebarCategory {
readonly workspaceOptimization: SidebarSection;
readonly siteStatistics: SidebarSection;
readonly teamStatistics: SidebarSection;
readonly serverLogs: SidebarSection;
constructor(container: Locator) {
super(container);
this.workspaceOptimization = this.section('Workspace Optimization');
this.siteStatistics = this.section('Site Statistics');
this.teamStatistics = this.section('Team Statistics');
this.serverLogs = this.section('Server Logs');
}
}
class UserManagementCategory extends SidebarCategory {
readonly users: SidebarSection;
readonly groups: SidebarSection;
readonly teams: SidebarSection;
readonly channels: SidebarSection;
readonly permissions: SidebarSection;
readonly delegatedGranularAdministration: SidebarSection;
constructor(container: Locator) {
super(container);
this.users = this.section('Users');
this.groups = this.section('Groups');
this.teams = this.section('Teams');
this.channels = this.section('Channels');
this.permissions = this.section('Permissions');
this.delegatedGranularAdministration = this.section('Delegated Granular Administration');
}
}
class SystemAttributesCategory extends SidebarCategory {
readonly userAttributes: SidebarSection;
readonly attributeBasedAccess: SidebarSection;
constructor(container: Locator) {
super(container);
this.userAttributes = this.section('User Attributes');
this.attributeBasedAccess = this.section('Attribute-Based Access');
}
}
class EnvironmentCategory extends SidebarCategory {
readonly webServer: SidebarSection;
readonly database: SidebarSection;
readonly elasticsearch: SidebarSection;
readonly fileStorage: SidebarSection;
readonly imageProxy: SidebarSection;
readonly smtp: SidebarSection;
readonly pushNotificationServer: SidebarSection;
readonly highAvailability: SidebarSection;
readonly cacheSettings: SidebarSection;
readonly rateLimiting: SidebarSection;
readonly logging: SidebarSection;
readonly sessionLengths: SidebarSection;
readonly performanceMonitoring: SidebarSection;
readonly developer: SidebarSection;
readonly mobileSecurity: SidebarSection;
constructor(container: Locator) {
super(container);
this.webServer = this.section('Web Server');
this.database = this.section('Database');
this.elasticsearch = this.section('Elasticsearch');
this.fileStorage = this.section('File Storage');
this.imageProxy = this.section('Image Proxy');
this.smtp = this.section('SMTP');
this.pushNotificationServer = this.section('Push Notification Server');
this.highAvailability = this.section('High Availability');
this.cacheSettings = this.section('Cache Settings');
this.rateLimiting = this.section('Rate Limiting');
this.logging = this.section('Logging');
this.sessionLengths = this.section('Session Lengths');
this.performanceMonitoring = this.section('Performance Monitoring');
this.developer = this.section('Developer');
this.mobileSecurity = this.section('Mobile Security');
}
}
class SiteConfigurationCategory extends SidebarCategory {
readonly customization: SidebarSection;
readonly localization: SidebarSection;
readonly usersAndTeams: SidebarSection;
readonly notifications: SidebarSection;
readonly systemWideNotifications: SidebarSection;
readonly emoji: SidebarSection;
readonly posts: SidebarSection;
readonly contentFlagging: SidebarSection;
readonly moveThread: SidebarSection;
readonly fileSharingAndDownloads: SidebarSection;
readonly publicLinks: SidebarSection;
readonly notices: SidebarSection;
constructor(container: Locator) {
super(container);
this.customization = this.section('Customization');
this.localization = this.section('Localization');
this.usersAndTeams = this.section('Users and Teams');
this.notifications = this.section('Notifications');
this.systemWideNotifications = this.section('System-wide Notifications');
this.emoji = this.section('Emoji');
this.posts = this.section('Posts');
this.contentFlagging = this.section('Content Flagging');
this.moveThread = this.section('Move Thread (Beta)');
this.fileSharingAndDownloads = this.section('File Sharing and Downloads');
this.publicLinks = this.section('Public Links');
this.notices = this.section('Notices');
}
}
class AuthenticationCategory extends SidebarCategory {
readonly signup: SidebarSection;
readonly email: SidebarSection;
readonly password: SidebarSection;
readonly mfa: SidebarSection;
readonly adLdap: SidebarSection;
readonly saml: SidebarSection;
readonly openIdConnect: SidebarSection;
readonly guestAccess: SidebarSection;
constructor(container: Locator) {
super(container);
this.signup = this.section('Signup');
this.email = this.section('Email');
this.password = this.section('Password');
this.mfa = this.section('MFA');
this.adLdap = this.section('AD/LDAP');
this.saml = this.section('SAML 2.0');
this.openIdConnect = this.section('OpenID Connect');
this.guestAccess = this.section('Guest Access');
}
}
class PluginsCategory extends SidebarCategory {
readonly pluginManagement: SidebarSection;
readonly agents: SidebarSection;
readonly calls: SidebarSection;
readonly playbooks: SidebarSection;
readonly boards: SidebarSection;
constructor(container: Locator) {
super(container);
this.pluginManagement = this.section('Plugin Management');
this.agents = this.section('Agents');
this.calls = this.section('Calls');
this.playbooks = this.section('Playbooks');
this.boards = this.section('Mattermost Boards');
}
getPlugin(pluginName: string): SidebarSection {
const link = this.sections.getByRole('link', {name: pluginName, exact: true});
return new SidebarSection(this.sections, link);
}
}
class IntegrationsCategory extends SidebarCategory {
readonly integrationManagement: SidebarSection;
readonly botAccounts: SidebarSection;
readonly gif: SidebarSection;
readonly cors: SidebarSection;
readonly embedding: SidebarSection;
constructor(container: Locator) {
super(container);
this.integrationManagement = this.section('Integration Management');
this.botAccounts = this.section('Bot Accounts');
this.gif = this.section('GIF');
this.cors = this.section('CORS');
this.embedding = this.section('Embedding');
}
}
class ComplianceCategory extends SidebarCategory {
readonly dataRetentionPolicies: SidebarSection;
readonly complianceExport: SidebarSection;
readonly complianceMonitoring: SidebarSection;
readonly auditLogging: SidebarSection;
readonly customTermsOfService: SidebarSection;
constructor(container: Locator) {
super(container);
this.dataRetentionPolicies = this.section('Data Retention Policies');
this.complianceExport = this.section('Compliance Export');
this.complianceMonitoring = this.section('Compliance Monitoring');
this.auditLogging = this.section('Audit Logging');
this.customTermsOfService = this.section('Custom Terms of Service');
}
}
class ExperimentalCategory extends SidebarCategory {
readonly features: SidebarSection;
readonly featureFlags: SidebarSection;
constructor(container: Locator) {
super(container);
this.features = this.section('Features');
this.featureFlags = this.section('Feature Flags');
}
}

View file

@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
/**
* System Console Sidebar Header component
*/
export default class SystemConsoleSidebarHeader {
readonly container: Locator;
readonly headerInfo: Locator;
readonly title: Locator;
readonly userName: Locator;
readonly menuButton: Locator;
constructor(container: Locator) {
this.container = container;
this.headerInfo = container.locator('.header__info');
this.title = container.getByText('System Console');
this.userName = container.getByText(/^@/);
this.menuButton = container.getByRole('button', {name: 'Menu Icon'});
}
async toBeVisible() {
await expect(this.container).toBeVisible();
await expect(this.title).toBeVisible();
}
}

View file

@ -3,101 +3,81 @@
import {Page} from '@playwright/test';
import {components} from '@/ui/components';
import SystemConsoleNavbar from '@/ui/components/system_console/navbar';
import SystemConsoleSidebar from '@/ui/components/system_console/sidebar';
import SystemConsoleHeader from '@/ui/components/system_console/header';
import EditionAndLicense from '@/ui/components/system_console/sections/about/edition_and_license';
import TeamStatistics from '@/ui/components/system_console/sections/reporting/team_statistics';
import Users from '@/ui/components/system_console/sections/user_management/users';
import DelegatedGranularAdministration from '@/ui/components/system_console/sections/user_management/delegated_granular_administration';
import MobileSecurity from '@/ui/components/system_console/sections/environment/mobile_security';
import Notifications from '@/ui/components/system_console/sections/site_configuration/notifications';
import FeatureDiscovery from '@/ui/components/system_console/sections/system_users/feature_discovery';
export default class SystemConsolePage {
readonly page: Page;
readonly sidebar;
readonly navbar;
// Layout
readonly navbar: SystemConsoleNavbar;
readonly sidebar: SystemConsoleSidebar;
readonly header: SystemConsoleHeader;
// About
readonly editionAndLicense: EditionAndLicense;
// Reporting
readonly teamStatistics: TeamStatistics;
// User Management
readonly users: Users;
readonly delegatedGranularAdministration: DelegatedGranularAdministration;
// Environment
readonly mobileSecurity: MobileSecurity;
// Site Configuration
readonly notifications: Notifications;
// System Console > Notifications
readonly notifications;
/**
* System Console -> User Management -> Users
*/
readonly systemUsers;
readonly systemUsersFilterPopover;
readonly systemUsersRoleMenu;
readonly systemUsersStatusMenu;
readonly systemUsersDateRangeMenu;
readonly systemUsersColumnToggleMenu;
readonly systemUsersActionMenus;
readonly mobileSecurity;
readonly featureDiscovery;
// modal
readonly confirmModal;
readonly exportModal;
readonly saveChangesModal;
// Feature Discovery (license-gated features)
readonly featureDiscovery: FeatureDiscovery;
constructor(page: Page) {
this.page = page;
// Layout
this.navbar = new SystemConsoleNavbar(page.locator('.backstage-navbar'));
this.sidebar = new SystemConsoleSidebar(page.locator('.admin-sidebar'));
const adminConsoleWrapper = page.locator('#adminConsoleWrapper');
this.header = new SystemConsoleHeader(adminConsoleWrapper);
// About
this.editionAndLicense = new EditionAndLicense(adminConsoleWrapper);
// Reporting
this.teamStatistics = new TeamStatistics(adminConsoleWrapper);
// User Management
this.users = new Users(adminConsoleWrapper);
this.delegatedGranularAdministration = new DelegatedGranularAdministration(adminConsoleWrapper);
// Environment
this.mobileSecurity = new MobileSecurity(adminConsoleWrapper);
// Site Configuration
// System Console > Notifications
this.notifications = new components.SystemConsoleNotifications(
page.getByTestId('sysconsole_section_notifications'),
);
this.notifications = new Notifications(adminConsoleWrapper);
// Areas of the page
this.navbar = new components.SystemConsoleNavbar(page.locator('.backstage-navbar'));
this.sidebar = new components.SystemConsoleSidebar(page.locator('.admin-sidebar'));
// Sections and sub-sections
this.systemUsers = new components.SystemUsers(page.getByTestId('systemUsersSection'));
this.mobileSecurity = new components.SystemConsoleMobileSecurity(
page.getByTestId('sysconsole_section_MobileSecuritySettings'),
this.page,
);
this.featureDiscovery = new components.SystemConsoleFeatureDiscovery(page.getByTestId('featureDiscovery'));
// Menus & Popovers
this.systemUsersFilterPopover = new components.SystemUsersFilterPopover(
page.locator('#systemUsersFilterPopover'),
);
this.systemUsersRoleMenu = new components.SystemUsersFilterMenu(page.locator('#DropdownInput_filterRole'));
this.systemUsersStatusMenu = new components.SystemUsersFilterMenu(page.locator('#DropdownInput_filterStatus'));
this.systemUsersColumnToggleMenu = new components.SystemUsersColumnToggleMenu(
page.locator('#systemUsersColumnTogglerMenu'),
);
this.systemUsersDateRangeMenu = new components.SystemUsersFilterMenu(
page.locator('#systemUsersDateRangeSelectorMenu'),
);
this.systemUsersActionMenus = Array.from(Array(10).keys()).map(
(index) => new components.SystemUsersFilterMenu(page.locator(`#actionMenu-systemUsersTable-${index}`)),
);
this.confirmModal = new components.GenericConfirmModal(page.locator('#confirmModal'));
this.exportModal = new components.GenericConfirmModal(page.getByRole('dialog', {name: 'Export user data'}));
this.saveChangesModal = new components.SystemUsers(page.locator('div.modal-content'));
// Feature Discovery
this.featureDiscovery = new FeatureDiscovery(adminConsoleWrapper);
}
async toBeVisible() {
await this.page.waitForLoadState('networkidle');
await this.sidebar.toBeVisible();
await this.navbar.toBeVisible();
await this.sidebar.toBeVisible();
}
async goto() {
await this.page.goto('/admin_console');
}
async saveRoleChange() {
await this.saveChangesModal.container.locator('button.btn-primary:has-text("Save")').click();
}
async clickResetButton() {
await this.saveChangesModal.container.locator('button.btn-primary:has-text("Reset")').click();
}
async clickUpdateEmailButton() {
await this.saveChangesModal.container.locator('button.btn-primary:has-text("Update")').click();
}
}

View file

@ -33,18 +33,18 @@ test('Push Notification Contents setting displays correctly and saves all option
// # Visit Notifications admin console page
await systemConsolePage.goto();
await systemConsolePage.toBeVisible();
await systemConsolePage.sidebar.goToItem('Notifications');
await systemConsolePage.sidebar.notifications.click();
// # Wait for Notifications section to load
const notifications = systemConsolePage.notifications;
await notifications.toBeVisible();
// * Verify that setting is visible and matches text content
await notifications.pushNotificationContents.scrollIntoViewIfNeeded();
await expect(notifications.pushNotificationContents).toBeVisible();
await notifications.pushNotificationContents.container.scrollIntoViewIfNeeded();
await notifications.pushNotificationContents.toBeVisible();
// * Verify that the help text is visible and matches text content
const helpText = notifications.pushNotificationContentsHelpText;
const helpText = notifications.pushNotificationContents.helpText;
await expect(helpText).toBeVisible();
const contents = [
@ -69,7 +69,7 @@ test('Push Notification Contents setting displays correctly and saves all option
await expect(strongElements.nth(4)).toHaveText(contents[8]);
// * Verify that the option/dropdown is visible and has default value
const dropdown = notifications.pushNotificationContentsDropdown;
const dropdown = notifications.pushNotificationContents.dropdown;
await expect(dropdown).toBeVisible();
await expect(dropdown).toHaveValue('full');
@ -86,7 +86,7 @@ test('Push Notification Contents setting displays correctly and saves all option
await dropdown.selectOption({label: option.label});
await expect(dropdown).toHaveValue(option.value);
await notifications.saveButton.click();
await notifications.save();
// * Verify config is saved
const {adminClient} = await pw.getAdminClient();
@ -111,31 +111,30 @@ test('MM-T1210 Can change Support Email setting', async ({pw}) => {
// # Visit Notifications admin console page
await systemConsolePage.goto();
await systemConsolePage.toBeVisible();
await systemConsolePage.sidebar.goToItem('Notifications');
await systemConsolePage.sidebar.notifications.click();
// # Wait for Notifications section to load
const notifications = systemConsolePage.notifications;
await notifications.toBeVisible();
// # Scroll Support Email section into view and verify that it's visible
const supportEmailSetting = notifications.supportEmailAddress;
await supportEmailSetting.scrollIntoViewIfNeeded();
await expect(supportEmailSetting).toBeVisible();
await notifications.supportEmailAddress.container.scrollIntoViewIfNeeded();
await notifications.supportEmailAddress.toBeVisible();
// * Verify that the help text is visible and matches text content
await expect(notifications.supportEmailHelpText).toBeVisible();
await expect(notifications.supportEmailHelpText).toHaveText('Email address displayed on support emails.');
await expect(notifications.supportEmailAddress.helpText).toBeVisible();
await expect(notifications.supportEmailAddress.helpText).toHaveText('Email address displayed on support emails.');
// # Clear and type new email
const newEmail = 'changed_for_test_support@example.com';
await notifications.supportEmailAddressInput.clear();
await notifications.supportEmailAddressInput.fill(newEmail);
await notifications.supportEmailAddress.clear();
await notifications.supportEmailAddress.fill(newEmail);
// * Verify that set value is visible and matches text
await expect(notifications.supportEmailAddressInput).toHaveValue(newEmail);
await expect(notifications.supportEmailAddress.input).toHaveValue(newEmail);
// # Save setting
await notifications.saveButton.click();
await notifications.save();
// * Verify that the config is correctly saved in the server
const {adminClient} = await pw.getAdminClient();
@ -158,29 +157,29 @@ test('MM-41671 cannot save the notifications page if mandatory fields are missin
// # Visit Notifications admin console page
await systemConsolePage.goto();
await systemConsolePage.toBeVisible();
await systemConsolePage.sidebar.goToItem('Notifications');
await systemConsolePage.sidebar.notifications.click();
// # Wait for Notifications section to load
const notifications = systemConsolePage.notifications;
await notifications.toBeVisible();
const tests = [
{name: 'Support Email Address', fieldInput: notifications.supportEmailAddressInput},
{name: 'Notification Display Name', fieldInput: notifications.notificationDisplayNameInput},
{name: 'Notification From Address', fieldInput: notifications.notificationFromAddressInput},
{name: 'Support Email Address', field: notifications.supportEmailAddress},
{name: 'Notification Display Name', field: notifications.notificationDisplayName},
{name: 'Notification From Address', field: notifications.notificationFromAddress},
];
for (const testCase of tests) {
// # Clear the field
await expect(testCase.fieldInput).toBeVisible();
await testCase.fieldInput.clear();
await testCase.field.toBeVisible();
await testCase.field.clear();
// * Error message is shown and save button is disabled
await expect(notifications.errorMessage).toHaveText(`"${testCase.name}" is required`);
await expect(notifications.saveButton).toBeDisabled();
// # Insert something in the field
await testCase.fieldInput.fill('anything');
await testCase.field.fill('anything');
// * Ensure no error message is shown and the save button is not disabled
await expect(notifications.errorMessage).toHaveCount(0);

View file

@ -25,135 +25,140 @@ test('should be able to enable mobile security settings when licensed', async ({
await systemConsolePage.toBeVisible();
// # Go to Mobile Security section
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
await systemConsolePage.mobileSecurity.toBeVisible();
// # Enable Biometric Authentication
await systemConsolePage.mobileSecurity.clickEnableBiometricAuthenticationToggleTrue();
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.selectTrue();
// * Verify only Biometric Authentication is enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(false);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(false);
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.toBeTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.toBeFalse();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.toBeFalse();
// # Save settings
await systemConsolePage.mobileSecurity.clickSaveButton();
await systemConsolePage.mobileSecurity.save();
// # Wait until the save button has settled
await pw.waitUntil(async () => (await systemConsolePage.mobileSecurity.saveButton.textContent()) === 'Save');
// # Go to any other section and come back to Mobile Security
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
await systemConsolePage.mobileSecurity.toBeVisible();
// * Verify Biometric Authentication is still enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(false);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(false);
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.toBeTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.toBeFalse();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.toBeFalse();
// # Enable Prevent Screen Capture
await systemConsolePage.mobileSecurity.clickPreventScreenCaptureToggleTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.selectTrue();
// * Verify only Biometric Authentication and Prevent Screen Capture are enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(false);
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.toBeTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.toBeTrue();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.toBeFalse();
// # Save settings
await systemConsolePage.mobileSecurity.clickSaveButton();
await systemConsolePage.mobileSecurity.save();
// # Wait until the save button has settled
await pw.waitUntil(async () => (await systemConsolePage.mobileSecurity.saveButton.textContent()) === 'Save');
// # Go to any other section and come back to Mobile Security
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
await systemConsolePage.mobileSecurity.toBeVisible();
// * Verify Biometric Authentication and Prevent Screen Capture are still enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(false);
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.toBeTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.toBeTrue();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.toBeFalse();
// # Enable Jailbreak Protection
await systemConsolePage.mobileSecurity.clickJailbreakProtectionToggleTrue();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.selectTrue();
// * Verify all toggles are enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.toBeTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.toBeTrue();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.toBeTrue();
// # Save settings
await systemConsolePage.mobileSecurity.clickSaveButton();
await systemConsolePage.mobileSecurity.save();
// # Wait until the save button has settled
await pw.waitUntil(async () => (await systemConsolePage.mobileSecurity.saveButton.textContent()) === 'Save');
// # Go to any other section and come back to Mobile Security
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
await systemConsolePage.mobileSecurity.toBeVisible();
// * Verify all toggles are still enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.toBeTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.toBeTrue();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.toBeTrue();
if (license.SkuShortName === 'advanced') {
// # Enable Secure File Preview
await systemConsolePage.mobileSecurity.clickEnableSecureFilePreviewToggleTrue();
await systemConsolePage.mobileSecurity.enableSecureFilePreviewMode.selectTrue();
// * Verify all toggles are enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.enableSecureFilePreviewToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.allowPdfLinkNavigationToggleTrue.isChecked()).toBe(false);
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.toBeTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.toBeTrue();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.toBeTrue();
await systemConsolePage.mobileSecurity.enableSecureFilePreviewMode.toBeTrue();
await systemConsolePage.mobileSecurity.allowPdfLinkNavigation.toBeFalse();
// # Save settings
await systemConsolePage.mobileSecurity.clickSaveButton();
await systemConsolePage.mobileSecurity.save();
// # Wait until the save button has settled
await pw.waitUntil(async () => (await systemConsolePage.mobileSecurity.saveButton.textContent()) === 'Save');
// # Go to any other section and come back to Mobile Security
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
await systemConsolePage.sidebar.mobileSecurity.click();
await systemConsolePage.mobileSecurity.toBeVisible();
// * Verify all toggles are still enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.enableSecureFilePreviewToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.allowPdfLinkNavigationToggleTrue.isChecked()).toBe(false);
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.toBeTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.toBeTrue();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.toBeTrue();
await systemConsolePage.mobileSecurity.enableSecureFilePreviewMode.toBeTrue();
await systemConsolePage.mobileSecurity.allowPdfLinkNavigation.toBeFalse();
// # Enable Allow PDF Link Navigation
await systemConsolePage.mobileSecurity.clickAllowPdfLinkNavigationToggleTrue();
await systemConsolePage.mobileSecurity.allowPdfLinkNavigation.selectTrue();
// * Verify all toggles are enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.enableSecureFilePreviewToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.allowPdfLinkNavigationToggleTrue.isChecked()).toBe(true);
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.toBeTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.toBeTrue();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.toBeTrue();
await systemConsolePage.mobileSecurity.enableSecureFilePreviewMode.toBeTrue();
await systemConsolePage.mobileSecurity.allowPdfLinkNavigation.toBeTrue();
// # Save settings
await systemConsolePage.mobileSecurity.clickSaveButton();
await systemConsolePage.mobileSecurity.save();
// # Wait until the save button has settled
await pw.waitUntil(async () => (await systemConsolePage.mobileSecurity.saveButton.textContent()) === 'Save');
// # Go to any other section and come back to Mobile Security
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
await systemConsolePage.sidebar.mobileSecurity.click();
await systemConsolePage.mobileSecurity.toBeVisible();
// * Verify all toggles are still enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.enableSecureFilePreviewToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.allowPdfLinkNavigationToggleTrue.isChecked()).toBe(true);
await systemConsolePage.mobileSecurity.enableBiometricAuthentication.toBeTrue();
await systemConsolePage.mobileSecurity.preventScreenCapture.toBeTrue();
await systemConsolePage.mobileSecurity.enableJailbreakProtection.toBeTrue();
await systemConsolePage.mobileSecurity.enableSecureFilePreviewMode.toBeTrue();
await systemConsolePage.mobileSecurity.allowPdfLinkNavigation.toBeTrue();
}
});
@ -179,7 +184,7 @@ test('should show mobile security upsell when not licensed', async ({pw}) => {
await systemConsolePage.toBeVisible();
// # Go to Mobile Security section
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
await systemConsolePage.featureDiscovery.toBeVisible();
// * Verify title is correct
@ -213,38 +218,39 @@ test('should show and enable Intune MAM when Enterprise Advanced licensed and Of
await systemConsolePage.toBeVisible();
// # Go to Mobile Security section
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
// * Verify Intune MAM toggle is visible
await expect(systemConsolePage.mobileSecurity.enableIntuneMAMToggleTrue).toBeVisible();
await expect(systemConsolePage.mobileSecurity.enableIntuneMAMToggleFalse).toBeVisible();
await expect(systemConsolePage.mobileSecurity.enableIntuneMAM.trueOption).toBeVisible();
await expect(systemConsolePage.mobileSecurity.enableIntuneMAM.falseOption).toBeVisible();
// # Enable Intune MAM
await systemConsolePage.mobileSecurity.clickEnableIntuneMAMToggleTrue();
await systemConsolePage.mobileSecurity.enableIntuneMAM.selectTrue();
// * Verify Intune MAM is enabled
await expect(await systemConsolePage.mobileSecurity.enableIntuneMAMToggleTrue.isChecked()).toBe(true);
await systemConsolePage.mobileSecurity.enableIntuneMAM.toBeTrue();
await systemConsolePage.mobileSecurity.selectIntuneAuthService('office365');
await systemConsolePage.mobileSecurity.authProvider.select('office365');
// # Fill in Intune configuration
await systemConsolePage.mobileSecurity.fillIntuneTenantId('12345678-1234-1234-1234-123456789012');
await systemConsolePage.mobileSecurity.fillIntuneClientId('87654321-4321-4321-4321-210987654321');
await systemConsolePage.mobileSecurity.tenantId.fill('12345678-1234-1234-1234-123456789012');
await systemConsolePage.mobileSecurity.clientId.fill('87654321-4321-4321-4321-210987654321');
// # Save settings
await systemConsolePage.mobileSecurity.clickSaveButton();
await systemConsolePage.mobileSecurity.save();
// # Wait until the save button has settled
await pw.waitUntil(async () => (await systemConsolePage.mobileSecurity.saveButton.textContent()) === 'Save');
// # Go to any other section and come back to Mobile Security
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
await systemConsolePage.mobileSecurity.toBeVisible();
// * Verify Intune MAM is still enabled
await expect(await systemConsolePage.mobileSecurity.enableIntuneMAMToggleTrue.isChecked()).toBe(true);
await systemConsolePage.mobileSecurity.enableIntuneMAM.toBeTrue();
});
test('should hide Intune MAM when Office365 is not configured', async ({pw}) => {
@ -271,12 +277,12 @@ test('should hide Intune MAM when Office365 is not configured', async ({pw}) =>
await systemConsolePage.toBeVisible();
// # Go to Mobile Security section
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
// * Verify Intune MAM toggle is visible
await expect(systemConsolePage.mobileSecurity.enableIntuneMAMToggleTrue).toBeVisible();
await expect(systemConsolePage.mobileSecurity.enableIntuneMAMToggleFalse).toBeVisible();
await expect(systemConsolePage.mobileSecurity.intuneAuthServiceDropdown).toBeDisabled();
await expect(systemConsolePage.mobileSecurity.enableIntuneMAM.trueOption).toBeVisible();
await expect(systemConsolePage.mobileSecurity.enableIntuneMAM.falseOption).toBeVisible();
await expect(systemConsolePage.mobileSecurity.authProvider.dropdown).toBeDisabled();
});
test('should configure new IntuneSettings with Office365 auth provider', async ({pw}) => {
@ -307,45 +313,42 @@ test('should configure new IntuneSettings with Office365 auth provider', async (
await systemConsolePage.toBeVisible();
// # Go to Mobile Security section
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
// * Verify new Intune toggle is visible
await expect(systemConsolePage.mobileSecurity.enableIntuneToggleTrue).toBeVisible();
await expect(systemConsolePage.mobileSecurity.enableIntuneToggleFalse).toBeVisible();
await expect(systemConsolePage.mobileSecurity.enableIntuneMAM.trueOption).toBeVisible();
await expect(systemConsolePage.mobileSecurity.enableIntuneMAM.falseOption).toBeVisible();
// # Enable Intune
await systemConsolePage.mobileSecurity.clickEnableIntuneToggleTrue();
await systemConsolePage.mobileSecurity.enableIntuneMAM.selectTrue();
// * Verify Intune is enabled
expect(await systemConsolePage.mobileSecurity.enableIntuneToggleTrue.isChecked()).toBe(true);
await systemConsolePage.mobileSecurity.enableIntuneMAM.toBeTrue();
// # Select Office365 as auth provider
await systemConsolePage.mobileSecurity.selectIntuneAuthService('office365');
await systemConsolePage.mobileSecurity.authProvider.select('office365');
// # Fill in Intune configuration
await systemConsolePage.mobileSecurity.fillIntuneTenantId('12345678-1234-1234-1234-123456789012');
await systemConsolePage.mobileSecurity.fillIntuneClientId('87654321-4321-4321-4321-210987654321');
await systemConsolePage.mobileSecurity.tenantId.fill('12345678-1234-1234-1234-123456789012');
await systemConsolePage.mobileSecurity.clientId.fill('87654321-4321-4321-4321-210987654321');
// # Save settings
await systemConsolePage.mobileSecurity.clickSaveButton();
await systemConsolePage.mobileSecurity.save();
// # Wait until the save button has settled
await pw.waitUntil(async () => (await systemConsolePage.mobileSecurity.saveButton.textContent()) === 'Save');
// # Go to any other section and come back to Mobile Security
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
await systemConsolePage.mobileSecurity.toBeVisible();
// * Verify Intune is still enabled and configured
expect(await systemConsolePage.mobileSecurity.enableIntuneToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.intuneTenantIdInput.inputValue()).toBe(
'12345678-1234-1234-1234-123456789012',
);
expect(await systemConsolePage.mobileSecurity.intuneClientIdInput.inputValue()).toBe(
'87654321-4321-4321-4321-210987654321',
);
await systemConsolePage.mobileSecurity.enableIntuneMAM.toBeTrue();
expect(await systemConsolePage.mobileSecurity.tenantId.getValue()).toBe('12345678-1234-1234-1234-123456789012');
expect(await systemConsolePage.mobileSecurity.clientId.getValue()).toBe('87654321-4321-4321-4321-210987654321');
});
test('should configure new IntuneSettings with SAML auth provider', async ({pw}) => {
@ -365,7 +368,7 @@ test('should configure new IntuneSettings with SAML auth provider', async ({pw})
const serverUrl = process.env.MM_SERVER_URL || 'http://localhost:8065';
// # Upload a valid SAML IdP certificate using fetch
const idpCert = `-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAKC1r6Qw3v6OMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQIDA1Tb21lLVN0YXRlMRYwFAYDVQQKDA1FeGFtcGxlIEluYy4wHhcNMTkwMTAxMDAwMDAwWhcNMjkwMTAxMDAwMDAwWjBFMQswCQYDVQQGEwJVUzEWMBQGA1UECAwNU29tZS1TdGF0ZTEWMBQGA1UECgwNRXhhbXBsZSBJbmMuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6QwIDAQABo1AwTjAdBgNVHQ4EFgQU6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAU6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKQw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw=\n-----END CERTIFICATE-----\n`;
const idpCert = `-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAKC1r6Qw3v6OMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAlVTMRYwFAYDVQQIDA1Tb21lLVN0YXRlMRYwFAYDVQQKDA1FeGFtcGxlIEluYy4wHhcNMTkwMTAxMDAwMDAwWhcNMjkwMTAxMDAwMDAwWjBFMQswCQYDVQQGEwJVUzEWMBQGA1UECAwNU29tZS1TdGF0ZTEWMBQGA1UECgwNRXhhbXBsZSBJbmMuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6QwIDAQABo1AwTjAdBgNVHQ4EFgQU6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAU6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKQw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw3v6OMC1r6Qw=\n-----END CERTIFICATE-----\n`;
const idpFormData = new FormData();
idpFormData.append(
'certificate',
@ -413,41 +416,38 @@ test('should configure new IntuneSettings with SAML auth provider', async ({pw})
await systemConsolePage.toBeVisible();
// # Go to Mobile Security section
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
// * Verify new Intune toggle is visible
await expect(systemConsolePage.mobileSecurity.enableIntuneToggleTrue).toBeVisible();
await expect(systemConsolePage.mobileSecurity.enableIntuneMAM.trueOption).toBeVisible();
// # Enable Intune
await systemConsolePage.mobileSecurity.clickEnableIntuneToggleTrue();
await systemConsolePage.mobileSecurity.enableIntuneMAM.selectTrue();
// # Select SAML as auth provider
await systemConsolePage.mobileSecurity.selectIntuneAuthService('saml');
await systemConsolePage.mobileSecurity.authProvider.select('saml');
// # Fill in Intune configuration
await systemConsolePage.mobileSecurity.fillIntuneTenantId('abcdef01-2345-6789-abcd-ef0123456789');
await systemConsolePage.mobileSecurity.fillIntuneClientId('fedcba98-7654-3210-fedc-ba9876543210');
await systemConsolePage.mobileSecurity.tenantId.fill('abcdef01-2345-6789-abcd-ef0123456789');
await systemConsolePage.mobileSecurity.clientId.fill('fedcba98-7654-3210-fedc-ba9876543210');
// # Save settings
await systemConsolePage.mobileSecurity.clickSaveButton();
await systemConsolePage.mobileSecurity.save();
// # Wait until the save button has settled
await pw.waitUntil(async () => (await systemConsolePage.mobileSecurity.saveButton.textContent()) === 'Save');
// # Go to any other section and come back to Mobile Security
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
await systemConsolePage.mobileSecurity.toBeVisible();
// * Verify Intune is still enabled and configured with SAML
expect(await systemConsolePage.mobileSecurity.enableIntuneToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.intuneTenantIdInput.inputValue()).toBe(
'abcdef01-2345-6789-abcd-ef0123456789',
);
expect(await systemConsolePage.mobileSecurity.intuneClientIdInput.inputValue()).toBe(
'fedcba98-7654-3210-fedc-ba9876543210',
);
await systemConsolePage.mobileSecurity.enableIntuneMAM.toBeTrue();
expect(await systemConsolePage.mobileSecurity.tenantId.getValue()).toBe('abcdef01-2345-6789-abcd-ef0123456789');
expect(await systemConsolePage.mobileSecurity.clientId.getValue()).toBe('fedcba98-7654-3210-fedc-ba9876543210');
});
test('should disable Intune inputs when toggle is off', async ({pw}) => {
@ -477,18 +477,18 @@ test('should disable Intune inputs when toggle is off', async ({pw}) => {
await systemConsolePage.toBeVisible();
// # Go to Mobile Security section
await systemConsolePage.sidebar.goToItem('Mobile Security');
await systemConsolePage.sidebar.mobileSecurity.click();
// * Verify Intune inputs are disabled when toggle is off
expect(await systemConsolePage.mobileSecurity.intuneAuthServiceDropdown.isDisabled()).toBe(true);
expect(await systemConsolePage.mobileSecurity.intuneTenantIdInput.isDisabled()).toBe(true);
expect(await systemConsolePage.mobileSecurity.intuneClientIdInput.isDisabled()).toBe(true);
expect(await systemConsolePage.mobileSecurity.authProvider.dropdown.isDisabled()).toBe(true);
expect(await systemConsolePage.mobileSecurity.tenantId.input.isDisabled()).toBe(true);
expect(await systemConsolePage.mobileSecurity.clientId.input.isDisabled()).toBe(true);
// # Enable Intune
await systemConsolePage.mobileSecurity.clickEnableIntuneToggleTrue();
await systemConsolePage.mobileSecurity.enableIntuneMAM.selectTrue();
// * Verify Intune inputs are now enabled
expect(await systemConsolePage.mobileSecurity.intuneAuthServiceDropdown.isDisabled()).toBe(false);
expect(await systemConsolePage.mobileSecurity.intuneTenantIdInput.isDisabled()).toBe(false);
expect(await systemConsolePage.mobileSecurity.intuneClientIdInput.isDisabled()).toBe(false);
expect(await systemConsolePage.mobileSecurity.authProvider.dropdown.isDisabled()).toBe(false);
expect(await systemConsolePage.mobileSecurity.tenantId.input.isDisabled()).toBe(false);
expect(await systemConsolePage.mobileSecurity.clientId.input.isDisabled()).toBe(false);
});

View file

@ -3,81 +3,7 @@
import {UserProfile} from '@mattermost/types/users';
import {expect, PlaywrightExtended, test} from '@mattermost/playwright-lib';
// setupSystemManagerRole configures the system manager with the given permission ("Can Edit", "Read only", "No access")
// for the given section and subsection (e.g. "permission_section_reporting_site_statistics" and "permission_section_reporting_team_statistics").
//
// We do this via the system console and not the API because this page has multiple queries to build up the
// final API call that are all ultimately part of the spec, and we want to test the effects of those, not
// merely a hand-created version of the same.
const setupDefaultSystemManagerRole = async (
pw: PlaywrightExtended,
adminUser: UserProfile,
sectionTestId: string,
subsectionTestId: string,
permissionText: string,
) => {
// Login as admin and navigate to System Console
const {systemConsolePage: adminConsolePage} = await pw.testBrowser.login(adminUser);
// Go to System Console
await adminConsolePage.goto();
// Login to the System Console and navigate to Delegated Granular Administration
await adminConsolePage.sidebar.goToItem('Delegated Granular Administration');
// Find the System Manager row in the table
const systemManagerText = adminConsolePage.page.getByText('System Manager', {exact: true}).first();
await expect(systemManagerText).toBeVisible();
// Click on the System Manager text to go to its settings page
await systemManagerText.click();
// Expand the section
const sectionReporting = adminConsolePage.page.locator(`data-testid=${sectionTestId}`);
const hideSubsectionsLink = sectionReporting.getByRole('button').filter({hasText: 'Hide'}).first();
const showSubsectionsLink = sectionReporting.getByRole('button').filter({hasText: 'Show'}).first();
// Check which one is visible and click if needed
const isHideVisible = await hideSubsectionsLink.isVisible();
const isShowVisible = !isHideVisible && (await showSubsectionsLink.isVisible());
if (isShowVisible) {
// Need to expand
await showSubsectionsLink.click();
}
// Get the whole row
const rowReporting = adminConsolePage.page.locator('.PermissionRow').filter({has: sectionReporting});
await rowReporting.click();
// Find the sub section
const subsectionTeamStatistics = rowReporting.locator(`data-testid=${subsectionTestId}`);
await subsectionTeamStatistics.click();
// Look for dropdown button
const dropdownButton = subsectionTeamStatistics.locator('button').first();
// Click the button to open the dropdown menu
await dropdownButton.click();
// Click on the desired option in the dropdown
const permissionOption = subsectionTeamStatistics.locator('.dropdown-menu').getByText(permissionText).first();
await permissionOption.click();
// Click Save button
const saveButton = adminConsolePage.page.getByRole('button', {name: 'Save'}).first();
await saveButton.click();
// Wait for save operation to complete
await adminConsolePage.page.waitForLoadState('networkidle');
// Go back to the main console
await adminConsolePage.goto();
await adminConsolePage.page.waitForLoadState('networkidle');
};
import {expect, PlaywrightExtended, SystemConsolePage, test} from '@mattermost/playwright-lib';
test(
'MM-63378 System Manager without team access permissions cannot view team details',
@ -97,88 +23,38 @@ test(
// Create another team of which the user is not a member.
const otherTeam = await adminClient.createTeam(await pw.random.team());
// Login as the user
const {systemConsolePage} = await pw.testBrowser.login(systemManagerUser);
// Configure the system manager with the default permissions (as admin).
await setupSystemManagerPermission(pw, adminUser, 'reporting', 'team_statistics', 'Can edit');
await setupSystemManagerPermission(pw, adminUser, 'userManagement', 'teams', 'Can edit');
// Configure the system manager with the default permissions.
await setupDefaultSystemManagerRole(
pw,
adminUser,
'permission_section_reporting',
'permission_section_reporting_team_statistics',
'Can edit',
);
await setupDefaultSystemManagerRole(
pw,
adminUser,
'permission_section_user_management',
'permission_section_user_management_teams',
'Can edit',
);
// Re-login as the system manager (the admin login above replaced the page context)
const {systemConsolePage} = await pw.testBrowser.login(systemManagerUser);
// Verify the system manager has access to the site statistics for all teams
await systemConsolePage.goto();
// Navigate to Team Statistics
await systemConsolePage.sidebar.goToItem('Team Statistics');
// Navigate to Team Statistics and verify access to user's team
await verifyTeamStatisticsAccess(systemConsolePage, team.id, team.display_name);
// Wait for page to fully load
await systemConsolePage.page.waitForLoadState('networkidle');
// Find the team filter dropdown
let teamFilterSelect = systemConsolePage.page.getByTestId('teamFilter');
await expect(teamFilterSelect).toBeVisible();
// Select the team by value
await teamFilterSelect.selectOption({value: team.id});
// Verify the text shows "Team Statistics for <team name>"
let teamStatsHeading = systemConsolePage.page.getByText(`Team Statistics for ${team.display_name}`, {
exact: true,
});
await expect(teamStatsHeading).toBeVisible();
// Select the other team by value
await teamFilterSelect.selectOption({value: otherTeam.id});
// Verify the text shows "Team Statistics for <team name>"
const otherTeamStatsHeading = systemConsolePage.page.getByText(
`Team Statistics for ${otherTeam.display_name}`,
{
exact: true,
},
);
await expect(otherTeamStatsHeading).toBeVisible();
// Select the other team by value and verify access
await systemConsolePage.teamStatistics.selectTeamById(otherTeam.id);
await systemConsolePage.teamStatistics.toHaveTeamHeader(otherTeam.display_name);
// Verify the user has API access to the otherTeam.
const fetchedOtherTeam = await systemManagerClient.getTeam(otherTeam.id);
expect(fetchedOtherTeam.id).toEqual(otherTeam.id);
// Configure the system manager without access to team user management
await setupDefaultSystemManagerRole(
pw,
adminUser,
'permission_section_user_management',
'permission_section_user_management_teams',
'No access',
);
await setupSystemManagerPermission(pw, adminUser, 'userManagement', 'teams', 'No access');
// Re-login as the system manager again after permission change
const {systemConsolePage: systemConsolePage2} = await pw.testBrowser.login(systemManagerUser);
// Verify the system manager only has access to the site statistics for the team they belong to
await systemConsolePage.goto();
await systemConsolePage2.goto();
// Navigate to Team Statistics
await systemConsolePage.sidebar.goToItem('Team Statistics');
// Find the team filter dropdown
teamFilterSelect = systemConsolePage.page.getByTestId('teamFilter');
await expect(teamFilterSelect).toBeVisible();
// Select the team by value
await teamFilterSelect.selectOption({value: team.id});
// Verify the text shows "Team Statistics for <team name>"
teamStatsHeading = systemConsolePage.page.getByText(`Team Statistics for ${team.display_name}`, {exact: true});
await expect(teamStatsHeading).toBeVisible();
// Navigate to Team Statistics and verify access to user's team
await verifyTeamStatisticsAccess(systemConsolePage2, team.id, team.display_name);
// Verify the user has no API access to the otherTeam.
let apiError: Error | null = null;
@ -191,3 +67,69 @@ test(
expect(apiError?.message).toContain('You do not have the appropriate permissions');
},
);
// Helper function to navigate to Team Statistics and verify team access
const verifyTeamStatisticsAccess = async (
systemConsolePage: SystemConsolePage,
teamId: string,
teamDisplayName: string,
) => {
// Navigate to Team Statistics
await systemConsolePage.sidebar.reporting.teamStatistics.click();
await systemConsolePage.teamStatistics.toBeVisible();
// Select the team by value
await systemConsolePage.teamStatistics.selectTeamById(teamId);
// Verify the text shows "Team Statistics for <team name>"
await systemConsolePage.teamStatistics.toHaveTeamHeader(teamDisplayName);
};
type PermissionValue = 'Can edit' | 'Read only' | 'No access';
// setupSystemManagerRole configures the system manager with the given permission ("Can edit", "Read only", "No access")
// for the given section and subsection.
//
// We do this via the system console and not the API because this page has multiple queries to build up the
// final API call that are all ultimately part of the spec, and we want to test the effects of those, not
// merely a hand-created version of the same.
const setupSystemManagerPermission = async (
pw: PlaywrightExtended,
adminUser: UserProfile,
sectionName: 'reporting' | 'userManagement',
subsectionName: string,
permission: PermissionValue,
) => {
// Login as admin and navigate to System Console
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
// Go to System Console
await systemConsolePage.goto();
// Navigate to Delegated Granular Administration
await systemConsolePage.sidebar.delegatedGranularAdministration.click();
await systemConsolePage.delegatedGranularAdministration.toBeVisible();
// Click on the System Manager row to go to its settings page
await systemConsolePage.delegatedGranularAdministration.adminRolesPanel.systemManager.clickEdit();
await systemConsolePage.delegatedGranularAdministration.systemRoles.toBeVisible();
// Get the section from privileges panel
const section = systemConsolePage.delegatedGranularAdministration.systemRoles.privilegesPanel[sectionName];
// Expand subsections if needed
await section.expandSubsections();
// Get the subsection and set permission
const subsection = section.getSubsection(subsectionName);
await subsection.setPermission(permission);
// Save
await systemConsolePage.delegatedGranularAdministration.systemRoles.save();
// Wait for save to complete - successful save redirects to system_roles list
await systemConsolePage.page.waitForURL('**/admin_console/user_management/system_roles');
// Wait for the page to fully load
await systemConsolePage.page.waitForLoadState('networkidle');
};

View file

@ -3,10 +3,153 @@
import {type PlaywrightExtended, expect, test} from '@mattermost/playwright-lib';
test('MM-T5520-1 should activate and deactivate users', async ({pw}) => {
const {getUser, systemConsolePage} = await setupAndGetRandomUser(pw);
const userRow = systemConsolePage.users.usersTable.getRowByIndex(0);
// # Open menu and deactivate the user
const actionMenu = await userRow.openActionMenu();
await actionMenu.clickDeactivate();
// # Press confirm on the modal
await systemConsolePage.users.confirmModal.confirm();
// * Verify user is deactivated
await expect(userRow.container.getByText('Deactivated')).toBeVisible();
expect((await getUser()).delete_at).toBeGreaterThan(0);
// # Open menu and reactivate the user
const actionMenu2 = await userRow.openActionMenu();
await actionMenu2.clickActivate();
// * Verify user is activated
await expect(userRow.container.getByText('Member')).toBeVisible();
});
test('MM-T5520-2 should change user roles', async ({pw}) => {
const {getUser, systemConsolePage} = await setupAndGetRandomUser(pw);
const userRow = systemConsolePage.users.usersTable.getRowByIndex(0);
// # Open menu and click Manage roles
const actionMenu = await userRow.openActionMenu();
await actionMenu.clickManageRoles();
// # Change to System Admin and click Save
const systemAdmin = systemConsolePage.page.locator('input[name="systemadmin"]');
await systemAdmin.waitFor();
await systemAdmin.click();
await systemConsolePage.users.manageRolesModal.save();
// * Verify that the role was updated
await expect(userRow.container.getByText('System Admin')).toBeVisible();
expect((await getUser()).roles).toContain('system_admin');
// # Open menu and click Manage roles
const actionMenu2 = await userRow.openActionMenu();
await actionMenu2.clickManageRoles();
// # Change to Member and click Save
const systemMember = systemConsolePage.page.locator('input[name="systemmember"]');
await systemMember.waitFor();
await systemMember.click();
await systemConsolePage.users.manageRolesModal.save();
// * Verify that the role was updated
await expect(userRow.container.getByText('Member')).toBeVisible();
expect((await getUser()).roles).toContain('system_user');
});
test('MM-T5520-3 should be able to manage teams', async ({pw}) => {
const {systemConsolePage} = await setupAndGetRandomUser(pw);
const userRow = systemConsolePage.users.usersTable.getRowByIndex(0);
// # Open menu and click Manage teams
const actionMenu = await userRow.openActionMenu();
await actionMenu.clickManageTeams();
// # Click Make Team Admin
const team = systemConsolePage.page.locator('div.manage-teams__team');
const teamDropdown = team.locator('div.MenuWrapper');
await teamDropdown.click();
const makeTeamAdmin = teamDropdown.getByText('Make Team Admin');
await makeTeamAdmin.click();
// * Verify role is updated
await expect(team.getByText('Team Admin')).toBeVisible();
// # Change back to Team Member
await teamDropdown.click();
const makeTeamMember = teamDropdown.getByText('Make Team Member');
await makeTeamMember.click();
// * Verify role is updated
await expect(team.getByText('Team Member')).toBeVisible();
// # Click Remove From Team
await teamDropdown.click();
const removeFromTeam = teamDropdown.getByText('Remove From Team');
await removeFromTeam.click();
// * The team should be detached
await team.waitFor({state: 'detached'});
await expect(team).not.toBeVisible();
});
test('MM-T5520-4 should reset the users password', async ({pw}) => {
const {systemConsolePage} = await setupAndGetRandomUser(pw);
const userRow = systemConsolePage.users.usersTable.getRowByIndex(0);
// # Open menu and click Reset Password
const actionMenu = await userRow.openActionMenu();
await actionMenu.clickResetPassword();
// # Enter a random password and click Reset
await systemConsolePage.users.resetPasswordModal.fillPassword(await pw.random.id());
await systemConsolePage.users.resetPasswordModal.reset();
});
test('MM-T5520-5 should change the users email', async ({pw}) => {
const {getUser, systemConsolePage} = await setupAndGetRandomUser(pw);
const newEmail = `${await pw.random.id()}@example.com`;
const userRow = systemConsolePage.users.usersTable.getRowByIndex(0);
// # Open menu and click Update Email
const actionMenu = await userRow.openActionMenu();
await actionMenu.clickUpdateEmail();
// # Enter new email and click Update
await systemConsolePage.users.updateEmailModal.fillEmail(newEmail);
await systemConsolePage.users.updateEmailModal.update();
// * Verify that the email updated
await expect(userRow.container.getByText(newEmail)).toBeVisible();
expect((await getUser()).email).toEqual(newEmail);
});
test('MM-T5520-6 should revoke sessions', async ({pw}) => {
const {systemConsolePage} = await setupAndGetRandomUser(pw);
const userRow = systemConsolePage.users.usersTable.getRowByIndex(0);
// # Open menu and revoke sessions
const actionMenu = await userRow.openActionMenu();
await actionMenu.clickRevokeSessions();
// # Press confirm on the modal
await systemConsolePage.users.confirmModal.confirm();
// * Verify no error is displayed
await expect(userRow.container.locator('.error')).not.toBeVisible();
});
/**
* Setup a new random user, and search for it such that it's the first row in the list
* @param pw
* @param pages
* @returns A function to get the refreshed user, and the System Console page for navigation
*/
async function setupAndGetRandomUser(pw: PlaywrightExtended) {
@ -19,7 +162,7 @@ async function setupAndGetRandomUser(pw: PlaywrightExtended) {
// # Log in as admin
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
// # Create a random user to edit for
// # Create a random user to edit
const user = await adminClient.createUser(await pw.random.user(), '', '');
const team = await adminClient.createTeam(await pw.random.team());
await adminClient.addToTeam(team.id, user.id);
@ -29,174 +172,15 @@ async function setupAndGetRandomUser(pw: PlaywrightExtended) {
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Search for user-1
await systemConsolePage.systemUsers.enterSearchText(user.email);
const userRow = await systemConsolePage.systemUsers.getNthRow(1);
await userRow.getByText(user.email).waitFor();
const innerText = await userRow.innerText();
expect(innerText).toContain(user.email);
// # Search for the user
await systemConsolePage.users.searchUsers(user.email);
// Wait for search results
const userRow = systemConsolePage.users.usersTable.getRowByIndex(0);
await expect(userRow.container.getByText(user.email)).toBeVisible();
return {getUser: () => adminClient.getUser(user.id), systemConsolePage};
}
test('MM-T5520-1 should activate and deactivate users', async ({pw}) => {
const {getUser, systemConsolePage} = await setupAndGetRandomUser(pw);
// # Open menu and deactivate the user
await systemConsolePage.systemUsers.actionMenuButtons[0].click();
const deactivate = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Deactivate');
await deactivate.click();
// # Press confirm on the modal
await systemConsolePage.confirmModal.confirm();
// * Verify user is deactivated
const firstRow = await systemConsolePage.systemUsers.getNthRow(1);
await firstRow.getByText('Deactivated').waitFor();
expect(await firstRow.innerText()).toContain('Deactivated');
expect((await getUser()).delete_at).toBeGreaterThan(0);
// # Open menu and reactivate the user
await systemConsolePage.systemUsers.actionMenuButtons[0].click();
const activate = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Activate');
await activate.click();
// * Verify user is activated
await firstRow.getByText('Member').waitFor();
expect(await firstRow.innerText()).toContain('Member');
});
test('MM-T5520-2 should change user roles', async ({pw}) => {
const {getUser, systemConsolePage} = await setupAndGetRandomUser(pw);
// # Open menu and click Manage roles
await systemConsolePage.systemUsers.actionMenuButtons[0].click();
let manageRoles = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Manage roles');
await manageRoles.click();
// # Change to System Admin and click Save
const systemAdmin = systemConsolePage.page.locator('input[name="systemadmin"]');
await systemAdmin.waitFor();
await systemAdmin.click();
systemConsolePage.saveRoleChange();
// * Verify that the modal closed and no error showed
await systemAdmin.waitFor({state: 'detached'});
// * Verify that the role was updated
const firstRow = await systemConsolePage.systemUsers.getNthRow(1);
expect(await firstRow.innerText()).toContain('System Admin');
expect((await getUser()).roles).toContain('system_admin');
// # Open menu and click Manage roles
await systemConsolePage.systemUsers.actionMenuButtons[0].click();
manageRoles = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Manage roles');
await manageRoles.click();
// # Change to Member and click Save
const systemMember = systemConsolePage.page.locator('input[name="systemmember"]');
await systemMember.waitFor();
await systemMember.click();
await systemConsolePage.saveRoleChange();
// * Verify that the modal closed and no error showed
await systemMember.waitFor({state: 'detached'});
// * Verify that the role was updated
expect(await firstRow.innerText()).toContain('Member');
expect((await getUser()).roles).toContain('system_user');
});
test('MM-T5520-3 should be able to manage teams', async ({pw}) => {
const {systemConsolePage} = await setupAndGetRandomUser(pw);
// # Open menu and click Manage teams
await systemConsolePage.systemUsers.actionMenuButtons[0].click();
const manageTeams = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Manage teams');
await manageTeams.click();
// # Click Make Team Admin
const team = systemConsolePage.page.locator('div.manage-teams__team');
const teamDropdown = team.locator('div.MenuWrapper');
await teamDropdown.click();
const makeTeamAdmin = teamDropdown.getByText('Make Team Admin');
await makeTeamAdmin.click();
// * Verify role is updated
expect(await team.innerText()).toContain('Team Admin');
// # Change back to Team Member
await teamDropdown.click();
const makeTeamMember = teamDropdown.getByText('Make Team Member');
await makeTeamMember.click();
// * Verify role is updated
expect(await team.innerText()).toContain('Team Member');
// # Click Remove From Team
await teamDropdown.click();
const removeFromTeam = teamDropdown.getByText('Remove From Team');
await removeFromTeam.click();
// * The team should be detached
await team.waitFor({state: 'detached'});
expect(team).not.toBeVisible();
});
test('MM-T5520-4 should reset the users password', async ({pw}) => {
const {systemConsolePage} = await setupAndGetRandomUser(pw);
// # Open menu and click Reset Password
await systemConsolePage.systemUsers.actionMenuButtons[0].click();
const resetPassword = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Reset password');
await resetPassword.click();
// # Enter a random password and click Save
const passwordInput = systemConsolePage.page.locator('input[type="password"]');
await passwordInput.fill(await pw.random.id());
await systemConsolePage.clickResetButton();
// * Verify that the modal closed and no error showed
await passwordInput.waitFor({state: 'detached'});
});
test('MM-T5520-5 should change the users email', async ({pw}) => {
const {getUser, systemConsolePage} = await setupAndGetRandomUser(pw);
const newEmail = `${await pw.random.id()}@example.com`;
// # Open menu and click Update Email
await systemConsolePage.systemUsers.actionMenuButtons[0].click();
const updateEmail = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Update email');
await updateEmail.click();
// # Enter new email and click Update
const emailInput = systemConsolePage.page.locator('input[type="email"]');
await emailInput.fill(newEmail);
await systemConsolePage.clickUpdateEmailButton();
// * Verify that the modal closed
await emailInput.waitFor({state: 'detached'});
// * Verify that the email updated
const firstRow = await systemConsolePage.systemUsers.getNthRow(1);
expect(await firstRow.innerText()).toContain(newEmail);
expect((await getUser()).email).toEqual(newEmail);
});
test('MM-T5520-6 should revoke sessions', async ({pw}) => {
const {systemConsolePage} = await setupAndGetRandomUser(pw);
// # Open menu and revoke sessions
await systemConsolePage.systemUsers.actionMenuButtons[0].click();
const removeSessions = await systemConsolePage.systemUsersActionMenus[0].getMenuItem('Revoke sessions');
await removeSessions.click();
// # Press confirm on the modal
await systemConsolePage.confirmModal.confirm();
const firstRow = await systemConsolePage.systemUsers.getNthRow(1);
expect(await firstRow.innerHTML()).not.toContain('class="error"');
});

View file

@ -23,28 +23,36 @@ test('MM-T5523-1 Sortable columns should sort the list when clicked', async ({pw
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// * Verify that 'Email' column has aria-sort attribute
const userDetailsColumnHeader = await systemConsolePage.systemUsers.getColumnHeader('Email');
expect(await userDetailsColumnHeader.isVisible()).toBe(true);
expect(userDetailsColumnHeader).toHaveAttribute('aria-sort');
const emailColumnHeader = systemConsolePage.users.usersTable.getColumnHeader('Email');
await expect(emailColumnHeader).toBeVisible();
await expect(emailColumnHeader).toHaveAttribute('aria-sort');
// # Store the first row's email before sorting
const firstRowWithoutSort = await systemConsolePage.systemUsers.getNthRow(1);
const firstRowEmailWithoutSort = await firstRowWithoutSort.getByText(pw.simpleEmailRe).allInnerTexts();
// # Store all emails before sorting to compare order
const rowsBeforeSort = await systemConsolePage.users.usersTable.bodyRows.count();
const emailsBeforeSort: string[] = [];
for (let i = 0; i < rowsBeforeSort; i++) {
const row = systemConsolePage.users.usersTable.getRowByIndex(i);
const email = await row.getEmail();
emailsBeforeSort.push(email);
}
// # Click on the 'Email' column header to sort
await systemConsolePage.systemUsers.clickSortOnColumn('Email');
await systemConsolePage.systemUsers.isLoadingComplete();
// # Click on the 'Email' column header to sort and wait for sort to complete
await systemConsolePage.users.usersTable.sortByColumn('Email');
// # Store the first row's email after sorting
const firstRowWithSort = await systemConsolePage.systemUsers.getNthRow(1);
const firstRowEmailWithSort = await firstRowWithSort.getByText(pw.simpleEmailRe).allInnerTexts();
// # Store all emails after sorting
const emailsAfterSort: string[] = [];
for (let i = 0; i < rowsBeforeSort; i++) {
const row = systemConsolePage.users.usersTable.getRowByIndex(i);
const email = await row.getEmail();
emailsAfterSort.push(email);
}
// * Verify that the first row is now different
expect(firstRowEmailWithoutSort).not.toBe(firstRowEmailWithSort);
// * Verify that the order has changed (emails array is different)
expect(emailsBeforeSort).not.toEqual(emailsAfterSort);
});
test('MM-T5523-2 Non sortable columns should not sort the list when clicked', async ({pw}) => {
@ -67,24 +75,24 @@ test('MM-T5523-2 Non sortable columns should not sort the list when clicked', as
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// * Verify that 'Last login' column does not have aria-sort attribute
const userDetailsColumnHeader = await systemConsolePage.systemUsers.getColumnHeader('Last login');
expect(await userDetailsColumnHeader.isVisible()).toBe(true);
expect(userDetailsColumnHeader).not.toHaveAttribute('aria-sort');
const lastLoginColumnHeader = systemConsolePage.users.usersTable.getColumnHeader('Last login');
await expect(lastLoginColumnHeader).toBeVisible();
await expect(lastLoginColumnHeader).not.toHaveAttribute('aria-sort');
// # Store the first row's email without sorting
const firstRowWithoutSort = await systemConsolePage.systemUsers.getNthRow(1);
const firstRowEmailWithoutSort = await firstRowWithoutSort.getByText(pw.simpleEmailRe).allInnerTexts();
const firstRowWithoutSort = systemConsolePage.users.usersTable.getRowByIndex(0);
const firstRowEmailWithoutSort = await firstRowWithoutSort.container.getByText(pw.simpleEmailRe).allInnerTexts();
// # Try to click on the 'Last login' column header to sort
await systemConsolePage.systemUsers.clickSortOnColumn('Last login');
await systemConsolePage.users.usersTable.clickSortOnColumn('Last login');
// # Store the first row's email after sorting
const firstRowWithSort = await systemConsolePage.systemUsers.getNthRow(1);
const firstRowEmailWithSort = await firstRowWithSort.getByText(pw.simpleEmailRe).allInnerTexts();
const firstRowWithSort = systemConsolePage.users.usersTable.getRowByIndex(0);
const firstRowEmailWithSort = await firstRowWithSort.container.getByText(pw.simpleEmailRe).allInnerTexts();
// * Verify that the first row's email is still the same
expect(firstRowEmailWithoutSort).toEqual(firstRowEmailWithSort);

View file

@ -18,15 +18,14 @@ test('MM-T5523-3 Should list the column names with checkboxes in the correct ord
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Open the column toggle menu
await systemConsolePage.systemUsers.openColumnToggleMenu();
await systemConsolePage.systemUsersColumnToggleMenu.toBeVisible();
const columnToggleMenu = await systemConsolePage.users.openColumnToggleMenu();
// # Get all the menu items
const menuItems = await systemConsolePage.systemUsersColumnToggleMenu.getAllMenuItems();
const menuItems = columnToggleMenu.getAllMenuItems();
const menuItemsTexts = await menuItems.allInnerTexts();
// * Verify menu items exists in the correct order
@ -59,23 +58,22 @@ test('MM-T5523-4 Should allow certain columns to be checked and others to be dis
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Open the column toggle menu
await systemConsolePage.systemUsers.openColumnToggleMenu();
await systemConsolePage.systemUsersColumnToggleMenu.toBeVisible();
const columnToggleMenu = await systemConsolePage.users.openColumnToggleMenu();
// * Verify that 'Display Name' is disabled
const displayNameMenuItem = await systemConsolePage.systemUsersColumnToggleMenu.getMenuItem('User details');
const displayNameMenuItem = await columnToggleMenu.getMenuItem('User details');
expect(displayNameMenuItem).toBeDisabled();
// * Verify that 'Actions' is disabled
const actionsMenuItem = await systemConsolePage.systemUsersColumnToggleMenu.getMenuItem('Actions');
const actionsMenuItem = await columnToggleMenu.getMenuItem('Actions');
expect(actionsMenuItem).toBeDisabled();
// * Verify that 'Email' however is enabled
const emailMenuItem = await systemConsolePage.systemUsersColumnToggleMenu.getMenuItem('Email');
const emailMenuItem = await columnToggleMenu.getMenuItem('Email');
expect(emailMenuItem).not.toBeDisabled();
});
@ -94,36 +92,35 @@ test('MM-T5523-5 Should show/hide the columns which are toggled on/off', async (
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Open the column toggle menu
await systemConsolePage.systemUsers.openColumnToggleMenu();
await systemConsolePage.systemUsersColumnToggleMenu.toBeVisible();
let columnToggleMenu = await systemConsolePage.users.openColumnToggleMenu();
// # Uncheck the Email and Last login columns to hide them
await systemConsolePage.systemUsersColumnToggleMenu.clickMenuItem('Email');
await systemConsolePage.systemUsersColumnToggleMenu.clickMenuItem('Last login');
await columnToggleMenu.clickMenuItem('Email');
await columnToggleMenu.clickMenuItem('Last login');
// * Close the column toggle menu
await systemConsolePage.systemUsersColumnToggleMenu.close();
await columnToggleMenu.close();
// * Verify that Email column and Last login column are hidden
expect(await systemConsolePage.systemUsers.doesColumnExist('Email')).toBe(false);
expect(await systemConsolePage.systemUsers.doesColumnExist('Last login')).toBe(false);
await expect(systemConsolePage.users.container.getByRole('columnheader', {name: 'Email'})).not.toBeVisible();
await expect(systemConsolePage.users.container.getByRole('columnheader', {name: 'Last login'})).not.toBeVisible();
// # Now open the column toggle menu again
await systemConsolePage.systemUsers.openColumnToggleMenu();
columnToggleMenu = await systemConsolePage.users.openColumnToggleMenu();
// # Check the Email column to show it
await systemConsolePage.systemUsersColumnToggleMenu.clickMenuItem('Email');
await columnToggleMenu.clickMenuItem('Email');
// * Close the column toggle menu
await systemConsolePage.systemUsersColumnToggleMenu.close();
await columnToggleMenu.close();
// * Verify that Email column is now shown
expect(await systemConsolePage.systemUsers.doesColumnExist('Email')).toBe(true);
await expect(systemConsolePage.users.container.getByRole('columnheader', {name: 'Email'})).toBeVisible();
// * Verify that however Last login column is still hidden as we did not check it on
expect(await systemConsolePage.systemUsers.doesColumnExist('Last login')).toBe(false);
await expect(systemConsolePage.users.container.getByRole('columnheader', {name: 'Last login'})).not.toBeVisible();
});

View file

@ -22,28 +22,28 @@ test.fixme('MM-T5522 Should begin export of data when export button is pressed',
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Change the export pw.duration to 30 days
await systemConsolePage.systemUsers.dateRangeSelectorMenuButton.click();
await systemConsolePage.systemUsersDateRangeMenu.clickMenuItem('All time');
// # Change the export duration to All time
const dateRangeMenu = await systemConsolePage.users.openDateRangeSelectorMenu();
await dateRangeMenu.clickMenuItem('All time');
// # Click Export button and confirm the modal
await systemConsolePage.systemUsers.exportButton.click();
await systemConsolePage.exportModal.confirm();
await systemConsolePage.users.clickExport();
await systemConsolePage.users.confirmModal.confirm();
// # Change the export pw.duration to all time
await systemConsolePage.systemUsers.dateRangeSelectorMenuButton.click();
await systemConsolePage.systemUsersDateRangeMenu.clickMenuItem('Last 30 days');
// # Change the export duration to Last 30 days
const dateRangeMenu2 = await systemConsolePage.users.openDateRangeSelectorMenu();
await dateRangeMenu2.clickMenuItem('Last 30 days');
// # Click Export button and confirm the modal
await systemConsolePage.systemUsers.exportButton.click();
await systemConsolePage.exportModal.confirm();
await systemConsolePage.users.clickExport();
await systemConsolePage.users.confirmModal.confirm();
// # Click Export again button and confirm the modal
await systemConsolePage.systemUsers.exportButton.click();
await systemConsolePage.exportModal.confirm();
await systemConsolePage.users.clickExport();
await systemConsolePage.users.confirmModal.confirm();
// * Verify that we are told that one is already running
expect(page.getByText('Export is in progress')).toBeVisible();

View file

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {test} from '@mattermost/playwright-lib';
import {expect, test} from '@mattermost/playwright-lib';
test('MM-T5521-7 Should be able to filter users with team filter', async ({pw}) => {
const {adminUser, adminClient} = await pw.initSetup();
@ -28,27 +28,26 @@ test('MM-T5521-7 Should be able to filter users with team filter', async ({pw})
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Open the filter's popover
await systemConsolePage.systemUsers.openFilterPopover();
await systemConsolePage.systemUsersFilterPopover.toBeVisible();
const filterPopover = await systemConsolePage.users.openFilterPopover();
// # Enter the team name of the first user and select it
await systemConsolePage.systemUsersFilterPopover.searchInTeamMenu(team1.display_name);
await systemConsolePage.systemUsersFilterPopover.teamMenuInput.press('Enter');
await filterPopover.searchInTeamMenu(team1.display_name);
await filterPopover.teamMenuInput.press('Enter');
// # Save the filter and close the popover
await systemConsolePage.systemUsersFilterPopover.save();
await systemConsolePage.systemUsersFilterPopover.close();
await systemConsolePage.systemUsers.isLoadingComplete();
await filterPopover.save();
await filterPopover.close();
await systemConsolePage.users.isLoadingComplete();
// * Verify that the user corresponding to the first team is visible as team-1 filter was applied
await systemConsolePage.systemUsers.verifyRowWithTextIsFound(user1.email);
await expect(systemConsolePage.users.container.getByText(user1.email)).toBeVisible();
// * Verify that the user corresponding to the second team is not visible as team-2 filter was not applied
await systemConsolePage.systemUsers.verifyRowWithTextIsNotFound(user2.email);
await expect(systemConsolePage.users.container.getByText(user2.email)).not.toBeVisible();
});
test('MM-T5521-8 Should be able to filter users with role filter', async ({pw}) => {
@ -73,37 +72,35 @@ test('MM-T5521-8 Should be able to filter users with role filter', async ({pw})
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Open the filter popover
await systemConsolePage.systemUsers.openFilterPopover();
await systemConsolePage.systemUsersFilterPopover.toBeVisible();
const filterPopover = await systemConsolePage.users.openFilterPopover();
// # Open the role filter in the popover
await systemConsolePage.systemUsersFilterPopover.openRoleMenu();
await systemConsolePage.systemUsersRoleMenu.toBeVisible();
// # Select the Guest role from the role filter
await systemConsolePage.systemUsersRoleMenu.clickMenuItem('Guest');
await systemConsolePage.systemUsersRoleMenu.close();
// # Open the role filter in the popover and select Guest
await filterPopover.openRoleMenu();
// Wait for dropdown options and click on Guest
const guestOption = systemConsolePage.page.getByText('Guest', {exact: true});
await guestOption.waitFor();
await guestOption.click();
// # Save the filter and close the popover
await systemConsolePage.systemUsersFilterPopover.save();
await systemConsolePage.systemUsersFilterPopover.close();
await systemConsolePage.systemUsers.isLoadingComplete();
await filterPopover.save();
await filterPopover.close();
await systemConsolePage.users.isLoadingComplete();
// # Search for the guest user with the filter already applied
await systemConsolePage.systemUsers.enterSearchText(guestUser.email);
await systemConsolePage.users.searchUsers(guestUser.email);
// * Verify that guest user is visible as a 'Guest' role filter was applied
await systemConsolePage.systemUsers.verifyRowWithTextIsFound(guestUser.email);
await expect(systemConsolePage.users.container.getByText(guestUser.email)).toBeVisible();
// # Search for the regular user with the filter already applied
await systemConsolePage.systemUsers.enterSearchText(regularUser.email);
await systemConsolePage.users.searchUsers(regularUser.email);
// * Verify that regular user is not visible as 'Guest' role filter was applied
await systemConsolePage.systemUsers.verifyRowWithTextIsFound('No data');
await expect(systemConsolePage.users.container.getByText('No data')).toBeVisible();
});
test('MM-T5521-9 Should be able to filter users with status filter', async ({pw}) => {
@ -128,35 +125,32 @@ test('MM-T5521-9 Should be able to filter users with status filter', async ({pw}
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Open the filter popover
await systemConsolePage.systemUsers.openFilterPopover();
await systemConsolePage.systemUsersFilterPopover.toBeVisible();
const filterPopover = await systemConsolePage.users.openFilterPopover();
// # Open the status filter in the popover
await systemConsolePage.systemUsersFilterPopover.openStatusMenu();
await systemConsolePage.systemUsersStatusMenu.toBeVisible();
await systemConsolePage.systemUsers.isLoadingComplete();
// # Select the Deactivated users from the status filter
await systemConsolePage.systemUsersStatusMenu.clickMenuItem('Deactivated users');
await systemConsolePage.systemUsersStatusMenu.close();
// # Open the status filter in the popover and select Deactivated users
await filterPopover.openStatusMenu();
// Wait for dropdown options and click on Deactivated users
const deactivatedOption = systemConsolePage.page.getByText('Deactivated users', {exact: true});
await deactivatedOption.waitFor();
await deactivatedOption.click();
// # Save the filter and close the popover
await systemConsolePage.systemUsersFilterPopover.save();
await systemConsolePage.systemUsersFilterPopover.close();
await filterPopover.save();
await filterPopover.close();
// # Search for the deactivated user with the filter already applied
await systemConsolePage.systemUsers.enterSearchText(deactivatedUser.email);
await systemConsolePage.users.searchUsers(deactivatedUser.email);
// * Verify that deactivated user is visible as a 'Deactivated' status filter was applied
await systemConsolePage.systemUsers.verifyRowWithTextIsFound(deactivatedUser.email);
await expect(systemConsolePage.users.container.getByText(deactivatedUser.email)).toBeVisible();
// # Search for the regular user with the filter already applied
await systemConsolePage.systemUsers.enterSearchText(regularUser.email);
await systemConsolePage.users.searchUsers(regularUser.email);
// * Verify that regular user is not visible as 'Deactivated' status filter was applied
await systemConsolePage.systemUsers.verifyRowWithTextIsFound('No data');
await expect(systemConsolePage.users.container.getByText('No data')).toBeVisible();
});

View file

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {test} from '@mattermost/playwright-lib';
import {expect, test} from '@mattermost/playwright-lib';
test('MM-T5521-1 Should be able to search users with their first names', async ({pw}) => {
const {adminUser, adminClient} = await pw.initSetup();
@ -22,17 +22,17 @@ test('MM-T5521-1 Should be able to search users with their first names', async (
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Enter the 'First Name' of the first user in the search box
await systemConsolePage.systemUsers.enterSearchText(user1.first_name);
await systemConsolePage.users.searchUsers(user1.first_name);
// * Verify that the searched user i.e first user is found in the list
await systemConsolePage.systemUsers.verifyRowWithTextIsFound(user1.email);
await expect(systemConsolePage.users.container.getByText(user1.email)).toBeVisible();
// * Verify that the second user doesnt appear in the list
await systemConsolePage.systemUsers.verifyRowWithTextIsNotFound(user2.email);
await expect(systemConsolePage.users.container.getByText(user2.email)).not.toBeVisible();
});
test('MM-T5521-2 Should be able to search users with their last names', async ({pw}) => {
@ -54,17 +54,17 @@ test('MM-T5521-2 Should be able to search users with their last names', async ({
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Enter the 'Last Name' of the user in the search box
await systemConsolePage.systemUsers.enterSearchText(user1.last_name);
await systemConsolePage.users.searchUsers(user1.last_name);
// * Verify that the searched user i.e first user is found in the list
await systemConsolePage.systemUsers.verifyRowWithTextIsFound(user1.email);
await expect(systemConsolePage.users.container.getByText(user1.email)).toBeVisible();
// * Verify that the second user doesnt appear in the list
await systemConsolePage.systemUsers.verifyRowWithTextIsNotFound(user2.email);
await expect(systemConsolePage.users.container.getByText(user2.email)).not.toBeVisible();
});
test('MM-T5521-3 Should be able to search users with their emails', async ({pw}) => {
@ -86,17 +86,17 @@ test('MM-T5521-3 Should be able to search users with their emails', async ({pw})
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// * Enter the 'Email' of the first user in the search box
await systemConsolePage.systemUsers.enterSearchText(user1.email);
await systemConsolePage.users.searchUsers(user1.email);
// * Verify that the searched user i.e first user is found in the list
await systemConsolePage.systemUsers.verifyRowWithTextIsFound(user1.email);
await expect(systemConsolePage.users.container.getByText(user1.email)).toBeVisible();
// * Verify that the second user doesnt appear in the list
await systemConsolePage.systemUsers.verifyRowWithTextIsNotFound(user2.email);
await expect(systemConsolePage.users.container.getByText(user2.email)).not.toBeVisible();
});
test('MM-T5521-4 Should be able to search users with their usernames', async ({pw}) => {
@ -118,17 +118,17 @@ test('MM-T5521-4 Should be able to search users with their usernames', async ({p
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// # Enter the 'Username' of the first user in the search box
await systemConsolePage.systemUsers.enterSearchText(user1.username);
await systemConsolePage.users.searchUsers(user1.username);
// * Verify that the searched user i.e first user is found in the list
await systemConsolePage.systemUsers.verifyRowWithTextIsFound(user1.email);
await expect(systemConsolePage.users.container.getByText(user1.email)).toBeVisible();
// * Verify that the another user is not visible
await systemConsolePage.systemUsers.verifyRowWithTextIsNotFound(user2.email);
await expect(systemConsolePage.users.container.getByText(user2.email)).not.toBeVisible();
});
test('MM-T5521-5 Should be able to search users with their nick names', async ({pw}) => {
@ -150,16 +150,16 @@ test('MM-T5521-5 Should be able to search users with their nick names', async ({
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.sidebar.users.click();
// # Enter the 'Nickname' of the first user in the search box
await systemConsolePage.systemUsers.enterSearchText(user1.nickname);
await systemConsolePage.users.searchUsers(user1.nickname);
// * Verify that the searched user i.e first user is found in the list
await systemConsolePage.systemUsers.verifyRowWithTextIsFound(user1.email);
await expect(systemConsolePage.users.container.getByText(user1.email)).toBeVisible();
// * Verify that the second user doesnt appear in the list
await systemConsolePage.systemUsers.verifyRowWithTextIsNotFound(user2.email);
await expect(systemConsolePage.users.container.getByText(user2.email)).not.toBeVisible();
});
test('MM-T5521-6 Should show no user is found when user doesnt exists', async ({pw}) => {
@ -177,10 +177,10 @@ test('MM-T5521-6 Should show no user is found when user doesnt exists', async ({
await systemConsolePage.toBeVisible();
// # Go to Users section
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.sidebar.users.click();
// # Enter random text in the search box
await systemConsolePage.systemUsers.enterSearchText(`!${pw.random.id(15)}_^^^_${pw.random.id(15)}!`);
await systemConsolePage.users.searchUsers(`!${pw.random.id(15)}_^^^_${pw.random.id(15)}!`);
await systemConsolePage.systemUsers.verifyRowWithTextIsFound('No data');
await expect(systemConsolePage.users.container.getByText('No data')).toBeVisible();
});

View file

@ -18,7 +18,7 @@ import {UserProfile} from '@mattermost/types/users';
import {Client4} from '@mattermost/client';
import {UserPropertyField} from '@mattermost/types/properties';
import {expect, test} from '@mattermost/playwright-lib';
import {expect, test, SystemConsolePage} from '@mattermost/playwright-lib';
import {
CustomProfileAttribute,
@ -87,7 +87,7 @@ let adminUser: UserProfile;
let testUser: UserProfile;
let attributeFieldsMap: Record<string, UserPropertyField>;
let adminClient: Client4;
let systemConsolePage: any;
let systemConsolePage: SystemConsolePage;
test.describe('System Console - Admin User Profile Editing', () => {
test.beforeEach(async ({pw}) => {
@ -119,13 +119,13 @@ test.describe('System Console - Admin User Profile Editing', () => {
// Navigate to system console users
await systemConsolePage.goto();
await systemConsolePage.toBeVisible();
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.users.click();
await systemConsolePage.users.toBeVisible();
// Search for target user and navigate to user detail page
await systemConsolePage.systemUsers.enterSearchText(testUser.email);
const userRow = await systemConsolePage.systemUsers.getNthRow(1);
await userRow.getByText(testUser.email).click();
await systemConsolePage.users.searchUsers(testUser.email);
const userRow = systemConsolePage.users.usersTable.getRowByIndex(0);
await userRow.container.getByText(testUser.email).click();
// Wait for user detail page to load
await systemConsolePage.page.waitForURL(`**/admin_console/user_management/user/${testUser.id}`);