mirror of
https://github.com/mattermost/mattermost.git
synced 2026-09-24 16:05:00 +08:00
E2E/Playwright: Reorg POM using accessibility locators (#37315)
* Use semantic locators in Playwright POM * Refactor test IDs for badges and modals to use more descriptive names * remove unused field-with-error
This commit is contained in:
@@ -29,11 +29,11 @@ export default class BrowseChannelsModal {
|
||||
}
|
||||
|
||||
async toBeDoneLoading() {
|
||||
await expect(this.container.locator('.loading-screen')).toHaveCount(0);
|
||||
await expect(this.container.getByTestId('loading-screen')).toHaveCount(0);
|
||||
}
|
||||
|
||||
async toHaveNResults(count: number) {
|
||||
await expect(this.results.locator('.more-modal__row')).toHaveCount(count);
|
||||
await expect(this.results.locator('[data-testid^="ChannelRow-"]')).toHaveCount(count);
|
||||
}
|
||||
|
||||
async fillSearchInput(text: string) {
|
||||
@@ -41,7 +41,7 @@ export default class BrowseChannelsModal {
|
||||
}
|
||||
|
||||
async toHaveChannelAsNthResult(channelName: string, index: number) {
|
||||
const row = this.results.locator('.more-modal__row').nth(index);
|
||||
const row = this.results.locator('[data-testid^="ChannelRow-"]').nth(index);
|
||||
|
||||
expect(await row.getAttribute('data-testid')).toEqual(`ChannelRow-${channelName}`);
|
||||
}
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@ export default class BurnOnReadConcealedPlaceholder {
|
||||
this.container = container;
|
||||
|
||||
// The container itself is the button - no need for nested locator
|
||||
this.icon = container.locator('.BurnOnReadConcealedPlaceholder__icon');
|
||||
this.text = container.locator('.BurnOnReadConcealedPlaceholder__text');
|
||||
this.icon = container.locator('svg');
|
||||
this.text = container.getByText('View message');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@ export default class BurnOnReadConfirmationModal {
|
||||
this.container = container;
|
||||
|
||||
// Modal elements
|
||||
this.title = container.locator('.modal-title, h1, [role="heading"]').first();
|
||||
this.message = container.locator('.modal-body, .modal-message').first();
|
||||
this.title = container.getByRole('heading').first();
|
||||
this.message = container.locator('#confirmModalBody');
|
||||
|
||||
// Action buttons - use flexible selectors
|
||||
this.deleteButton = container.getByRole('button', {name: /delete|burn|confirm/i});
|
||||
|
||||
@@ -11,8 +11,8 @@ export default class BurnOnReadTimerChip {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.flameIcon = container.locator('.BurnOnReadTimerChip__icon');
|
||||
this.timerText = container.locator('.BurnOnReadTimerChip__time');
|
||||
this.flameIcon = container.locator('svg');
|
||||
this.timerText = container.locator('span');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -32,16 +32,16 @@ export default class ChannelsCenterView {
|
||||
this.container = container;
|
||||
this.page = page;
|
||||
|
||||
this.header = new ChannelsHeader(this.container.locator('.channel-header'));
|
||||
this.header = new ChannelsHeader(this.container.locator('#channel-header'));
|
||||
this.postCreate = new ChannelsPostCreate(container.getByTestId('post-create'));
|
||||
this.scheduledDraftOptions = new ChannelsPostCreate(container.locator('#dropdown_send_post_options'));
|
||||
this.postEdit = new ChannelsPostEdit(container.locator('.post-edit__container'));
|
||||
this.postEdit = new ChannelsPostEdit(container.getByTestId('post-edit-container'));
|
||||
this.scheduledPostIndicator = new ScheduledPostIndicator(container.getByTestId('scheduledPostIndicator'));
|
||||
this.editedPostIcon = (postID: string) => container.locator(`#postEdited_${postID}`);
|
||||
this.channelBanner = container.getByTestId('channel_banner_container');
|
||||
this.autotranslationBadge = container.getByTestId('autotranslation-badge');
|
||||
this.flagPostConfirmationDialog = new FlagPostConfirmationDialog(
|
||||
page.locator('#FlagPostModal div.modal-content'),
|
||||
page.getByRole('dialog', {name: 'Quarantine for Review'}),
|
||||
page,
|
||||
);
|
||||
}
|
||||
@@ -185,7 +185,7 @@ export default class ChannelsCenterView {
|
||||
}
|
||||
|
||||
async assertChannelBannerHasEmoticon() {
|
||||
const emoji = this.channelBanner.locator('.emoticon:not(.emoticon--unicode)').first();
|
||||
const emoji = this.channelBanner.getByTestId(/^postEmoji\./).first();
|
||||
await expect(emoji).toBeVisible();
|
||||
|
||||
const backgroundImage = await emoji.evaluate((el) => {
|
||||
@@ -196,7 +196,7 @@ export default class ChannelsCenterView {
|
||||
}
|
||||
|
||||
async assertChannelBannerImageEmojiSize(expectedSizePx: number) {
|
||||
const emoji = this.channelBanner.locator('.emoticon:not(.emoticon--unicode)').first();
|
||||
const emoji = this.channelBanner.getByTestId(/^postEmoji\./).first();
|
||||
await expect(emoji).toBeVisible();
|
||||
|
||||
const {width, height} = await emoji.evaluate((el) => {
|
||||
@@ -214,7 +214,7 @@ export default class ChannelsCenterView {
|
||||
}
|
||||
|
||||
async assertChannelBannerUnicodeEmojiSize(expectedSizePx: number) {
|
||||
const emoji = this.channelBanner.locator('.emoticon--unicode').first();
|
||||
const emoji = this.channelBanner.getByTestId('channel-banner-unicode-emoji').first();
|
||||
await expect(emoji).toBeVisible();
|
||||
|
||||
const fontSize = await emoji.evaluate((el) => {
|
||||
|
||||
+2
-2
@@ -28,9 +28,9 @@ export default class ChannelSettingsModal {
|
||||
this.infoTab = container.getByRole('tab', {name: 'info'});
|
||||
this.configurationTab = container.getByRole('tab', {name: 'configuration'});
|
||||
|
||||
this.infoSettings = new InfoSettings(container.locator('.ChannelSettingsModal__infoTab'));
|
||||
this.infoSettings = new InfoSettings(container.getByTestId('channel-settings-info-tab'));
|
||||
this.configurationSettings = new ConfigurationSettings(
|
||||
container.locator('.ChannelSettingsModal__configurationTab'),
|
||||
container.getByTestId('channel-settings-configuration-tab'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ export default class DeleteScheduledPostModal {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.body = container.locator('.modal-body');
|
||||
this.deleteButton = container.locator('button:has-text("Yes, delete")');
|
||||
this.cancelButton = container.locator('button:has-text("Cancel")');
|
||||
this.body = container.locator('#confirmModalBody');
|
||||
this.deleteButton = container.getByRole('button', {name: 'Yes, delete'});
|
||||
this.cancelButton = container.getByRole('button', {name: 'Cancel'});
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class DirectChannelsModal {
|
||||
this.container = container;
|
||||
|
||||
this.goButton = container.getByRole('button', {name: 'Go'});
|
||||
this.results = container.locator('.more-modal__list');
|
||||
this.results = container.getByTestId('more-modal-list');
|
||||
this.searchInput = container.getByRole('combobox', {name: 'Search for people'});
|
||||
}
|
||||
|
||||
@@ -28,9 +28,7 @@ export default class DirectChannelsModal {
|
||||
await this.fillSearchInput(user.username);
|
||||
|
||||
// This may fail if there's too many group channels containing the provided user
|
||||
const row = this.results
|
||||
.locator('.more-modal__row:not(:has(.more-modal__gm-icon))')
|
||||
.getByText(`@${user.username}`, {exact: false});
|
||||
const row = this.results.getByTestId('direct-message-row').getByText(`@${user.username}`, {exact: false});
|
||||
|
||||
await row.click();
|
||||
|
||||
@@ -38,7 +36,7 @@ export default class DirectChannelsModal {
|
||||
}
|
||||
|
||||
async toHaveNUsersSelected(count: number) {
|
||||
await expect(this.results.locator('.react-select_multi-value')).toHaveCount(count);
|
||||
await expect(this.container.getByRole('button', {name: /^Remove /})).toHaveCount(count);
|
||||
}
|
||||
|
||||
async goToChannel() {
|
||||
@@ -48,7 +46,9 @@ export default class DirectChannelsModal {
|
||||
}
|
||||
|
||||
async toHaveNResults(count: number) {
|
||||
await expect(this.results.locator('.more-modal__row')).toHaveCount(count);
|
||||
await expect(
|
||||
this.results.locator('[data-testid="direct-message-row"], [data-testid="group-message-row"]'),
|
||||
).toHaveCount(count);
|
||||
}
|
||||
|
||||
async fillSearchInput(text: string) {
|
||||
@@ -56,7 +56,9 @@ export default class DirectChannelsModal {
|
||||
}
|
||||
|
||||
async toHaveUserAsNthResult(user: UserProfile, index: number) {
|
||||
const row = this.results.locator('.more-modal__row').nth(index);
|
||||
const row = this.results
|
||||
.locator('[data-testid="direct-message-row"], [data-testid="group-message-row"]')
|
||||
.nth(index);
|
||||
|
||||
await expect(row).toContainText(`@${user.username}`);
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ export default class DraftPost {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.panelHeader = container.locator('.PanelHeader');
|
||||
this.panelBody = container.locator('.DraftPanelBody');
|
||||
this.panelHeader = container.getByTestId('draft-panel-header');
|
||||
this.panelBody = container.getByTestId('draft-panel-body');
|
||||
|
||||
this.postBody = container.locator('.post__body');
|
||||
this.postHeader = container.locator('.post__header');
|
||||
this.postImage = container.locator('.post__img');
|
||||
this.postBody = container.getByTestId('draft-post-body');
|
||||
this.postHeader = container.getByTestId('draft-post-header');
|
||||
this.postImage = container.getByTestId('draft-post-img');
|
||||
|
||||
this.deleteButton = container.locator('#draft_icon-trash-can-outline_delete');
|
||||
this.editButton = container.locator('#draft_icon-pencil-outline_edit');
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class EmojiGifPicker {
|
||||
|
||||
this.gifTab = container.getByText('GIFs');
|
||||
this.gifSearchInput = container.getByPlaceholder('Search GIPHY');
|
||||
this.gifPickerItems = container.locator('.gif-picker__items');
|
||||
this.gifPickerItems = container.getByTestId('gif-picker-items');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -48,8 +48,8 @@ export default class EmojiGifPicker {
|
||||
async getNthGif(n: number) {
|
||||
await expect(this.gifPickerItems).toBeVisible();
|
||||
|
||||
await this.gifPickerItems.locator('img').nth(n).waitFor();
|
||||
const nthGif = this.gifPickerItems.locator('img').nth(n);
|
||||
await this.gifPickerItems.getByRole('img').nth(n).waitFor();
|
||||
const nthGif = this.gifPickerItems.getByRole('img').nth(n);
|
||||
await expect(nthGif).toBeVisible();
|
||||
|
||||
const nthGifSrc = await nthGif.getAttribute('src');
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class FindChannelsModal {
|
||||
this.container = container;
|
||||
|
||||
this.input = container.getByRole('combobox', {name: 'quick switch input'});
|
||||
this.searchList = container.locator('.suggestion-list__item');
|
||||
this.searchList = container.getByRole('option');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
+8
-9
@@ -25,15 +25,14 @@ export default class FlagPostConfirmationDialog {
|
||||
|
||||
this.flagPostReasonInput = container.locator('#FlagPostModal__reason');
|
||||
this.flagPostCommentInput = container.locator('#FlagPostModal__comment');
|
||||
this.cancelButton = container.locator('button.btn.btn-tertiary');
|
||||
this.submitButton = container.locator('button.btn-primary.confirm');
|
||||
this.postContainer = container.locator('[data-testid="FlagPostModal__post-preview_container"]');
|
||||
this.postText = container.locator('div.post-message__text');
|
||||
this.flagReasonOption = page.locator('.react-select__menu-list');
|
||||
this.flagReasonMenuItems = (reason: string) =>
|
||||
this.flagReasonOption.locator(`div.react-select__option:has-text("${reason}")`);
|
||||
this.cannotFlagPostErrorMessage = container.locator('div.FlagPostModal__request-error span');
|
||||
this.requireCommentsErrorMessage = container.locator('div.AdvancedTextbox__error-message span');
|
||||
this.cancelButton = container.getByRole('button', {name: 'Cancel'});
|
||||
this.submitButton = container.getByRole('button', {name: 'Submit'});
|
||||
this.postContainer = container.getByTestId('FlagPostModal__post-preview_container');
|
||||
this.postText = container.getByTestId('post-message-text');
|
||||
this.flagReasonOption = page.getByRole('listbox');
|
||||
this.flagReasonMenuItems = (reason: string) => this.flagReasonOption.getByRole('option', {name: reason});
|
||||
this.cannotFlagPostErrorMessage = container.getByTestId('flag-post-request-error-text');
|
||||
this.requireCommentsErrorMessage = container.getByTestId('advanced-textbox-error-text');
|
||||
}
|
||||
|
||||
async fillFlagComment(comment: string) {
|
||||
|
||||
@@ -15,7 +15,7 @@ export default class ChannelsHeader {
|
||||
this.container = container;
|
||||
|
||||
this.title = container.locator('#channelHeaderTitle');
|
||||
this.channelMenuDropdown = container.locator('[aria-controls="channelHeaderDropdownMenu"]');
|
||||
this.channelMenuDropdown = container.locator('#channelHeaderDropdownButton');
|
||||
this.callButton = container.getByRole('button', {name: /call/i}).first();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ export default class MembersInvitedModal {
|
||||
this.doneButton = container.getByRole('button', {name: 'Done'});
|
||||
this.inviteMoreButton = container.getByRole('button', {name: 'Invite More People'});
|
||||
|
||||
this.sentSection = container.locator('.invitation-modal-confirm--sent');
|
||||
this.notSentSection = container.locator('.invitation-modal-confirm--not-sent');
|
||||
this.sentSection = container.getByTestId('invitation-sent-section');
|
||||
this.notSentSection = container.getByTestId('invitation-not-sent-section');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -36,7 +36,7 @@ export default class MembersInvitedModal {
|
||||
*/
|
||||
async getSentResultReason(): Promise<string> {
|
||||
await expect(this.sentSection).toBeVisible();
|
||||
return (await this.sentSection.locator('.InviteResultRow .reason').textContent()) ?? '';
|
||||
return (await this.sentSection.getByTestId('invitation-result-reason').textContent()) ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ export default class MembersInvitedModal {
|
||||
*/
|
||||
async getNotSentResultReason(): Promise<string> {
|
||||
await expect(this.notSentSection).toBeVisible();
|
||||
return (await this.notSentSection.locator('.InviteResultRow .reason').textContent()) ?? '';
|
||||
return (await this.notSentSection.getByTestId('invitation-result-reason').textContent()) ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,7 +47,7 @@ export default class MessagePriority {
|
||||
async verifyPriorityMenuVisible() {
|
||||
await expect(this.priorityMenu).toBeVisible();
|
||||
// Look for beta text in header
|
||||
await expect(this.priorityMenu.locator('text=Message Priority')).toBeVisible();
|
||||
await expect(this.priorityMenu.getByText('Message Priority')).toBeVisible();
|
||||
}
|
||||
|
||||
async closePriorityMenu() {
|
||||
@@ -56,7 +56,7 @@ export default class MessagePriority {
|
||||
}
|
||||
|
||||
async verifyNoPriorityLabel(postText: string) {
|
||||
const post = this.container.locator(`text=${postText}`);
|
||||
const post = this.container.getByText(postText);
|
||||
await expect(post).toBeVisible();
|
||||
|
||||
// Verify no priority label exists
|
||||
|
||||
@@ -18,8 +18,8 @@ export default class NewChannelModal {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.displayNameInput = container.locator('[name="new-channel-modal-name"]');
|
||||
this.urlSection = container.locator('.new-channel-modal__url');
|
||||
this.displayNameInput = container.getByLabel('Channel name');
|
||||
this.urlSection = container.getByTestId('urlInputLabel');
|
||||
this.purposeInput = container.locator('#new-channel-modal-purpose');
|
||||
this.publicTypeButton = container.locator('#public-private-selector-button-O');
|
||||
this.privateTypeButton = container.locator('#public-private-selector-button-P');
|
||||
|
||||
@@ -29,20 +29,20 @@ export default class ChannelsPost {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.body = container.locator('.post__body');
|
||||
this.body = container.getByTestId('post-body');
|
||||
|
||||
this.profileIcon = container.locator('.profile-icon');
|
||||
this.profileIcon = container.getByTestId('profile-icon');
|
||||
|
||||
this.removePostButton = container.locator('.post__remove');
|
||||
this.removePostButton = container.getByTestId('post-remove-button');
|
||||
|
||||
this.postMenu = new PostMenu(container.locator('.post-menu'));
|
||||
this.threadFooter = new ThreadFooter(container.locator('.ThreadFooter'));
|
||||
this.postMenu = new PostMenu(container.getByTestId(/^post-menu($|-)/));
|
||||
this.threadFooter = new ThreadFooter(container.getByTestId('thread-footer'));
|
||||
|
||||
// Burn-on-Read components
|
||||
this.burnOnReadBadge = new BurnOnReadBadge(container.locator('.BurnOnReadBadge'));
|
||||
this.burnOnReadTimerChip = new BurnOnReadTimerChip(container.locator('.BurnOnReadTimerChip'));
|
||||
this.burnOnReadBadge = new BurnOnReadBadge(container.getByTestId(/^burn-on-read-badge-/));
|
||||
this.burnOnReadTimerChip = new BurnOnReadTimerChip(container.getByTestId('burn-on-read-timer-chip'));
|
||||
this.concealedPlaceholder = new BurnOnReadConcealedPlaceholder(
|
||||
container.locator('.BurnOnReadConcealedPlaceholder'),
|
||||
container.getByTestId(/^burn-on-read-concealed-/),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,12 +41,12 @@ export default class ChannelsPostCreate {
|
||||
this.scheduleMessageButton = container.getByLabel('Schedule message');
|
||||
this.priorityButton = container.getByLabel('Message priority');
|
||||
this.suggestionList = container.getByRole('listbox', {name: 'Suggestions'});
|
||||
this.filePreview = container.locator('.file-preview__container');
|
||||
this.filePreview = container.getByTestId('file-preview-container');
|
||||
|
||||
// Burn-on-Read elements
|
||||
// Use a flexible locator that matches the aria-label pattern
|
||||
this.burnOnReadButton = container.getByRole('button', {name: /Burn-on-read/i});
|
||||
this.burnOnReadLabel = container.locator('.BurnOnReadLabel');
|
||||
this.burnOnReadLabel = container.getByTestId('burn-on-read-label');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -170,8 +170,8 @@ export default class ChannelsPostCreate {
|
||||
async waitUntilFilePreviewContains(files: string[], timeout = duration.ten_sec) {
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const previews = this.filePreview.locator('.file-preview');
|
||||
const details = this.filePreview.locator('.post-image__details');
|
||||
const previews = this.filePreview.getByTestId('file-preview-item');
|
||||
const details = this.filePreview.getByTestId('post-image-details');
|
||||
|
||||
const [previewsCount, detailsCount] = await Promise.all([previews.count(), details.count()]);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export default class ChannelsPostEdit {
|
||||
|
||||
this.attachmentButton = container.locator('#fileUploadButton');
|
||||
this.emojiButton = container.getByLabel('select an emoji');
|
||||
this.sendMessageButton = container.locator('.save');
|
||||
this.sendMessageButton = container.getByRole('button', {name: 'Save'});
|
||||
this.deleteConfirmationDialog = new DeletePostConfirmationDialog(container.page().locator('#deletePostModal'));
|
||||
this.restorePostConfirmationDialog = new RestorePostConfirmationDialog(
|
||||
container.page().locator('#restorePostModal'),
|
||||
@@ -64,12 +64,12 @@ export default class ChannelsPostEdit {
|
||||
}
|
||||
|
||||
async removeFile(fileName: string) {
|
||||
const files = await this.container.locator('.file-preview').all();
|
||||
const files = await this.container.getByTestId('file-preview-item').all();
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const textContent = await files[i].textContent();
|
||||
if (textContent?.includes(fileName)) {
|
||||
const removeButton = files[i].locator('.icon-close');
|
||||
const removeButton = files[i].getByTestId('file-preview-remove');
|
||||
await removeButton.click();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ export default class ProfileModal {
|
||||
this.securityTab = new SecurityTab(container.getByRole('tabpanel', {name: 'Security'}));
|
||||
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
this.saveButton = container.locator('button:has-text("Save")');
|
||||
this.cancelButton = container.locator('button:has-text("Cancel")');
|
||||
this.saveButton = container.getByRole('button', {name: 'Save'});
|
||||
this.cancelButton = container.getByRole('button', {name: 'Cancel'});
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -19,8 +19,8 @@ export default class ScheduleMessageModal {
|
||||
this.timeButton = container.getByTestId('time_button');
|
||||
this.timeOptionDropdown = container.getByLabel('Choose a time');
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
this.scheduleButton = container.locator('button:has-text("Schedule")');
|
||||
this.cancelButton = container.locator('button:has-text("Cancel")');
|
||||
this.scheduleButton = container.getByRole('button', {name: 'Schedule'});
|
||||
this.cancelButton = container.getByRole('button', {name: 'Cancel'});
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -74,7 +74,7 @@ export default class ScheduleMessageModal {
|
||||
await dateLocator.click();
|
||||
|
||||
// Wait for the date-picker calendar to fully close before returning.
|
||||
const calendarPopper = this.container.locator('.date-picker__popper');
|
||||
const calendarPopper = this.container.getByTestId('date-picker-popper');
|
||||
await calendarPopper.waitFor({state: 'hidden'});
|
||||
|
||||
// if day is single digit then prefix with a 0
|
||||
|
||||
@@ -27,12 +27,12 @@ export default class ScheduledPost {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.panelHeader = container.locator('.PanelHeader');
|
||||
this.panelBody = container.locator('.DraftPanelBody');
|
||||
this.panelHeader = container.getByTestId('draft-panel-header');
|
||||
this.panelBody = container.getByTestId('draft-panel-body');
|
||||
|
||||
this.postBody = container.locator('.post__body');
|
||||
this.postHeader = container.locator('.post__header');
|
||||
this.postImage = container.locator('.post__img');
|
||||
this.postBody = container.getByTestId('draft-post-body');
|
||||
this.postHeader = container.getByTestId('draft-post-header');
|
||||
this.postImage = container.getByTestId('draft-post-img');
|
||||
|
||||
this.deleteButton = container.locator('#draft_icon-trash-can-outline_delete');
|
||||
this.editButton = container.locator('#draft_icon-pencil-outline_edit');
|
||||
@@ -41,8 +41,8 @@ export default class ScheduledPost {
|
||||
this.sendNowButton = container.locator('#draft_icon-send-outline_sendNow');
|
||||
|
||||
this.editTextBox = container.getByTestId('edit_textbox');
|
||||
this.saveButton = container.locator('button:has-text("Save")');
|
||||
this.cancelButton = container.locator('button:has-text("Cancel")');
|
||||
this.saveButton = container.getByRole('button', {name: 'Save'});
|
||||
this.cancelButton = container.getByRole('button', {name: 'Cancel'});
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -17,8 +17,8 @@ export default class ScheduledPostIndicator {
|
||||
|
||||
this.icon = container.getByTestId('scheduledPostIcon');
|
||||
this.messageText = container.locator('span').first();
|
||||
this.seeAllLink = container.locator('a:has-text("See all")');
|
||||
this.scheduledMessageLink = container.locator('a:has-text("scheduled message")');
|
||||
this.seeAllLink = container.getByRole('link', {name: 'See all.'});
|
||||
this.scheduledMessageLink = container.getByRole('link', {name: /scheduled messages?/});
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -22,9 +22,9 @@ export default class SearchBox {
|
||||
this.filesButton = container.getByRole('button', {name: 'Files'});
|
||||
this.searchInput = container.getByLabel('Search messages');
|
||||
this.searchBoxClose = container.getByTestId('searchBoxClose');
|
||||
this.selectedSuggestion = container.locator('.suggestion--selected').locator('.suggestion-list__main');
|
||||
this.selectedSuggestion = container.getByTestId('suggestion-selected').getByTestId('suggestion-list__main');
|
||||
this.searchHints = container.locator('#searchHints');
|
||||
this.clearButton = container.locator('.input-clear-x');
|
||||
this.clearButton = container.getByTestId('input-clear');
|
||||
}
|
||||
|
||||
// clearIfPossible clears the search input if the clear button is visible. Returns true if the clear button was clicked.
|
||||
@@ -42,6 +42,6 @@ export default class SearchBox {
|
||||
}
|
||||
|
||||
getSelectedSuggestion() {
|
||||
return this.searchHints.locator('.suggestion--selected');
|
||||
return this.searchHints.getByTestId('suggestion-selected');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ export default class SendMessageNowModal {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.body = container.locator('.modal-body');
|
||||
this.sendNowButton = container.locator('button:has-text("Yes, send now")');
|
||||
this.cancelButton = container.locator('button:has-text("Cancel")');
|
||||
this.body = container.locator('#confirmModalBody');
|
||||
this.sendNowButton = container.getByRole('button', {name: 'Yes, send now'});
|
||||
this.cancelButton = container.getByRole('button', {name: 'Cancel'});
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ export default class DisplaySettings {
|
||||
|
||||
readonly title;
|
||||
public id = '#displaySettings';
|
||||
readonly expandedSection;
|
||||
public expandedSectionId = '.section-max';
|
||||
readonly expandedSection;
|
||||
|
||||
readonly themeEditButton;
|
||||
readonly clockDisplayEditButton;
|
||||
@@ -64,7 +64,7 @@ export default class DisplaySettings {
|
||||
this.container = container;
|
||||
|
||||
this.title = container.getByRole('heading', {name: 'Display Settings', exact: true});
|
||||
this.expandedSection = container.locator(this.expandedSectionId);
|
||||
this.expandedSection = container.locator('section');
|
||||
|
||||
// Edit buttons for each setting section - IDs are {section}Edit pattern from webapp
|
||||
this.themeEditButton = container.locator('#themeEdit');
|
||||
@@ -93,8 +93,10 @@ export default class DisplaySettings {
|
||||
}
|
||||
|
||||
async verifySectionIsExpanded(section: DisplaySettingsSection) {
|
||||
await expect(this.container.locator('.section-min', {hasText: sectionTitles[section]})).not.toBeVisible();
|
||||
await expect(
|
||||
this.container.getByTestId('section-min').filter({hasText: sectionTitles[section]}),
|
||||
).not.toBeVisible();
|
||||
|
||||
await expect(this.container.locator('.section-max', {hasText: sectionTitles[section]})).toBeVisible();
|
||||
await expect(this.container.locator('section', {hasText: sectionTitles[section]})).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default class SettingsModal {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.content = container.locator('.modal-content');
|
||||
this.content = container;
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
|
||||
this.notificationsTab = container.getByRole('tab', {name: 'notifications'});
|
||||
|
||||
@@ -20,8 +20,8 @@ export default class ChannelsSidebarLeft {
|
||||
this.teamMenuButton = container.locator('#sidebarTeamMenuButton');
|
||||
this.browseOrCreateChannelButton = container.locator('#browseOrAddChannelMenuButton');
|
||||
this.findChannelButton = container.getByRole('button', {name: 'Find Channels'});
|
||||
this.scheduledPostBadge = container.locator('span.scheduledPostBadge');
|
||||
this.unreadChannelFilter = container.locator('.SidebarFilters_filterButton');
|
||||
this.scheduledPostBadge = container.getByTestId('scheduled-post-badge');
|
||||
this.unreadChannelFilter = container.getByTestId('sidebar-unread-filter-button');
|
||||
this.openDirectMessageButton = container.getByRole('button', {name: 'Write a direct message'});
|
||||
}
|
||||
|
||||
@@ -75,6 +75,6 @@ export default class ChannelsSidebarLeft {
|
||||
* Gets all unread channel items in the sidebar.
|
||||
*/
|
||||
getUnreadChannels(): Locator {
|
||||
return this.container.locator('.SidebarLink.unread-title');
|
||||
return this.container.getByTestId('sidebar-unread-channel');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,15 +31,19 @@ export default class ChannelsSidebarRight {
|
||||
this.container = container;
|
||||
|
||||
this.scheduledPostIndicator = new ScheduledPostIndicator(container.getByTestId('scheduledPostIndicator'));
|
||||
this.scheduledDraftChannelInfoMessage = container.locator('div.ScheduledPostIndicator span');
|
||||
this.scheduledDraftSeeAllLink = container.locator('a:has-text("See all")');
|
||||
this.scheduledDraftChannelInfoMessageText = container.locator('span:has-text("Message scheduled for")');
|
||||
this.rhsPostBody = container.locator('.post-message__text');
|
||||
this.scheduledDraftChannelInfoMessage = container.getByTestId('scheduledPostIndicator').locator('span');
|
||||
this.scheduledDraftSeeAllLink = container
|
||||
.getByTestId('scheduledPostIndicator')
|
||||
.getByRole('link', {name: 'See all.'});
|
||||
this.scheduledDraftChannelInfoMessageText = container
|
||||
.getByTestId('scheduledPostIndicator')
|
||||
.getByText(/Message scheduled for/);
|
||||
this.rhsPostBody = container.getByTestId('post-message-text');
|
||||
this.postCreate = new ChannelsPostCreate(container.getByTestId('comment-create'), true);
|
||||
this.closeButton = container.locator('.sidebar--right__close');
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
|
||||
this.editTextbox = container.locator('#edit_textbox');
|
||||
this.postEdit = new ChannelsPostEdit(container.locator('.post-edit__container'));
|
||||
this.postEdit = new ChannelsPostEdit(container.getByTestId('post-edit-container'));
|
||||
this.currentVersionEditedPosttext = (postID: any) => container.locator(`#rhsPostMessageText_${postID} p`);
|
||||
this.restorePreviousPostVersionIcon = container.locator(
|
||||
'button[aria-label="Select to restore an old message."]',
|
||||
|
||||
+7
-7
@@ -25,14 +25,14 @@ export default class TeamSettingsModal {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.closeButton = container.locator('.modal-header button.close').first();
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
|
||||
this.infoTab = container.locator('[data-testid="info-tab-button"]');
|
||||
this.accessTab = container.locator('[data-testid="access-tab-button"]');
|
||||
this.accessPoliciesTab = container.locator('[data-testid="access_policies-tab-button"]');
|
||||
this.infoTab = container.getByTestId('info-tab-button');
|
||||
this.accessTab = container.getByTestId('access-tab-button');
|
||||
this.accessPoliciesTab = container.getByTestId('access_policies-tab-button');
|
||||
|
||||
this.saveButton = container.locator('button[data-testid="SaveChangesPanel__save-btn"]');
|
||||
this.undoButton = container.locator('button[data-testid="SaveChangesPanel__cancel-btn"]');
|
||||
this.saveButton = container.getByTestId('SaveChangesPanel__save-btn');
|
||||
this.undoButton = container.getByTestId('SaveChangesPanel__cancel-btn');
|
||||
|
||||
this.infoSettings = new InfoSettings(container);
|
||||
this.accessSettings = new AccessSettings(container);
|
||||
@@ -81,7 +81,7 @@ export default class TeamSettingsModal {
|
||||
}
|
||||
|
||||
async verifyUnsavedChanges() {
|
||||
const warningText = this.container.locator('.SaveChangesPanel:has-text("You have unsaved changes")');
|
||||
const warningText = this.container.getByText('You have unsaved changes');
|
||||
await expect(warningText).toBeVisible({timeout: 3000});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class ThreadFooter {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.replyButton = container.locator('.ReplyButton');
|
||||
this.replyButton = container.getByTestId('thread-footer-reply-button');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -16,11 +16,11 @@ export default class Footer {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.copyright = container.locator('.footer-copyright');
|
||||
this.aboutLink = container.locator('text=About');
|
||||
this.privacyPolicyLink = container.locator('text=Privacy Policy');
|
||||
this.termsLink = container.locator('text=Terms');
|
||||
this.helpLink = container.locator('text=Help');
|
||||
this.copyright = container.getByTestId('footer-copyright');
|
||||
this.aboutLink = container.getByText('About');
|
||||
this.privacyPolicyLink = container.getByText('Privacy Policy');
|
||||
this.termsLink = container.getByText('Terms');
|
||||
this.helpLink = container.getByText('Help');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class MainHeader {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.logo = container.locator('.header-logo-link');
|
||||
this.logo = container.getByTestId('header-logo-link');
|
||||
this.backButton = container.getByTestId('back_button');
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ export class RadioSetting {
|
||||
readonly container: Locator;
|
||||
readonly trueOption: Locator;
|
||||
readonly falseOption: Locator;
|
||||
readonly helpText: Locator;
|
||||
readonly helpText: Locator | null;
|
||||
|
||||
constructor(container: Locator) {
|
||||
constructor(container: Locator, settingId?: string) {
|
||||
this.container = container;
|
||||
this.trueOption = container.getByRole('radio', {name: 'True'});
|
||||
this.falseOption = container.getByRole('radio', {name: 'False'});
|
||||
this.helpText = container.locator('.help-text');
|
||||
this.helpText = settingId ? container.getByTestId(settingId + 'help-text') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,13 +69,13 @@ export class TextInputSetting {
|
||||
readonly container: Locator;
|
||||
readonly label: Locator;
|
||||
readonly input: Locator;
|
||||
readonly helpText: Locator;
|
||||
readonly helpText: Locator | null;
|
||||
|
||||
constructor(container: Locator, labelText: string) {
|
||||
constructor(container: Locator, labelText: string, settingId?: string) {
|
||||
this.container = container;
|
||||
this.label = container.getByText(labelText);
|
||||
this.input = container.locator('input.form-control').first();
|
||||
this.helpText = container.locator('.help-text');
|
||||
this.input = container.getByRole('textbox').first();
|
||||
this.helpText = settingId ? container.getByTestId(settingId + 'help-text') : null;
|
||||
}
|
||||
|
||||
async fill(value: string) {
|
||||
@@ -103,13 +103,13 @@ export class NumberInputSetting {
|
||||
readonly container: Locator;
|
||||
readonly label: Locator;
|
||||
readonly input: Locator;
|
||||
readonly helpText: Locator;
|
||||
readonly helpText: Locator | null;
|
||||
|
||||
constructor(container: Locator, labelText: string) {
|
||||
constructor(container: Locator, labelText: string, settingId?: string) {
|
||||
this.container = container;
|
||||
this.label = container.getByText(labelText);
|
||||
this.input = container.getByRole('spinbutton');
|
||||
this.helpText = container.locator('.help-text');
|
||||
this.helpText = settingId ? container.getByTestId(settingId + 'help-text') : null;
|
||||
}
|
||||
|
||||
async fill(value: string) {
|
||||
@@ -136,14 +136,14 @@ export class DropdownSetting {
|
||||
readonly container: Locator;
|
||||
readonly label: Locator;
|
||||
readonly dropdown: Locator;
|
||||
readonly helpText: Locator;
|
||||
readonly helpText: Locator | null;
|
||||
|
||||
constructor(container: Locator, labelText: string) {
|
||||
constructor(container: Locator, labelText: string, settingId?: string) {
|
||||
this.container = container;
|
||||
this.label = container.getByText(labelText);
|
||||
// Scope combobox to this form-group (unscoped matches e.g. sidebar search).
|
||||
this.dropdown = container.getByRole('combobox').first();
|
||||
this.helpText = container.locator('.help-text');
|
||||
this.helpText = settingId ? container.getByTestId(settingId + 'help-text') : null;
|
||||
}
|
||||
|
||||
async select(option: string) {
|
||||
@@ -171,8 +171,8 @@ export class AdminSectionPanel {
|
||||
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');
|
||||
this.description = container.getByTestId('admin-section-panel-description');
|
||||
this.body = container.getByTestId('admin-section-panel-body');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class BaseModal {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.title = container.locator('.modal-title');
|
||||
this.title = container.getByRole('heading');
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
this.cancelButton = container.getByRole('button', {name: 'Cancel'});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class SystemConsoleHeader {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.title = container.locator('.admin-console__header');
|
||||
this.title = container.getByTestId('admin-console-header');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class SystemConsoleNavbar {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.backLink = container.locator('.backstage-navbar__back');
|
||||
this.backLink = container.getByTestId('backstage-navbar-back');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
+16
-12
@@ -36,17 +36,15 @@ export default class MobileSecurity {
|
||||
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'}),
|
||||
container.getByTestId('MobileSecuritySettings.General'),
|
||||
);
|
||||
this.microsoftIntune = new MicrosoftIntunePanel(container.getByTestId('MobileSecuritySettings.Intune'));
|
||||
this.mobileEphemeralMode = new MobileEphemeralModePanel(
|
||||
container.locator('.AdminSectionPanel').filter({hasText: 'Mobile Ephemeral Mode'}),
|
||||
container.getByTestId('MobileSecuritySettings.EphemeralMode'),
|
||||
);
|
||||
|
||||
this.saveButton = container.getByRole('button', {name: 'Save'});
|
||||
this.errorMessage = container.locator('.error-message');
|
||||
this.errorMessage = container.getByTestId('errorMessage');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -143,16 +141,19 @@ class MobileEphemeralModePanel extends AdminSectionPanel {
|
||||
this.body.getByRole('group', {name: /Enable Mobile Ephemeral Mode/}),
|
||||
);
|
||||
this.disconnectionTimeout = new NumberInputSetting(
|
||||
this.body.locator('.form-group').filter({hasText: 'Disconnection Timeout (seconds):'}),
|
||||
this.body.getByTestId('MobileEphemeralModeSettings.DisconnectionTimeoutSeconds'),
|
||||
'Disconnection Timeout (seconds):',
|
||||
'MobileEphemeralModeSettings.DisconnectionTimeoutSeconds',
|
||||
);
|
||||
this.offlinePersistenceTimer = new NumberInputSetting(
|
||||
this.body.locator('.form-group').filter({hasText: 'Offline Persistence Timer (hours):'}),
|
||||
this.body.getByTestId('MobileEphemeralModeSettings.OfflinePersistenceTimerHours'),
|
||||
'Offline Persistence Timer (hours):',
|
||||
'MobileEphemeralModeSettings.OfflinePersistenceTimerHours',
|
||||
);
|
||||
this.autoCacheCleanup = new NumberInputSetting(
|
||||
this.body.locator('.form-group').filter({hasText: 'Auto Cache Cleanup (days):'}),
|
||||
this.body.getByTestId('MobileEphemeralModeSettings.AutoCacheCleanupDays'),
|
||||
'Auto Cache Cleanup (days):',
|
||||
'MobileEphemeralModeSettings.AutoCacheCleanupDays',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -169,16 +170,19 @@ class MicrosoftIntunePanel extends AdminSectionPanel {
|
||||
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:'}),
|
||||
this.body.getByTestId('IntuneSettings.AuthService'),
|
||||
'Auth Provider:',
|
||||
'IntuneSettings.AuthService',
|
||||
);
|
||||
this.tenantId = new TextInputSetting(
|
||||
this.body.locator('.form-group').filter({hasText: 'Tenant ID:'}),
|
||||
this.body.getByTestId('IntuneSettings.TenantId'),
|
||||
'Tenant ID:',
|
||||
'IntuneSettings.TenantId',
|
||||
);
|
||||
this.clientId = new TextInputSetting(
|
||||
this.body.locator('.form-group').filter({hasText: 'Application (Client) ID:'}),
|
||||
this.body.getByTestId('IntuneSettings.ClientId'),
|
||||
'Application (Client) ID:',
|
||||
'IntuneSettings.ClientId',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-34
@@ -33,39 +33,25 @@ export default class TeamStatistics {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.header = container.locator('.team-statistics__header');
|
||||
this.header = container.getByTestId('team-statistics-header');
|
||||
|
||||
this.teamFilterDropdown = container.getByTestId('teamFilter');
|
||||
|
||||
this.banner = container.locator('.banner');
|
||||
this.banner = container.getByTestId('team-statistics-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.totalActivatedUsers = new StatCard(container.getByTestId('totalActiveUsersCard'), 'totalActiveUsers');
|
||||
this.publicChannels = new StatCard(container.getByTestId('publicChannelsCard'), 'publicChannels');
|
||||
this.privateChannels = new StatCard(container.getByTestId('privateChannelsCard'), 'privateChannels');
|
||||
this.totalPosts = new StatCard(container.getByTestId('totalPostsCard'), 'totalPosts');
|
||||
|
||||
this.totalPostsChart = new ChartSection(
|
||||
container.locator('.total-count.by-day').filter({hasText: 'Total Posts'}),
|
||||
);
|
||||
this.totalPostsChart = new ChartSection(container.getByTestId('totalPostsChart'), 'totalPosts');
|
||||
this.activeUsersWithPostsChart = new ChartSection(
|
||||
container.locator('.total-count.by-day').filter({hasText: 'Active Users With Posts'}),
|
||||
container.getByTestId('activeUsersWithPostsChart'),
|
||||
'activeUsersWithPosts',
|
||||
);
|
||||
|
||||
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'}),
|
||||
);
|
||||
this.recentActiveUsers = new TableSection(container.getByTestId('recentActiveUsersChart'), 'recentActiveUsers');
|
||||
this.newlyCreatedUsers = new TableSection(container.getByTestId('newlyCreatedUsersChart'), 'newlyCreatedUsers');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -103,10 +89,11 @@ class StatCard {
|
||||
readonly title: Locator;
|
||||
readonly value: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
constructor(container: Locator, id: string) {
|
||||
this.container = container;
|
||||
this.title = container.locator('.title');
|
||||
this.value = container.locator('.content');
|
||||
// statistic_count.tsx renders data-testid="{id}Title" and data-testid="{id}" for title/value
|
||||
this.title = container.getByTestId(id + 'Title');
|
||||
this.value = container.getByTestId(id);
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -123,10 +110,11 @@ class ChartSection {
|
||||
readonly title: Locator;
|
||||
readonly content: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
constructor(container: Locator, id: string) {
|
||||
this.container = container;
|
||||
this.title = container.locator('.title');
|
||||
this.content = container.locator('.content');
|
||||
// line_chart.tsx renders data-testid="{id}Title" and data-testid="{id}Content"
|
||||
this.title = container.getByTestId(id + 'Title');
|
||||
this.content = container.getByTestId(id + 'Content');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -144,10 +132,11 @@ class TableSection {
|
||||
readonly title: Locator;
|
||||
readonly table: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
constructor(container: Locator, id: string) {
|
||||
this.container = container;
|
||||
this.title = container.locator('.title');
|
||||
this.table = container.locator('table');
|
||||
// table_chart.tsx renders data-testid="{id}Title" and data-testid="{id}Content"
|
||||
this.title = container.getByTestId(id + 'Title');
|
||||
this.table = container.getByTestId(id + 'Content').locator('table');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
+4
-4
@@ -28,15 +28,15 @@ export default class Localization {
|
||||
|
||||
this.header = container.getByText('Localization', {exact: true});
|
||||
this.featureDiscoveryBlock = container.getByText('Remove language barriers with auto-translation');
|
||||
this.autoTranslationSection = container.locator('.autotranslation-section-header');
|
||||
this.autoTranslationToggle = container.locator('.autotranslation-section-toggle').locator('button');
|
||||
this.autoTranslationSection = container.getByTestId('autotranslation-section-header');
|
||||
this.autoTranslationToggle = container.getByTestId('autotranslation-section-toggle').locator('button');
|
||||
this.providerDropdown = container.getByTestId('Providerdropdown');
|
||||
this.mattermostAgentsInactiveNotice = container.getByText(
|
||||
'LLMs must first be configured in the Agents plugin.',
|
||||
);
|
||||
this.mattermostAgentsConfigLink = container.getByRole('link', {name: /Go to Agents plugin config/i});
|
||||
this.libreTranslateUrlInput = container.locator('input[id="URL"]');
|
||||
this.libreTranslateApiKeyInput = container.locator('input[id="APIKey"]');
|
||||
this.libreTranslateUrlInput = container.locator('#URL');
|
||||
this.libreTranslateApiKeyInput = container.locator('#APIKey');
|
||||
this.targetLanguagesMultiSelect = container.getByTestId('TargetLanguages');
|
||||
this.saveButton = container.getByRole('button', {name: 'Save'});
|
||||
}
|
||||
|
||||
+15
-8
@@ -57,37 +57,44 @@ export default class Notifications {
|
||||
);
|
||||
|
||||
this.emailNotificationContents = new DropdownSetting(
|
||||
container.locator('.form-group').filter({hasText: 'Email Notification Contents:'}),
|
||||
container.getByTestId('EmailSettings.EmailNotificationContentsType'),
|
||||
'Email Notification Contents:',
|
||||
'EmailSettings.EmailNotificationContentsType',
|
||||
);
|
||||
this.pushNotificationContents = new DropdownSetting(
|
||||
container.locator('.form-group').filter({hasText: 'Push Notification Contents:'}),
|
||||
container.getByTestId('EmailSettings.PushNotificationContents'),
|
||||
'Push Notification Contents:',
|
||||
'EmailSettings.PushNotificationContents',
|
||||
);
|
||||
|
||||
this.notificationDisplayName = new TextInputSetting(
|
||||
container.locator('.form-group').filter({hasText: 'Notification Display Name:'}),
|
||||
container.getByTestId('EmailSettings.FeedbackName'),
|
||||
'Notification Display Name:',
|
||||
'EmailSettings.FeedbackName',
|
||||
);
|
||||
this.notificationFromAddress = new TextInputSetting(
|
||||
container.locator('.form-group').filter({hasText: 'Notification From Address:'}),
|
||||
container.getByTestId('EmailSettings.FeedbackEmail'),
|
||||
'Notification From Address:',
|
||||
'EmailSettings.FeedbackEmail',
|
||||
);
|
||||
this.supportEmailAddress = new TextInputSetting(
|
||||
container.locator('.form-group').filter({hasText: 'Support Email Address:'}),
|
||||
container.getByTestId('SupportSettings.SupportEmail'),
|
||||
'Support Email Address:',
|
||||
'SupportSettings.SupportEmail',
|
||||
);
|
||||
this.notificationReplyToAddress = new TextInputSetting(
|
||||
container.locator('.form-group').filter({hasText: 'Notification Reply-To Address:'}),
|
||||
container.getByTestId('EmailSettings.ReplyToAddress'),
|
||||
'Notification Reply-To Address:',
|
||||
'EmailSettings.ReplyToAddress',
|
||||
);
|
||||
this.notificationFooterMailingAddress = new TextInputSetting(
|
||||
container.locator('.form-group').filter({hasText: 'Notification Footer Mailing Address:'}),
|
||||
container.getByTestId('EmailSettings.FeedbackOrganization'),
|
||||
'Notification Footer Mailing Address:',
|
||||
'EmailSettings.FeedbackOrganization',
|
||||
);
|
||||
|
||||
this.saveButton = container.getByRole('button', {name: 'Save'});
|
||||
this.errorMessage = container.locator('.has-error');
|
||||
this.errorMessage = container.getByTestId('errorMessage');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
+12
-11
@@ -244,9 +244,10 @@ export default class SystemProperties {
|
||||
/**
|
||||
* Resolves the warning AlertBanner whose title text matches `title`.
|
||||
* Banners stack below the table; one per unique error type.
|
||||
* Each banner has data-testid set to its validation warning id (e.g. 'user_properties.validation.name_required').
|
||||
*/
|
||||
validationBannerByTitle(title: string | RegExp): Locator {
|
||||
return this.container.locator('.AlertBanner').filter({hasText: title});
|
||||
return this.container.getByTestId(/^user_properties\.validation/).filter({hasText: title});
|
||||
}
|
||||
|
||||
validationMessage(text: string | RegExp): Locator {
|
||||
@@ -260,17 +261,17 @@ export default class SystemProperties {
|
||||
* render numbered chips + an add-value input instead of the react-select.
|
||||
*/
|
||||
lastRankValues(): Locator {
|
||||
return this.container.locator('.user-property-rank-values').last();
|
||||
return this.container.getByTestId('user-property-rank-values').last();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add-value input inside the ranked-values cell (auto-assigns next rank).
|
||||
* Located by class, not placeholder: the placeholder ('Add values… (required)')
|
||||
* Located by testid, not placeholder: the placeholder ('Add values… (required)')
|
||||
* only renders in the empty state and disappears once a value is added, so
|
||||
* placeholder-based lookups break when adding more than one value.
|
||||
*/
|
||||
rankAddInput(): Locator {
|
||||
return this.lastRankValues().locator('.user-property-rank-values__add-input');
|
||||
return this.lastRankValues().getByTestId('user-property-rank-values__add-input');
|
||||
}
|
||||
|
||||
async addRankValueToLast(value: string) {
|
||||
@@ -285,31 +286,31 @@ export default class SystemProperties {
|
||||
}
|
||||
}
|
||||
|
||||
/** All ranked chip buttons, in DOM order (ascending rank, left→right). */
|
||||
/** All ranked chip spans, in DOM order (ascending rank, left→right). */
|
||||
rankChips(): Locator {
|
||||
return this.container.locator('.user-property-rank-values__chip');
|
||||
return this.container.getByTestId('user-property-rank-values__chip');
|
||||
}
|
||||
|
||||
/**
|
||||
* A single ranked chip button by its option label. Unlike the test-id
|
||||
* A single ranked chip span by its option label. Unlike the test-id
|
||||
* locator, this works for both newly-added options (no server id yet) and
|
||||
* API-created options (real id). Exact text avoids matching "Secret"
|
||||
* inside "TopSecret".
|
||||
*/
|
||||
rankChip(name: string): Locator {
|
||||
return this.container
|
||||
.locator('.user-property-rank-values__chip')
|
||||
.getByTestId('user-property-rank-values__chip')
|
||||
.filter({has: this.page.getByText(name, {exact: true})});
|
||||
}
|
||||
|
||||
/** The numbered badge inside a chip; its text is the rank integer. */
|
||||
rankBadge(name: string): Locator {
|
||||
return this.rankChip(name).locator('.rank-badge');
|
||||
return this.rankChip(name).getByTestId('rank-badge');
|
||||
}
|
||||
|
||||
/** Ordered chip labels as displayed (trimmed of the badge text). */
|
||||
async rankChipLabels(): Promise<string[]> {
|
||||
return this.rankChips().locator('.user-property-rank-values__chip-label').allInnerTexts();
|
||||
return this.rankChips().getByTestId('user-property-rank-values__chip-label').allInnerTexts();
|
||||
}
|
||||
|
||||
// Per-chip popover (rendered at page level via portal).
|
||||
@@ -357,7 +358,7 @@ export default class SystemProperties {
|
||||
}
|
||||
|
||||
rankedModalRows(): Locator {
|
||||
return this.rankedModal().locator('.ranked-schema-modal__row');
|
||||
return this.rankedModal().getByTestId('rankedSchemaRow');
|
||||
}
|
||||
|
||||
rankedModalSaveButton(): Locator {
|
||||
|
||||
+10
-10
@@ -45,7 +45,7 @@ class AdminRolesPanel {
|
||||
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'));
|
||||
this.dataGrid = new DataGrid(container.getByTestId('dataGrid'));
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -89,32 +89,32 @@ class DataGrid {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.header = container.locator('.DataGrid_header');
|
||||
this.rows = container.locator('.DataGrid_rows');
|
||||
this.header = container.getByTestId('dataGrid-header');
|
||||
this.rows = container.getByTestId('dataGrid-rows');
|
||||
|
||||
// Individual role rows
|
||||
this.systemAdmin = new RoleRow(
|
||||
this.rows.locator('.DataGrid_row').filter({hasText: 'System Admin'}),
|
||||
this.rows.getByTestId('dataGrid-row').filter({hasText: 'System Admin'}),
|
||||
'system_admin_edit',
|
||||
);
|
||||
this.systemManager = new RoleRow(
|
||||
this.rows.locator('.DataGrid_row').filter({hasText: 'System Manager'}),
|
||||
this.rows.getByTestId('dataGrid-row').filter({hasText: 'System Manager'}),
|
||||
'system_manager_edit',
|
||||
);
|
||||
this.userManager = new RoleRow(
|
||||
this.rows.locator('.DataGrid_row').filter({hasText: 'User Manager'}),
|
||||
this.rows.getByTestId('dataGrid-row').filter({hasText: 'User Manager'}),
|
||||
'system_user_manager_edit',
|
||||
);
|
||||
this.customGroupManager = new RoleRow(
|
||||
this.rows.locator('.DataGrid_row').filter({hasText: 'Custom Group Manager'}),
|
||||
this.rows.getByTestId('dataGrid-row').filter({hasText: 'Custom Group Manager'}),
|
||||
'system_custom_group_admin_edit',
|
||||
);
|
||||
this.sharedChannelManager = new RoleRow(
|
||||
this.rows.locator('.DataGrid_row').filter({hasText: 'Shared Channel Manager'}),
|
||||
this.rows.getByTestId('dataGrid-row').filter({hasText: 'Shared Channel Manager'}),
|
||||
'system_shared_channel_manager_edit',
|
||||
);
|
||||
this.viewer = new RoleRow(
|
||||
this.rows.locator('.DataGrid_row').filter({hasText: 'Viewer'}),
|
||||
this.rows.getByTestId('dataGrid-row').filter({hasText: 'Viewer'}),
|
||||
'system_read_only_admin_edit',
|
||||
);
|
||||
}
|
||||
@@ -134,7 +134,7 @@ class RoleRow {
|
||||
constructor(container: Locator, editTestId: string) {
|
||||
this.container = container;
|
||||
|
||||
const cells = container.locator('.DataGrid_cell');
|
||||
const cells = container.getByTestId('dataGrid-cell');
|
||||
this.roleName = cells.nth(0);
|
||||
this.description = cells.nth(1);
|
||||
this.type = cells.nth(2);
|
||||
|
||||
+7
-13
@@ -19,16 +19,10 @@ export default class PermissionsSystemScheme {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.systemSchemeHeader = container.locator('.admin-console__header').getByText('System Scheme', {exact: true});
|
||||
this.channelAdministratorsSection = container
|
||||
.locator('.permissions-block')
|
||||
.filter({hasText: 'Channel Administrators'});
|
||||
this.teamAdministratorsSection = container
|
||||
.locator('.permissions-block')
|
||||
.filter({hasText: 'Team Administrators'});
|
||||
this.systemAdministratorsSection = container
|
||||
.locator('.permissions-block')
|
||||
.filter({hasText: 'System Administrators'});
|
||||
this.systemSchemeHeader = container.getByText('System Scheme', {exact: true});
|
||||
this.channelAdministratorsSection = container.locator('#channelAdministratorsSection');
|
||||
this.teamAdministratorsSection = container.locator('#teamAdministratorsSection');
|
||||
this.systemAdministratorsSection = container.locator('#systemAdministratorsSection');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -40,7 +34,7 @@ export default class PermissionsSystemScheme {
|
||||
* There can be two (public and private channel).
|
||||
*/
|
||||
getManageChannelAutoTranslationRows(section: Locator): Locator {
|
||||
return section.locator('.permission-row').filter({hasText: 'Manage Channel Auto Translation'});
|
||||
return section.getByTestId('permissionRow').filter({hasText: 'Manage Channel Auto Translation'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +51,7 @@ export default class PermissionsSystemScheme {
|
||||
}
|
||||
for (let i = 0; i < count; i++) {
|
||||
const row = rows.nth(i);
|
||||
await expect(row.locator('.permission-check.checked')).toBeVisible();
|
||||
await expect(row.getByTestId('permissionCheckbox-checked')).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +69,7 @@ export default class PermissionsSystemScheme {
|
||||
}
|
||||
for (let i = 0; i < count; i++) {
|
||||
const row = rows.nth(i);
|
||||
await expect(row.locator('.permission-check.checked')).not.toBeVisible();
|
||||
await expect(row.getByTestId('permissionCheckbox-checked')).not.toBeVisible();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-22
@@ -29,15 +29,15 @@ export default class SystemRoles {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.backLink = container.locator('.admin-console__header .back');
|
||||
this.roleName = container.locator('.admin-console__header span').last();
|
||||
this.backLink = container.getByTestId('adminHeader-backLink');
|
||||
this.roleName = container.getByTestId('adminHeader-roleName');
|
||||
|
||||
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');
|
||||
this.errorMessage = container.getByTestId('saveChangesPanel-errorMessage');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -123,14 +123,14 @@ class PermissionSection {
|
||||
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 data-testid to find the row containing this section
|
||||
this.row = panelContainer.getByTestId(`permissionRow_${this.sectionName}`);
|
||||
this.title = this.container.getByTestId('permissionSection-title');
|
||||
this.description = this.container.getByTestId('permissionSection-description');
|
||||
this.subsectionsToggle = this.container.getByTestId('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');
|
||||
this.subsectionsContainer = this.row.getByTestId('permissionSubsections');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -141,7 +141,7 @@ class PermissionSection {
|
||||
* 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()) ?? '';
|
||||
return (await this.dropdownButton.getByTestId('permissionDropdownButton-text').textContent()) ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,7 +157,7 @@ class PermissionSection {
|
||||
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});
|
||||
const menuItem = menuWrapper.getByRole('menuitem').filter({hasText: permission});
|
||||
await expect(menuItem).toBeVisible();
|
||||
await menuItem.click();
|
||||
|
||||
@@ -255,8 +255,8 @@ class PermissionSubsection {
|
||||
|
||||
constructor(panelContainer: Locator, testId: string) {
|
||||
this.container = panelContainer.getByTestId(testId);
|
||||
this.title = this.container.locator('.PermissionSectionText_title');
|
||||
this.description = this.container.locator('.PermissionSection_description');
|
||||
this.title = this.container.getByTestId('permissionSection-title');
|
||||
this.description = this.container.getByTestId('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
|
||||
@@ -271,7 +271,7 @@ class PermissionSubsection {
|
||||
* 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()) ?? '';
|
||||
return (await this.dropdownButton.getByTestId('permissionDropdownButton-text').textContent()) ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,7 +291,7 @@ class PermissionSubsection {
|
||||
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});
|
||||
const menuItem = menuWrapper.getByRole('menuitem').filter({hasText: permission});
|
||||
await expect(menuItem).toBeVisible();
|
||||
await menuItem.click();
|
||||
|
||||
@@ -317,10 +317,10 @@ class AssignedPeoplePanel {
|
||||
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');
|
||||
this.userRows = container.getByTestId('dataGrid-rows').getByTestId('dataGrid-row');
|
||||
this.paginationInfo = container.getByTestId('dataGrid-footer-paginationInfo');
|
||||
this.previousPageButton = container.getByRole('button', {name: 'Previous page'});
|
||||
this.nextPageButton = container.getByRole('button', {name: 'Next page'});
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -369,9 +369,9 @@ class AssignedUserRow {
|
||||
|
||||
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.avatar = container.locator('img').first();
|
||||
this.name = container.getByTestId('userGrid-displayName');
|
||||
this.email = container.getByTestId('userGrid-email');
|
||||
this.removeLink = container.getByRole('link', {name: 'Remove'});
|
||||
}
|
||||
|
||||
|
||||
+23
-26
@@ -17,7 +17,7 @@ export class SaveChangesModal extends ConfirmModal {
|
||||
constructor(container: Locator) {
|
||||
super(container);
|
||||
this.messageBody = this.container.locator('#confirmModalBody');
|
||||
this.changesList = this.messageBody.locator('ul.changes-list');
|
||||
this.changesList = this.messageBody.getByTestId('changesList');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,19 +60,17 @@ export default class UserDetail {
|
||||
readonly errorMessage: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container.locator('.SystemUserDetail');
|
||||
this.container = container.getByTestId('systemUserDetail');
|
||||
|
||||
// Header
|
||||
this.backLink = this.container.locator('.admin-console__header .back');
|
||||
this.backLink = this.container.getByTestId('adminHeader-backLink');
|
||||
this.header = this.container.getByText('User Configuration', {exact: true});
|
||||
|
||||
// User Card
|
||||
this.userCard = new AdminUserCard(this.container.locator('.AdminUserCard'));
|
||||
this.userCard = new AdminUserCard(this.container.getByTestId('adminUserCard'));
|
||||
|
||||
// Team Membership Panel
|
||||
this.teamMembershipPanel = new TeamMembershipPanel(
|
||||
this.container.locator('.AdminPanel').filter({hasText: 'Team Membership'}),
|
||||
);
|
||||
this.teamMembershipPanel = new TeamMembershipPanel(this.container.locator('#teamMembershipPanel'));
|
||||
|
||||
// Save Changes confirmation modal (page-level, rendered outside container via portal)
|
||||
this.saveChangesModal = new SaveChangesModal(
|
||||
@@ -82,7 +80,7 @@ export default class UserDetail {
|
||||
// Save section
|
||||
this.saveButton = this.container.getByTestId('saveSetting');
|
||||
this.cancelButton = this.container.getByRole('button', {name: 'Cancel'});
|
||||
this.errorMessage = this.container.locator('.error-message');
|
||||
this.errorMessage = this.container.getByTestId('saveChangesPanel-errorMessage');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -138,25 +136,26 @@ class AdminUserCard {
|
||||
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');
|
||||
const header = container.getByTestId('adminUserCard-header');
|
||||
this.profileImage = header.locator('img').first();
|
||||
this.displayName = container.getByTestId('adminUserCard-userInfo').locator('span').first();
|
||||
this.nickname = container.getByTestId('adminUserCard-userNickname');
|
||||
this.userId = container.getByTestId('adminUserCard-userId');
|
||||
|
||||
// Body
|
||||
this.body = container.locator('.AdminUserCard__body');
|
||||
this.twoColumnLayout = this.body.locator('.two-column-layout');
|
||||
this.fieldRows = this.body.locator('.field-row');
|
||||
this.body = container.getByTestId('adminUserCard-body');
|
||||
this.twoColumnLayout = this.body.getByTestId('twoColumnLayout');
|
||||
this.fieldRows = this.body.getByTestId('fieldRow');
|
||||
|
||||
// System fields — use exact label text to avoid substring matches (e.g., "Email" vs "Work Email")
|
||||
this.usernameInput = this.getFieldInputByExactLabel('Username');
|
||||
this.emailInput = this.getFieldInputByExactLabel('Email');
|
||||
this.authDataInput = this.getFieldInputByExactLabel('Auth Data');
|
||||
this.authenticationMethod = this.getFieldColumn('Authentication Method').locator('label > span').last();
|
||||
this.authenticationMethod =
|
||||
this.getFieldColumn('Authentication Method').getByTestId('authenticationMethodValue');
|
||||
|
||||
// Footer
|
||||
const footer = container.locator('.AdminUserCard__footer');
|
||||
const footer = container.getByTestId('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'});
|
||||
@@ -167,12 +166,10 @@ class AdminUserCard {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the .field-column container for a field by its exact label text.
|
||||
* Get the field-column container for a field by its exact label text.
|
||||
*/
|
||||
private getFieldColumn(labelText: string): Locator {
|
||||
return this.body
|
||||
.locator('.field-column')
|
||||
.filter({has: this.body.page().locator(`span:text-is("${labelText}")`)});
|
||||
return this.body.getByTestId('fieldColumn').filter({has: this.body.page().getByText(labelText, {exact: true})});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,10 +188,10 @@ class AdminUserCard {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the .field-error validation message locator for a field by its exact label text.
|
||||
* Get the field-error validation message locator for a field by its exact label text.
|
||||
*/
|
||||
getFieldError(labelText: string): Locator {
|
||||
return this.getFieldColumn(labelText).locator('.field-error');
|
||||
return this.getFieldColumn(labelText).getByTestId('fieldError');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +206,7 @@ class AdminUserCard {
|
||||
|
||||
/** The menu-button for a ranked CPA field, located by its exact label. */
|
||||
getCpaRankPicker(labelText: string): Locator {
|
||||
return this.getFieldColumn(labelText).locator('.cpa-rank-select__button');
|
||||
return this.getFieldColumn(labelText).getByRole('button');
|
||||
}
|
||||
|
||||
/** The open ranked-value menu (rendered at page level via portal). */
|
||||
@@ -245,7 +242,7 @@ class TeamMembershipPanel {
|
||||
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');
|
||||
this.teamRows = container.getByTestId('teamRow');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
+6
-6
@@ -72,7 +72,7 @@ export default class Users {
|
||||
|
||||
// Modals
|
||||
this.confirmModal = new ConfirmModal(this.page.locator('#confirmModal'));
|
||||
this.manageRolesModal = new ManageRolesModal(this.page.locator('.manage-teams'));
|
||||
this.manageRolesModal = new ManageRolesModal(this.page.locator('#manageRolesModal'));
|
||||
this.resetPasswordModal = new ResetPasswordModal(this.page.locator('#resetPasswordModal'));
|
||||
this.updateEmailModal = new UpdateEmailModal(this.page.locator('#resetEmailModal'));
|
||||
|
||||
@@ -91,15 +91,15 @@ export default class Users {
|
||||
|
||||
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.roleFilterMenu = new FilterMenu(this.page.getByTestId('dropdownMenu').first());
|
||||
this.statusFilterMenu = new FilterMenu(this.page.getByTestId('dropdownMenu').first());
|
||||
this.dateRangeMenu = new DateRangeMenu(this.page.locator('#systemUsersDateRangeSelectorMenu'));
|
||||
|
||||
const footer = container.locator('.adminConsoleListTabletOptionalFoot');
|
||||
this.paginationInfo = footer.locator('span').first();
|
||||
const footer = container.getByTestId('listTableFoot');
|
||||
this.paginationInfo = footer.getByTestId('listTableFoot-paginationInfo');
|
||||
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');
|
||||
this.rowsPerPageSelector = footer.getByTestId('listTableFoot-pageSize').getByRole('combobox');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ export class ResetPasswordModal extends BaseModal {
|
||||
constructor(container: Locator) {
|
||||
super(container);
|
||||
this.resetButton = container.getByRole('button', {name: 'Reset'});
|
||||
this.passwordInput = container.locator('input[type="password"]');
|
||||
this.passwordInput = container.getByLabel('New password');
|
||||
}
|
||||
|
||||
async reset() {
|
||||
@@ -56,7 +56,7 @@ export class UpdateEmailModal extends BaseModal {
|
||||
constructor(container: Locator) {
|
||||
super(container);
|
||||
this.updateButton = container.getByRole('button', {name: 'Update'});
|
||||
this.emailInput = container.locator('input[type="email"]');
|
||||
this.emailInput = container.getByLabel('New email');
|
||||
}
|
||||
|
||||
async update() {
|
||||
|
||||
+16
-16
@@ -53,8 +53,8 @@ export class UsersTable {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.headerRow = container.locator('thead tr');
|
||||
this.bodyRows = container.locator('tbody tr');
|
||||
this.headerRow = container.getByTestId('listTableHeadRow');
|
||||
this.bodyRows = container.getByTestId('listTableBodyRow');
|
||||
|
||||
// Column headers
|
||||
this.userDetailsHeader = container.locator('#systemUsersTable-header-usernameColumn');
|
||||
@@ -136,7 +136,7 @@ export class UsersTable {
|
||||
* Read all visible email cells in one pass (avoids torn reads while the table re-renders).
|
||||
*/
|
||||
async getVisibleEmails(): Promise<string[]> {
|
||||
const texts = await this.bodyRows.locator('.emailColumn').allTextContents();
|
||||
const texts = await this.bodyRows.getByTestId('emailColumn').allTextContents();
|
||||
return texts.map((text) => text.trim()).filter(Boolean);
|
||||
}
|
||||
}
|
||||
@@ -175,20 +175,20 @@ export class UserRow {
|
||||
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.channelCountCell = container.locator('.channelCountColumn');
|
||||
this.actionsCell = container.locator('.actionsColumn');
|
||||
this.userDetailsCell = container.getByTestId('usernameColumn');
|
||||
this.emailCell = container.getByTestId('emailColumn');
|
||||
this.memberSinceCell = container.getByTestId('createAtColumn');
|
||||
this.lastLoginCell = container.getByTestId('lastLoginColumn');
|
||||
this.lastActivityCell = container.getByTestId('lastStatusAtColumn');
|
||||
this.lastPostCell = container.getByTestId('lastPostDateColumn');
|
||||
this.daysActiveCell = container.getByTestId('daysActiveColumn');
|
||||
this.messagesPostedCell = container.getByTestId('totalPostsColumn');
|
||||
this.channelCountCell = container.getByTestId('channelCountColumn');
|
||||
this.actionsCell = container.getByTestId('actionsColumn');
|
||||
|
||||
this.profilePicture = this.userDetailsCell.locator('.profilePicture');
|
||||
this.displayName = this.userDetailsCell.locator('.displayName');
|
||||
this.userName = this.userDetailsCell.locator('.userName');
|
||||
this.profilePicture = this.userDetailsCell.getByTestId('profilePicture');
|
||||
this.displayName = this.userDetailsCell.getByTestId('displayName');
|
||||
this.userName = this.userDetailsCell.getByTestId('userName');
|
||||
|
||||
this.actionMenuButton = this.actionsCell.getByRole('button');
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export default class SystemConsoleSidebar {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.header = new SystemConsoleSidebarHeader(container.locator('.AdminSidebarHeader'));
|
||||
this.header = new SystemConsoleSidebarHeader(container.locator('#admin-sidebar-header'));
|
||||
this.searchInput = container.getByPlaceholder('Find settings');
|
||||
|
||||
this.about = new AboutCategory(container.getByTestId('about'));
|
||||
@@ -120,8 +120,8 @@ class SidebarCategory {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.title = container.locator('.category-title');
|
||||
this.sections = container.locator('ul.sections');
|
||||
this.title = container.getByTestId('sidebar-category-title');
|
||||
this.sections = container.getByTestId('sidebar-category-sections');
|
||||
}
|
||||
|
||||
protected section(name: string): SidebarSection {
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class SystemConsoleSidebarHeader {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.headerInfo = container.locator('.header__info');
|
||||
this.headerInfo = container.getByTestId('admin-sidebar-header-info');
|
||||
this.title = container.getByText('System Console');
|
||||
this.userName = container.getByText(/^@/);
|
||||
this.menuButton = container.getByRole('button', {name: 'Menu Icon'});
|
||||
|
||||
@@ -64,13 +64,13 @@ export default class ChannelsPage {
|
||||
this.centerView = new components.ChannelsCenterView(page.getByTestId('channel_view'), page);
|
||||
this.sidebarLeft = new components.ChannelsSidebarLeft(page.locator('#SidebarContainer'));
|
||||
this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right'));
|
||||
this.appBar = new components.ChannelsAppBar(page.locator('.app-bar'));
|
||||
this.appBar = new components.ChannelsAppBar(page.getByTestId('app-bar'));
|
||||
this.messagePriority = new components.MessagePriority(page.locator('body'));
|
||||
this.userAccountMenuButton = page.getByRole('button', {name: "User's account menu"});
|
||||
|
||||
// Modals
|
||||
this.channelSettingsModal = new ChannelSettingsModal(page.getByRole('dialog', {name: 'Channel Settings'}));
|
||||
this.createTeamForm = new CreateTeamForm(page.locator('.signup-team__container'));
|
||||
this.createTeamForm = new CreateTeamForm(page.getByTestId('create-team-form'));
|
||||
this.deletePostModal = new components.DeletePostModal(page.locator('#deletePostModal'));
|
||||
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
|
||||
this.newChannelModal = new NewChannelModal(page.getByRole('dialog', {name: 'Create a new channel'}));
|
||||
@@ -94,14 +94,14 @@ export default class ChannelsPage {
|
||||
|
||||
// Popovers
|
||||
this.emojiGifPickerPopup = new components.EmojiGifPicker(page.locator('#emojiGifPicker'));
|
||||
this.scheduledDraftModal = new components.ScheduledDraftModal(page.locator('div.modal-content'));
|
||||
this.scheduledDraftModal = new components.ScheduledDraftModal(page.getByRole('dialog', {name: /scheduled/i}));
|
||||
this.scheduleMessageModal = new components.ScheduleMessageModal(
|
||||
page.getByRole('dialog', {name: 'Schedule message'}),
|
||||
);
|
||||
this.userProfilePopover = new components.UserProfilePopover(page.locator('.user-profile-popover'));
|
||||
this.userProfilePopover = new components.UserProfilePopover(page.getByTestId('user-profile-popover'));
|
||||
|
||||
// Posts
|
||||
this.postContainer = page.locator('div.post-message__text');
|
||||
this.postContainer = page.getByTestId('post-message-text');
|
||||
this.archivedChannelMessage = page.locator('#channelArchivedMessage');
|
||||
|
||||
page.locator('#channelHeaderDropdownMenu');
|
||||
|
||||
@@ -27,11 +27,11 @@ export default class ContentReviewPage {
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.cards = page.locator('[data-testid="property-card-view"]');
|
||||
this.cards = page.getByTestId('property-card-view');
|
||||
this.rhsCard = page.getByTestId('rhsPostView').getByTestId('property-card-view');
|
||||
this.keepMessageButton = this.rhsCard.getByTestId('data-spillage-action-keep-message');
|
||||
this.removeMessageButton = this.rhsCard.getByTestId('data-spillage-action-remove-message');
|
||||
this.postActionConformationModal = page.locator('div.GenericModal__wrapper');
|
||||
this.postActionConformationModal = page.getByTestId('keep-remove-flagged-message-confirmation-modal');
|
||||
this.cancelButton = this.postActionConformationModal.getByRole('button', {name: 'Cancel'});
|
||||
this.confirmRemoveMessageButton = this.postActionConformationModal.getByRole('button', {
|
||||
name: 'Remove message',
|
||||
@@ -54,10 +54,10 @@ export default class ContentReviewPage {
|
||||
|
||||
async setReportCardByPostID(postID: string) {
|
||||
this.reportCard = this.page
|
||||
.locator('div.DataSpillageReport')
|
||||
.getByTestId('data-spillage-report')
|
||||
.filter({has: this.page.locator(`#postMessageText_${postID}`)});
|
||||
if ((await this.reportCard.count()) === 0) {
|
||||
this.reportCard = this.page.locator('div.DataSpillageReport').first();
|
||||
this.reportCard = this.page.getByTestId('data-spillage-report').first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export default class ContentReviewPage {
|
||||
|
||||
async openViewDetails() {
|
||||
this.ensureReportCardSet();
|
||||
const button = this.reportCard!.locator('button:has-text("View Details")');
|
||||
const button = this.reportCard!.getByRole('button', {name: 'View details'});
|
||||
await button.scrollIntoViewIfNeeded();
|
||||
await button.click();
|
||||
}
|
||||
@@ -89,16 +89,14 @@ export default class ContentReviewPage {
|
||||
}
|
||||
|
||||
async openCardByMessage(message: string) {
|
||||
const targetCard = this.page
|
||||
.locator('div.DataSpillageReport')
|
||||
.filter({has: this.page.locator(`.row:has-text("${message}")`)});
|
||||
const targetCard = this.page.getByTestId('data-spillage-report').filter({
|
||||
has: this.page.getByTestId('property-card-row').filter({hasText: message}),
|
||||
});
|
||||
await targetCard.first().click();
|
||||
}
|
||||
|
||||
private field(fieldName: string): Locator {
|
||||
return this.rhsCard.locator('.row', {
|
||||
has: this.rhsCard.locator(`.field:has-text("${fieldName}")`),
|
||||
});
|
||||
return this.rhsCard.getByTestId('property-card-row').filter({hasText: fieldName});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,7 +104,8 @@ export default class ContentReviewPage {
|
||||
*/
|
||||
async getValueForField(fieldName: string): Promise<string> {
|
||||
await expect(this.rhsCard).toBeVisible({timeout: 10000});
|
||||
const valueLocator = this.rhsCard.locator(`.row:has(.field:has-text("${fieldName}")) .value`);
|
||||
const row = this.rhsCard.getByTestId('property-card-row').filter({hasText: fieldName});
|
||||
const valueLocator = row.getByTestId('property-card-field-value');
|
||||
await expect(valueLocator).toBeVisible({timeout: 5000});
|
||||
return valueLocator.innerText();
|
||||
}
|
||||
@@ -120,30 +119,31 @@ export default class ContentReviewPage {
|
||||
}
|
||||
|
||||
async expectTextProperty(fieldName: string, expected: string) {
|
||||
await expect(this.field(fieldName).locator('.TextProperty')).toHaveText(expected);
|
||||
await expect(this.field(fieldName).getByTestId('text-property')).toHaveText(expected);
|
||||
}
|
||||
|
||||
async expectUser(fieldName: string, expected: string) {
|
||||
await expect(this.rhsCard).toBeVisible({timeout: 10000});
|
||||
|
||||
const userButton = this.rhsCard.locator(`.row:has(.field:has-text("${fieldName}")) .user-popover`);
|
||||
const row = this.rhsCard.getByTestId('property-card-row').filter({hasText: fieldName});
|
||||
const userProperty = row.getByTestId('user-property');
|
||||
|
||||
// Wait for either visible or attached then read text
|
||||
await userButton.waitFor({state: 'attached', timeout: 10000});
|
||||
const text = (await userButton.innerText()).trim();
|
||||
await userProperty.waitFor({state: 'attached', timeout: 10000});
|
||||
const text = (await userProperty.innerText()).trim();
|
||||
expect(text).toBe(expected);
|
||||
}
|
||||
|
||||
async expectTeam(expected: string) {
|
||||
await expect(this.rhsCard.locator('.TeamPropertyRenderer')).toContainText(expected);
|
||||
await expect(this.rhsCard.getByTestId('team-property')).toContainText(expected);
|
||||
}
|
||||
|
||||
async expectChannel(expected: string) {
|
||||
await expect(this.rhsCard.locator('.ChannelPropertyRenderer')).toContainText(expected);
|
||||
await expect(this.rhsCard.getByTestId('channel-property')).toContainText(expected);
|
||||
}
|
||||
|
||||
async expectMessageContains(expected: string) {
|
||||
await expect(this.rhsCard.locator('.post-message__text')).toContainText(expected);
|
||||
await expect(this.rhsCard.getByTestId('post-message-text')).toContainText(expected);
|
||||
}
|
||||
|
||||
async waitForRHSVisible() {
|
||||
@@ -156,29 +156,34 @@ export default class ContentReviewPage {
|
||||
|
||||
async verifyFlaggedPostStatus(expected: string) {
|
||||
this.ensureReportCardSet();
|
||||
await expect(this.reportCard!.locator('.row:has-text("Status") .SelectProperty')).toHaveText(expected);
|
||||
const statusRow = this.reportCard!.getByTestId('property-card-row').filter({hasText: 'Status'});
|
||||
await expect(statusRow.getByTestId('select-property')).toHaveText(expected);
|
||||
}
|
||||
|
||||
async verifyFlaggedPostReason(expected: string) {
|
||||
this.ensureReportCardSet();
|
||||
await expect(this.reportCard!.locator('.row:has-text("Reason") .SelectProperty')).toHaveText(expected);
|
||||
const reasonRow = this.reportCard!.getByTestId('property-card-row').filter({hasText: 'Reason'});
|
||||
await expect(reasonRow.getByTestId('select-property')).toHaveText(expected);
|
||||
}
|
||||
|
||||
async verifyFlaggedPostMessage(expected: string) {
|
||||
this.ensureReportCardSet();
|
||||
await expect(this.reportCard!.locator('.row:has-text("Message") .post-message__text')).toHaveText(expected);
|
||||
const messageRow = this.reportCard!.getByTestId('property-card-row').filter({hasText: 'Message'});
|
||||
await expect(messageRow.getByTestId('post-message-text')).toHaveText(expected);
|
||||
}
|
||||
|
||||
async verifyFlaggedPostMessageInRHS(expected: string) {
|
||||
await expect(this.rhsCard.locator('.row:has-text("Message") .post-message__text')).toHaveText(expected);
|
||||
const messageRow = this.rhsCard.getByTestId('property-card-row').filter({hasText: 'Message'});
|
||||
await expect(messageRow.getByTestId('post-message-text')).toHaveText(expected);
|
||||
}
|
||||
|
||||
async verifyFlaggedPostMessageInCenter(postID: string, expected: string) {
|
||||
const centerCard = this.page
|
||||
.getByTestId('channel_view')
|
||||
.locator('div.DataSpillageReport')
|
||||
.getByTestId('data-spillage-report')
|
||||
.filter({has: this.page.locator(`#postMessageText_${postID}`)});
|
||||
await expect(centerCard.locator('.row:has-text("Message") .post-message__text')).toHaveText(expected);
|
||||
const messageRow = centerCard.getByTestId('property-card-row').filter({hasText: 'Message'});
|
||||
await expect(messageRow.getByTestId('post-message-text')).toHaveText(expected);
|
||||
}
|
||||
|
||||
async clickKeepMessage() {
|
||||
|
||||
@@ -19,11 +19,11 @@ export default class DraftsPage {
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
this.draftsHeader = page.locator('.Drafts__header');
|
||||
this.draftsHeader = page.getByTestId('drafts-header');
|
||||
this.tab = page.getByRole('tab', {name: 'Drafts'});
|
||||
this.badge = this.tab.locator('span.MuiBadge-badge');
|
||||
this.badge = this.tab.getByTestId('drafts-tab-counter-badge');
|
||||
|
||||
this.noDrafts = page.locator('.no-results__wrapper');
|
||||
this.noDrafts = page.getByTestId('no-results-wrapper');
|
||||
|
||||
this.scheduleMessageModal = new components.ScheduleMessageModal(
|
||||
page.getByRole('dialog', {name: 'Schedule message'}),
|
||||
@@ -42,7 +42,7 @@ export default class DraftsPage {
|
||||
|
||||
async getBadgeCountOnTab() {
|
||||
await expect(this.tab).toBeVisible();
|
||||
const badge = this.tab.locator('span.MuiBadge-badge');
|
||||
const badge = this.tab.getByTestId('drafts-tab-counter-badge');
|
||||
await expect(badge).toBeVisible();
|
||||
return badge.textContent();
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ export default class LandingLoginPage {
|
||||
this.page = page;
|
||||
this.isMobile = isMobile;
|
||||
|
||||
this.viewInAppButton = page.locator('text=View in App');
|
||||
this.viewInDesktopAppButton = page.locator('text=View in Desktop App');
|
||||
this.viewInBrowserButton = page.locator('text=View in Browser');
|
||||
this.viewInAppButton = page.getByText('View in App');
|
||||
this.viewInDesktopAppButton = page.getByText('View in Desktop App');
|
||||
this.viewInBrowserButton = page.getByText('View in Browser');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -22,8 +22,6 @@ export default class LoginPage {
|
||||
readonly createAccountLink;
|
||||
readonly forgotPasswordLink;
|
||||
readonly userErrorLabel;
|
||||
readonly fieldWithError;
|
||||
readonly formContainer;
|
||||
|
||||
readonly header;
|
||||
readonly footer;
|
||||
@@ -31,23 +29,21 @@ export default class LoginPage {
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
this.title = page.locator('h1:has-text("Log in to your account")');
|
||||
this.subtitle = page.locator('text=Collaborate with your team in real-time');
|
||||
this.bodyCard = page.locator('.login-body-card-content');
|
||||
this.title = page.getByRole('heading', {name: 'Log in to your account'});
|
||||
this.subtitle = page.getByText('Collaborate with your team in real-time');
|
||||
this.bodyCard = page.getByTestId('login-body-card');
|
||||
this.loginInput = page.locator('#input_loginId');
|
||||
this.loginPlaceholder = page.locator('[placeholder="Email or Username"]');
|
||||
this.loginWithAdLdapPlaceholder = page.locator('[placeholder="Email, Username or AD/LDAP Username"]');
|
||||
this.loginPlaceholder = page.getByPlaceholder('Email or Username');
|
||||
this.loginWithAdLdapPlaceholder = page.getByPlaceholder('Email, Username or AD/LDAP Username');
|
||||
this.passwordInput = page.locator('#input_password-input');
|
||||
this.passwordToggleButton = page.locator('#password_toggle');
|
||||
this.signInButton = page.locator('button:has-text("Log in")');
|
||||
this.createAccountLink = page.locator("text=Don't have an account?");
|
||||
this.forgotPasswordLink = page.locator('text=Forgot your password?');
|
||||
this.userErrorLabel = page.locator('text=Please enter your email or username');
|
||||
this.fieldWithError = page.locator('.with-error');
|
||||
this.formContainer = page.locator('.signup-team__container');
|
||||
this.signInButton = page.getByRole('button', {name: 'Log in'});
|
||||
this.createAccountLink = page.getByRole('link', {name: "Don't have an account?"});
|
||||
this.forgotPasswordLink = page.getByText('Forgot your password?');
|
||||
this.userErrorLabel = page.getByText('Please enter your email or username');
|
||||
|
||||
this.header = new components.MainHeader(page.locator('.hfroute-header'));
|
||||
this.footer = new components.Footer(page.locator('.hfroute-footer'));
|
||||
this.header = new components.MainHeader(page.getByTestId('hfroute-header'));
|
||||
this.footer = new components.Footer(page.getByTestId('hfroute-footer'));
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -60,19 +60,21 @@ class CreateRecapModal {
|
||||
}
|
||||
|
||||
getChannelOption(channelName: string) {
|
||||
return this.container.locator('.channel-selector-item').filter({hasText: channelName});
|
||||
return this.container.getByTestId('channel-selector-item').filter({hasText: channelName});
|
||||
}
|
||||
|
||||
async selectChannel(channelName: string) {
|
||||
const channelOption = this.getChannelOption(channelName);
|
||||
await expect(channelOption).toBeVisible();
|
||||
await channelOption.click();
|
||||
await expect(channelOption.locator('input[type="checkbox"]')).toBeChecked();
|
||||
await expect(channelOption.getByRole('checkbox')).toBeChecked();
|
||||
}
|
||||
|
||||
async expectSummaryChannels(channelNames: string[]) {
|
||||
for (const channelName of channelNames) {
|
||||
await expect(this.container.locator('.summary-channel-item').filter({hasText: channelName})).toBeVisible();
|
||||
await expect(
|
||||
this.container.getByTestId('summary-channel-item').filter({hasText: channelName}),
|
||||
).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,11 +95,11 @@ class RecapChannelCard {
|
||||
private readonly page: Page,
|
||||
readonly container: Locator,
|
||||
) {
|
||||
this.channelButton = container.locator('.recap-channel-name-tag');
|
||||
this.collapseButton = container.locator('.recap-channel-collapse-button');
|
||||
// Scope to header actions so we do not match the parent .recap-channel-header (role="button").
|
||||
this.channelButton = container.getByTestId('recap-channel-name-tag');
|
||||
this.collapseButton = container.getByTestId('recap-channel-collapse-button');
|
||||
// Scope to header actions so we do not match the parent recap-channel-header (role="button").
|
||||
this.menuButton = container
|
||||
.locator('.recap-channel-header-actions')
|
||||
.getByTestId('recap-channel-header-actions')
|
||||
.getByRole('button', {name: /Options for /});
|
||||
}
|
||||
|
||||
@@ -133,9 +135,9 @@ class RecapItem {
|
||||
private readonly page: Page,
|
||||
readonly container: Locator,
|
||||
) {
|
||||
this.header = container.locator('.recap-item-header');
|
||||
this.header = container.getByTestId('recap-item-header');
|
||||
this.markReadButton = container.getByRole('button', {name: 'Mark read'});
|
||||
this.deleteButton = container.locator('.recap-delete-button');
|
||||
this.deleteButton = container.getByTestId('recap-delete-button');
|
||||
this.menuButton = this.header.getByRole('button', {name: /Options for /});
|
||||
}
|
||||
|
||||
@@ -185,7 +187,7 @@ class RecapItem {
|
||||
getChannelCard(channelName: string) {
|
||||
return new RecapChannelCard(
|
||||
this.page,
|
||||
this.container.locator('.recap-channel-card').filter({hasText: channelName}).first(),
|
||||
this.container.getByTestId('recap-channel-card').filter({hasText: channelName}).first(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -269,7 +271,8 @@ export default class RecapsPage {
|
||||
return new RecapItem(
|
||||
this.page,
|
||||
this.page
|
||||
.locator('.recap-item, .recap-processing')
|
||||
.getByTestId('recap-item')
|
||||
.or(this.page.getByTestId('recap-processing'))
|
||||
.filter({
|
||||
has: this.page.getByRole('heading', {name: title, exact: true}),
|
||||
})
|
||||
|
||||
@@ -13,7 +13,6 @@ export default class ResetPasswordPage {
|
||||
readonly subtitle;
|
||||
readonly emailInput;
|
||||
readonly resetButton;
|
||||
readonly formContainer;
|
||||
|
||||
readonly header;
|
||||
readonly footer;
|
||||
@@ -21,13 +20,12 @@ export default class ResetPasswordPage {
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
this.title = page.locator('h1:has-text("Password Reset")');
|
||||
this.subtitle = page.locator('text=To reset your password, enter the email address you used to sign up');
|
||||
this.title = page.getByRole('heading', {name: 'Password Reset'});
|
||||
this.subtitle = page.getByText('To reset your password, enter the email address you used to sign up');
|
||||
this.emailInput = page.locator('#passwordResetEmailInput');
|
||||
this.resetButton = page.locator('#passwordResetButton');
|
||||
this.formContainer = page.locator('.signup-team__container');
|
||||
|
||||
this.header = new components.MainHeader(page.locator('.signup-header'));
|
||||
this.header = new components.MainHeader(page.getByTestId('signup-header'));
|
||||
this.footer = new components.Footer(page.locator('#footer_section'));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ export default class ScheduledPostsPage {
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
this.draftsHeader = page.locator('.Drafts__header');
|
||||
this.draftsHeader = page.getByTestId('drafts-header');
|
||||
this.tab = page.getByRole('tab', {name: 'Scheduled'});
|
||||
this.badge = this.tab.locator('span.MuiBadge-badge');
|
||||
this.badge = this.tab.getByTestId('scheduled-posts-tab-counter-badge');
|
||||
|
||||
this.noScheduledDrafts = page.locator('.no-results__wrapper');
|
||||
this.noScheduledDrafts = page.getByTestId('no-results-wrapper');
|
||||
|
||||
this.scheduleMessageModal = new components.ScheduleMessageModal(
|
||||
page.getByRole('dialog', {name: 'Schedule message'}),
|
||||
@@ -46,7 +46,7 @@ export default class ScheduledPostsPage {
|
||||
|
||||
async getBadgeCountOnTab() {
|
||||
await expect(this.tab).toBeVisible();
|
||||
const badge = this.tab.locator('span.MuiBadge-badge');
|
||||
const badge = this.tab.getByTestId('scheduled-posts-tab-counter-badge');
|
||||
await expect(badge).toBeVisible();
|
||||
return badge.textContent();
|
||||
}
|
||||
|
||||
@@ -32,30 +32,30 @@ export default class SignupPage {
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
this.title = page.locator('h1:has-text("Let’s get started")');
|
||||
this.subtitle = page.locator('text=Create your Mattermost account to start collaborating with your team');
|
||||
this.bodyCard = page.locator('.signup-body-card-content');
|
||||
this.loginLink = page.locator('text=Log in');
|
||||
this.title = page.getByRole('heading', {name: 'Let’s get started'});
|
||||
this.subtitle = page.getByText('Create your Mattermost account to start collaborating with your team');
|
||||
this.bodyCard = page.getByTestId('signup-body-card');
|
||||
this.loginLink = page.getByText('Log in');
|
||||
this.emailInput = page.locator('#input_email');
|
||||
this.usernameInput = page.locator('#input_name');
|
||||
this.passwordInput = page.locator('#input_password-input');
|
||||
this.passwordToggleButton = page.locator('#password_toggle');
|
||||
this.createAccountButton = page.locator('button:has-text("Create account")');
|
||||
this.emailError = page.locator('text=Please enter a valid email address');
|
||||
this.usernameError = page.locator(
|
||||
'text=Usernames have to begin with a lowercase letter and be 3-22 characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.',
|
||||
this.createAccountButton = page.getByRole('button', {name: 'Create account'});
|
||||
this.emailError = page.getByText('Please enter a valid email address');
|
||||
this.usernameError = page.getByText(
|
||||
'Usernames have to begin with a lowercase letter and be 3-22 characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.',
|
||||
);
|
||||
this.passwordError = page.locator('text=/Must be \\d+-72 characters long\\./');
|
||||
this.passwordError = page.getByText(/Must be \d+-72 characters long\./);
|
||||
|
||||
const termsAndPrivacyBlock = page.locator('.check-input');
|
||||
this.termsAndPrivacyCheckBox = termsAndPrivacyBlock.getByRole('checkbox', {
|
||||
const signupBodyCard = page.getByTestId('signup-body-card');
|
||||
this.termsAndPrivacyCheckBox = signupBodyCard.getByRole('checkbox', {
|
||||
name: 'Terms and privacy policy checkbox',
|
||||
});
|
||||
this.termsAndPrivacyAcceptableUsePolicyLink = termsAndPrivacyBlock.locator('text=Acceptable Use Policy');
|
||||
this.termsAndPrivacyPrivacyPolicyLink = termsAndPrivacyBlock.locator('text=Privacy Policy');
|
||||
this.termsAndPrivacyAcceptableUsePolicyLink = signupBodyCard.getByText('Acceptable Use Policy');
|
||||
this.termsAndPrivacyPrivacyPolicyLink = signupBodyCard.getByText('Privacy Policy');
|
||||
|
||||
this.header = new components.MainHeader(page.locator('.hfroute-header'));
|
||||
this.footer = new components.Footer(page.locator('.hfroute-footer'));
|
||||
this.header = new components.MainHeader(page.getByTestId('hfroute-header'));
|
||||
this.footer = new components.Footer(page.getByTestId('hfroute-footer'));
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
||||
@@ -57,8 +57,8 @@ export default class SystemConsolePage {
|
||||
this.page = page;
|
||||
|
||||
// Layout
|
||||
this.navbar = new SystemConsoleNavbar(page.locator('.backstage-navbar'));
|
||||
this.sidebar = new SystemConsoleSidebar(page.locator('.admin-sidebar'));
|
||||
this.navbar = new SystemConsoleNavbar(page.getByTestId('backstage-navbar'));
|
||||
this.sidebar = new SystemConsoleSidebar(page.getByTestId('admin-sidebar'));
|
||||
|
||||
const adminConsoleWrapper = page.locator('#adminConsoleWrapper');
|
||||
this.header = new SystemConsoleHeader(adminConsoleWrapper);
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class ThreadsPage {
|
||||
|
||||
this.threadsList = page.locator('#threads-list');
|
||||
|
||||
this.noThreadSelected = page.locator('.no-results__title', {
|
||||
this.noThreadSelected = page.getByTestId('no-results-title').filter({
|
||||
hasText: /Looks like you’re all caught up|Catch up on your threads/,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user