From 8fa7e72a6ed2d796db9f63243f1d29635b26dd9c Mon Sep 17 00:00:00 2001 From: sabril <5334504+saturninoabril@users.noreply.github.com> Date: Mon, 6 Jul 2026 11:45:22 +0800 Subject: [PATCH] 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 --- .../channels/browse_channels_modal.ts | 6 +- .../burn_on_read_concealed_placeholder.ts | 4 +- .../burn_on_read_confirmation_modal.ts | 4 +- .../channels/burn_on_read_timer_chip.ts | 4 +- .../src/ui/components/channels/center_view.ts | 12 +- .../channel_settings_modal.ts | 4 +- .../channels/delete_scheduled_post_modal.ts | 6 +- .../channels/direct_channels_modal.ts | 16 +- .../src/ui/components/channels/draft_post.ts | 10 +- .../components/channels/emoji_gif_picker.ts | 6 +- .../channels/find_channels_modal.ts | 2 +- .../channels/flag_post_confirmation_dialog.ts | 17 +- .../lib/src/ui/components/channels/header.ts | 2 +- .../channels/members_invited_modal.ts | 8 +- .../components/channels/message_priority.ts | 4 +- .../components/channels/new_channel_modal.ts | 4 +- .../lib/src/ui/components/channels/post.ts | 16 +- .../src/ui/components/channels/post_create.ts | 8 +- .../src/ui/components/channels/post_edit.ts | 6 +- .../ui/components/channels/profile_modal.ts | 4 +- .../channels/schedule_message_modal.ts | 6 +- .../ui/components/channels/scheduled_post.ts | 14 +- .../channels/scheduled_post_indicator.ts | 4 +- .../src/ui/components/channels/search_box.ts | 6 +- .../channels/send_message_now_modal.ts | 6 +- .../channels/settings/display_settings.ts | 10 +- .../channels/settings/settings_modal.ts | 2 +- .../ui/components/channels/sidebar_left.ts | 6 +- .../ui/components/channels/sidebar_right.ts | 16 +- .../team_settings/team_settings_modal.ts | 14 +- .../ui/components/channels/thread_footer.ts | 2 +- .../lib/src/ui/components/footer.ts | 10 +- .../lib/src/ui/components/main_header.ts | 2 +- .../system_console/base_components.ts | 30 +- .../components/system_console/base_modal.ts | 2 +- .../ui/components/system_console/header.ts | 2 +- .../ui/components/system_console/navbar.ts | 2 +- .../sections/environment/mobile_security.ts | 28 +- .../sections/reporting/team_statistics.ts | 57 +- .../site_configuration/localization.ts | 8 +- .../site_configuration/notifications.ts | 23 +- .../system_attributes/system_properties.ts | 23 +- .../delegated_granular_administration.ts | 20 +- .../permissions_system_scheme.ts | 20 +- .../sections/user_management/system_roles.ts | 44 +- .../sections/user_management/user_detail.ts | 49 +- .../sections/user_management/users/index.ts | 12 +- .../sections/user_management/users/modals.ts | 4 +- .../user_management/users/users_table.ts | 32 +- .../ui/components/system_console/sidebar.ts | 6 +- .../system_console/sidebar_header.ts | 2 +- .../playwright/lib/src/ui/pages/channels.ts | 10 +- .../lib/src/ui/pages/content_review_dm.ts | 55 +- .../playwright/lib/src/ui/pages/drafts.ts | 8 +- .../lib/src/ui/pages/landing_login.ts | 6 +- .../playwright/lib/src/ui/pages/login.ts | 26 +- .../playwright/lib/src/ui/pages/recaps.ts | 25 +- .../lib/src/ui/pages/reset_password.ts | 8 +- .../lib/src/ui/pages/scheduled_posts.ts | 8 +- .../playwright/lib/src/ui/pages/signup.ts | 30 +- .../lib/src/ui/pages/system_console.ts | 4 +- .../playwright/lib/src/ui/pages/threads.ts | 2 +- .../searchable_channel_list.test.tsx.snap | 1 + .../__snapshots__/user_list.test.tsx.snap | 2 + .../add_groups_to_channel_modal.test.tsx.snap | 1 + .../add_groups_to_team_modal.test.tsx.snap | 1 + .../add_users_to_team_modal.test.tsx.snap | 1 + .../cluster_settings.test.tsx.snap | 8 + .../database_settings.test.tsx.snap | 1 + .../elasticsearch_settings.test.tsx.snap | 3 + .../message_export_settings.test.tsx.snap | 4 + .../__snapshots__/push_settings.test.tsx.snap | 2 + .../__snapshots__/policies.test.tsx.snap | 24 +- .../policy_details.test.tsx.snap | 8 + .../__snapshots__/channel_list.test.tsx.snap | 33 +- .../__snapshots__/admin_setting.test.tsx.snap | 1 + .../__snapshots__/admin_sidebar.test.tsx.snap | 204 +++++ .../admin_sidebar/admin_sidebar.tsx | 5 +- .../admin_sidebar/admin_sidebar_category.tsx | 11 +- .../admin_sidebar_header.tsx | 10 +- .../admin_user_card.test.tsx.snap | 32 + .../admin_user_card/admin_user_card.tsx | 35 +- .../custom_plugin_settings.test.tsx.snap | 3 + ...om_terms_of_service_settings.test.tsx.snap | 1 + .../__snapshots__/data_grid.test.tsx.snap | 18 + .../admin_console/data_grid/data_grid.tsx | 27 +- .../data_grid/data_grid_header.tsx | 5 +- .../admin_console/data_grid/data_grid_row.tsx | 2 + .../data_retention_settings.test.tsx.snap | 46 +- .../__snapshots__/channel_list.test.tsx.snap | 64 +- .../custom_policy_form.test.tsx.snap | 14 + .../global_policy_form.test.tsx.snap | 1 + .../__snapshots__/team_list.test.tsx.snap | 53 +- .../group_settings.test.tsx.snap | 1 + .../__snapshots__/group_details.test.tsx.snap | 8 + .../license_settings.test.tsx.snap | 16 + .../admin_console/list_table/list_table.tsx | 17 +- .../localization/auto_translation.tsx | 10 +- .../manage_roles_modal/manage_roles_modal.tsx | 1 + .../manage_teams_modal.test.tsx.snap | 1 + .../manage_teams_modal/manage_teams_modal.tsx | 1 + .../manage_tokens_modal.test.tsx.snap | 1 + .../member_list_group.test.tsx.snap | 13 +- .../permission_checkbox.test.tsx.snap | 124 +-- .../permission_group.test.tsx.snap | 764 ++++++++++-------- .../permission_row.test.tsx.snap | 315 ++++---- .../permission_schemes_settings.test.tsx.snap | 6 + .../permission_checkbox.tsx | 6 +- .../permission_row.tsx | 1 + ...ssion_system_scheme_settings.test.tsx.snap | 333 ++++++++ .../permission_system_scheme_settings.tsx | 3 + .../plugin_management.test.tsx.snap | 15 + .../admin_console/save_changes_panel.tsx | 5 +- .../admin_console/schema_admin_settings.tsx | 17 +- .../__snapshots__/logs.test.tsx.snap | 29 +- .../system_properties/rank_badge.tsx | 1 + .../ranked_schema_modal.test.tsx | 4 +- .../system_properties/ranked_schema_modal.tsx | 5 +- .../user_properties_rank_values.tsx | 12 +- .../user_properties_table.tsx | 1 + .../__snapshots__/system_roles.test.tsx.snap | 5 + .../__snapshots__/system_role.test.tsx.snap | 18 +- .../system_role_permission.test.tsx.snap | 4 + ...tem_role_permission_dropdown.test.tsx.snap | 2 + .../system_role_permissions.test.tsx.snap | 88 ++ .../add_users_to_role_modal.test.tsx.snap | 5 + .../system_roles/system_role/system_role.tsx | 3 +- .../system_role/system_role_permission.tsx | 21 +- .../system_role_permission_dropdown.tsx | 5 +- .../system_role_users.test.tsx.snap | 42 +- .../system_user_detail.test.tsx.snap | 161 +++- .../system_user_detail/system_user_detail.tsx | 35 +- .../system_user_detail/team_list/team_row.tsx | 5 +- .../system_users/system_users.tsx | 3 + .../channel_settings.test.tsx.snap | 1 + .../channel_details.test.tsx.snap | 12 + .../channel_members.test.tsx.snap | 13 +- .../__snapshots__/channel_list.test.tsx.snap | 120 ++- .../group_members_modal.test.tsx.snap | 3 + .../users_to_remove.test.tsx.snap | 74 +- .../__snapshots__/team_settings.test.tsx.snap | 1 + .../__snapshots__/team_details.test.tsx.snap | 4 + .../__snapshots__/team_members.test.tsx.snap | 13 +- .../__snapshots__/user_grid.test.tsx.snap | 96 ++- .../user_grid/user_grid_name.tsx | 12 +- .../__snapshots__/line_chart.test.tsx.snap | 9 + .../src/components/analytics/line_chart.tsx | 15 +- .../components/analytics/statistic_count.tsx | 5 +- .../src/components/analytics/table_chart.tsx | 17 +- .../team_analytics/team_analytics.tsx | 25 +- .../__snapshots__/app_bar.test.tsx.snap | 2 + .../src/components/app_bar/app_bar.tsx | 5 +- .../backstage/components/backstage_navbar.tsx | 6 +- .../burn_on_read/burn_on_read_label.tsx | 5 +- .../channel_members_modal.test.tsx.snap | 2 + .../channel_settings_configuration_tab.tsx | 5 +- .../channel_settings_info_tab.tsx | 5 +- .../infinite_scroll.test.tsx.snap | 1 + .../src/components/common/back_button.tsx | 5 +- .../create_recap_modal/channel_selector.tsx | 1 + .../create_recap_modal/channel_summary.tsx | 1 + .../__snapshots__/create_team.test.tsx.snap | 2 + .../components/create_team/create_team.tsx | 5 +- .../components/date_picker/date_picker.tsx | 1 + .../__snapshots__/draft_row.test.tsx.snap | 2 + .../drafts_and_schedule_posts_page_header.tsx | 1 + .../drafts/drafts_and_schedule_posts_tabs.tsx | 2 + .../drafts/drafts_link/drafts_link.tsx | 1 + .../__snapshots__/panel_body.test.tsx.snap | 20 + .../__snapshots__/panel_header.test.tsx.snap | 2 + .../components/drafts/panel/panel_body.tsx | 20 +- .../components/drafts/panel/panel_header.tsx | 5 +- .../src/components/dropdown_input.tsx | 10 + .../src/components/edit_post/edit_post.tsx | 5 +- .../__snapshots__/file_preview.test.tsx.snap | 15 + .../components/file_preview/file_preview.tsx | 12 +- .../flag_message_modal/flag_post_modal.tsx | 2 +- .../gif_picker/gif_picker_items.tsx | 5 +- .../components/header_footer_route/footer.tsx | 6 +- .../components/header_footer_route/header.tsx | 7 +- .../__snapshots__/edit_command.test.tsx.snap | 2 + .../edit_incoming_webhook.test.tsx.snap | 2 + .../edit_oauth_app.test.tsx.snap | 1 + .../edit_outgoing_webhook.test.tsx.snap | 1 + ...d_outgoing_oauth_connections.test.tsx.snap | 1 + .../invitation_modal/result_table.tsx | 12 +- .../src/components/loading_screen.tsx | 1 + .../channels/src/components/login/login.tsx | 1 + .../more_direct_channels.test.tsx.snap | 2 + .../__snapshots__/list_item.test.tsx.snap | 3 + .../list_item/list_item.tsx | 1 + .../__snapshots__/multiselect.test.tsx.snap | 3 + .../multiselect/multiselect_list.tsx | 5 +- .../new_search/search_box_input.tsx | 1 + .../no_results_indicator.test.tsx.snap | 17 + .../no_results_indicator.tsx | 6 +- .../marketplace_modal.test.tsx.snap | 4 + .../src/components/post/post_component.tsx | 1 + .../src/components/post/post_options.tsx | 6 +- .../post_edit_history.test.tsx.snap | 1 + .../edited_post_item.test.tsx.snap | 1 + .../__snapshots__/post_view.test.tsx.snap | 2 + .../burn_on_read_timer_chip.tsx | 1 + .../post_message_preview.test.tsx.snap | 6 + .../post_message_view.test.tsx.snap | 5 + .../post_message_view/post_message_view.tsx | 1 + .../profile_picture.test.tsx.snap | 5 + .../src/components/profile_picture/index.tsx | 10 +- .../profile_popover_controller.tsx | 1 + .../properties_card_view.tsx | 11 +- .../components/recaps/recap_channel_card.tsx | 8 +- .../src/components/recaps/recap_item.tsx | 7 +- .../components/recaps/recap_processing.tsx | 5 +- .../__snapshots__/select_team.test.tsx.snap | 8 + .../src/components/setting_item_min.tsx | 1 + .../channel_filter.test.tsx.snap | 2 + .../sidebar/channel_filter/channel_filter.tsx | 1 + .../sidebar_channel/channel_mention_badge.tsx | 4 +- .../sidebar_channel_link.test.tsx.snap | 1 + .../sidebar_channel_link.tsx | 1 + .../sidebar_direct_channel.test.tsx.snap | 3 + .../signup/__snapshots__/signup.test.tsx.snap | 3 + .../channels/src/components/signup/signup.tsx | 1 + .../at_mention_suggestion.test.tsx.snap | 2 + .../at_mention_suggestion.tsx | 1 + .../suggestion/channel_mention_provider.tsx | 1 + .../command_provider.test.tsx.snap | 1 + .../suggestion/generic_channel_provider.tsx | 5 +- .../suggestion/generic_user_provider.tsx | 1 + .../search_channel_suggestion.test.tsx.snap | 12 + .../search_channel_suggestion.tsx | 1 + ...arch_channel_with_permissions_provider.tsx | 1 + .../suggestion/search_user_provider.tsx | 5 +- .../src/components/suggestion/suggestion.tsx | 1 + .../suggestion/switch_channel_provider.tsx | 5 +- .../team_members_modal.test.tsx.snap | 1 + .../terms_of_service.test.tsx.snap | 1 + .../__snapshots__/thread_footer.test.tsx.snap | 5 + .../thread_footer/thread_footer.tsx | 6 +- .../virtualized_thread_list_row.test.tsx.snap | 3 + .../user_groups_modal.test.tsx.snap | 2 + .../join_leave_section.test.tsx.snap | 1 + .../user_settings_display.test.tsx.snap | 173 ++++ .../user_settings_theme.test.tsx.snap | 1 + .../user_settings_notifications.test.tsx.snap | 34 + .../__snapshots__/index.test.tsx.snap | 1 + .../email_notification_setting.test.tsx.snap | 3 + .../plugin/__snapshots__/index.test.tsx.snap | 2 + .../user_settings_security.test.tsx.snap | 10 + .../__snapshots__/mfa_section.test.tsx.snap | 2 + .../widgets/admin_console/admin_header.tsx | 1 + .../admin_console/admin_section_panel.tsx | 17 +- .../advanced_textbox/advanced_textbox.tsx | 2 +- .../__snapshots__/post_type.test.tsx.snap | 1 + .../src/utils/text_formatting.test.ts | 4 +- webapp/channels/src/utils/text_formatting.tsx | 2 +- 256 files changed, 3459 insertions(+), 1104 deletions(-) diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/browse_channels_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/browse_channels_modal.ts index 74ff0933c1a..c5242bf73f7 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/browse_channels_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/browse_channels_modal.ts @@ -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}`); } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_concealed_placeholder.ts b/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_concealed_placeholder.ts index eae0e98b09d..5f71dcb3a83 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_concealed_placeholder.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_concealed_placeholder.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_confirmation_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_confirmation_modal.ts index 00100395c97..47683d3de51 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_confirmation_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_confirmation_modal.ts @@ -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}); diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_timer_chip.ts b/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_timer_chip.ts index 0ec580d3da8..68c9940c790 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_timer_chip.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/burn_on_read_timer_chip.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/center_view.ts b/e2e-tests/playwright/lib/src/ui/components/channels/center_view.ts index 3d1a519e253..f9c6c6f888c 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/center_view.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/center_view.ts @@ -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) => { diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/channel_settings_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/channel_settings_modal.ts index 116e4a38f1d..cb4cbba5785 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/channel_settings_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/channel_settings_modal.ts @@ -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'), ); } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/delete_scheduled_post_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/delete_scheduled_post_modal.ts index b6e91a2b42b..19f62f44b57 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/delete_scheduled_post_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/delete_scheduled_post_modal.ts @@ -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'}); } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/direct_channels_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/direct_channels_modal.ts index 63d787d6d23..4850beb78e5 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/direct_channels_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/direct_channels_modal.ts @@ -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}`); } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/draft_post.ts b/e2e-tests/playwright/lib/src/ui/components/channels/draft_post.ts index 5f09dea7902..c8e45cc6860 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/draft_post.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/draft_post.ts @@ -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'); diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/emoji_gif_picker.ts b/e2e-tests/playwright/lib/src/ui/components/channels/emoji_gif_picker.ts index e395e7b2c89..bf4a1ea5b1b 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/emoji_gif_picker.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/emoji_gif_picker.ts @@ -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'); diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/find_channels_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/find_channels_modal.ts index 82b73079094..28b41f59243 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/find_channels_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/find_channels_modal.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/flag_post_confirmation_dialog.ts b/e2e-tests/playwright/lib/src/ui/components/channels/flag_post_confirmation_dialog.ts index cc394c281ed..d43672a8547 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/flag_post_confirmation_dialog.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/flag_post_confirmation_dialog.ts @@ -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) { diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/header.ts b/e2e-tests/playwright/lib/src/ui/components/channels/header.ts index 9fe49e106b0..f8457d9a5a3 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/header.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/header.ts @@ -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(); } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/members_invited_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/members_invited_modal.ts index 3d00c17daeb..a9de7efb617 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/members_invited_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/members_invited_modal.ts @@ -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 { 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 { await expect(this.notSentSection).toBeVisible(); - return (await this.notSentSection.locator('.InviteResultRow .reason').textContent()) ?? ''; + return (await this.notSentSection.getByTestId('invitation-result-reason').textContent()) ?? ''; } /** diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/message_priority.ts b/e2e-tests/playwright/lib/src/ui/components/channels/message_priority.ts index 070ff39cda4..a50bb4abafc 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/message_priority.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/message_priority.ts @@ -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 diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/new_channel_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/new_channel_modal.ts index b0a2080d675..29f98c442cf 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/new_channel_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/new_channel_modal.ts @@ -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'); diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/post.ts b/e2e-tests/playwright/lib/src/ui/components/channels/post.ts index 00bf1d8dafc..3142c58791d 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/post.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/post.ts @@ -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-/), ); } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/post_create.ts b/e2e-tests/playwright/lib/src/ui/components/channels/post_create.ts index 2dd3f0bc771..bfcddc29eff 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/post_create.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/post_create.ts @@ -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()]); diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/post_edit.ts b/e2e-tests/playwright/lib/src/ui/components/channels/post_edit.ts index c18f566ad7b..ec82527ba36 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/post_edit.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/post_edit.ts @@ -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; } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/profile_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/profile_modal.ts index 85f5e205cb5..3c03fa4cc8d 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/profile_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/profile_modal.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/schedule_message_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/schedule_message_modal.ts index 2686f267899..61668ff2159 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/schedule_message_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/schedule_message_modal.ts @@ -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 diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/scheduled_post.ts b/e2e-tests/playwright/lib/src/ui/components/channels/scheduled_post.ts index ef21bf09f7e..4301f0be67c 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/scheduled_post.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/scheduled_post.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/scheduled_post_indicator.ts b/e2e-tests/playwright/lib/src/ui/components/channels/scheduled_post_indicator.ts index f7518442ab0..7d8f82e2ac0 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/scheduled_post_indicator.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/scheduled_post_indicator.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/search_box.ts b/e2e-tests/playwright/lib/src/ui/components/channels/search_box.ts index 3e530abca5b..a4eb9fa0b3d 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/search_box.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/search_box.ts @@ -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'); } } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/send_message_now_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/send_message_now_modal.ts index 9ca3fdf6b15..c41195b82c6 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/send_message_now_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/send_message_now_modal.ts @@ -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'}); } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/settings/display_settings.ts b/e2e-tests/playwright/lib/src/ui/components/channels/settings/display_settings.ts index 2dbe9ab3097..8bf071faede 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/settings/display_settings.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/settings/display_settings.ts @@ -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(); } } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/settings/settings_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/settings/settings_modal.ts index d986bf0b11b..39f2dbf54d3 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/settings/settings_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/settings/settings_modal.ts @@ -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'}); diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/sidebar_left.ts b/e2e-tests/playwright/lib/src/ui/components/channels/sidebar_left.ts index ab7e142a766..71fece2d579 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/sidebar_left.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/sidebar_left.ts @@ -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'); } } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/sidebar_right.ts b/e2e-tests/playwright/lib/src/ui/components/channels/sidebar_right.ts index 59636ad7293..35757eaed7f 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/sidebar_right.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/sidebar_right.ts @@ -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."]', diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/team_settings/team_settings_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/team_settings/team_settings_modal.ts index e3ae7ce7d4b..c0d72ff4916 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/team_settings/team_settings_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/team_settings/team_settings_modal.ts @@ -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}); } } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/thread_footer.ts b/e2e-tests/playwright/lib/src/ui/components/channels/thread_footer.ts index b0a9bb2d698..f3dc0179083 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/thread_footer.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/thread_footer.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/footer.ts b/e2e-tests/playwright/lib/src/ui/components/footer.ts index e41e4745c16..d978fe88fc5 100644 --- a/e2e-tests/playwright/lib/src/ui/components/footer.ts +++ b/e2e-tests/playwright/lib/src/ui/components/footer.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/main_header.ts b/e2e-tests/playwright/lib/src/ui/components/main_header.ts index ffabd2c15af..d58fa808fd7 100644 --- a/e2e-tests/playwright/lib/src/ui/components/main_header.ts +++ b/e2e-tests/playwright/lib/src/ui/components/main_header.ts @@ -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'); } diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/base_components.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/base_components.ts index 6b4e556814c..b08742d793d 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/base_components.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/base_components.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/base_modal.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/base_modal.ts index 3c70ad2d587..7be6f774d2a 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/base_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/base_modal.ts @@ -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'}); } diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/header.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/header.ts index a498b3b8129..e7a614c8f03 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/header.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/header.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/navbar.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/navbar.ts index e1b0dd393d8..17d991120ac 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/navbar.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/navbar.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/environment/mobile_security.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/environment/mobile_security.ts index 5809809ea36..0d8705c3010 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/environment/mobile_security.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/environment/mobile_security.ts @@ -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', ); } } diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/reporting/team_statistics.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/reporting/team_statistics.ts index 68cd16dcffc..8a0609402a7 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/reporting/team_statistics.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/reporting/team_statistics.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/site_configuration/localization.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/site_configuration/localization.ts index 5c0b2a2f395..d0826c443e3 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/site_configuration/localization.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/site_configuration/localization.ts @@ -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'}); } diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/site_configuration/notifications.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/site_configuration/notifications.ts index 59b025b3395..a8ccb8fe49b 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/site_configuration/notifications.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/site_configuration/notifications.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/system_attributes/system_properties.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/system_attributes/system_properties.ts index ffa1327585b..f59bc24d6f1 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/system_attributes/system_properties.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/system_attributes/system_properties.ts @@ -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 { - 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 { diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/delegated_granular_administration.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/delegated_granular_administration.ts index 25a33ea2084..65af875ad9a 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/delegated_granular_administration.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/delegated_granular_administration.ts @@ -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); diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/permissions_system_scheme.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/permissions_system_scheme.ts index bf32ff2f3bb..744596b562c 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/permissions_system_scheme.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/permissions_system_scheme.ts @@ -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(); } } } diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/system_roles.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/system_roles.ts index b51be40629f..020cfa44633 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/system_roles.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/system_roles.ts @@ -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 { - 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 { - 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'}); } diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/user_detail.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/user_detail.ts index a9c7e7fe1b5..7c0c57f95ef 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/user_detail.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/user_detail.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/index.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/index.ts index 260211cf635..20a7b84980c 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/index.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/index.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/modals.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/modals.ts index 2bd5f2d73f2..be5deb01bb2 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/modals.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/modals.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/users_table.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/users_table.ts index d3c3895ef3e..b9411cae0a2 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/users_table.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sections/user_management/users/users_table.ts @@ -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 { - 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'); diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sidebar.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sidebar.ts index 9edceecf054..618388b00ca 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sidebar.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sidebar.ts @@ -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 { diff --git a/e2e-tests/playwright/lib/src/ui/components/system_console/sidebar_header.ts b/e2e-tests/playwright/lib/src/ui/components/system_console/sidebar_header.ts index 1f7d3aebeef..c3509344b3e 100644 --- a/e2e-tests/playwright/lib/src/ui/components/system_console/sidebar_header.ts +++ b/e2e-tests/playwright/lib/src/ui/components/system_console/sidebar_header.ts @@ -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'}); diff --git a/e2e-tests/playwright/lib/src/ui/pages/channels.ts b/e2e-tests/playwright/lib/src/ui/pages/channels.ts index 288838ece0c..8f3482035b1 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/channels.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/channels.ts @@ -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'); diff --git a/e2e-tests/playwright/lib/src/ui/pages/content_review_dm.ts b/e2e-tests/playwright/lib/src/ui/pages/content_review_dm.ts index f564cce5ba5..35acfdd347c 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/content_review_dm.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/content_review_dm.ts @@ -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 { 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() { diff --git a/e2e-tests/playwright/lib/src/ui/pages/drafts.ts b/e2e-tests/playwright/lib/src/ui/pages/drafts.ts index ce0b939c907..a0df16cb3b6 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/drafts.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/drafts.ts @@ -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(); } diff --git a/e2e-tests/playwright/lib/src/ui/pages/landing_login.ts b/e2e-tests/playwright/lib/src/ui/pages/landing_login.ts index 150865823e2..a2b225402ac 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/landing_login.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/landing_login.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/pages/login.ts b/e2e-tests/playwright/lib/src/ui/pages/login.ts index 85ea36c3afd..565c951cf42 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/login.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/login.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/pages/recaps.ts b/e2e-tests/playwright/lib/src/ui/pages/recaps.ts index f152852c473..1ca1eb84704 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/recaps.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/recaps.ts @@ -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}), }) diff --git a/e2e-tests/playwright/lib/src/ui/pages/reset_password.ts b/e2e-tests/playwright/lib/src/ui/pages/reset_password.ts index 904a5887127..11837c000d4 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/reset_password.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/reset_password.ts @@ -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')); } diff --git a/e2e-tests/playwright/lib/src/ui/pages/scheduled_posts.ts b/e2e-tests/playwright/lib/src/ui/pages/scheduled_posts.ts index 6b07e4d70dd..6024ddb0a8d 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/scheduled_posts.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/scheduled_posts.ts @@ -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(); } diff --git a/e2e-tests/playwright/lib/src/ui/pages/signup.ts b/e2e-tests/playwright/lib/src/ui/pages/signup.ts index bfa7b6871ee..560a5f8055c 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/signup.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/signup.ts @@ -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() { diff --git a/e2e-tests/playwright/lib/src/ui/pages/system_console.ts b/e2e-tests/playwright/lib/src/ui/pages/system_console.ts index c2e845dc0a1..6a2e21c921e 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/system_console.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/system_console.ts @@ -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); diff --git a/e2e-tests/playwright/lib/src/ui/pages/threads.ts b/e2e-tests/playwright/lib/src/ui/pages/threads.ts index 5dc6cc2a59e..3284f4bb402 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/threads.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/threads.ts @@ -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/, }); } diff --git a/webapp/channels/src/components/__snapshots__/searchable_channel_list.test.tsx.snap b/webapp/channels/src/components/__snapshots__/searchable_channel_list.test.tsx.snap index 41493a125df..aa2f6cca105 100644 --- a/webapp/channels/src/components/__snapshots__/searchable_channel_list.test.tsx.snap +++ b/webapp/channels/src/components/__snapshots__/searchable_channel_list.test.tsx.snap @@ -100,6 +100,7 @@ exports[`components/SearchableChannelList should match init snapshot 1`] = ` >
some-user profile image some-user profile image
High Availability
@@ -22,6 +23,7 @@ exports[`components/ClusterSettings should match snapshot, compression disabled >
High Availability
@@ -379,6 +382,7 @@ exports[`components/ClusterSettings should match snapshot, compression enabled 1 >
High Availability
@@ -736,6 +741,7 @@ exports[`components/ClusterSettings should match snapshot, encryption disabled 1 >
High Availability
@@ -1093,6 +1100,7 @@ exports[`components/ClusterSettings should match snapshot, encryption enabled 1` >
Database
diff --git a/webapp/channels/src/components/admin_console/__snapshots__/elasticsearch_settings.test.tsx.snap b/webapp/channels/src/components/admin_console/__snapshots__/elasticsearch_settings.test.tsx.snap index 0181ec981e6..2158f8e3921 100644 --- a/webapp/channels/src/components/admin_console/__snapshots__/elasticsearch_settings.test.tsx.snap +++ b/webapp/channels/src/components/admin_console/__snapshots__/elasticsearch_settings.test.tsx.snap @@ -11,6 +11,7 @@ exports[`components/ElasticSearchSettings should match snapshot, disabled 1`] = >
Elasticsearch
@@ -768,6 +769,7 @@ exports[`components/ElasticSearchSettings should match snapshot, enabled 1`] = ` >
Elasticsearch
@@ -1503,6 +1505,7 @@ exports[`components/ElasticSearchSettings should match snapshot, sniff enabled 1 >
Elasticsearch
diff --git a/webapp/channels/src/components/admin_console/__snapshots__/message_export_settings.test.tsx.snap b/webapp/channels/src/components/admin_console/__snapshots__/message_export_settings.test.tsx.snap index 44057cb6445..0c5cd6d3527 100644 --- a/webapp/channels/src/components/admin_console/__snapshots__/message_export_settings.test.tsx.snap +++ b/webapp/channels/src/components/admin_console/__snapshots__/message_export_settings.test.tsx.snap @@ -11,6 +11,7 @@ exports[`components/MessageExportSettings should match snapshot, disabled, actia >
Compliance Export
@@ -250,6 +251,7 @@ exports[`components/MessageExportSettings should match snapshot, disabled, globa >
Compliance Export
@@ -663,6 +665,7 @@ exports[`components/MessageExportSettings should match snapshot, enabled, actian >
Compliance Export
@@ -899,6 +902,7 @@ exports[`components/MessageExportSettings should match snapshot, enabled, global >
Compliance Export
diff --git a/webapp/channels/src/components/admin_console/__snapshots__/push_settings.test.tsx.snap b/webapp/channels/src/components/admin_console/__snapshots__/push_settings.test.tsx.snap index 6dd94bda3b0..2ba33363c89 100644 --- a/webapp/channels/src/components/admin_console/__snapshots__/push_settings.test.tsx.snap +++ b/webapp/channels/src/components/admin_console/__snapshots__/push_settings.test.tsx.snap @@ -11,6 +11,7 @@ exports[`components/PushSettings should match snapshot, licensed 1`] = ` >
Push Notification Server
@@ -272,6 +273,7 @@ exports[`components/PushSettings should match snapshot, unlicensed 1`] = ` >
Push Notification Server
diff --git a/webapp/channels/src/components/admin_console/access_control/__snapshots__/policies.test.tsx.snap b/webapp/channels/src/components/admin_console/access_control/__snapshots__/policies.test.tsx.snap index 2852450a854..f1efd32d466 100644 --- a/webapp/channels/src/components/admin_console/access_control/__snapshots__/policies.test.tsx.snap +++ b/webapp/channels/src/components/admin_console/access_control/__snapshots__/policies.test.tsx.snap @@ -31,6 +31,7 @@ exports[`components/admin_console/access_control/PolicyList should match snapsho