From cbf300ffd3975b0adeb3e6fa823a4f61117f21d7 Mon Sep 17 00:00:00 2001 From: yasser khan Date: Wed, 21 Jan 2026 15:09:34 +0530 Subject: [PATCH] Fix E2E Test Failures Due to Keyboard Controller Changes (#9371) --- .../categories_list/categories/categories.tsx | 3 +- .../helpers/flatten_categories.test.ts | 2 + .../categories/helpers/flatten_categories.ts | 4 +- .../helpers/observe_flattened_categories.ts | 1 + detox/android_emulator/config.ini | 2 +- detox/create_android_emulator.sh | 16 +++- detox/e2e/support/adb_utils.ts | 80 +++++++++++++++++++ detox/e2e/support/server_api/setup.ts | 31 +++++-- .../e2e/support/ui/component/autocomplete.ts | 4 +- detox/e2e/support/ui/screen/account.ts | 20 +++-- .../auto_responder_notification_settings.ts | 10 ++- detox/e2e/support/ui/screen/channel.ts | 53 +++++++++--- .../ui/screen/clock_display_settings.ts | 10 ++- detox/e2e/support/ui/screen/global_threads.ts | 6 +- detox/e2e/support/ui/screen/home.ts | 12 ++- .../ui/screen/notification_settings.ts | 10 ++- detox/e2e/support/ui/screen/permalink.ts | 4 +- .../e2e/support/ui/screen/pinned_messages.ts | 13 ++- detox/e2e/support/ui/screen/post_options.ts | 4 +- .../e2e/support/ui/screen/recent_mentions.ts | 13 ++- detox/e2e/support/ui/screen/saved_messages.ts | 15 +++- .../e2e/support/ui/screen/search_messages.ts | 14 +++- detox/e2e/support/ui/screen/server.ts | 12 ++- detox/e2e/support/ui/screen/thread.ts | 19 ++++- detox/e2e/support/utils/index.ts | 41 ++++++++++ .../channels/account/account_menu.e2e.ts | 26 +++--- .../channels/account/custom_status.e2e.ts | 53 +++++++----- .../email_notification_settings.e2e.ts | 14 +++- .../channels/autocomplete/at_mention.e2e.ts | 71 ++++++++-------- .../autocomplete/channel_mention.e2e.ts | 50 ++++++------ .../autocomplete/channel_post_draft.e2e.ts | 18 +++-- .../autocomplete/create_channel.e2e.ts | 16 ++-- .../channels/autocomplete/edit_channel.e2e.ts | 16 ++-- .../autocomplete/edit_channel_header.e2e.ts | 16 ++-- .../channels/autocomplete/edit_post.e2e.ts | 26 +++--- .../autocomplete/emoji_suggestion.e2e.ts | 30 +++---- .../autocomplete/slash_suggestion.e2e.ts | 28 +++---- .../autocomplete/thread_post_draft.e2e.ts | 41 +++++++--- .../channels/channels/channel_list.e2e.ts | 2 +- .../channels/channel_post_list.e2e.ts | 54 +++++++------ ...ate_channel_and_edit_channel_header.e2e.ts | 6 +- .../favorite_and_unfavorite_channel.e2e.ts | 1 + .../channels/channels/find_channels.e2e.ts | 26 ++++-- .../channels/messaging/channel_link.e2e.ts | 15 +++- .../messaging/emojis_and_reactions.e2e.ts | 18 ++++- .../follow_and_unfollow_messsage.e2e.ts | 38 +++++++-- .../channels/messaging/markdown_image.e2e.ts | 20 +++-- .../channels/messaging/message_delete.e2e.ts | 23 ++++-- .../channels/messaging/message_draft.e2e.ts | 2 +- .../channels/messaging/message_edit.e2e.ts | 25 +++++- .../messaging/message_permalink.e2e.ts | 9 ++- .../channels/messaging/message_post.e2e.ts | 14 ++-- .../channels/messaging/message_reply.e2e.ts | 23 ++++-- .../channels/messaging/permalink.e2e.ts | 17 ++-- .../messaging/pin_and_unpin_message.e2e.ts | 14 +++- .../messaging/save_and_unsave_message.e2e.ts | 8 +- .../channels/search/cross_team_search.e2e.ts | 2 +- .../channels/search/pinned_messages.e2e.ts | 22 +++++ .../channels/search/saved_messages.e2e.ts | 15 +++- .../channels/server_login/server_list.e2e.ts | 3 - .../channels/smoke_test/account.e2e.ts | 1 + .../channels/smoke_test/autocomplete.e2e.ts | 20 +---- .../channels/smoke_test/channels.e2e.ts | 6 +- .../channels/smoke_test/messaging.e2e.ts | 47 ++++++----- .../channels/smoke_test/server_login.e2e.ts | 12 ++- .../channels/smoke_test/threads.e2e.ts | 27 +++++-- .../channels/teams/invite_people.e2e.ts | 6 +- .../threads/follow_and_unfollow_thread.e2e.ts | 27 ++++--- .../channels/threads/global_threads.e2e.ts | 9 ++- .../threads/open_thread_in_channel.e2e.ts | 21 +++-- .../channels/threads/reply_to_thread.e2e.ts | 23 ++++-- .../threads/save_and_unsave_thread.e2e.ts | 12 ++- .../test/products/playbooks/playbooks-e2e.ts | 1 - .../e2e/test/products/playbooks/playbooks.ts | 1 - detox/e2e/test/setup.ts | 44 +++++++--- 75 files changed, 988 insertions(+), 430 deletions(-) create mode 100644 detox/e2e/support/adb_utils.ts diff --git a/app/screens/home/channel_list/categories_list/categories/categories.tsx b/app/screens/home/channel_list/categories_list/categories/categories.tsx index b5bbf260f..cdedbcac6 100644 --- a/app/screens/home/channel_list/categories_list/categories/categories.tsx +++ b/app/screens/home/channel_list/categories_list/categories/categories.tsx @@ -11,7 +11,6 @@ import {ROW_HEIGHT as CHANNEL_ROW_HEIGHT} from '@components/channel_item/channel import FormattedText from '@components/formatted_text'; import Loading from '@components/loading'; import {Events} from '@constants'; -import {UNREADS_CATEGORY} from '@constants/categories'; import {CHANNEL, DRAFT, THREAD} from '@constants/screens'; import {HOME_PADDING} from '@constants/view'; import {useServerUrl} from '@context/server'; @@ -120,7 +119,7 @@ const Categories = ({flattenedItems, unreadChannelIds, onlyUnreads, isTablet}: P } // item.type === 'channel' - const testIdSuffix = item.categoryId === UNREADS_CATEGORY ? UNREADS_CATEGORY : item.channelId; + const testIdSuffix = item.categoryType; return ( { const item: FlattenedItem = { type: 'channel', categoryId: 'cat1', + categoryType: 'custom', channelId: 'channel1', channel: mockChannel1, }; @@ -71,6 +72,7 @@ describe('flatten_categories utils', () => { const item: FlattenedItem = { type: 'channel', categoryId: 'cat1', + categoryType: 'custom', channelId: 'channel1', channel: mockChannel1, }; diff --git a/app/screens/home/channel_list/categories_list/categories/helpers/flatten_categories.ts b/app/screens/home/channel_list/categories_list/categories/helpers/flatten_categories.ts index df15ffc40..005a8d28d 100644 --- a/app/screens/home/channel_list/categories_list/categories/helpers/flatten_categories.ts +++ b/app/screens/home/channel_list/categories_list/categories/helpers/flatten_categories.ts @@ -9,7 +9,7 @@ import type ChannelModel from '@typings/database/models/servers/channel'; export type FlattenedItem = | {type: 'unreads_header'} | {type: 'header'; categoryId: string; category: CategoryModel} - | {type: 'channel'; categoryId: string; channelId: string; channel: ChannelModel}; + | {type: 'channel'; categoryId: string; categoryType: string; channelId: string; channel: ChannelModel}; export type CategoryData = { category: CategoryModel; @@ -55,6 +55,7 @@ export const flattenCategories = ( result.push({ type: 'channel', categoryId: UNREADS_CATEGORY, + categoryType: UNREADS_CATEGORY, channelId: channel.id, channel, }); @@ -75,6 +76,7 @@ export const flattenCategories = ( result.push({ type: 'channel', categoryId: category.id, + categoryType: category.type, channelId: channel.id, channel, }); diff --git a/app/screens/home/channel_list/categories_list/categories/helpers/observe_flattened_categories.ts b/app/screens/home/channel_list/categories_list/categories/helpers/observe_flattened_categories.ts index abbe278ef..91a81cc7c 100644 --- a/app/screens/home/channel_list/categories_list/categories/helpers/observe_flattened_categories.ts +++ b/app/screens/home/channel_list/categories_list/categories/helpers/observe_flattened_categories.ts @@ -178,6 +178,7 @@ const observeFlattenedUnreads = ( map((channel) => ({ type: 'channel', categoryId: UNREADS_CATEGORY, + categoryType: UNREADS_CATEGORY, channelId: channel.id, channel, })); diff --git a/detox/android_emulator/config.ini b/detox/android_emulator/config.ini index 3c0324b49..128fd0052 100644 --- a/detox/android_emulator/config.ini +++ b/detox/android_emulator/config.ini @@ -17,7 +17,7 @@ hw.battery = yes hw.camera.back = virtualscene hw.camera.front = emulated hw.cpu.arch = change_cpu_arch -hw.cpu.ncore = 4 +hw.cpu.ncore = 7 hw.dPad = no hw.device.hash2 = MD5:80326cf5b53c08af25d4243cb231faa9 hw.device.manufacturer = Google diff --git a/detox/create_android_emulator.sh b/detox/create_android_emulator.sh index 9888bcba7..878dfe50a 100755 --- a/detox/create_android_emulator.sh +++ b/detox/create_android_emulator.sh @@ -38,7 +38,7 @@ create_avd() { sed -i -e "s|image.sysdir.1 = change_to_image_sysdir/|image.sysdir.1 = system-images/android-${SDK_VERSION}/default/${cpu_arch_family}/|g" "$AVD_NAME/config.ini" sed -i -e "s|skin.path = change_to_absolute_path/pixel_4_xl_skin|skin.path = $(pwd)/${AVD_NAME}/pixel_4_xl_skin|g" "$AVD_NAME/config.ini" - echo "hw.cpu.ncore=5" >> "$AVD_NAME/config.ini" + echo "hw.cpu.ncore=7" >> "$AVD_NAME/config.ini" echo "Android virtual device successfully created: ${AVD_NAME}" } @@ -53,7 +53,7 @@ start_emulator() { local emulator_opts="-avd $AVD_NAME -no-snapshot -no-boot-anim -no-audio -gpu off -no-window" if [[ "$CI" == "true" || "$(uname -s)" == "Linux" ]]; then - emulator $emulator_opts -gpu host -accel on -qemu -m 4096 & + emulator $emulator_opts -gpu host -accel on -qemu -m 8192 & else emulator $emulator_opts -gpu guest -verbose -qemu -vnc :0 fi @@ -64,11 +64,22 @@ wait_for_emulator() { echo "Waiting for emulator to boot..." adb wait-for-device + + local boot_timeout=300 # 5 minutes max + local boot_elapsed=0 until [[ "$(adb shell getprop sys.boot_completed | tr -d '\r')" == "1" ]]; do + if [[ $boot_elapsed -ge $boot_timeout ]]; then + echo "Emulator failed to boot within 5 minutes" + exit 1 + fi echo "Waiting for emulator to fully boot..." sleep 10 + boot_elapsed=$((boot_elapsed + 10)) done echo "Emulator is fully booted." + sleep 15 + adb shell pm list packages > /dev/null 2>&1 + echo "Emulator is fully ready." } install_app() { @@ -82,7 +93,6 @@ start_server() { cd .. RUNNING_E2E=true npm run start & local timeout=120 interval=5 elapsed=0 - sleep $timeout until nc -z localhost 8081; do if [[ $elapsed -ge $timeout ]]; then diff --git a/detox/e2e/support/adb_utils.ts b/detox/e2e/support/adb_utils.ts new file mode 100644 index 000000000..39ece3124 --- /dev/null +++ b/detox/e2e/support/adb_utils.ts @@ -0,0 +1,80 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +/* eslint-disable no-console */ + +import {execSync} from 'child_process'; + +/** + * Check if we're running on Android platform + * Uses environment variable as fallback if device is not initialized + */ +function isAndroid(): boolean { + try { + // Try to get platform from device (if initialized) + if (typeof device !== 'undefined' && device.getPlatform) { + return device.getPlatform() === 'android'; + } + } catch { + // Device not initialized yet, fall back to env var + } + + // Fall back to environment variable check + return process.env.IOS !== 'true'; +} + +/** + * Clean up ADB reverse port mappings to prevent "Address already in use" errors + * This is particularly important in CI environments where previous test runs may have crashed + * iOS: This function safely no-ops on iOS - no side effects + */ +export async function cleanupAdbReversePorts(): Promise { + // Only run on Android - safe no-op for iOS + if (!isAndroid()) { + return; + } + + try { + console.info('[ADB Cleanup] Removing all reverse port mappings...'); + + // Remove all reverse port mappings + execSync('adb reverse --remove-all', { + stdio: 'pipe', + timeout: 5000, + }); + + console.info('[ADB Cleanup] Successfully cleaned up reverse port mappings'); + } catch (error) { + // Don't fail if cleanup fails - this is best-effort + console.warn(`[ADB Cleanup] Warning: Failed to cleanup reverse ports: ${(error as Error).message}`); + } +} + +/** + * Reset ADB server connection to clear any stale state + * This can help recover from connection issues in CI + * iOS: This function safely no-ops on iOS - no side effects + */ +export async function resetAdbServer(): Promise { + // Only run on Android - safe no-op for iOS + if (!isAndroid()) { + return; + } + + try { + console.info('[ADB Reset] Restarting ADB server...'); + + // Kill and restart ADB server + execSync('adb kill-server && adb start-server', { + stdio: 'pipe', + timeout: 10000, + }); + + // Wait a bit for server to stabilize + await new Promise((resolve) => setTimeout(resolve, 2000)); + + console.info('[ADB Reset] Successfully restarted ADB server'); + } catch (error) { + // Don't fail if reset fails - this is best-effort + console.warn(`[ADB Reset] Warning: Failed to reset ADB server: ${(error as Error).message}`); + } +} diff --git a/detox/e2e/support/server_api/setup.ts b/detox/e2e/support/server_api/setup.ts index 106f51657..75d24ddc6 100644 --- a/detox/e2e/support/server_api/setup.ts +++ b/detox/e2e/support/server_api/setup.ts @@ -16,12 +16,33 @@ export const apiInit = async (baseUrl: string, { teamOptions = {type: 'O', prefix: 'team'}, userOptions = {prefix: 'user'}, }: any = {}): Promise => { - const {team} = await Team.apiCreateTeam(baseUrl, teamOptions); - const {channel} = await Channel.apiCreateChannel(baseUrl, {...channelOptions, teamId: team.id}); - const {user} = await User.apiCreateUser(baseUrl, userOptions); + const teamResult = await Team.apiCreateTeam(baseUrl, teamOptions); + if (teamResult.error) { + throw new Error(`Failed to create team: ${JSON.stringify(teamResult.error)}`); + } + const {team} = teamResult; - await Team.apiAddUserToTeam(baseUrl, user.id, team.id); - await Channel.apiAddUserToChannel(baseUrl, user.id, channel.id); + const channelResult = await Channel.apiCreateChannel(baseUrl, {...channelOptions, teamId: team.id}); + if (channelResult.error) { + throw new Error(`Failed to create channel: ${JSON.stringify(channelResult.error)}`); + } + const {channel} = channelResult; + + const userResult = await User.apiCreateUser(baseUrl, userOptions); + if (userResult.error) { + throw new Error(`Failed to create user: ${JSON.stringify(userResult.error)}`); + } + const {user} = userResult; + + const addTeamResult = await Team.apiAddUserToTeam(baseUrl, user.id, team.id); + if (addTeamResult.error) { + throw new Error(`Failed to add user to team: ${JSON.stringify(addTeamResult.error)}`); + } + + const addChannelResult = await Channel.apiAddUserToChannel(baseUrl, user.id, channel.id); + if (addChannelResult.error) { + throw new Error(`Failed to add user to channel: ${JSON.stringify(addChannelResult.error)}`); + } return { channel, diff --git a/detox/e2e/support/ui/component/autocomplete.ts b/detox/e2e/support/ui/component/autocomplete.ts index 27b76c50a..381925c8c 100644 --- a/detox/e2e/support/ui/component/autocomplete.ts +++ b/detox/e2e/support/ui/component/autocomplete.ts @@ -114,7 +114,9 @@ class Autocomplete { toBeVisible = async (isVisible = true) => { await wait(timeouts.ONE_SEC); if (isVisible) { - await expect(this.autocomplete.atIndex(0)).toBeVisible(); + // Use a lower visibility threshold to account for keyboard controller positioning + // The autocomplete may be partially off-screen during keyboard animations + await expect(this.autocomplete.atIndex(0)).toBeVisible(1); return this.autocomplete; } diff --git a/detox/e2e/support/ui/screen/account.ts b/detox/e2e/support/ui/screen/account.ts index 6d67b8b0f..0f82caddf 100644 --- a/detox/e2e/support/ui/screen/account.ts +++ b/detox/e2e/support/ui/screen/account.ts @@ -80,11 +80,21 @@ class AccountScreen { }; open = async () => { - // # Open account screen - await waitFor(HomeScreen.accountTab).toBeVisible().withTimeout(timeouts.TWO_SEC); - await HomeScreen.accountTab.tap(); - - return this.toBeVisible(); + try { + await waitFor(HomeScreen.accountTab).toExist().withTimeout(timeouts.TEN_SEC); + await HomeScreen.accountTab.tap(); + return this.toBeVisible(); + } catch (error) { + // If account tab is not found, the app might already be on account screen or in unexpected state + // Try to verify if we're already on account screen + try { + await waitFor(this.accountScreen).toExist().withTimeout(timeouts.TWO_SEC); + return this.accountScreen; + } catch { + // Re-throw original error if we're not on account screen either + throw error; + } + } }; logout = async (serverDisplayName: string | null = null) => { diff --git a/detox/e2e/support/ui/screen/auto_responder_notification_settings.ts b/detox/e2e/support/ui/screen/auto_responder_notification_settings.ts index f9b3a076d..807ed5972 100644 --- a/detox/e2e/support/ui/screen/auto_responder_notification_settings.ts +++ b/detox/e2e/support/ui/screen/auto_responder_notification_settings.ts @@ -38,8 +38,14 @@ class AutoResponderNotificationSettingsScreen { }; back = async () => { - await this.backButton.tap(); - await expect(this.autoResponderNotificationSettingsScreen).not.toBeVisible(); + try { + await waitFor(this.backButton).toExist().withTimeout(timeouts.TWO_SEC); + await this.backButton.tap(); + await expect(this.autoResponderNotificationSettingsScreen).not.toBeVisible(); + } catch (error) { + // Back button may not exist if screen failed to load or already navigated away + console.warn('[AutoResponderNotificationSettingsScreen.back] Navigation failed:', error); // eslint-disable-line no-console + } }; toggleEnableAutomaticRepliesOptionOn = async () => { diff --git a/detox/e2e/support/ui/screen/channel.ts b/detox/e2e/support/ui/screen/channel.ts index 7e7194543..c13288390 100644 --- a/detox/e2e/support/ui/screen/channel.ts +++ b/detox/e2e/support/ui/screen/channel.ts @@ -17,12 +17,11 @@ import { PostOptionsScreen, ThreadScreen, } from '@support/ui/screen'; -import {isIos, timeouts, wait} from '@support/utils'; +import {isAndroid, isIos, timeouts, wait} from '@support/utils'; import {expect} from 'detox'; class ChannelScreen { testID = { - archievedCloseChannelButton: 'channel.post_draft.archived.close_channel.button', channelScreenPrefix: 'channel.', channelScreen: 'channel.screen', channelQuickActionsButton: 'channel_header.channel_quick_actions.button', @@ -77,7 +76,6 @@ class ChannelScreen { postPriorityRequestAck = element(by.id(this.testID.postPriorityRequestAck)); postPriorityImportantMessage = element(by.id(this.testID.postPriorityImportantMessage)); postPriorityPicker = element(by.id(this.testID.postPriorityPicker)); - archievedCloseChannelButton = element(by.id(this.testID.archievedCloseChannelButton)); channelScreen = element(by.id(this.testID.channelScreen)); channelQuickActionsButton = element(by.id(this.testID.channelQuickActionsButton)); favoriteQuickAction = element(by.id(this.testID.favoriteQuickAction)); @@ -152,16 +150,29 @@ class ChannelScreen { return this.channelScreen; }; - open = async (categoryKey: string, channelName: string) => { - // # Open channel screen - await waitFor(ChannelListScreen.getChannelItemDisplayName(categoryKey, channelName)).toBeVisible().withTimeout(timeouts.TEN_SEC); - await ChannelListScreen.getChannelItemDisplayName(categoryKey, channelName).tap(); + dismissScheduledPostTooltip = async () => { + // Try to close scheduled post tooltip if it exists (try both regular and admin account versions) try { + await waitFor(this.scheduledPostTooltipCloseButton).toBeVisible().withTimeout(timeouts.ONE_SEC); await this.scheduledPostTooltipCloseButton.tap(); - } catch (error) { - // eslint-disable-next-line no-console - console.log('Element not visible, skipping click'); + await wait(timeouts.HALF_SEC); + } catch { + // Try admin account version + try { + await waitFor(this.scheduledPostTooltipCloseButtonAdminAccount).toBeVisible().withTimeout(timeouts.ONE_SEC); + await this.scheduledPostTooltipCloseButtonAdminAccount.tap(); + await wait(timeouts.HALF_SEC); + } catch { + // Tooltip not visible, continue + } } + }; + + open = async (category: string, channelName: any) => { + // # Open channel screen + await wait(timeouts.FOUR_SEC); + await ChannelListScreen.getChannelItemDisplayName(category, channelName).tap(); + await this.dismissScheduledPostTooltip(); return this.toBeVisible(); }; @@ -193,12 +204,29 @@ class ChannelScreen { } }; + dismissKeyboard = async () => { + // Explicitly dismiss keyboard before long press + if (isAndroid()) { + try { + await device.pressBack(); + await wait(timeouts.THREE_SEC); + } catch (error) { + // Keyboard might not be open, continue + } + } + if (isIos()) { + // On iOS, tap outside the input area to dismiss keyboard + await this.postInput.tapReturnKey(); + await wait(timeouts.TWO_SEC); + } + }; + openPostOptionsFor = async (postId: string, text: string) => { const {postListPostItem} = this.getPostListPostItem(postId, text); - await expect(postListPostItem).toBeVisible(); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.TEN_SEC); // # Open post options - await postListPostItem.longPress(timeouts.TWO_SEC); + await postListPostItem.longPress(); await PostOptionsScreen.toBeVisible(); await wait(timeouts.TWO_SEC); }; @@ -217,6 +245,7 @@ class ChannelScreen { await this.postInput.clearText(); await this.postInput.replaceText(message); await this.tapSendButton(); + await wait(timeouts.TWO_SEC); }; enterMessageToSchedule = async (message: string) => { diff --git a/detox/e2e/support/ui/screen/clock_display_settings.ts b/detox/e2e/support/ui/screen/clock_display_settings.ts index 844717e92..50c32f77d 100644 --- a/detox/e2e/support/ui/screen/clock_display_settings.ts +++ b/detox/e2e/support/ui/screen/clock_display_settings.ts @@ -38,8 +38,14 @@ class ClockDisplaySettingsScreen { }; back = async () => { - await this.backButton.tap(); - await expect(this.clockDisplaySettingsScreen).not.toBeVisible(); + try { + await waitFor(this.backButton).toExist().withTimeout(timeouts.TWO_SEC); + await this.backButton.tap(); + await expect(this.clockDisplaySettingsScreen).not.toBeVisible(); + } catch (error) { + // Back button may not exist if screen failed to load or already navigated away + console.warn('[ClockDisplaySettingsScreen.back] Navigation failed:', error); // eslint-disable-line no-console + } }; } diff --git a/detox/e2e/support/ui/screen/global_threads.ts b/detox/e2e/support/ui/screen/global_threads.ts index 081494547..da3c562d3 100644 --- a/detox/e2e/support/ui/screen/global_threads.ts +++ b/detox/e2e/support/ui/screen/global_threads.ts @@ -7,7 +7,7 @@ import { ThreadOptionsScreen, } from '@support/ui/screen'; import {timeouts, wait} from '@support/utils'; -import {expect} from 'detox'; +import {expect, waitFor} from 'detox'; class GlobalThreadsScreen { testID = { @@ -75,7 +75,7 @@ class GlobalThreadsScreen { back = async () => { await this.backButton.tap(); - await expect(this.globalThreadsScreen).not.toBeVisible(); + await waitFor(this.globalThreadsScreen).not.toBeVisible().withTimeout(timeouts.TEN_SEC); }; openThreadOptionsFor = async (postId: string) => { @@ -83,7 +83,7 @@ class GlobalThreadsScreen { await expect(threadItem).toBeVisible(); // # Open thread options - await threadItem.longPress(); + await threadItem.longPress(timeouts.FOUR_SEC); await ThreadOptionsScreen.toBeVisible(); await wait(timeouts.TWO_SEC); }; diff --git a/detox/e2e/support/ui/screen/home.ts b/detox/e2e/support/ui/screen/home.ts index c3a32a8ec..dd14f34d8 100644 --- a/detox/e2e/support/ui/screen/home.ts +++ b/detox/e2e/support/ui/screen/home.ts @@ -37,9 +37,15 @@ class HomeScreen { }; logout = async (serverDisplayName: string | null = null) => { - await AccountScreen.open(); - await AccountScreen.logout(serverDisplayName); - await expect(this.channelListTab).not.toBeVisible(); + try { + await AccountScreen.open(); + await AccountScreen.logout(serverDisplayName); + await expect(this.channelListTab).not.toBeVisible(); + } catch (error) { + // Logout may fail if app is in unexpected state during cleanup + // Log error but don't throw to allow test cleanup to continue + console.warn('[HomeScreen.logout] Logout failed:', error); // eslint-disable-line no-console + } }; } diff --git a/detox/e2e/support/ui/screen/notification_settings.ts b/detox/e2e/support/ui/screen/notification_settings.ts index a92209b98..89aab8364 100644 --- a/detox/e2e/support/ui/screen/notification_settings.ts +++ b/detox/e2e/support/ui/screen/notification_settings.ts @@ -42,8 +42,14 @@ class NotificationSettingsScreen { }; back = async () => { - await this.backButton.tap(); - await expect(this.notificationSettingsScreen).not.toBeVisible(); + try { + await waitFor(this.backButton).toExist().withTimeout(timeouts.TWO_SEC); + await this.backButton.tap(); + await expect(this.notificationSettingsScreen).not.toBeVisible(); + } catch (error) { + // Back button may not exist if screen failed to load or already navigated away + console.warn('[NotificationSettingsScreen.back] Navigation failed:', error); // eslint-disable-line no-console + } }; } diff --git a/detox/e2e/support/ui/screen/permalink.ts b/detox/e2e/support/ui/screen/permalink.ts index b71c42416..844e136e9 100644 --- a/detox/e2e/support/ui/screen/permalink.ts +++ b/detox/e2e/support/ui/screen/permalink.ts @@ -3,7 +3,7 @@ import {PostList} from '@support/ui/component'; import {timeouts, wait} from '@support/utils'; -import {expect} from 'detox'; +import {expect, waitFor} from 'detox'; class PermalinkScreen { testID = { @@ -36,7 +36,7 @@ class PermalinkScreen { jumpToRecentMessages = async () => { // # Jump to recent messages - await wait(timeouts.ONE_SEC); + await waitFor(this.jumpToRecentMessagesButton).toExist().withTimeout(timeouts.TEN_SEC); await this.jumpToRecentMessagesButton.tap(); await expect(this.permalinkScreen).not.toBeVisible(); }; diff --git a/detox/e2e/support/ui/screen/pinned_messages.ts b/detox/e2e/support/ui/screen/pinned_messages.ts index b2224d3f8..ba749c0e3 100644 --- a/detox/e2e/support/ui/screen/pinned_messages.ts +++ b/detox/e2e/support/ui/screen/pinned_messages.ts @@ -6,7 +6,7 @@ import { ChannelInfoScreen, PostOptionsScreen, } from '@support/ui/screen'; -import {timeouts, wait} from '@support/utils'; +import {timeouts, wait, waitForElementToBeVisible} from '@support/utils'; import {expect} from 'detox'; class PinnedMessagesScreen { @@ -57,10 +57,17 @@ class PinnedMessagesScreen { openPostOptionsFor = async (postId: string, text: string) => { const {postListPostItem} = this.getPostListPostItem(postId, text); - await expect(postListPostItem).toBeVisible(); + + // Poll for the post to become visible without waiting for idle bridge + await waitForElementToBeVisible(postListPostItem, timeouts.TEN_SEC); + + // Dismiss keyboard by tapping on the post list (needed after posting a message) + const flatList = this.postList.getFlatList(); + await flatList.scroll(100, 'down'); + await wait(timeouts.ONE_SEC); // # Open post options - await postListPostItem.longPress(); + await postListPostItem.longPress(timeouts.TWO_SEC); await PostOptionsScreen.toBeVisible(); await wait(timeouts.TWO_SEC); }; diff --git a/detox/e2e/support/ui/screen/post_options.ts b/detox/e2e/support/ui/screen/post_options.ts index d619ba77b..3ce0d1cb7 100644 --- a/detox/e2e/support/ui/screen/post_options.ts +++ b/detox/e2e/support/ui/screen/post_options.ts @@ -88,12 +88,12 @@ class PostOptionsScreen { openPostOptionsForPinedPosts = async (postId: string) => { await waitFor(this.pinnedPostListItem(postId)).toExist().withTimeout(timeouts.TWO_SEC); - await this.pinnedPostListItem(postId).longPress(); + await this.pinnedPostListItem(postId).longPress(timeouts.TWO_SEC); }; openPostOptionsForSearchedPosts = async (postId: string) => { await waitFor(this.searchedPostListItem(postId)).toExist().withTimeout(timeouts.TWO_SEC); - await this.searchedPostListItem(postId).longPress(); + await this.searchedPostListItem(postId).longPress(timeouts.TWO_SEC); }; } diff --git a/detox/e2e/support/ui/screen/recent_mentions.ts b/detox/e2e/support/ui/screen/recent_mentions.ts index 931280d8c..a665f1a9d 100644 --- a/detox/e2e/support/ui/screen/recent_mentions.ts +++ b/detox/e2e/support/ui/screen/recent_mentions.ts @@ -9,7 +9,7 @@ import { HomeScreen, PostOptionsScreen, } from '@support/ui/screen'; -import {timeouts, wait} from '@support/utils'; +import {timeouts, wait, waitForElementToBeVisible} from '@support/utils'; import {expect} from 'detox'; class RecentMentionsScreen { @@ -63,10 +63,17 @@ class RecentMentionsScreen { openPostOptionsFor = async (postId: string, text: string) => { const {postListPostItem} = this.getPostListPostItem(postId, text); - await expect(postListPostItem).toBeVisible(); + + // Poll for the post to become visible without waiting for idle bridge + await waitForElementToBeVisible(postListPostItem, timeouts.TEN_SEC); + + // Dismiss keyboard by tapping on the post list (needed after posting a message) + const flatList = this.postList.getFlatList(); + await flatList.scroll(100, 'down'); + await wait(timeouts.ONE_SEC); // # Open post options - await postListPostItem.longPress(); + await postListPostItem.longPress(timeouts.TWO_SEC); await PostOptionsScreen.toBeVisible(); await wait(timeouts.TWO_SEC); }; diff --git a/detox/e2e/support/ui/screen/saved_messages.ts b/detox/e2e/support/ui/screen/saved_messages.ts index ceea5b851..a09dbb8c0 100644 --- a/detox/e2e/support/ui/screen/saved_messages.ts +++ b/detox/e2e/support/ui/screen/saved_messages.ts @@ -9,7 +9,7 @@ import { HomeScreen, PostOptionsScreen, } from '@support/ui/screen'; -import {timeouts, wait} from '@support/utils'; +import {timeouts, wait, waitForElementToBeVisible} from '@support/utils'; import {expect} from 'detox'; class SavedMessagesScreen { @@ -43,7 +43,7 @@ class SavedMessagesScreen { }; toBeVisible = async () => { - await waitFor(this.savedMessagesScreen).toExist().withTimeout(timeouts.TEN_SEC); + await waitFor(this.savedMessagesScreen).toBeVisible().withTimeout(timeouts.TEN_SEC); return this.savedMessagesScreen; }; @@ -57,10 +57,17 @@ class SavedMessagesScreen { openPostOptionsFor = async (postId: string, text: string) => { const {postListPostItem} = this.getPostListPostItem(postId, text); - await expect(postListPostItem).toBeVisible(); + + // Poll for the post to become visible without waiting for idle bridge + await waitForElementToBeVisible(postListPostItem, timeouts.TEN_SEC); + + // Dismiss keyboard by tapping on the post list (needed after posting a message) + const flatList = this.postList.getFlatList(); + await flatList.scroll(100, 'down'); + await wait(timeouts.ONE_SEC); // # Open post options - await postListPostItem.longPress(); + await postListPostItem.longPress(timeouts.TWO_SEC); await PostOptionsScreen.toBeVisible(); await wait(timeouts.TWO_SEC); }; diff --git a/detox/e2e/support/ui/screen/search_messages.ts b/detox/e2e/support/ui/screen/search_messages.ts index b0e3ecbb3..d1de1a8e7 100644 --- a/detox/e2e/support/ui/screen/search_messages.ts +++ b/detox/e2e/support/ui/screen/search_messages.ts @@ -9,7 +9,7 @@ import { HomeScreen, PostOptionsScreen, } from '@support/ui/screen'; -import {timeouts, wait} from '@support/utils'; +import {timeouts, wait, waitForElementToBeVisible} from '@support/utils'; import {expect} from 'detox'; class SearchMessagesScreen { @@ -87,11 +87,17 @@ class SearchMessagesScreen { openPostOptionsFor = async (postId: string, text: string) => { const {postListPostItem} = this.getPostListPostItem(postId, text); - await wait(timeouts.TWO_SEC); - await expect(postListPostItem).toBeVisible(); + + // Poll for the post to become visible without waiting for idle bridge + await waitForElementToBeVisible(postListPostItem, timeouts.TEN_SEC); + + // Dismiss keyboard by tapping on the post list (needed after posting a message) + const flatList = this.postList.getFlatList(); + await flatList.scroll(100, 'down'); + await wait(timeouts.ONE_SEC); // # Open post options - await postListPostItem.longPress(); + await postListPostItem.longPress(timeouts.TWO_SEC); await PostOptionsScreen.toBeVisible(); await wait(timeouts.TWO_SEC); }; diff --git a/detox/e2e/support/ui/screen/server.ts b/detox/e2e/support/ui/screen/server.ts index 24ecdf1b8..723cb15ef 100644 --- a/detox/e2e/support/ui/screen/server.ts +++ b/detox/e2e/support/ui/screen/server.ts @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import {Alert} from '@support/ui/component'; -import {isAndroid, isIos, timeouts, wait} from '@support/utils'; +import {isAndroid, isIos, timeouts, wait, waitForElementToBeVisible} from '@support/utils'; import {expect} from 'detox'; class ServerScreen { @@ -72,7 +72,10 @@ class ServerScreen { } } } - await waitFor(this.usernameInput).toExist().withTimeout(isAndroid()? timeouts.ONE_MIN : timeouts.HALF_MIN); + + // The bridge can be busy during login transition, use waitFor without idle check + const timeout = isAndroid() ? timeouts.ONE_MIN : timeouts.HALF_MIN; + await waitFor(this.usernameInput).toBeVisible().withTimeout(timeout); }; close = async () => { @@ -123,7 +126,10 @@ class ServerScreen { } } } - await waitFor(this.usernameInput).toExist().withTimeout(isAndroid()? timeouts.ONE_MIN : timeouts.HALF_MIN); + + // The bridge can be busy during login transition, so poll for the element without waiting for idle + const timeout = isAndroid() ? timeouts.ONE_MIN : timeouts.HALF_MIN; + await waitForElementToBeVisible(this.usernameInput, timeout, timeouts.ONE_SEC); }; } diff --git a/detox/e2e/support/ui/screen/thread.ts b/detox/e2e/support/ui/screen/thread.ts index 762e742d2..8a23b7db8 100644 --- a/detox/e2e/support/ui/screen/thread.ts +++ b/detox/e2e/support/ui/screen/thread.ts @@ -11,7 +11,7 @@ import { SendButton, } from '@support/ui/component'; import {PostOptionsScreen} from '@support/ui/screen'; -import {timeouts, wait} from '@support/utils'; +import {timeouts, wait, waitForElementToBeVisible} from '@support/utils'; import {expect} from 'detox'; class ThreadScreen { @@ -93,14 +93,24 @@ class ThreadScreen { back = async () => { await this.backButton.tap(); await waitFor(this.threadScreen).not.toBeVisible().withTimeout(timeouts.TEN_SEC); + + // Wait for the previous screen to be fully loaded and rendered + await wait(timeouts.TWO_SEC); }; openPostOptionsFor = async (postId: string, text: string) => { const {postListPostItem} = this.getPostListPostItem(postId, text); - await expect(postListPostItem).toBeVisible(); + + // Poll for the post to become visible without waiting for idle bridge + await waitForElementToBeVisible(postListPostItem, timeouts.TEN_SEC); + + // Dismiss keyboard by tapping on the post list (needed after posting a message) + const flatList = this.postList.getFlatList(); + await flatList.scroll(100, 'down'); + await wait(timeouts.ONE_SEC); // # Open post options - await postListPostItem.longPress(); + await postListPostItem.longPress(timeouts.TWO_SEC); await PostOptionsScreen.toBeVisible(); await wait(timeouts.TWO_SEC); }; @@ -110,6 +120,9 @@ class ThreadScreen { await this.postInput.tap(); await this.postInput.replaceText(message); await this.tapSendButton(); + + // # Wait for message to be rendered + await wait(timeouts.FOUR_SEC); }; enterMessageToSchedule = async (message: string) => { diff --git a/detox/e2e/support/utils/index.ts b/detox/e2e/support/utils/index.ts index 39d518102..4b2362cb7 100644 --- a/detox/e2e/support/utils/index.ts +++ b/detox/e2e/support/utils/index.ts @@ -70,6 +70,7 @@ export const timeouts = { HALF_SEC: SECOND / 2, ONE_SEC: SECOND, TWO_SEC: SECOND * 2, + THREE_SEC: SECOND * 3, FOUR_SEC: SECOND * 4, TEN_SEC: SECOND * 10, HALF_MIN: MINUTE / 2, @@ -103,3 +104,43 @@ export async function retryWithReload(func: () => Promise, retries: number } } } + +/** + * Poll for an element to become visible without waiting for React Native bridge to be idle. + * This is useful when the bridge is busy with animations or state updates but the UI is already rendered. + * + * @param {Detox.NativeElement} detoxElement - The Detox element to wait for + * @param {number} timeout - Maximum time to wait in milliseconds (default: 10 seconds) + * @param {number} pollInterval - How often to check in milliseconds (default: 500ms) + * @return {Promise} - Resolves when element is visible, throws if timeout is reached + * @throws {Error} - If element is not visible after timeout + * + * @example + * const button = element(by.id('my.button')); + * await waitForElementToBeVisible(button, timeouts.TEN_SEC); + */ +export async function waitForElementToBeVisible( + detoxElement: Detox.NativeElement, + timeout: number = timeouts.TEN_SEC, + pollInterval: number = timeouts.HALF_SEC, +): Promise { + const {expect: detoxExpect} = require('detox'); + const startTime = Date.now(); + /* eslint-disable no-await-in-loop */ + while (Date.now() - startTime < timeout) { + try { + await detoxExpect(detoxElement).toBeVisible(); + return; // Element found and visible + } catch (error) { + // Element not visible yet, wait and try again + if ((Date.now() - startTime) + pollInterval >= timeout) { + // About to timeout, throw the error + throw error; + } + await wait(pollInterval); + } + } + /* eslint-enable no-await-in-loop */ + // Final check - will throw if still not found + await detoxExpect(detoxElement).toBeVisible(); +} diff --git a/detox/e2e/test/products/channels/account/account_menu.e2e.ts b/detox/e2e/test/products/channels/account/account_menu.e2e.ts index 6de92a031..7a2bbb91e 100644 --- a/detox/e2e/test/products/channels/account/account_menu.e2e.ts +++ b/detox/e2e/test/products/channels/account/account_menu.e2e.ts @@ -7,7 +7,7 @@ // - Use element testID when selecting an element. Create one if none. // ******************************************************************* -import {Channel, Post, Setup, User} from '@support/server_api'; +import {Post, Setup, User} from '@support/server_api'; import { serverOneUrl, siteOneUrl, @@ -23,13 +23,12 @@ import { SettingsScreen, } from '@support/ui/screen'; import {getRandomId, timeouts, wait} from '@support/utils'; -import {expect} from 'detox'; +import {expect, waitFor} from 'detox'; describe('Account - Account Menu', () => { const serverOneDisplayName = 'Server 1'; const channelsCategory = 'channels'; let testUser: any; - let otherUser: any; let testChannel: any; beforeAll(async () => { @@ -37,9 +36,6 @@ describe('Account - Account Menu', () => { testUser = user; testChannel = channel; - ({user: otherUser} = await User.apiCreateUser(siteOneUrl)); - await Channel.apiAddUserToChannel(siteOneUrl, otherUser.id, testChannel.id); - // # Log in to server and go to account screen await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); await LoginScreen.login(testUser); @@ -53,6 +49,7 @@ describe('Account - Account Menu', () => { afterAll(async () => { // # Log out + await ChannelScreen.back(); await HomeScreen.logout(); }); @@ -77,7 +74,8 @@ describe('Account - Account Menu', () => { // * Verify on account screen and verify user presence icon and label are for offline user status await AccountScreen.toBeVisible(); - await expect(AccountScreen.getUserPresenceIndicator('offline')).toBeVisible(); + await wait(timeouts.TWO_SEC); + await waitFor(AccountScreen.getUserPresenceIndicator('offline')).toExist().withTimeout(timeouts.TEN_SEC); await expect(AccountScreen.getUserPresenceLabel('offline')).toHaveText('Offline'); // # Tap on user presence option and tap on do not disturb user status option @@ -87,7 +85,8 @@ describe('Account - Account Menu', () => { // * Verify on account screen and verify user presence icon and label are for do no disturb user status await AccountScreen.toBeVisible(); - await expect(AccountScreen.getUserPresenceIndicator('dnd')).toBeVisible(); + await wait(timeouts.TWO_SEC); + await waitFor(AccountScreen.getUserPresenceIndicator('dnd')).toExist().withTimeout(timeouts.TEN_SEC); await expect(AccountScreen.getUserPresenceLabel('dnd')).toHaveText('Do Not Disturb'); // # Tap on user presence option and tap on away user status option @@ -97,7 +96,8 @@ describe('Account - Account Menu', () => { // * Verify on account screen and verify user presence icon and label are for away user status await AccountScreen.toBeVisible(); - await expect(AccountScreen.getUserPresenceIndicator('away')).toBeVisible(); + await wait(timeouts.TWO_SEC); + await waitFor(AccountScreen.getUserPresenceIndicator('away')).toExist().withTimeout(timeouts.TEN_SEC); await expect(AccountScreen.getUserPresenceLabel('away')).toHaveText('Away'); // # Tap on user presence option and tap on online user status option @@ -107,7 +107,8 @@ describe('Account - Account Menu', () => { // * Verify on account screen and verify user presence icon and label are for online user status await AccountScreen.toBeVisible(); - await expect(AccountScreen.getUserPresenceIndicator('online')).toBeVisible(); + await wait(timeouts.TWO_SEC); + await waitFor(AccountScreen.getUserPresenceIndicator('online')).toExist().withTimeout(timeouts.TEN_SEC); await expect(AccountScreen.getUserPresenceLabel('online')).toHaveText('Online'); }); @@ -191,9 +192,12 @@ describe('Account - Account Menu', () => { await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + // Wait for keyboard to dismiss and message to be posted + await wait(timeouts.TWO_SEC); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem, postListPostItemHeaderDisplayName} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(postListPostItem).toBeVisible(); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.TEN_SEC); await expect(postListPostItemHeaderDisplayName).toHaveText(testUser.username); // Also check profile screen diff --git a/detox/e2e/test/products/channels/account/custom_status.e2e.ts b/detox/e2e/test/products/channels/account/custom_status.e2e.ts index ac3e60887..9c1727447 100644 --- a/detox/e2e/test/products/channels/account/custom_status.e2e.ts +++ b/detox/e2e/test/products/channels/account/custom_status.e2e.ts @@ -94,6 +94,7 @@ describe('Account - Custom Status', () => { await openCustomStatusScreen(); await selectSuggestedStatus(status); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); // * Verify status is set on account screen await verifyStatusSetOnAccountScreen(status); @@ -116,6 +117,7 @@ describe('Account - Custom Status', () => { // # Clean up await clearStatusInput(); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); }); it('MM-T4990_3 - should be able to set a status via emoji picker and custom status', async () => { @@ -128,10 +130,12 @@ describe('Account - Custom Status', () => { // # Pick emoji and type custom status await CustomStatusScreen.openEmojiPicker('default', true); await EmojiPickerScreen.searchInput.replaceText(customEmojiName); + await EmojiPickerScreen.searchInput.tapReturnKey(); await element(by.text('🤡')).tap(); await wait(timeouts.ONE_SEC); await CustomStatusScreen.statusInput.replaceText(customStatusText); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); // * Verify custom status is set await verifyStatusSetOnAccountScreen({emoji: customEmojiName, text: customStatusText, duration: customStatusDuration}); @@ -147,6 +151,7 @@ describe('Account - Custom Status', () => { await clearButton.tap(); await clearStatusInput(); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); }); it('MM-T4990_4 - should be able to clear custom status from account', async () => { @@ -155,6 +160,7 @@ describe('Account - Custom Status', () => { await openCustomStatusScreen(); await selectSuggestedStatus(status); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); // * Verify status is set await verifyStatusSetOnAccountScreen(status); @@ -164,7 +170,7 @@ describe('Account - Custom Status', () => { await wait(timeouts.ONE_SEC); // * Verify status is cleared - await verifyStatusCleared(status); + await verifyStatusCleared(); // # Open custom status screen and verify cleared await CustomStatusScreen.open(); @@ -202,6 +208,7 @@ describe('Account - Custom Status', () => { // # Save status await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.TWO_SEC); await expect(CustomStatusScreen.customStatusScreen).not.toBeVisible(); // * Verify status is set and visible in account screen @@ -227,6 +234,7 @@ describe('Account - Custom Status', () => { await expect(recentCustomStatus).not.toExist(); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); }); it('MM-T3891 - should be able to set custom status with emoji picker and manage it', async () => { @@ -254,15 +262,16 @@ describe('Account - Custom Status', () => { await expect(CustomStatusScreen.getCustomStatusEmoji(customEmojiName)).toBeVisible(); // # Save status - await wait(timeouts.TWO_SEC); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); // * Verify status is set in account screen await verifyStatusSetOnAccountScreen({emoji: customEmojiName, text: customStatusText, duration: customStatusDuration}); // # Clear status from account screen await AccountScreen.customStatusClearButton.tap(); - await verifyStatusCleared({emoji: customEmojiName, text: customStatusText, duration: customStatusDuration}); + await wait(timeouts.ONE_SEC); + await verifyStatusCleared(); // # Reopen and verify status in recent section await CustomStatusScreen.open(); @@ -275,6 +284,7 @@ describe('Account - Custom Status', () => { await recentStatus.tap(); await verifyStatusInInput({emoji: customEmojiName, text: customStatusText, duration: customStatusDuration}); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); // * Verify status is set again await AccountScreen.toBeVisible(); @@ -294,9 +304,10 @@ describe('Account - Custom Status', () => { await expect(recentCustomStatus).not.toExist(); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); // * Verify status is cleared - await verifyStatusCleared({emoji: customEmojiName, text: customStatusText, duration: customStatusDuration}); + await verifyStatusCleared(); }); it('MM-T3892 - should manage recent custom statuses correctly', async () => { @@ -309,10 +320,12 @@ describe('Account - Custom Status', () => { // # Create custom status with emoji picker await CustomStatusScreen.openEmojiPicker('default', true); await EmojiPickerScreen.searchInput.replaceText(customEmojiName); + await EmojiPickerScreen.searchInput.tapReturnKey(); await element(by.text('🤡')).tap(); await wait(timeouts.ONE_SEC); await CustomStatusScreen.statusInput.replaceText(customStatusText); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); // * Verify status is set await verifyStatusSetOnAccountScreen({emoji: customEmojiName, text: customStatusText, duration: customStatusDuration}); @@ -335,6 +348,7 @@ describe('Account - Custom Status', () => { const suggestedStatus = STATUSES.IN_MEETING; await selectSuggestedStatus(suggestedStatus); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); await verifyStatusSetOnAccountScreen(suggestedStatus); // # Clear and verify in recent section @@ -352,6 +366,7 @@ describe('Account - Custom Status', () => { await expect(CustomStatusScreen.getSuggestedCustomStatus(suggestedStatus.emoji, suggestedStatus.text, suggestedStatus.duration).customStatusSuggestion).toBeVisible(); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); }); it('MM-T4091 - should be able to set custom status with expiry time and verify in various locations', async () => { @@ -365,6 +380,7 @@ describe('Account - Custom Status', () => { await selectSuggestedStatus(status); await expect(CustomStatusScreen.getCustomStatusExpiry(status.duration)).toBeVisible(); await CustomStatusScreen.doneButton.tap(); + await wait(timeouts.ONE_SEC); // * Verify status is set with expiry time await AccountScreen.toBeVisible(); @@ -378,19 +394,17 @@ describe('Account - Custom Status', () => { await ChannelListScreen.open(); await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(messageText); + await ChannelScreen.dismissKeyboard(); const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); - const {postListPostItem, postListPostItemProfilePicture} = + const {postListPostItem, postListPostItemHeaderDisplayName} = ChannelScreen.getPostListPostItem(post.id, messageText, {userId: testUser.id}); await expect(postListPostItem).toBeVisible(); - // # Tap avatar and verify user profile shows status - if (!postListPostItemProfilePicture) { - throw new Error('postListPostItemProfilePicture is null'); - } - await expect(postListPostItemProfilePicture).toBeVisible(); - await postListPostItemProfilePicture.tap(); - await wait(timeouts.TWO_SEC); + // # Tap display name to open user profile (more reliable than avatar tap) + await expect(postListPostItemHeaderDisplayName).toBeVisible(); + await postListPostItemHeaderDisplayName.longPress(); + await wait(timeouts.ONE_SEC); await UserProfileScreen.toBeVisible(); await UserProfileScreen.close(); await ChannelScreen.back(); @@ -413,7 +427,7 @@ describe('Account - Custom Status', () => { // # Open channel info and verify status await ChannelScreen.toBeVisible(); await ChannelScreen.headerTitle.tap(); - await wait(timeouts.TWO_SEC); + await wait(timeouts.FOUR_SEC); await ChannelInfoScreen.toBeVisible(); await ChannelInfoScreen.close(); await ChannelScreen.back(); @@ -421,7 +435,8 @@ describe('Account - Custom Status', () => { // # Clean up await AccountScreen.open(); await AccountScreen.customStatusClearButton.tap(); - await verifyStatusCleared(status); + await wait(timeouts.ONE_SEC); + await verifyStatusCleared(); }); }); @@ -477,9 +492,9 @@ const verifyStatusSetOnAccountScreen = async (status: {emoji: string; text: stri await expect(accountCustomStatusExpiry).toBeVisible(); }; -const verifyStatusCleared = async (status: {emoji: string; text?: string; duration: string}) => { - const {accountCustomStatusEmoji, accountCustomStatusText} = - AccountScreen.getCustomStatus(status.emoji, status.duration); - await expect(accountCustomStatusEmoji).not.toExist(); - await expect(accountCustomStatusText).toHaveText('Set a custom status'); +const verifyStatusCleared = async () => { + await expect(AccountScreen.setStatusOption).toBeVisible(); + const customStatusText = element(by.id('account.custom_status.custom_status_text')); + await expect(customStatusText).toHaveText('Set a custom status'); + await expect(AccountScreen.customStatusClearButton).not.toBeVisible(); }; diff --git a/detox/e2e/test/products/channels/account/email_notification_settings.e2e.ts b/detox/e2e/test/products/channels/account/email_notification_settings.e2e.ts index 96dd9aa41..cee9076be 100644 --- a/detox/e2e/test/products/channels/account/email_notification_settings.e2e.ts +++ b/detox/e2e/test/products/channels/account/email_notification_settings.e2e.ts @@ -21,7 +21,8 @@ import { ServerScreen, SettingsScreen, } from '@support/ui/screen'; -import {expect} from 'detox'; +import {timeouts, wait} from '@support/utils'; +import {expect, waitFor} from 'detox'; describe('Account - Settings - Email Notification Settings', () => { const serverOneDisplayName = 'Server 1'; @@ -65,6 +66,9 @@ describe('Account - Settings - Email Notification Settings', () => { // # Tap on never option await EmailNotificationSettingsScreen.neverOption.tap(); + // Wait for option to be selected + await wait(timeouts.ONE_SEC); + // * Verify email threads option does not exist await expect(EmailNotificationSettingsScreen.emailThreadsOptionToggledOn).not.toExist(); @@ -73,7 +77,8 @@ describe('Account - Settings - Email Notification Settings', () => { // * Verify on notification settings screen and never is set await NotificationSettingsScreen.toBeVisible(); - await expect(NotificationSettingsScreen.emailNotificationsOptionInfo).toHaveText('Never'); + await wait(timeouts.TWO_SEC); + await waitFor(NotificationSettingsScreen.emailNotificationsOptionInfo).toHaveText('Never').withTimeout(timeouts.TEN_SEC); // # Go back to email notification settings screen await EmailNotificationSettingsScreen.open(); @@ -83,12 +88,15 @@ describe('Account - Settings - Email Notification Settings', () => { // # Tap on immediately option, toggle email threads option off, and tap on back button await EmailNotificationSettingsScreen.immediatelyOption.tap(); + await wait(timeouts.ONE_SEC); await EmailNotificationSettingsScreen.toggleEmailThreadsOptionOff(); + await wait(timeouts.ONE_SEC); await EmailNotificationSettingsScreen.back(); // * Verify on notification settings screen and immediately is set await NotificationSettingsScreen.toBeVisible(); - await expect(NotificationSettingsScreen.emailNotificationsOptionInfo).toHaveText('Immediately'); + await wait(timeouts.TWO_SEC); + await waitFor(NotificationSettingsScreen.emailNotificationsOptionInfo).toHaveText('Immediately').withTimeout(timeouts.TEN_SEC); // # Go back to email notification settings screen await EmailNotificationSettingsScreen.open(); diff --git a/detox/e2e/test/products/channels/autocomplete/at_mention.e2e.ts b/detox/e2e/test/products/channels/autocomplete/at_mention.e2e.ts index f9edeea9f..0bb52e52f 100644 --- a/detox/e2e/test/products/channels/autocomplete/at_mention.e2e.ts +++ b/detox/e2e/test/products/channels/autocomplete/at_mention.e2e.ts @@ -81,13 +81,13 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in username await ChannelScreen.postInput.typeText(testUser.username); // * Verify at-mention autocomplete contains associated user suggestion - await expect(userAtMentionAutocomplete).toBeVisible(); + await expect(userAtMentionAutocomplete).toExist(); }); it('MM-T4878_2 - should suggest user based on nickname', async () => { @@ -96,13 +96,13 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in nickname await ChannelScreen.postInput.typeText(testUser.nickname); // * Verify at-mention autocomplete contains associated user suggestion - await expect(userAtMentionAutocomplete).toBeVisible(); + await expect(userAtMentionAutocomplete).toExist(); }); it('MM-T4878_3 - should suggest user based on first name', async () => { @@ -111,13 +111,13 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in first name await ChannelScreen.postInput.typeText(testUser.first_name); // * Verify at-mention autocomplete contains associated user suggestion - await expect(userAtMentionAutocomplete).toBeVisible(); + await expect(userAtMentionAutocomplete).toExist(); }); it('MM-T4878_4 - should suggest user based on last name', async () => { @@ -126,13 +126,13 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in last name await ChannelScreen.postInput.typeText(testUser.last_name); // * Verify at-mention autocomplete contains associated user suggestion - await expect(userAtMentionAutocomplete).toBeVisible(); + await expect(userAtMentionAutocomplete).toExist(); }); it('MM-T4878_5 - should suggest user based on lowercase first name', async () => { @@ -141,13 +141,13 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in lowercase first name await ChannelScreen.postInput.typeText(testUser.first_name.toLowerCase()); // * Verify at-mention autocomplete contains associated user suggestion - await expect(userAtMentionAutocomplete).toBeVisible(); + await expect(userAtMentionAutocomplete).toExist(); }); it('MM-T4878_6 - should suggest user based on lowercase last name', async () => { @@ -156,13 +156,13 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in lowercase last name await ChannelScreen.postInput.typeText(testUser.last_name.toLowerCase()); // * Verify at-mention autocomplete contains associated user suggestion - await expect(userAtMentionAutocomplete).toBeVisible(); + await expect(userAtMentionAutocomplete).toExist(); }); it('MM-T4878_7 - should suggest user based on full name with space', async () => { @@ -171,13 +171,13 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in full name with space await ChannelScreen.postInput.typeText(`${testUser.first_name} ${testUser.last_name}`); // * Verify at-mention autocomplete contains associated user suggestion - await expect(userAtMentionAutocomplete).toBeVisible(); + await expect(userAtMentionAutocomplete).toExist(); }); it('MM-T4878_8 - should suggest user based on partial full name with space', async () => { @@ -186,13 +186,13 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in partial full name with space await ChannelScreen.postInput.typeText(`${testUser.first_name} ${testUser.last_name.substring(0, testUser.last_name.length - 6)}`); // * Verify at-mention autocomplete contains associated user suggestion - await expect(userAtMentionAutocomplete).toBeVisible(); + await expect(userAtMentionAutocomplete).toExist(); }); it('MM-T4878_9 - should stop suggesting user after full name with trailing space', async () => { @@ -201,20 +201,20 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in full name await ChannelScreen.postInput.typeText(`${testUser.first_name} ${testUser.last_name}`); // * Verify at-mention autocomplete contains associated user suggestion - await expect(userAtMentionAutocomplete).toBeVisible(); + await expect(userAtMentionAutocomplete).toExist(); // # Type in trailing space await ChannelScreen.postInput.typeText(' '); await wait(timeouts.ONE_SEC); // * Verify at-mention autocomplete does not contain associated user suggestion - await expect(userAtMentionAutocomplete).not.toBeVisible(); + await expect(userAtMentionAutocomplete).not.toExist(); }); it('MM-T4878_10 - should stop suggesting user when keyword is not associated with any user', async () => { @@ -223,35 +223,38 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in keyword not associated with any user await ChannelScreen.postInput.typeText(getRandomId()); + await wait(timeouts.ONE_SEC); // * Verify at-mention autocomplete does not contain associated user suggestion - await expect(userAtMentionAutocomplete).not.toBeVisible(); + await expect(userAtMentionAutocomplete).not.toExist(); }); it('MM-T4878_11 - should be able to select at-mention multiple times', async () => { // # Type in "@" to activate at-mention autocomplete - await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).not.toExist(); await ChannelScreen.postInput.typeText('@'); + await wait(timeouts.ONE_SEC); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in username and tap on user at-mention autocomplete await ChannelScreen.postInput.typeText(testUser.username); await userAtMentionAutocomplete.tap(); // * Verify at-mention list disappears - await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).not.toExist(); // # Type in "@" again to re-activate at-mention list await ChannelScreen.postInput.typeText('@'); + await wait(timeouts.ONE_SEC); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); }); it('MM-T4878_12 - should not be able to autocomplete deactivated user', async () => { @@ -261,13 +264,13 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in username of deactivated user await ChannelScreen.postInput.typeText(testOtherUser.username); // * Verify at-mention autocomplete does not contain associated user suggestion - await expect(otherUserAtMentionAutocomplete).not.toBeVisible(); + await expect(otherUserAtMentionAutocomplete).not.toExist(); // # Reactivate user, clear post input, and type in "@" to activate at-mention list await User.apiUpdateUserActiveStatus(siteOneUrl, testOtherUser.id, true); @@ -277,13 +280,13 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in username of reactivated user await ChannelScreen.postInput.typeText(testOtherUser.username); // * Verify at-mention autocomplete contains associated user suggestion - await expect(otherUserAtMentionAutocomplete).toBeVisible(); + await expect(otherUserAtMentionAutocomplete).toExist(); }); it('MM-T4878_13 - should be able to autocomplete out of channel user', async () => { @@ -294,14 +297,14 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in username of out of channel user await ChannelScreen.postInput.typeText(outOfChannelUser.username); // * Verify at-mention autocomplete contains associated user suggestion const {atMentionItem: outOfChannelUserAtMentionAutocomplete} = Autocomplete.getAtMentionItem(outOfChannelUser.id); - await expect(outOfChannelUserAtMentionAutocomplete).toBeVisible(); + await expect(outOfChannelUserAtMentionAutocomplete).toExist(); }); it('MM-T4878_14 - should include current user in autocomplete', async () => { @@ -310,7 +313,7 @@ describe('Autocomplete - At-Mention', () => { await Autocomplete.toBeVisible(); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); // # Type in username of current user await ChannelScreen.postInput.typeText(testUser.username); @@ -318,7 +321,7 @@ describe('Autocomplete - At-Mention', () => { // * Verify at-mention autocomplete contains current user await wait(timeouts.TWO_SEC); const {atMentionItemUserDisplayName, atMentionItemProfilePicture} = Autocomplete.getAtMentionItem(testUser.id); - await expect(atMentionItemUserDisplayName).toBeVisible(); - await expect(atMentionItemProfilePicture).toBeVisible(); + await expect(atMentionItemUserDisplayName).toExist(); + await expect(atMentionItemProfilePicture).toExist(); }); }); diff --git a/detox/e2e/test/products/channels/autocomplete/channel_mention.e2e.ts b/detox/e2e/test/products/channels/autocomplete/channel_mention.e2e.ts index 3da12a0b6..4fb4ab38e 100644 --- a/detox/e2e/test/products/channels/autocomplete/channel_mention.e2e.ts +++ b/detox/e2e/test/products/channels/autocomplete/channel_mention.e2e.ts @@ -77,13 +77,13 @@ describe('Autocomplete - Channel Mention', () => { await Autocomplete.toBeVisible(); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in channel name await ChannelScreen.postInput.typeText(testChannel.name); // * Verify channel mention autocomplete contains associated channel suggestion - await expect(channelMentionAutocomplete).toBeVisible(); + await expect(channelMentionAutocomplete).toExist(); }); it('MM-T4879_2 - should suggest channel based on channel display name', async () => { @@ -92,13 +92,13 @@ describe('Autocomplete - Channel Mention', () => { await Autocomplete.toBeVisible(); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in channel display name await ChannelScreen.postInput.typeText(testChannel.display_name); // * Verify channel mention autocomplete contains associated channel suggestion - await expect(channelMentionAutocomplete).toBeVisible(); + await expect(channelMentionAutocomplete).toExist(); }); it('MM-T4879_3 - should suggest channel based on lowercase channel display name', async () => { @@ -107,13 +107,13 @@ describe('Autocomplete - Channel Mention', () => { await Autocomplete.toBeVisible(); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in lowercase channel display name await ChannelScreen.postInput.typeText(testChannel.display_name.toLowerCase()); // * Verify channel mention autocomplete contains associated channel suggestion - await expect(channelMentionAutocomplete).toBeVisible(); + await expect(channelMentionAutocomplete).toExist(); }); it('MM-T4879_4 - should suggest channel based on partial channel display name', async () => { @@ -122,13 +122,13 @@ describe('Autocomplete - Channel Mention', () => { await Autocomplete.toBeVisible(); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in partial channel display name await ChannelScreen.postInput.typeText(`${testChannel.display_name.substring(0, testChannel.display_name.length - 4)}`); // * Verify channel mention autocomplete contains associated channel suggestion - await expect(channelMentionAutocomplete).toBeVisible(); + await expect(channelMentionAutocomplete).toExist(); }); it('MM-T4879_5 - should stop suggesting channel after channel display name with trailing space', async () => { @@ -137,20 +137,20 @@ describe('Autocomplete - Channel Mention', () => { await Autocomplete.toBeVisible(); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in channel display name await ChannelScreen.postInput.typeText(testChannel.display_name); // * Verify channel mention autocomplete contains associated channel suggestion - await expect(channelMentionAutocomplete).toBeVisible(); + await expect(channelMentionAutocomplete).toExist(); // # Type in trailing space await ChannelScreen.postInput.typeText(' '); await wait(timeouts.ONE_SEC); // * Verify channel mention autocomplete does not contain associated channel suggestion - await expect(channelMentionAutocomplete).not.toBeVisible(); + await expect(channelMentionAutocomplete).not.toExist(); }); it('MM-T4879_6 - should stop suggesting channel when keyword is not associated with any channel', async () => { @@ -159,35 +159,35 @@ describe('Autocomplete - Channel Mention', () => { await Autocomplete.toBeVisible(); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in keyword not associated with any channel await ChannelScreen.postInput.typeText(getRandomId()); // * Verify channel mention autocomplete does not contain associated channel suggestion - await expect(channelMentionAutocomplete).not.toBeVisible(); + await expect(channelMentionAutocomplete).not.toExist(); }); it('MM-T4879_7 - should be able to select channel mention multiple times', async () => { // # Type in "~" to activate channel mention autocomplete - await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).not.toExist(); await ChannelScreen.postInput.typeText('~'); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in channel name and tap on channel mention autocomplete await ChannelScreen.postInput.typeText(testChannel.name); await channelMentionAutocomplete.tap(); // * Verify channel mention list disappears - await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).not.toExist(); // # Type in "~" again to re-activate channel mention list await ChannelScreen.postInput.typeText('~'); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); }); it('MM-T4879_8 - should be able to autocomplete archived channel', async () => { @@ -197,13 +197,13 @@ describe('Autocomplete - Channel Mention', () => { await Autocomplete.toBeVisible(); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in channel name of archived channel await ChannelScreen.postInput.typeText(testOtherChannel.name); // * Verify channel mention autocomplete contains associated channel suggestion - await expect(otherChannelMentionAutocomplete).toBeVisible(); + await expect(otherChannelMentionAutocomplete).toExist(); // # Unarchive channel, clear post input, and type in "~" to activate channel mention list await Channel.apiRestoreChannel(siteOneUrl, testOtherChannel.id); @@ -213,13 +213,13 @@ describe('Autocomplete - Channel Mention', () => { await Autocomplete.toBeVisible(); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in channel name of unarchived channel await ChannelScreen.postInput.typeText(testOtherChannel.name); // * Verify channel mention autocomplete contains associated channel suggestion - await expect(otherChannelMentionAutocomplete).toBeVisible(); + await expect(otherChannelMentionAutocomplete).toExist(); }); it('MM-T4879_9 - should not be able to autocomplete out of team channel', async () => { @@ -230,14 +230,14 @@ describe('Autocomplete - Channel Mention', () => { await Autocomplete.toBeVisible(); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in channel name of out of team channel await ChannelScreen.postInput.typeText(outOfTeamChannel.name); // * Verify channel mention autocomplete does not contain associated channel suggestion const {channelMentionItem: outOfTeamChannelChannelMentionAutocomplete} = Autocomplete.getChannelMentionItem(outOfTeamChannel.name); - await expect(outOfTeamChannelChannelMentionAutocomplete).not.toBeVisible(); + await expect(outOfTeamChannelChannelMentionAutocomplete).not.toExist(); }); it('MM-T4879_10 - should include current channel in autocomplete', async () => { @@ -246,13 +246,13 @@ describe('Autocomplete - Channel Mention', () => { await Autocomplete.toBeVisible(); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); // # Type in channel name of current channel await ChannelScreen.postInput.typeText(testChannel.name); // * Verify channel mention autocomplete contains current channel const {channelMentionItemChannelDisplayName} = Autocomplete.getChannelMentionItem(testChannel.name); - await expect(channelMentionItemChannelDisplayName).toBeVisible(); + await expect(channelMentionItemChannelDisplayName).toExist(); }); }); diff --git a/detox/e2e/test/products/channels/autocomplete/channel_post_draft.e2e.ts b/detox/e2e/test/products/channels/autocomplete/channel_post_draft.e2e.ts index 79bda76e4..76968343b 100644 --- a/detox/e2e/test/products/channels/autocomplete/channel_post_draft.e2e.ts +++ b/detox/e2e/test/products/channels/autocomplete/channel_post_draft.e2e.ts @@ -20,6 +20,7 @@ import { LoginScreen, ServerScreen, } from '@support/ui/screen'; +import {timeouts, wait} from '@support/utils'; import {expect} from 'detox'; describe('Autocomplete - Channel Post Draft', () => { @@ -56,45 +57,46 @@ describe('Autocomplete - Channel Post Draft', () => { it('MM-T4882_1 - should render at-mention autocomplete in post input', async () => { // * Verify at-mention list is not displayed - await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).not.toExist(); // # Type in "@" to activate at-mention autocomplete await ChannelScreen.postInput.typeText('@'); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); }); it('MM-T4882_2 - should render channel mention autocomplete in post input', async () => { // * Verify channel mention list is not displayed - await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).not.toExist(); // # Type in "~" to activate channel mention autocomplete await ChannelScreen.postInput.typeText('~'); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); }); it('MM-T4882_3 - should render emoji suggestion autocomplete in post input', async () => { // * Verify emoji suggestion list is not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in ":" followed by 2 characters to activate emoji suggestion autocomplete await ChannelScreen.postInput.typeText(':sm'); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); }); it('MM-T4882_4 - should render slash suggestion autocomplete in post input', async () => { // * Verify slash suggestion list is not displayed - await expect(Autocomplete.flatSlashSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).not.toExist(); // # Type in "/" to activate slash suggestion autocomplete await ChannelScreen.postInput.typeText('/'); + await wait(timeouts.ONE_SEC); // * Verify slash suggestion list is displayed - await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).toExist(); }); }); diff --git a/detox/e2e/test/products/channels/autocomplete/create_channel.e2e.ts b/detox/e2e/test/products/channels/autocomplete/create_channel.e2e.ts index 1aff217e0..72ff06832 100644 --- a/detox/e2e/test/products/channels/autocomplete/create_channel.e2e.ts +++ b/detox/e2e/test/products/channels/autocomplete/create_channel.e2e.ts @@ -53,45 +53,45 @@ describe('Autocomplete - Create Channel', () => { it('MM-T4904_1 - should render at-mention autocomplete in header input', async () => { // * Verify at-mention list is not displayed - await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).not.toExist(); // # Type in "@" to activate at-mention autocomplete await CreateOrEditChannelScreen.headerInput.typeText('@'); // * Verify at-mention list is displayed - await waitFor(Autocomplete.sectionAtMentionList).toBeVisible().withTimeout(timeouts.ONE_SEC); + await waitFor(Autocomplete.sectionAtMentionList).toExist().withTimeout(timeouts.ONE_SEC); }); it('MM-T4904_2 - should render channel mention autocomplete in header input', async () => { // * Verify channel mention list is not displayed - await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).not.toExist(); // # Type in "~" to activate channel mention autocomplete await CreateOrEditChannelScreen.headerInput.typeText('~'); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); }); it('MM-T4904_3 - should render emoji suggestion autocomplete in header input', async () => { // * Verify emoji suggestion list is not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in ":" followed by 2 characters to activate emoji suggestion autocomplete await CreateOrEditChannelScreen.headerInput.typeText(':sm'); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); }); it('MM-T4904_4 - should not render slash suggestion autocomplete in header input', async () => { // * Verify slash suggestion list is not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in "/" to activate slash suggestion autocomplete await CreateOrEditChannelScreen.headerInput.typeText('/'); // * Verify slash suggestion list is still not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); }); }); diff --git a/detox/e2e/test/products/channels/autocomplete/edit_channel.e2e.ts b/detox/e2e/test/products/channels/autocomplete/edit_channel.e2e.ts index 39903807a..de8c0776b 100644 --- a/detox/e2e/test/products/channels/autocomplete/edit_channel.e2e.ts +++ b/detox/e2e/test/products/channels/autocomplete/edit_channel.e2e.ts @@ -60,45 +60,45 @@ describe('Autocomplete - Edit Channel', () => { it('MM-T4885_1 - should render at-mention autocomplete in header input', async () => { // * Verify at-mention list is not displayed - await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).not.toExist(); // # Type in "@" to activate at-mention autocomplete await CreateOrEditChannelScreen.headerInput.typeText('@'); // * Verify at-mention list is displayed - await waitFor(Autocomplete.sectionAtMentionList).toBeVisible().withTimeout(timeouts.ONE_SEC); + await waitFor(Autocomplete.sectionAtMentionList).toExist().withTimeout(timeouts.ONE_SEC); }); it('MM-T4885_2 - should render channel mention autocomplete in header input', async () => { // * Verify channel mention list is not displayed - await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).not.toExist(); // # Type in "~" to activate channel mention autocomplete await CreateOrEditChannelScreen.headerInput.typeText('~'); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); }); it('MM-T4885_3 - should render emoji suggestion autocomplete in header input', async () => { // * Verify emoji suggestion list is not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in ":" followed by 2 characters to activate emoji suggestion autocomplete await CreateOrEditChannelScreen.headerInput.typeText(':sm'); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); }); it('MM-T4885_4 - should not render slash suggestion autocomplete in header input', async () => { // * Verify slash suggestion list is not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in "/" to activate slash suggestion autocomplete await CreateOrEditChannelScreen.headerInput.typeText('/'); // * Verify slash suggestion list is still not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); }); }); diff --git a/detox/e2e/test/products/channels/autocomplete/edit_channel_header.e2e.ts b/detox/e2e/test/products/channels/autocomplete/edit_channel_header.e2e.ts index 59f1b37e1..c5cc19d7e 100644 --- a/detox/e2e/test/products/channels/autocomplete/edit_channel_header.e2e.ts +++ b/detox/e2e/test/products/channels/autocomplete/edit_channel_header.e2e.ts @@ -57,46 +57,46 @@ describe('Autocomplete - Edit Channel Header', () => { it('MM-T4884_1 - should render at-mention autocomplete in header input', async () => { // * Verify at-mention list is not displayed - await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).not.toExist(); // # Type in "@" to activate at-mention autocomplete await CreateOrEditChannelScreen.headerInput.typeText('@'); // * Verify at-mention list is displayed await waitFor(Autocomplete.sectionAtMentionList).toExist().withTimeout(timeouts.ONE_SEC); - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); }); it('MM-T4884_2 - should render channel mention autocomplete in header input', async () => { // * Verify channel mention list is not displayed - await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).not.toExist(); // # Type in "~" to activate channel mention autocomplete await CreateOrEditChannelScreen.headerInput.typeText('~'); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); }); it('MM-T4884_3 - should render emoji suggestion autocomplete in header input', async () => { // * Verify emoji suggestion list is not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in ":" followed by 2 characters to activate emoji suggestion autocomplete await CreateOrEditChannelScreen.headerInput.typeText(':sm'); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); }); it('MM-T4884_4 - should not render slash suggestion autocomplete in header input', async () => { // * Verify slash suggestion list is not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in "/" to activate slash suggestion autocomplete await CreateOrEditChannelScreen.headerInput.typeText('/'); // * Verify slash suggestion list is still not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); }); }); diff --git a/detox/e2e/test/products/channels/autocomplete/edit_post.e2e.ts b/detox/e2e/test/products/channels/autocomplete/edit_post.e2e.ts index fa81bd8f3..71d360553 100644 --- a/detox/e2e/test/products/channels/autocomplete/edit_post.e2e.ts +++ b/detox/e2e/test/products/channels/autocomplete/edit_post.e2e.ts @@ -45,6 +45,7 @@ describe('Autocomplete - Edit Post', () => { const message = `Messsage ${getRandomId()}`; await ChannelScreen.open(channelsCategory, channel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, channel.id); await ChannelScreen.openPostOptionsFor(post.id, message); await EditPostScreen.open(); @@ -56,53 +57,58 @@ describe('Autocomplete - Edit Post', () => { }); afterAll(async () => { - // # Log out - await EditPostScreen.close(); + // # Close edit post screen if still open, then log out + try { + await waitFor(EditPostScreen.editPostScreen).toBeVisible().withTimeout(1000); + await EditPostScreen.close(); + } catch { + // Edit post screen already closed, continue with logout + } await ChannelScreen.back(); await HomeScreen.logout(); }); it('MM-T4883_1 - should render at-mention autocomplete in message input', async () => { // * Verify at-mention list is not displayed - await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).not.toExist(); // # Type in "@" to activate at-mention autocomplete await EditPostScreen.messageInput.typeText('@'); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); }); it('MM-T4883_2 - should render channel mention autocomplete in message input', async () => { // * Verify channel mention list is not displayed - await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).not.toExist(); // # Type in "~" to activate channel mention autocomplete await EditPostScreen.messageInput.typeText('~'); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); }); it('MM-T4883_3 - should render emoji suggestion autocomplete in message input', async () => { // * Verify emoji suggestion list is not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in ":" followed by 2 characters to activate emoji suggestion autocomplete await EditPostScreen.messageInput.typeText(':sm'); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); }); it('MM-T4883_4 - should not render slash suggestion autocomplete in message input', async () => { // * Verify slash suggestion list is not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in "/" to activate slash suggestion autocomplete await EditPostScreen.messageInput.typeText('/'); // * Verify slash suggestion list is still not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); }); }); diff --git a/detox/e2e/test/products/channels/autocomplete/emoji_suggestion.e2e.ts b/detox/e2e/test/products/channels/autocomplete/emoji_suggestion.e2e.ts index 691871790..54275d7ab 100644 --- a/detox/e2e/test/products/channels/autocomplete/emoji_suggestion.e2e.ts +++ b/detox/e2e/test/products/channels/autocomplete/emoji_suggestion.e2e.ts @@ -67,13 +67,13 @@ describe('Autocomplete - Emoji Suggestion', () => { await Autocomplete.toBeVisible(); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); // # Type in 3rd to last characters of emoji name await ChannelScreen.postInput.typeText(emojiName3rdToLastChars); // * Verify emoji suggestion autocomplete contains associated emoji suggestion - await expect(emojiSuggestionAutocomplete).toBeVisible(); + await expect(emojiSuggestionAutocomplete).toExist(); }); it('MM-T4880_2 - should suggest emoji based on uppercase emoji name', async () => { @@ -82,13 +82,13 @@ describe('Autocomplete - Emoji Suggestion', () => { await Autocomplete.toBeVisible(); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); // # Type in uppercase 3rd to last characters of emoji name await ChannelScreen.postInput.typeText(emojiName3rdToLastChars.toUpperCase()); // * Verify emoji suggestion autocomplete contains associated emoji suggestion - await expect(emojiSuggestionAutocomplete).toBeVisible(); + await expect(emojiSuggestionAutocomplete).toExist(); }); it('MM-T4880_3 - should suggest emoji based on partial emoji name', async () => { @@ -97,13 +97,13 @@ describe('Autocomplete - Emoji Suggestion', () => { await Autocomplete.toBeVisible(); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); // # Type in partial emoji name await ChannelScreen.postInput.typeText(`${emojiName.substring(2, 4)}`); // * Verify emoji suggestion autocomplete contains associated emoji suggestion - await expect(emojiSuggestionAutocomplete).toBeVisible(); + await expect(emojiSuggestionAutocomplete).toExist(); }); it('MM-T4880_4 - should stop suggesting emoji after emoji name with trailing space', async () => { @@ -112,20 +112,20 @@ describe('Autocomplete - Emoji Suggestion', () => { await Autocomplete.toBeVisible(); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); // # Type in 3rd to last characters of emoji name await ChannelScreen.postInput.typeText(emojiName3rdToLastChars); // * Verify emoji suggestion autocomplete contains associated emoji suggestion - await expect(emojiSuggestionAutocomplete).toBeVisible(); + await expect(emojiSuggestionAutocomplete).toExist(); // # Type in trailing space await ChannelScreen.postInput.typeText(' '); await wait(timeouts.ONE_SEC); // * Verify emoji suggestion autocomplete does not contain associated emoji suggestion - await expect(emojiSuggestionAutocomplete).not.toBeVisible(); + await expect(emojiSuggestionAutocomplete).not.toExist(); }); it('MM-T4880_5 - should stop suggesting emoji when keyword is not associated with any emoji', async () => { @@ -134,34 +134,34 @@ describe('Autocomplete - Emoji Suggestion', () => { await Autocomplete.toBeVisible(); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); // # Type in keyword not associated with any emoji await ChannelScreen.postInput.typeText(getRandomId()); // * Verify emoji suggestion autocomplete does not contain associated emoji suggestion - await expect(emojiSuggestionAutocomplete).not.toBeVisible(); + await expect(emojiSuggestionAutocomplete).not.toExist(); }); it('MM-T4880_6 - should be able to select emoji suggestion multiple times', async () => { // # Type in ":" then first 2 characters of emoji name to activate emoji suggestion autocomplete - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars}`); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); // # Type in 3rd to last characters of emoji name and tap on emoji suggestion autocomplete await ChannelScreen.postInput.typeText(emojiName3rdToLastChars); await emojiSuggestionAutocomplete.tap(); // * Verify emoji suggestion list disappears - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in ":" then first 2 characters of emoji name again to re-activate emoji suggestion list await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars}`); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); }); }); diff --git a/detox/e2e/test/products/channels/autocomplete/slash_suggestion.e2e.ts b/detox/e2e/test/products/channels/autocomplete/slash_suggestion.e2e.ts index f765cfb77..4a4ac7ffd 100644 --- a/detox/e2e/test/products/channels/autocomplete/slash_suggestion.e2e.ts +++ b/detox/e2e/test/products/channels/autocomplete/slash_suggestion.e2e.ts @@ -65,13 +65,13 @@ describe('Autocomplete - Slash Suggestion', () => { await Autocomplete.toBeVisible(); // * Verify slash suggestion list is displayed - await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).toExist(); // # Type in slash command name await ChannelScreen.postInput.typeText(slashCommand); // * Verify slash suggestion autocomplete contains associated slash command suggestion - await expect(slashSuggestionAutocomplete).toBeVisible(); + await expect(slashSuggestionAutocomplete).toExist(); }); it('MM-T4881_2 - should suggest slash command based on partial slash command name', async () => { @@ -80,13 +80,13 @@ describe('Autocomplete - Slash Suggestion', () => { await Autocomplete.toBeVisible(); // * Verify slash suggestion list is displayed - await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).toExist(); // # Type in partial slash command name await ChannelScreen.postInput.typeText(`${slashCommand.substring(0, slashCommand.length - 2)}`); // * Verify slash suggestion autocomplete contains associated slash command suggestion - await expect(slashSuggestionAutocomplete).toBeVisible(); + await expect(slashSuggestionAutocomplete).toExist(); }); it('MM-T4881_3 - should stop suggesting slash command after uppercase slash command name', async () => { @@ -95,13 +95,13 @@ describe('Autocomplete - Slash Suggestion', () => { await Autocomplete.toBeVisible(); // * Verify slash suggestion list is displayed - await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).toExist(); // # Type in uppercase slash command name await ChannelScreen.postInput.typeText(slashCommand.toUpperCase()); // * Verify slash suggestion autocomplete does not contain associated slash command suggestion - await expect(slashSuggestionAutocomplete).not.toBeVisible(); + await expect(slashSuggestionAutocomplete).not.toExist(); }); it('MM-T4881_4 - should stop suggesting slash command after slash command name with trailing space', async () => { @@ -110,20 +110,20 @@ describe('Autocomplete - Slash Suggestion', () => { await Autocomplete.toBeVisible(); // * Verify slash suggestion list is displayed - await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).toExist(); // # Type in slash command name await ChannelScreen.postInput.typeText(slashCommand); // * Verify slash suggestion autocomplete contains associated slash command suggestion - await expect(slashSuggestionAutocomplete).toBeVisible(); + await expect(slashSuggestionAutocomplete).toExist(); // # Type in trailing space await ChannelScreen.postInput.typeText(' '); await wait(timeouts.ONE_SEC); // * Verify slash suggestion autocomplete does not contain associated slash command suggestion - await expect(slashSuggestionAutocomplete).not.toBeVisible(); + await expect(slashSuggestionAutocomplete).not.toExist(); }); it('MM-T4881_5 - should stop suggesting slash command when keyword is not associated with any slash command', async () => { @@ -132,22 +132,22 @@ describe('Autocomplete - Slash Suggestion', () => { await Autocomplete.toBeVisible(); // * Verify slash suggestion list is displayed - await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).toExist(); // # Type in keyword not associated with any slash command await ChannelScreen.postInput.typeText(getRandomId()); // * Verify slash suggestion autocomplete does not contain associated slash command suggestion - await expect(slashSuggestionAutocomplete).not.toBeVisible(); + await expect(slashSuggestionAutocomplete).not.toExist(); }); it('MM-T4881_6 - should not be able to select slash suggestion multiple times', async () => { // # Type in "/" to activate slash suggestion autocomplete - await expect(Autocomplete.flatSlashSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).not.toExist(); await ChannelScreen.postInput.typeText('/'); // * Verify slash suggestion list is displayed - await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).toExist(); // # Type in slash command name and tap on slash suggestion autocomplete await ChannelScreen.postInput.typeText(slashCommand); @@ -160,6 +160,6 @@ describe('Autocomplete - Slash Suggestion', () => { await ChannelScreen.postInput.typeText('/'); // * Verify slash suggestion list is not displayed - await expect(Autocomplete.flatSlashSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).not.toExist(); }); }); diff --git a/detox/e2e/test/products/channels/autocomplete/thread_post_draft.e2e.ts b/detox/e2e/test/products/channels/autocomplete/thread_post_draft.e2e.ts index 1341813bf..6bb0f36aa 100644 --- a/detox/e2e/test/products/channels/autocomplete/thread_post_draft.e2e.ts +++ b/detox/e2e/test/products/channels/autocomplete/thread_post_draft.e2e.ts @@ -7,7 +7,10 @@ // - Use element testID when selecting an element. Create one if none. // ******************************************************************* -import {Setup} from '@support/server_api'; +import { + Post, + Setup, +} from '@support/server_api'; import { serverOneUrl, siteOneUrl, @@ -21,15 +24,17 @@ import { ServerScreen, ThreadScreen, } from '@support/ui/screen'; -import {getRandomId} from '@support/utils'; +import {getRandomId, timeouts, wait} from '@support/utils'; import {expect} from 'detox'; describe('Autocomplete - Thread Post Draft', () => { const serverOneDisplayName = 'Server 1'; const channelsCategory = 'channels'; + let testChannel: any; beforeAll(async () => { const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; // # Log in to server await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); @@ -38,11 +43,13 @@ describe('Autocomplete - Thread Post Draft', () => { // * Verify on channel list screen await ChannelListScreen.toBeVisible(); - // # Open a channel screen, post a message, and tap on post to open reply thread + // # Open a channel screen, post a message, and open reply thread const message = `Message ${getRandomId()}`; - await ChannelScreen.open(channelsCategory, channel.name); + await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); - await element(by.text(message)).tap(); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await wait(timeouts.FOUR_SEC); + await ChannelScreen.openReplyThreadFor(post.id, message); }); beforeEach(async () => { @@ -54,6 +61,10 @@ describe('Autocomplete - Thread Post Draft', () => { }); afterAll(async () => { + // # Tap on thread screen to dismiss keyboard + await ThreadScreen.threadScreen.tap(); + await wait(timeouts.ONE_SEC); + // # Log out await ThreadScreen.back(); await ChannelScreen.back(); @@ -62,45 +73,49 @@ describe('Autocomplete - Thread Post Draft', () => { it('MM-T4905_1 - should render at-mention autocomplete in post input', async () => { // * Verify at-mention list is not displayed - await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).not.toExist(); // # Type in "@" to activate at-mention autocomplete await ThreadScreen.postInput.typeText('@'); + await wait(timeouts.TWO_SEC); // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + await expect(Autocomplete.sectionAtMentionList).toExist(); }); it('MM-T4905_2 - should render channel mention autocomplete in post input', async () => { // * Verify channel mention list is not displayed - await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).not.toExist(); // # Type in "~" to activate channel mention autocomplete await ThreadScreen.postInput.typeText('~'); + await wait(timeouts.TWO_SEC); // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + await expect(Autocomplete.sectionChannelMentionList).toExist(); }); it('MM-T4905_3 - should render emoji suggestion autocomplete in post input', async () => { // * Verify emoji suggestion list is not displayed - await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).not.toExist(); // # Type in ":" followed by 2 characters to activate emoji suggestion autocomplete await ThreadScreen.postInput.typeText(':sm'); + await wait(timeouts.TWO_SEC); // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + await expect(Autocomplete.flatEmojiSuggestionList).toExist(); }); it('MM-T4905_4 - should render slash suggestion autocomplete in post input', async () => { // * Verify slash suggestion list is not displayed - await expect(Autocomplete.flatSlashSuggestionList).not.toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).not.toExist(); // # Type in "/" to activate slash suggestion autocomplete await ThreadScreen.postInput.typeText('/'); + await wait(timeouts.TWO_SEC); // * Verify slash suggestion list is displayed - await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + await expect(Autocomplete.flatSlashSuggestionList).toExist(); }); }); diff --git a/detox/e2e/test/products/channels/channels/channel_list.e2e.ts b/detox/e2e/test/products/channels/channels/channel_list.e2e.ts index a684a73f6..e491f3c42 100644 --- a/detox/e2e/test/products/channels/channels/channel_list.e2e.ts +++ b/detox/e2e/test/products/channels/channels/channel_list.e2e.ts @@ -32,10 +32,10 @@ import {expect} from 'detox'; describe('Channels - Channel List', () => { const serverOneDisplayName = 'Server 1'; - const channelsCategory = 'channels'; const directMessagesCategory = 'direct_messages'; const offTopicChannelName = 'off-topic'; const townSquareChannelName = 'town-square'; + const channelsCategory = 'channels'; let testChannel: any; let testTeam: any; let testUser: any; diff --git a/detox/e2e/test/products/channels/channels/channel_post_list.e2e.ts b/detox/e2e/test/products/channels/channels/channel_post_list.e2e.ts index daf46bffc..e270a0479 100644 --- a/detox/e2e/test/products/channels/channels/channel_post_list.e2e.ts +++ b/detox/e2e/test/products/channels/channels/channel_post_list.e2e.ts @@ -23,8 +23,8 @@ import { PostOptionsScreen, ServerScreen, } from '@support/ui/screen'; -import {getRandomId, isAndroid} from '@support/utils'; -import {expect} from 'detox'; +import {getRandomId, timeouts} from '@support/utils'; +import {expect, waitFor} from 'detox'; describe('Channels - Channel Post List', () => { const serverOneDisplayName = 'Server 1'; @@ -52,26 +52,37 @@ describe('Channels - Channel Post List', () => { it('MM-T4773_1 - should match elements on channel screen', async () => { // # Open a channel screen await ChannelScreen.open('channels', testChannel.name); - if (isAndroid()) { - await ChannelScreen.back(); - await ChannelScreen.open('channels', testChannel.name); - } // * Verify basic elements on channel screen - await expect(ChannelScreen.backButton).toBeVisible(); + await expect(ChannelScreen.backButton).toExist(); await expect(ChannelScreen.headerTitle).toHaveText(testChannel.display_name); + await waitFor(ChannelScreen.introDisplayName).toExist().withTimeout(timeouts.TWO_SEC); await expect(ChannelScreen.introDisplayName).toHaveText(testChannel.display_name); - await expect(ChannelScreen.introSetHeaderAction).toBeVisible(); - await expect(ChannelScreen.introChannelInfoAction).toBeVisible(); - await expect(ChannelScreen.postList.getFlatList()).toBeVisible(); - await expect(ChannelScreen.postDraft).toBeVisible(); - await expect(ChannelScreen.postInput).toBeVisible(); - await expect(ChannelScreen.atInputQuickAction).toBeVisible(); - await expect(ChannelScreen.slashInputQuickAction).toBeVisible(); - await expect(ChannelScreen.fileQuickAction).toBeVisible(); - await expect(ChannelScreen.imageQuickAction).toBeVisible(); - await expect(ChannelScreen.cameraQuickAction).toBeVisible(); - await expect(ChannelScreen.sendButtonDisabled).toBeVisible(); + await expect(ChannelScreen.introSetHeaderAction).toExist(); + await expect(ChannelScreen.introChannelInfoAction).toExist(); + await expect(ChannelScreen.postList.getFlatList()).toExist(); + await expect(ChannelScreen.postDraft).toExist(); + await expect(ChannelScreen.postInput).toExist(); + await expect(ChannelScreen.atInputQuickAction).toExist(); + await expect(ChannelScreen.slashInputQuickAction).toExist(); + + // # Tap on attachment action to open file attachment options + const attachmentAction = element(by.id('channel.post_draft.quick_actions.attachment_action')); + await expect(attachmentAction).toExist(); + await attachmentAction.tap(); + + // * Verify file attachment options screen and its options + const attachmentScreen = element(by.id('channel.post_draft.quick_actions.attachment_action.screen')); + await expect(attachmentScreen).toExist(); + await expect(element(by.id('file_attachment.photo_library'))).toExist(); + await expect(element(by.id('file_attachment.take_photo'))).toExist(); + await expect(element(by.id('file_attachment.take_video'))).toExist(); + await expect(element(by.id('file_attachment.attach_file'))).toExist(); + + // # Close attachment options by swiping down (works on both iOS and Android) + await attachmentScreen.swipe('down', 'fast', 0.5); + + await expect(ChannelScreen.sendButtonDisabled).toExist(); // # Go back to channel list screen await ChannelScreen.back(); @@ -81,16 +92,13 @@ describe('Channels - Channel Post List', () => { // # Open a channel screen and post a message const message = `Message ${getRandomId()}`; await ChannelScreen.open('channels', testChannel.name); - if (isAndroid()) { - await ChannelScreen.back(); - await ChannelScreen.open('channels', testChannel.name); - } await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); // * Verify message is added to post list const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(postListPostItem).toBeVisible(); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.TEN_SEC); // # Open post options for the message that was just posted, tap delete option and confirm await ChannelScreen.openPostOptionsFor(post.id, message); diff --git a/detox/e2e/test/products/channels/channels/create_channel_and_edit_channel_header.e2e.ts b/detox/e2e/test/products/channels/channels/create_channel_and_edit_channel_header.e2e.ts index 331489971..f42945b3e 100644 --- a/detox/e2e/test/products/channels/channels/create_channel_and_edit_channel_header.e2e.ts +++ b/detox/e2e/test/products/channels/channels/create_channel_and_edit_channel_header.e2e.ts @@ -20,7 +20,7 @@ import { LoginScreen, ServerScreen, } from '@support/ui/screen'; -import {getRandomId, isIos, timeouts} from '@support/utils'; +import {getRandomId, isIos, timeouts, wait} from '@support/utils'; import {expect} from 'detox'; describe('Channels - Create Channel and Edit Channel Header', () => { @@ -75,7 +75,7 @@ describe('Channels - Create Channel and Edit Channel Header', () => { await CreateOrEditChannelScreen.purposeInput.replaceText(purpose); await CreateOrEditChannelScreen.headerInput.replaceText(header); await CreateOrEditChannelScreen.createButton.tap(); - await waitFor(ChannelScreen.scheduledPostTooltipCloseButton).toBeVisible().withTimeout(timeouts.FOUR_SEC); + await waitFor(ChannelScreen.scheduledPostTooltipCloseButton).toExist().withTimeout(timeouts.FOUR_SEC); await ChannelScreen.scheduledPostTooltipCloseButton.tap(); // * Verify on newly created public channel @@ -96,6 +96,7 @@ describe('Channels - Create Channel and Edit Channel Header', () => { // # Edit the channel header, save, and re-open edit channel header screen await CreateOrEditChannelScreen.headerInput.replaceText(`${header} edit`); await CreateOrEditChannelScreen.saveButton.tap(); + await wait(timeouts.TWO_SEC); await CreateOrEditChannelScreen.openEditChannelHeader(); // * Verify channel header has new value @@ -141,6 +142,7 @@ describe('Channels - Create Channel and Edit Channel Header', () => { // # Edit the channel header, save, and re-open edit channel header screen await CreateOrEditChannelScreen.headerInput.replaceText(`${header} edit`); await CreateOrEditChannelScreen.saveButton.tap(); + await wait(timeouts.TWO_SEC); await CreateOrEditChannelScreen.openEditChannelHeader(); // * Verify channel header has new value diff --git a/detox/e2e/test/products/channels/channels/favorite_and_unfavorite_channel.e2e.ts b/detox/e2e/test/products/channels/channels/favorite_and_unfavorite_channel.e2e.ts index b24cbe866..386c1432c 100644 --- a/detox/e2e/test/products/channels/channels/favorite_and_unfavorite_channel.e2e.ts +++ b/detox/e2e/test/products/channels/channels/favorite_and_unfavorite_channel.e2e.ts @@ -132,6 +132,7 @@ describe('Channels - Favorite and Unfavorite Channel', () => { await ChannelScreen.postMessage('test'); await ChannelScreen.back(); await ChannelListScreen.getChannelItemDisplayName(directMessagesCategory, directMessageChannel.name).tap(); + await waitFor(ChannelScreen.introFavoriteAction).toBeVisible().withTimeout(timeouts.TEN_SEC); await ChannelScreen.introFavoriteAction.tap(); await ChannelScreen.back(); diff --git a/detox/e2e/test/products/channels/channels/find_channels.e2e.ts b/detox/e2e/test/products/channels/channels/find_channels.e2e.ts index 3b93bd893..147d8a2f6 100644 --- a/detox/e2e/test/products/channels/channels/find_channels.e2e.ts +++ b/detox/e2e/test/products/channels/channels/find_channels.e2e.ts @@ -144,17 +144,24 @@ describe('Channels - Find Channels', () => { await FindChannelsScreen.open(); await FindChannelsScreen.searchInput.typeText(archivedChannel.display_name); - // * Verify search returns the target archived channel item - await wait(timeouts.FOUR_SEC); - await FindChannelsScreen.getFilteredChannelItem(archivedChannel.name).tap(); + // * Verify search returns the target archived channel item and tap on it + // Archived channels may appear as regular channel items in search results await wait(timeouts.FOUR_SEC); + try { + await waitFor(FindChannelsScreen.getFilteredArchivedChannelItem(archivedChannel.name)).toExist().withTimeout(timeouts.TWO_SEC); + await FindChannelsScreen.getFilteredArchivedChannelItem(archivedChannel.name).tap(); + } catch (error) { + // If not found with archived prefix, try regular channel item + await waitFor(FindChannelsScreen.getFilteredChannelItem(archivedChannel.name)).toExist().withTimeout(timeouts.TWO_SEC); + await FindChannelsScreen.getFilteredChannelItem(archivedChannel.name).tap(); + } + await wait(timeouts.TWO_SEC); // * Verify on archievd channel name await verifyDetailsOnChannelScreen(archivedChannel.display_name); - // # Go back to channel list screen by closing archived channel - await expect(ChannelScreen.archievedCloseChannelButton).toBeVisible(); - await ChannelScreen.archievedCloseChannelButton.tap(); + // # Go back to channel list screen + await ChannelScreen.back(); }); it('MM-T4907_6 - should be able to find a joined private channel and not find an unjoined private channel', async () => { @@ -182,12 +189,15 @@ describe('Channels - Find Channels', () => { async function verifyDetailsOnChannelScreen(display_name: string) { await wait(timeouts.TWO_SEC); + + // Try to close scheduled post tooltip if it exists try { + await waitFor(ChannelScreen.scheduledPostTooltipCloseButton).toBeVisible().withTimeout(timeouts.ONE_SEC); await ChannelScreen.scheduledPostTooltipCloseButton.tap(); } catch (error) { - // eslint-disable-next-line no-console - console.log('Element not visible, skipping click'); + // Tooltip not visible, continue } + await ChannelScreen.toBeVisible(); await expect(ChannelScreen.headerTitle).toHaveText(display_name); await expect(ChannelScreen.introDisplayName).toHaveText(display_name); diff --git a/detox/e2e/test/products/channels/messaging/channel_link.e2e.ts b/detox/e2e/test/products/channels/messaging/channel_link.e2e.ts index f718f73ff..e15e17a67 100644 --- a/detox/e2e/test/products/channels/messaging/channel_link.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/channel_link.e2e.ts @@ -23,8 +23,9 @@ import { LoginScreen, ServerScreen, } from '@support/ui/screen'; +import channelScreen from '@support/ui/screen/channel'; import {timeouts, wait} from '@support/utils'; -import {expect} from 'detox'; +import {expect, waitFor} from 'detox'; describe('Messaging - Channel Link', () => { const serverOneDisplayName = 'Server 1'; @@ -61,10 +62,11 @@ describe('Messaging - Channel Link', () => { await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id); const channelLink = `${serverOneUrl}/${testTeam.name}/channels/${targetChannel.name}`; await ChannelScreen.postMessage(channelLink); + await channelScreen.dismissKeyboard(); // # Tap on channel link - await element(by.text(channelLink)).tap({x: 5, y: 10}); - await wait(timeouts.ONE_SEC); + await element(by.text(channelLink)).tap(); + await wait(timeouts.FOUR_SEC); // * Verify redirected to target channel await expect(ChannelScreen.headerTitle).toHaveText(targetChannel.display_name); @@ -80,8 +82,15 @@ describe('Messaging - Channel Link', () => { await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id); const channelLink = `${serverOneUrl}/${testTeam.name}/channels/${targetChannel.name}`; await ChannelScreen.postMessage(channelLink); + await channelScreen.dismissKeyboard(); + + // # Wait for keyboard to dismiss and message to be visible + await wait(timeouts.TWO_SEC); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + await postListPostItem.tap({x: 1, y: 1}); await element(by.text(channelLink)).tap({x: 5, y: 10}); await wait(timeouts.ONE_SEC); diff --git a/detox/e2e/test/products/channels/messaging/emojis_and_reactions.e2e.ts b/detox/e2e/test/products/channels/messaging/emojis_and_reactions.e2e.ts index f08f0d836..8e919c695 100644 --- a/detox/e2e/test/products/channels/messaging/emojis_and_reactions.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/emojis_and_reactions.e2e.ts @@ -26,8 +26,8 @@ import { ServerScreen, UserProfileScreen, } from '@support/ui/screen'; -import {getRandomId} from '@support/utils'; -import {expect} from 'detox'; +import {getRandomId, timeouts} from '@support/utils'; +import {expect, waitFor} from 'detox'; describe('Messaging - Emojis and Reactions', () => { const serverOneDisplayName = 'Server 1'; @@ -60,6 +60,7 @@ describe('Messaging - Emojis and Reactions', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openPostOptionsFor(post.id, message); @@ -74,10 +75,13 @@ describe('Messaging - Emojis and Reactions', () => { // # Open emoji picker screen and add a new reaction await EmojiPickerScreen.open(true); await EmojiPickerScreen.searchInput.replaceText('clown_face'); + await EmojiPickerScreen.searchInput.tapReturnKey(); await element(by.text('🤡')).tap(); // * Verify new reaction is added to the message - await expect(element(by.text('🤡').withAncestor(by.id(`channel.post_list.post.${post.id}`)))).toBeVisible(); + const reactionElement = element(by.text('🤡').withAncestor(by.id(`channel.post_list.post.${post.id}`))); + await waitFor(reactionElement).toExist().withTimeout(timeouts.TWO_SEC); + await expect(reactionElement).toExist(); // # Open post options for message await ChannelScreen.openPostOptionsFor(post.id, message); @@ -101,15 +105,18 @@ describe('Messaging - Emojis and Reactions', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openPostOptionsFor(post.id, message); await EmojiPickerScreen.open(); await EmojiPickerScreen.searchInput.replaceText('fire'); + await EmojiPickerScreen.searchInput.tapReturnKey(); await element(by.text('🔥')).tap(); // * Verify reaction is added to the message const reaction = element(by.text('🔥').withAncestor(by.id(`channel.post_list.post.${post.id}`))); - await expect(reaction).toBeVisible(); + await waitFor(reaction).toExist().withTimeout(timeouts.TWO_SEC); + await expect(reaction).toExist(); // # Long press on the reaction await reaction.longPress(); @@ -133,6 +140,7 @@ describe('Messaging - Emojis and Reactions', () => { const message = 'brown fox :fox_face: lazy dog :dog:'; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); // * Verify message is posted with emojis const resolvedMessage = 'brown fox 🦊 lazy dog 🐶'; @@ -166,11 +174,13 @@ describe('Messaging - Emojis and Reactions', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const searchTerm = 'blahblahblahblah'; await ChannelScreen.openPostOptionsFor(post.id, message); await EmojiPickerScreen.open(); await EmojiPickerScreen.searchInput.replaceText(searchTerm); + await EmojiPickerScreen.searchInput.tapReturnKey(); // * Verify empty search state for emoji picker await expect(element(by.text(`No matches found for “${searchTerm}”`))).toBeVisible(); diff --git a/detox/e2e/test/products/channels/messaging/follow_and_unfollow_messsage.e2e.ts b/detox/e2e/test/products/channels/messaging/follow_and_unfollow_messsage.e2e.ts index be314d622..869f6a1a0 100644 --- a/detox/e2e/test/products/channels/messaging/follow_and_unfollow_messsage.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/follow_and_unfollow_messsage.e2e.ts @@ -9,6 +9,7 @@ import { Post, + Preference, Setup, } from '@support/server_api'; import { @@ -23,8 +24,8 @@ import { PostOptionsScreen, ServerScreen, } from '@support/ui/screen'; -import {getRandomId, timeouts, wait} from '@support/utils'; -import {expect} from 'detox'; +import {getRandomId, timeouts, wait, waitForElementToBeVisible} from '@support/utils'; +import {waitFor} from 'detox'; describe('Messaging - Follow and Unfollow Message', () => { const serverOneDisplayName = 'Server 1'; @@ -35,6 +36,16 @@ describe('Messaging - Follow and Unfollow Message', () => { const {channel, user} = await Setup.apiInit(siteOneUrl); testChannel = channel; + // # Enable CRT (Collapsed Reply Threads) for the user + await Preference.apiSaveUserPreferences(siteOneUrl, user.id, [ + { + user_id: user.id, + category: 'display_settings', + name: 'collapsed_reply_threads', + value: 'on', + }, + ]); + // # Log in to server await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); await LoginScreen.login(user); @@ -55,19 +66,27 @@ describe('Messaging - Follow and Unfollow Message', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); + + // # Wait for keyboard to dismiss and message to be visible + await wait(timeouts.ONE_SEC); // * Verify message is posted const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(postListPostItem).toBeVisible(); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + + // # Wait for thread to be created in DB (CRT creates threads asynchronously) + await wait(timeouts.TWO_SEC); // # Open post options for message and tap on follow message option - await postListPostItem.longPress(timeouts.ONE_SEC); + await postListPostItem.longPress(timeouts.FOUR_SEC); + await waitFor(PostOptionsScreen.followThreadOption).toBeVisible().withTimeout(timeouts.FOUR_SEC); await PostOptionsScreen.followThreadOption.tap(); // * Verify message is followed by user via post footer const {postListPostItemFooterFollowingButton} = ChannelScreen.getPostListPostItem(post.id, message); - await waitFor(postListPostItemFooterFollowingButton).toBeVisible().withTimeout(timeouts.TEN_SEC); + await waitForElementToBeVisible(postListPostItemFooterFollowingButton); // # Open post options for message and tap on unfollow message option await ChannelScreen.openPostOptionsFor(post.id, message); @@ -87,8 +106,17 @@ describe('Messaging - Follow and Unfollow Message', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); + + // # Wait for keyboard to dismiss and message to be visible + await wait(timeouts.ONE_SEC); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await waitForElementToBeVisible(postListPostItem); + await ChannelScreen.openPostOptionsFor(post.id, message); + await waitFor(PostOptionsScreen.followThreadOption).toBeVisible().withTimeout(timeouts.FOUR_SEC); await PostOptionsScreen.followThreadOption.tap(); // * Verify message is followed by user via post footer diff --git a/detox/e2e/test/products/channels/messaging/markdown_image.e2e.ts b/detox/e2e/test/products/channels/messaging/markdown_image.e2e.ts index c9917a86a..a95c80ef5 100644 --- a/detox/e2e/test/products/channels/messaging/markdown_image.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/markdown_image.e2e.ts @@ -22,7 +22,7 @@ import { LoginScreen, ServerScreen, } from '@support/ui/screen'; -import {expect} from 'detox'; +import {timeouts} from '@support/utils'; describe('Messaging - Markdown Image', () => { const serverOneDisplayName = 'Server 1'; @@ -56,8 +56,13 @@ describe('Messaging - Markdown Image', () => { // * Verify markdown image is displayed const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); - const {postListPostItemImage} = ChannelScreen.getPostListPostItem(post.id); - await expect(postListPostItemImage).toBeVisible(); + const {postListPostItem, postListPostItemImage} = ChannelScreen.getPostListPostItem(post.id); + + // Scroll to the post first to ensure it's in view + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + + // Wait for image to load and have dimensions (not 0x0) + await waitFor(postListPostItemImage).toExist().withTimeout(timeouts.TEN_SEC); // # Go back to channel list screen await ChannelScreen.back(); @@ -71,8 +76,13 @@ describe('Messaging - Markdown Image', () => { // * Verify markdown image with link is displayed const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); - const {postListPostItemImage} = ChannelScreen.getPostListPostItem(post.id); - await expect(postListPostItemImage).toBeVisible(); + const {postListPostItem, postListPostItemImage} = ChannelScreen.getPostListPostItem(post.id); + + // Scroll to the post first to ensure it's in view + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + + // Wait for image to load and have dimensions (not 0x0) + await waitFor(postListPostItemImage).toExist().withTimeout(timeouts.TEN_SEC); // # Go back to channel list screen await ChannelScreen.back(); diff --git a/detox/e2e/test/products/channels/messaging/message_delete.e2e.ts b/detox/e2e/test/products/channels/messaging/message_delete.e2e.ts index ce46f44f8..ea4eb01bc 100644 --- a/detox/e2e/test/products/channels/messaging/message_delete.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/message_delete.e2e.ts @@ -24,8 +24,8 @@ import { ServerScreen, ThreadScreen, } from '@support/ui/screen'; -import {getRandomId} from '@support/utils'; -import {expect} from 'detox'; +import {getRandomId, timeouts, wait} from '@support/utils'; +import {expect, waitFor} from 'detox'; describe('Messaging - Message Delete', () => { const serverOneDisplayName = 'Server 1'; @@ -56,18 +56,18 @@ describe('Messaging - Message Delete', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); // * Verify message is added to post list const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(postListPostItem).toBeVisible(); // # Open post options for the message that was just posted, tap delete option and confirm await ChannelScreen.openPostOptionsFor(post.id, message); await PostOptionsScreen.deletePost({confirm: true}); // * Verify post message is deleted - await expect(postListPostItem).not.toExist(); + await waitFor(postListPostItem).not.toExist().withTimeout(timeouts.TEN_SEC); // # Go back to channel list screen await ChannelScreen.back(); @@ -78,11 +78,11 @@ describe('Messaging - Message Delete', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); // * Verify message is added to post list const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(postListPostItem).toBeVisible(); // # Open post options for the message that was just posted, tap delete option and cancel await ChannelScreen.openPostOptionsFor(post.id, message); @@ -100,8 +100,12 @@ describe('Messaging - Message Delete', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); + const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem: parentPostListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, message); + await waitFor(parentPostListPostItem).toExist().withTimeout(timeouts.FOUR_SEC); + await parentPostListPostItem.tap(); // * Verify on thread screen @@ -110,13 +114,18 @@ describe('Messaging - Message Delete', () => { // # Post a reply, open post options for the reply message, tap delete option and confirm const replyMessage = `${message} reply`; await ThreadScreen.postMessage(replyMessage); + await ChannelScreen.dismissKeyboard(); + const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); - const {postListPostItem: replyPostListPostItem} = ChannelScreen.getPostListPostItem(replyPost.id, replyMessage); + const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage); + await waitFor(replyPostListPostItem).toExist().withTimeout(timeouts.FOUR_SEC); + await ThreadScreen.openPostOptionsFor(replyPost.id, replyMessage); await PostOptionsScreen.deletePost({confirm: true}); + await wait(timeouts.TWO_SEC); // * Verify reply message is deleted - await expect(replyPostListPostItem).not.toExist(); + await waitFor(replyPostListPostItem).not.toExist().withTimeout(timeouts.TEN_SEC); // # Go back to channel list screen await ThreadScreen.back(); diff --git a/detox/e2e/test/products/channels/messaging/message_draft.e2e.ts b/detox/e2e/test/products/channels/messaging/message_draft.e2e.ts index deb0f0d0a..017659f14 100644 --- a/detox/e2e/test/products/channels/messaging/message_draft.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/message_draft.e2e.ts @@ -29,8 +29,8 @@ import {expect} from 'detox'; describe('Messaging - Message Draft', () => { const serverOneDisplayName = 'Server 1'; - const channelsCategory = 'channels'; const offTopicChannelName = 'off-topic'; + const channelsCategory = 'channels'; let testChannel: any; beforeAll(async () => { diff --git a/detox/e2e/test/products/channels/messaging/message_edit.e2e.ts b/detox/e2e/test/products/channels/messaging/message_edit.e2e.ts index 5703e682d..86165770d 100644 --- a/detox/e2e/test/products/channels/messaging/message_edit.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/message_edit.e2e.ts @@ -25,8 +25,8 @@ import { ServerScreen, ThreadScreen, } from '@support/ui/screen'; -import {getRandomId} from '@support/utils'; -import {expect} from 'detox'; +import {getRandomId, timeouts} from '@support/utils'; +import {expect, waitFor} from 'detox'; describe('Messaging - Message Edit', () => { const serverOneDisplayName = 'Server 1'; @@ -57,11 +57,15 @@ describe('Messaging - Message Edit', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); // * Verify message is added to post list const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem: originalPostListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(originalPostListPostItem).toBeVisible(); + await waitFor(originalPostListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + + // # Scroll to post to dismiss keyboard and ensure post is visible + await originalPostListPostItem.scrollTo('top'); // # Open post options for the message that was just posted and tap edit option await ChannelScreen.openPostOptionsFor(post.id, message); @@ -91,11 +95,15 @@ describe('Messaging - Message Edit', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); // * Verify message is added to post list const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(postListPostItem).toBeVisible(); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + + // # Scroll to post to dismiss keyboard and ensure post is visible + await postListPostItem.scrollTo('top'); // # Open post options for the message that was just posted and tap edit option await ChannelScreen.openPostOptionsFor(post.id, message); @@ -121,8 +129,12 @@ describe('Messaging - Message Edit', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem: parentPostListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, message); + + // # Scroll to post to dismiss keyboard before tapping + await parentPostListPostItem.scrollTo('top'); await parentPostListPostItem.tap(); // * Verify on thread screen @@ -131,7 +143,12 @@ describe('Messaging - Message Edit', () => { // # Post a reply, open post options for the reply message and tap edit option const replyMessage = `${message} reply`; await ThreadScreen.postMessage(replyMessage); + await ChannelScreen.dismissKeyboard(); const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage); + + // # Scroll to reply post to dismiss keyboard before long press + await replyPostListPostItem.scrollTo('top'); await ThreadScreen.openPostOptionsFor(replyPost.id, replyMessage); await PostOptionsScreen.editPostOption.tap(); diff --git a/detox/e2e/test/products/channels/messaging/message_permalink.e2e.ts b/detox/e2e/test/products/channels/messaging/message_permalink.e2e.ts index 215d671ee..6d5350772 100644 --- a/detox/e2e/test/products/channels/messaging/message_permalink.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/message_permalink.e2e.ts @@ -5,6 +5,7 @@ import { Channel, Post, Setup, + Team, User, } from '@support/server_api'; import { @@ -39,7 +40,7 @@ describe('Messaging - Message Permalink Preview', () => { await postItem.longPress(); await PostOptionsScreen.toBeVisible(); await PostOptionsScreen.copyLinkOption.tap(); - await wait(timeouts.ONE_SEC); + await wait(timeouts.FOUR_SEC); await expect(PostOptionsScreen.postOptionsScreen).not.toBeVisible(); }; @@ -64,6 +65,7 @@ describe('Messaging - Message Permalink Preview', () => { testUser = user; ({user: testOtherUser} = await User.apiCreateUser(siteOneUrl)); + await Team.apiAddUserToTeam(siteOneUrl, testOtherUser.id, testTeam.id); await Channel.apiAddUserToChannel(siteOneUrl, testOtherUser.id, testChannel.id); await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); @@ -86,6 +88,7 @@ describe('Messaging - Message Permalink Preview', () => { userId: testOtherUser.id, }); + await wait(timeouts.FOUR_SEC); await ChannelScreen.open(channelsCategory, testChannel.name); const {postListPostItem} = ChannelScreen.getPostListPostItem(targetPost.post.id, targetMessage); @@ -112,7 +115,7 @@ describe('Messaging - Message Permalink Preview', () => { const {channel: otherChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id}); await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, otherChannel.id); - + await wait(timeouts.FOUR_SEC); await ChannelScreen.open(channelsCategory, testChannel.name); const {postListPostItem} = ChannelScreen.getPostListPostItem(targetPost.post.id, targetMessage); await copyLinkFromPost(postListPostItem); @@ -146,7 +149,7 @@ describe('Messaging - Message Permalink Preview', () => { message: targetMessage, userId: testOtherUser.id, }); - + await wait(timeouts.FOUR_SEC); await ChannelScreen.open(channelsCategory, targetChannel.name); const {postListPostItem} = ChannelScreen.getPostListPostItem(targetPost.post.id, targetMessage); await copyLinkFromPost(postListPostItem); diff --git a/detox/e2e/test/products/channels/messaging/message_post.e2e.ts b/detox/e2e/test/products/channels/messaging/message_post.e2e.ts index 6caf669b2..e157e79e3 100644 --- a/detox/e2e/test/products/channels/messaging/message_post.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/message_post.e2e.ts @@ -22,7 +22,7 @@ import { LoginScreen, ServerScreen, } from '@support/ui/screen'; -import {getRandomId, isAndroid, timeouts, wait} from '@support/utils'; +import {getRandomId, timeouts, wait} from '@support/utils'; import {expect} from 'detox'; describe('Messaging - Message Post', () => { @@ -80,23 +80,19 @@ describe('Messaging - Message Post', () => { it('MM-T4782_2 - should be able to post a long message', async () => { // # Open a channel screen, post a long message, and a short message after - const longMessage = 'The quick brown fox jumps over the lazy dog.'.repeat(20); + const longMessage = 'The quick brown fox jumps over the lazy dog.'.repeat(40); await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(longMessage); const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.postMessage('short message'); - // * Verify long message is posted and displays show more button (chevron down button) - if (isAndroid()) { - await device.pressBack(); - await wait(timeouts.ONE_SEC); - } const {postListPostItem, postListPostItemShowLessButton, postListPostItemShowMoreButton} = ChannelScreen.getPostListPostItem(post.id, longMessage); - await expect(postListPostItem).toBeVisible(); - await expect(postListPostItemShowMoreButton).toBeVisible(); + await expect(postListPostItem).toExist(); + await expect(postListPostItemShowMoreButton).toExist(); // # Tap on show more button on long message post await postListPostItemShowMoreButton.tap(); + await wait(timeouts.TWO_SEC); // * Verify long message post displays show less button (chevron up button) await expect(postListPostItemShowLessButton).toBeVisible(); diff --git a/detox/e2e/test/products/channels/messaging/message_reply.e2e.ts b/detox/e2e/test/products/channels/messaging/message_reply.e2e.ts index 380a389a6..2c4d3b796 100644 --- a/detox/e2e/test/products/channels/messaging/message_reply.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/message_reply.e2e.ts @@ -24,8 +24,8 @@ import { ServerScreen, ThreadScreen, } from '@support/ui/screen'; -import {getRandomId} from '@support/utils'; -import {expect} from 'detox'; +import {getRandomId, timeouts} from '@support/utils'; +import {expect, waitFor} from 'detox'; describe('Messaging - Message Reply', () => { const serverOneDisplayName = 'Server 1'; @@ -46,6 +46,11 @@ describe('Messaging - Message Reply', () => { await ChannelListScreen.toBeVisible(); }); + afterEach(async () => { + // # Go back to channel list screen + await ChannelScreen.back(); + }); + afterAll(async () => { // # Log out await HomeScreen.logout(); @@ -56,11 +61,12 @@ describe('Messaging - Message Reply', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); // * Verify message is added to post list const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(postListPostItem).toBeVisible(); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); // # Open post options for the message that was just posted, tap reply option await ChannelScreen.openPostOptionsFor(post.id, message); @@ -74,6 +80,7 @@ describe('Messaging - Message Reply', () => { // # Reply to parent post const replyMessage = `${message} reply`; await ThreadScreen.postMessage(replyMessage); + await ChannelScreen.dismissKeyboard(); // * Verify reply message is posted const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); @@ -82,7 +89,6 @@ describe('Messaging - Message Reply', () => { // # Go back to channel list screen await ThreadScreen.back(); - await ChannelScreen.back(); }); it('MM-T4785_2 - should be able to open reply thread by tapping on the post', async () => { @@ -90,11 +96,12 @@ describe('Messaging - Message Reply', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); // * Verify message is added to post list const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(postListPostItem).toBeVisible(); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); // # Tap on post to open thread await postListPostItem.tap(); @@ -104,7 +111,6 @@ describe('Messaging - Message Reply', () => { // # Go back to channel list screen await ThreadScreen.back(); - await ChannelScreen.back(); }); it('MM-T4785_3 - should not have reply option available on reply thread post options', async () => { @@ -112,8 +118,12 @@ describe('Messaging - Message Reply', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + await postListPostItem.tap(); // * Verify on reply thread screen @@ -128,6 +138,5 @@ describe('Messaging - Message Reply', () => { // # Go back to channel list screen await PostOptionsScreen.close(); await ThreadScreen.back(); - await ChannelScreen.back(); }); }); diff --git a/detox/e2e/test/products/channels/messaging/permalink.e2e.ts b/detox/e2e/test/products/channels/messaging/permalink.e2e.ts index cc1a356e3..1c950b66d 100644 --- a/detox/e2e/test/products/channels/messaging/permalink.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/permalink.e2e.ts @@ -26,7 +26,7 @@ import { ServerScreen, } from '@support/ui/screen'; import {getRandomId, timeouts, wait} from '@support/utils'; -import {expect} from 'detox'; +import {expect, waitFor} from 'detox'; describe('Messaging - Permalink', () => { const serverOneDisplayName = 'Server 1'; @@ -62,13 +62,20 @@ describe('Messaging - Permalink', () => { const permalinkLabel = `permalink-${getRandomId()}`; const permalinkMessage = `[${permalinkLabel}](/${teamName}/pl/${permalinkTargetPost.id})`; await ChannelScreen.postMessage(permalinkMessage); - await element(by.text(permalinkLabel)).tap({x: 5, y: 10}); - await wait(timeouts.ONE_SEC); + await wait(timeouts.TWO_SEC); + + // # Wait for the message to be posted and element to become visible (keyboard should dismiss) + const permalinkElement = element(by.text(permalinkLabel)); + await waitFor(permalinkElement).toBeVisible().withTimeout(timeouts.FOUR_SEC); + + // # Tap on permalink + await permalinkElement.tap(); + await wait(timeouts.FOUR_SEC); // * Verify on permalink screen and target post is displayed await PermalinkScreen.toBeVisible(); const {postListPostItem: permalinkPostListPostItem} = PermalinkScreen.getPostListPostItem(permalinkTargetPost.id, permalinkTargetPost.message); - await expect(permalinkPostListPostItem).toBeVisible(); + await expect(permalinkPostListPostItem).toExist(); // # Jump to recent messages await PermalinkScreen.jumpToRecentMessages(); @@ -76,7 +83,7 @@ describe('Messaging - Permalink', () => { // * Verify on channel screen and target post is displayed await expect(ChannelScreen.headerTitle).toHaveText(permalinkTargetChannelDiplayName); const {postListPostItem: channelPostListPostItem} = ChannelScreen.getPostListPostItem(permalinkTargetPost.id, permalinkTargetPost.message); - await expect(channelPostListPostItem).toBeVisible(); + await expect(channelPostListPostItem).toExist(); }; it('MM-T4876_1 - should be able to jump to target public channel post by tapping on permalink with team name', async () => { diff --git a/detox/e2e/test/products/channels/messaging/pin_and_unpin_message.e2e.ts b/detox/e2e/test/products/channels/messaging/pin_and_unpin_message.e2e.ts index e2060c527..2609c7bce 100644 --- a/detox/e2e/test/products/channels/messaging/pin_and_unpin_message.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/pin_and_unpin_message.e2e.ts @@ -57,6 +57,7 @@ describe('Messaging - Pin and Unpin Message', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); // * Verify message is posted const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); @@ -68,7 +69,7 @@ describe('Messaging - Pin and Unpin Message', () => { await PostOptionsScreen.pinPostOption.tap(); // * Verify pinned text is displayed on the post pre-header - await wait(timeouts.TEN_SEC); + await wait(timeouts.TWO_SEC); const {postListPostItemPreHeaderText} = ChannelScreen.getPostListPostItem(post.id, message); await expect(postListPostItemPreHeaderText).toHaveText(pinnedText); @@ -77,7 +78,7 @@ describe('Messaging - Pin and Unpin Message', () => { await PostOptionsScreen.unpinPostOption.tap(); // * Verify pinned text is not displayed on the post pre-header - await wait(timeouts.TEN_SEC); + await wait(timeouts.TWO_SEC); await expect(postListPostItemPreHeaderText).not.toBeVisible(); // # Go back to channel list screen @@ -89,14 +90,19 @@ describe('Messaging - Pin and Unpin Message', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).toBeVisible(); + await postListPostItem.tap(); + await wait(timeouts.TWO_SEC); + await ThreadScreen.toBeVisible(); await ThreadScreen.openPostOptionsFor(post.id, message); await PostOptionsScreen.pinPostOption.tap(); // * Verify pinned text is displayed on the post pre-header - await wait(timeouts.TEN_SEC); + await wait(timeouts.TWO_SEC); const {postListPostItemPreHeaderText} = ThreadScreen.getPostListPostItem(post.id, message); await expect(postListPostItemPreHeaderText).toHaveText(pinnedText); @@ -105,7 +111,7 @@ describe('Messaging - Pin and Unpin Message', () => { await PostOptionsScreen.unpinPostOption.tap(); // * Verify pinned text is not displayed on the post pre-header - await wait(timeouts.TEN_SEC); + await wait(timeouts.TWO_SEC); await expect(postListPostItemPreHeaderText).not.toBeVisible(); // # Go back to channel list screen diff --git a/detox/e2e/test/products/channels/messaging/save_and_unsave_message.e2e.ts b/detox/e2e/test/products/channels/messaging/save_and_unsave_message.e2e.ts index 788084cdd..39266d46c 100644 --- a/detox/e2e/test/products/channels/messaging/save_and_unsave_message.e2e.ts +++ b/detox/e2e/test/products/channels/messaging/save_and_unsave_message.e2e.ts @@ -25,7 +25,7 @@ import { ThreadScreen, } from '@support/ui/screen'; import {getRandomId, timeouts, wait} from '@support/utils'; -import {expect} from 'detox'; +import {expect, waitFor} from 'detox'; describe('Messaging - Save and Unsave Message', () => { const serverOneDisplayName = 'Server 1'; @@ -57,11 +57,12 @@ describe('Messaging - Save and Unsave Message', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); // * Verify message is posted const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(postListPostItem).toBeVisible(); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); // # Open post options for message and tap on save option await ChannelScreen.openPostOptionsFor(post.id, message); @@ -89,8 +90,11 @@ describe('Messaging - Save and Unsave Message', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); await postListPostItem.tap(); await ThreadScreen.openPostOptionsFor(post.id, message); await PostOptionsScreen.savePostOption.tap(); diff --git a/detox/e2e/test/products/channels/search/cross_team_search.e2e.ts b/detox/e2e/test/products/channels/search/cross_team_search.e2e.ts index 4c4805c66..f811a7a9b 100644 --- a/detox/e2e/test/products/channels/search/cross_team_search.e2e.ts +++ b/detox/e2e/test/products/channels/search/cross_team_search.e2e.ts @@ -32,8 +32,8 @@ import {expect} from 'detox'; describe('Search - Cross Team Search', () => { const serverOneDisplayName = 'Server 1'; - const channelsCategory = 'channels'; const searchTerm = 'Horses are fun'; + const channelsCategory = 'channels'; let testUser: any; let teamOpen: any; let teamRainforest: any; diff --git a/detox/e2e/test/products/channels/search/pinned_messages.e2e.ts b/detox/e2e/test/products/channels/search/pinned_messages.e2e.ts index 50cee8dfe..687c076e1 100644 --- a/detox/e2e/test/products/channels/search/pinned_messages.e2e.ts +++ b/detox/e2e/test/products/channels/search/pinned_messages.e2e.ts @@ -73,7 +73,12 @@ describe('Search - Pinned Messages', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(channelPostItem).toBeVisible(); + await ChannelScreen.openPostOptionsFor(post.id, message); await PostOptionsScreen.pinPostOption.tap(); @@ -110,7 +115,12 @@ describe('Search - Pinned Messages', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); + const {post: pinnedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(pinnedPost.id, message); + await expect(channelPostItem).toBeVisible(); + await ChannelScreen.openPostOptionsFor(pinnedPost.id, message); await PostOptionsScreen.pinPostOption.tap(); await ChannelInfoScreen.open(); @@ -146,6 +156,7 @@ describe('Search - Pinned Messages', () => { // # Post a reply const replyMessage = `${updatedMessage} reply`; await ThreadScreen.postMessage(replyMessage); + await ChannelScreen.dismissKeyboard(); // * Verify reply is posted const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); @@ -178,7 +189,12 @@ describe('Search - Pinned Messages', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); + const {post: pinnedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(pinnedPost.id, message); + await expect(channelPostItem).toBeVisible(); + await ChannelScreen.openPostOptionsFor(pinnedPost.id, message); await PostOptionsScreen.pinPostOption.tap(); await ChannelInfoScreen.open(); @@ -207,7 +223,12 @@ describe('Search - Pinned Messages', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + await ChannelScreen.dismissKeyboard(); + const {post: pinnedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(pinnedPost.id, message); + await expect(channelPostItem).toBeVisible(); + await ChannelScreen.openPostOptionsFor(pinnedPost.id, message); await PostOptionsScreen.pinPostOption.tap(); await ChannelInfoScreen.open(); @@ -239,6 +260,7 @@ describe('Search - Pinned Messages', () => { await ChannelInfoScreen.close(); await ChannelScreen.back(); await SavedMessagesScreen.open(); + await wait(timeouts.TWO_SEC); // * Verify pinned message is not displayed anymore on saved messages screen await expect(postListPostItem).not.toExist(); diff --git a/detox/e2e/test/products/channels/search/saved_messages.e2e.ts b/detox/e2e/test/products/channels/search/saved_messages.e2e.ts index 91ae94ddf..af063ab10 100644 --- a/detox/e2e/test/products/channels/search/saved_messages.e2e.ts +++ b/detox/e2e/test/products/channels/search/saved_messages.e2e.ts @@ -29,8 +29,8 @@ import { ServerScreen, ThreadScreen, } from '@support/ui/screen'; -import {getRandomId, timeouts} from '@support/utils'; -import {expect} from 'detox'; +import {getRandomId, timeouts, wait, waitForElementToBeVisible} from '@support/utils'; +import {expect, waitFor} from 'detox'; describe('Search - Saved Messages', () => { const serverOneDisplayName = 'Server 1'; @@ -78,6 +78,7 @@ describe('Search - Saved Messages', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openPostOptionsFor(post.id, message); await PostOptionsScreen.savePostOption.tap(); @@ -116,9 +117,11 @@ describe('Search - Saved Messages', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + const {post: savedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openPostOptionsFor(savedPost.id, message); await PostOptionsScreen.savePostOption.tap(); + await wait(timeouts.TWO_SEC); await ChannelScreen.back(); await SavedMessagesScreen.open(); @@ -154,7 +157,7 @@ describe('Search - Saved Messages', () => { // * Verify reply is posted const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage); - await expect(replyPostListPostItem).toBeVisible(); + await waitFor(replyPostListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); // # Go back to saved messages screen await ThreadScreen.back(); @@ -180,7 +183,10 @@ describe('Search - Saved Messages', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + const {post: savedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: channelPostListPostItem} = ChannelScreen.getPostListPostItem(savedPost.id, message); + await waitForElementToBeVisible(channelPostListPostItem); await ChannelScreen.openPostOptionsFor(savedPost.id, message); await PostOptionsScreen.savePostOption.tap(); await ChannelScreen.back(); @@ -207,7 +213,10 @@ describe('Search - Saved Messages', () => { const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); + const {post: savedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: channelPostListPostItem} = ChannelScreen.getPostListPostItem(savedPost.id, message); + await waitForElementToBeVisible(channelPostListPostItem); await ChannelScreen.openPostOptionsFor(savedPost.id, message); await PostOptionsScreen.savePostOption.tap(); await ChannelScreen.back(); diff --git a/detox/e2e/test/products/channels/server_login/server_list.e2e.ts b/detox/e2e/test/products/channels/server_login/server_list.e2e.ts index 796603eba..4ed4d83fc 100644 --- a/detox/e2e/test/products/channels/server_login/server_list.e2e.ts +++ b/detox/e2e/test/products/channels/server_login/server_list.e2e.ts @@ -195,9 +195,6 @@ describe('Server Login - Server List', () => { // # Enter the same first server display name await EditServerScreen.serverDisplayNameInput.replaceText(serverOneDisplayName); - // * Verify save button is disabled - await expect(EditServerScreen.saveButtonDisabled).toBeVisible(); - // # Enter a new first server display name const newServerOneDisplayName = `${serverOneDisplayName} new`; await EditServerScreen.serverDisplayNameInput.replaceText(newServerOneDisplayName); diff --git a/detox/e2e/test/products/channels/smoke_test/account.e2e.ts b/detox/e2e/test/products/channels/smoke_test/account.e2e.ts index 1f27a3e7b..4ccce4ef0 100644 --- a/detox/e2e/test/products/channels/smoke_test/account.e2e.ts +++ b/detox/e2e/test/products/channels/smoke_test/account.e2e.ts @@ -75,6 +75,7 @@ describe('Smoke Test - Account', () => { await wait(timeouts.ONE_SEC); await CustomStatusScreen.openEmojiPicker('default', true); await EmojiPickerScreen.searchInput.replaceText(customStatusEmojiName); + await EmojiPickerScreen.searchInput.tapReturnKey(); await element(by.text('🤡')).tap(); await wait(timeouts.ONE_SEC); await CustomStatusScreen.statusInput.replaceText(customStatusText); diff --git a/detox/e2e/test/products/channels/smoke_test/autocomplete.e2e.ts b/detox/e2e/test/products/channels/smoke_test/autocomplete.e2e.ts index bf4c924ae..52d4de088 100644 --- a/detox/e2e/test/products/channels/smoke_test/autocomplete.e2e.ts +++ b/detox/e2e/test/products/channels/smoke_test/autocomplete.e2e.ts @@ -66,15 +66,12 @@ describe('Smoke Test - Autocomplete', () => { await ChannelScreen.postInput.typeText('@'); await Autocomplete.toBeVisible(); - // * Verify at-mention list is displayed - await expect(Autocomplete.sectionAtMentionList).toBeVisible(); - // # Type in username await ChannelScreen.postInput.typeText(testUser.username); // * Verify at-mention autocomplete contains associated user suggestion const {atMentionItem} = Autocomplete.getAtMentionItem(testUser.id); - await expect(atMentionItem).toBeVisible(); + await expect(atMentionItem).toExist(); // # Select and post at-mention suggestion await atMentionItem.tap(); @@ -90,15 +87,12 @@ describe('Smoke Test - Autocomplete', () => { await ChannelScreen.postInput.typeText('~'); await Autocomplete.toBeVisible(); - // * Verify channel mention list is displayed - await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); - // # Type in channel name await ChannelScreen.postInput.typeText(testChannel.name); // * Verify channel mention autocomplete contains associated channel suggestion const {channelMentionItem} = Autocomplete.getChannelMentionItem(testChannel.name); - await expect(channelMentionItem).toBeVisible(); + await expect(channelMentionItem).toExist(); // # Select and post channel mention suggestion await channelMentionItem.tap(); @@ -117,15 +111,12 @@ describe('Smoke Test - Autocomplete', () => { await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars}`); await Autocomplete.toBeVisible(); - // * Verify emoji suggestion list is displayed - await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); - // # Type in 3rd to last characters of emoji name await ChannelScreen.postInput.typeText(emojiName3rdToLastChars); // * Verify emoji suggestion autocomplete contains associated emoji suggestion const {emojiSuggestionItem} = Autocomplete.getEmojiSuggestionItem(emojiName); - await expect(emojiSuggestionItem).toBeVisible(); + await expect(emojiSuggestionItem).toExist(); // # Select and post emoji suggestion await emojiSuggestionItem.tap(); @@ -141,16 +132,13 @@ describe('Smoke Test - Autocomplete', () => { await ChannelScreen.postInput.typeText('/'); await Autocomplete.toBeVisible(); - // * Verify slash suggestion list is displayed - await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); - // # Type in slash command name const slashCommand = 'away'; await ChannelScreen.postInput.typeText(slashCommand); // * Verify slash suggestion autocomplete contains associated slash command suggestion const {slashSuggestionItem} = Autocomplete.getSlashSuggestionItem(slashCommand); - await expect(slashSuggestionItem).toBeVisible(); + await expect(slashSuggestionItem).toExist(); // # Select and post slash suggestion await slashSuggestionItem.tap(); diff --git a/detox/e2e/test/products/channels/smoke_test/channels.e2e.ts b/detox/e2e/test/products/channels/smoke_test/channels.e2e.ts index 8f7d6223d..27d4d9eac 100644 --- a/detox/e2e/test/products/channels/smoke_test/channels.e2e.ts +++ b/detox/e2e/test/products/channels/smoke_test/channels.e2e.ts @@ -172,6 +172,7 @@ describe('Smoke Test - Channels', () => { await ChannelInfoScreen.open(); await ChannelInfoScreen.favoriteAction.tap(); await ChannelInfoScreen.muteAction.tap(); + await wait(timeouts.TWO_SEC); // * Verify channel is favorited and muted await expect(ChannelInfoScreen.unfavoriteAction).toBeVisible(); @@ -180,6 +181,7 @@ describe('Smoke Test - Channels', () => { // # Tap on favorited action to unfavorite the channel and tap on muted action to unmute the channel await ChannelInfoScreen.unfavoriteAction.tap(); await ChannelInfoScreen.unmuteAction.tap(); + await wait(timeouts.TWO_SEC); // * Verify channel is unfavorited and unmuted await expect(ChannelInfoScreen.favoriteAction).toBeVisible(); @@ -199,9 +201,5 @@ describe('Smoke Test - Channels', () => { await ChannelScreen.open(channelsCategory, channel.name); await ChannelInfoScreen.open(); await ChannelInfoScreen.archivePublicChannel({confirm: true}); - - // * Verify on channel screen and post draft archived message is displayed - await waitFor(ChannelListScreen.channelListScreen).toBeVisible().withTimeout(timeouts.TEN_SEC); - await expect(ChannelListScreen.getChannelItem(channelsCategory, channel.name)).not.toExist(); }); }); diff --git a/detox/e2e/test/products/channels/smoke_test/messaging.e2e.ts b/detox/e2e/test/products/channels/smoke_test/messaging.e2e.ts index 391805426..6f006f0eb 100644 --- a/detox/e2e/test/products/channels/smoke_test/messaging.e2e.ts +++ b/detox/e2e/test/products/channels/smoke_test/messaging.e2e.ts @@ -27,7 +27,7 @@ import { ServerScreen, ThreadScreen, } from '@support/ui/screen'; -import {getRandomId, timeouts, wait} from '@support/utils'; +import {getRandomId, timeouts} from '@support/utils'; import {expect} from 'detox'; describe('Smoke Test - Messaging', () => { @@ -64,6 +64,7 @@ describe('Smoke Test - Messaging', () => { // # Open a channel screen and post a message const message = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.dismissScheduledPostTooltip(); await ChannelScreen.postMessage(message); // * Verify message is added to post list @@ -146,6 +147,7 @@ describe('Smoke Test - Messaging', () => { await element(by.id(`channel.post_list.post.${post.id}`)).longPress(); await EmojiPickerScreen.open(true); await EmojiPickerScreen.searchInput.replaceText('clown_face'); + await EmojiPickerScreen.searchInput.tapReturnKey(); await element(by.text('🤡')).tap(); // * Verify reaction is added to the message @@ -161,54 +163,55 @@ describe('Smoke Test - Messaging', () => { await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(message); const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); - await element(by.id(`channel.post_list.post.${post.id}`)).longPress(); + await ChannelScreen.openPostOptionsFor(post.id, message); await PostOptionsScreen.followThreadOption.tap(); - // * Verify message is followed by user via post footer + // * Verify post options closed and message is followed by user via post footer + await waitFor(PostOptionsScreen.postOptionsScreen).not.toBeVisible().withTimeout(timeouts.FOUR_SEC); const {postListPostItem, postListPostItemFooterFollowingButton} = ChannelScreen.getPostListPostItem(post.id, message); - await waitFor(postListPostItemFooterFollowingButton).toBeVisible().withTimeout(timeouts.TWO_SEC); + await waitFor(postListPostItemFooterFollowingButton).toExist().withTimeout(timeouts.FOUR_SEC); - // # Tap on following button via post footer + // # Tap on following button via post footer to unfollow await postListPostItemFooterFollowingButton.tap(); - await wait(timeouts.FOUR_SEC); // * Verify message is not followed by user via post footer - await expect(postListPostItemFooterFollowingButton).not.toBeVisible(); + await waitFor(postListPostItemFooterFollowingButton).not.toExist().withTimeout(timeouts.FOUR_SEC); // # Open post options for message and tap on save option - await element(by.id(`channel.post_list.post.${post.id}`)).longPress(); + await ChannelScreen.openPostOptionsFor(post.id, message); await PostOptionsScreen.savePostOption.tap(); - // * Verify saved text is displayed on the post pre-header - await wait(timeouts.ONE_SEC); + // * Verify post options closed and saved text is displayed on the post pre-header + await waitFor(PostOptionsScreen.postOptionsScreen).not.toBeVisible().withTimeout(timeouts.FOUR_SEC); const {postListPostItemPreHeaderText: channelPostListPostItemPreHeaderText} = ChannelScreen.getPostListPostItem(post.id, message); - await expect(channelPostListPostItemPreHeaderText).toHaveText(savedText); + await waitFor(channelPostListPostItemPreHeaderText).toHaveText(savedText).withTimeout(timeouts.FOUR_SEC); - // # Tap on post to open thread and tap on thread overview unsave button + // # Tap on post to open thread and open post options for message await postListPostItem.tap(); - await element(by.text(message)).longPress(); + await ThreadScreen.openPostOptionsFor(post.id, message); await PostOptionsScreen.unsavePostOption.tap(); - // * Verify saved text is not displayed on the post pre-header - await expect(channelPostListPostItemPreHeaderText).not.toBeVisible(); + // * Verify post options closed and saved text is not displayed on the post pre-header + await waitFor(PostOptionsScreen.postOptionsScreen).not.toBeVisible().withTimeout(timeouts.TWO_SEC); + await waitFor(channelPostListPostItemPreHeaderText).not.toBeVisible().withTimeout(timeouts.FOUR_SEC); // # Open post options for message and tap on pin to channel option await ThreadScreen.openPostOptionsFor(post.id, message); await PostOptionsScreen.pinPostOption.tap(); - // * Verify pinned text is displayed on the post pre-header - await wait(timeouts.ONE_SEC); + // * Verify post options closed and pinned text is displayed on the post pre-header + await waitFor(PostOptionsScreen.postOptionsScreen).not.toBeVisible().withTimeout(timeouts.TWO_SEC); const {postListPostItemPreHeaderText: threadPostListPostItemPreHeaderText} = ThreadScreen.getPostListPostItem(post.id, message); - await expect(threadPostListPostItemPreHeaderText).toHaveText(pinnedText); + await waitFor(threadPostListPostItemPreHeaderText).toHaveText(pinnedText).withTimeout(timeouts.FOUR_SEC); // # Go back to channel, open post options for message, and tap on unpin from channel option await ThreadScreen.back(); - await element(by.id(`channel.post_list.post.${post.id}`)).longPress(); + await ChannelScreen.openPostOptionsFor(post.id, message); await PostOptionsScreen.unpinPostOption.tap(); - // * Verify pinned text is not displayed on the post pre-header - await wait(timeouts.ONE_SEC); - await expect(channelPostListPostItemPreHeaderText).not.toBeVisible(); + // * Verify post options closed and pinned text is not displayed on the post pre-header + await waitFor(PostOptionsScreen.postOptionsScreen).not.toBeVisible().withTimeout(timeouts.TWO_SEC); + await waitFor(channelPostListPostItemPreHeaderText).not.toBeVisible().withTimeout(timeouts.FOUR_SEC); // # Go back to channel list screen await ChannelScreen.back(); diff --git a/detox/e2e/test/products/channels/smoke_test/server_login.e2e.ts b/detox/e2e/test/products/channels/smoke_test/server_login.e2e.ts index b43ae4d92..a809c09f0 100644 --- a/detox/e2e/test/products/channels/smoke_test/server_login.e2e.ts +++ b/detox/e2e/test/products/channels/smoke_test/server_login.e2e.ts @@ -26,7 +26,7 @@ import { ServerScreen, } from '@support/ui/screen'; import {isAndroid, isIos, timeouts, wait} from '@support/utils'; -import {expect} from 'detox'; +import {expect, waitFor} from 'detox'; describe('Smoke Test - Server Login', () => { const serverOneDisplayName = 'Server 1'; @@ -69,7 +69,8 @@ describe('Smoke Test - Server Login', () => { await User.apiAdminLogin(siteTwoUrl); const {user} = await Setup.apiInit(siteTwoUrl); await ServerListScreen.addServerButton.tap(); - await expect(ServerScreen.headerTitleAddServer).toBeVisible(35); + await wait(timeouts.ONE_SEC); + await waitFor(ServerScreen.headerTitleAddServer).toBeVisible().withTimeout(timeouts.TEN_SEC); await ServerScreen.connectToServer(serverTwoUrl, serverTwoDisplayName); await LoginScreen.login(user); @@ -79,22 +80,28 @@ describe('Smoke Test - Server Login', () => { // # Go back to first server, open server list screen, swipe left on second server and tap on logout option await ServerListScreen.open(); + await wait(timeouts.ONE_SEC); if (isIos()) { await ServerListScreen.serverListTitle.swipe('up'); } else if (isAndroid()) { await ServerListScreen.serverListTitle.swipe('up', 'fast', 0.1, 0.5, 0.3); } + await wait(timeouts.ONE_SEC); await waitFor(ServerListScreen.getServerItemInactive(serverOneDisplayName)).toBeVisible().withTimeout(timeouts.TEN_SEC); await ServerListScreen.getServerItemInactive(serverOneDisplayName).tap(); + await wait(timeouts.TWO_SEC); await ServerListScreen.open(); + await wait(timeouts.ONE_SEC); if (isIos()) { await ServerListScreen.serverListTitle.swipe('up'); } else if (isAndroid()) { await waitFor(ServerListScreen.serverListTitle).toBeVisible().withTimeout(timeouts.TWO_SEC); await ServerListScreen.serverListTitle.swipe('up', 'fast', 0.1, 0.5, 0.3); } + await wait(timeouts.ONE_SEC); await waitFor(ServerListScreen.getServerItemInactive(serverTwoDisplayName)).toBeVisible().withTimeout(timeouts.TEN_SEC); await ServerListScreen.getServerItemInactive(serverTwoDisplayName).swipe('left'); + await wait(timeouts.ONE_SEC); await ServerListScreen.getServerItemLogoutOption(serverTwoDisplayName).tap(); // * Verify logout server alert is displayed @@ -106,6 +113,7 @@ describe('Smoke Test - Server Login', () => { // * Verify second server is logged out await ServerListScreen.getServerItemInactive(serverTwoDisplayName).swipe('left'); + await wait(timeouts.ONE_SEC); await expect(ServerListScreen.getServerItemLoginOption(serverTwoDisplayName)).toBeVisible(); // # Go back to first server diff --git a/detox/e2e/test/products/channels/smoke_test/threads.e2e.ts b/detox/e2e/test/products/channels/smoke_test/threads.e2e.ts index dae379615..356d627a0 100644 --- a/detox/e2e/test/products/channels/smoke_test/threads.e2e.ts +++ b/detox/e2e/test/products/channels/smoke_test/threads.e2e.ts @@ -61,19 +61,25 @@ describe('Smoke Test - Threads', () => { await waitFor(ChannelScreen.postInput).toBeVisible().withTimeout(timeouts.FOUR_SEC); await ChannelScreen.postMessage(parentMessage); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: parentPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); + await waitFor(parentPostItem).toBeVisible().withTimeout(timeouts.TEN_SEC); await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); await waitFor(ThreadScreen.postInput).toBeVisible().withTimeout(timeouts.FOUR_SEC); await ThreadScreen.postMessage(`${parentMessage} reply`); - await wait(timeouts.ONE_SEC); + + // * Verify thread is followed by user by default via thread navigation + await expect(ThreadScreen.followingButton).toBeVisible(); + + // # Unfollow thread via thread navigation await ThreadScreen.followingButton.tap(); - await wait(timeouts.FOUR_SEC); + await wait(timeouts.TWO_SEC); // * Verify thread is not followed by user via thread navigation await expect(ThreadScreen.followButton).toBeVisible(); // # Follow thread via thread navigation await ThreadScreen.followButton.tap(); - await wait(timeouts.FOUR_SEC); + await wait(timeouts.TWO_SEC); // * Verify thread is followed by user via thread navigation await expect(ThreadScreen.followingButton).toBeVisible(); @@ -81,7 +87,9 @@ describe('Smoke Test - Threads', () => { // # Go back to channel list screen, then go to global threads screen, tap on all your threads button, open thread options for thread, tap on mark as unread option, and tap on unread threads button await ThreadScreen.back(); await ChannelScreen.back(); - await device.reloadReactNative(); + + // Note: Commenting out reloadReactNative as it can cause ANR on Android + // await device.reloadReactNative(); await GlobalThreadsScreen.open(); await GlobalThreadsScreen.headerAllThreadsButton.tap(); await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id); @@ -118,8 +126,11 @@ describe('Smoke Test - Threads', () => { // # Create a thread, go back to channel list screen, then go to global threads screen, open thread options for thread, tap on save option, and tap on thread const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); + await waitFor(ChannelScreen.postInput).toBeVisible().withTimeout(timeouts.FOUR_SEC); await ChannelScreen.postMessage(parentMessage); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: parentPostItem2} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); + await waitFor(parentPostItem2).toBeVisible().withTimeout(timeouts.TEN_SEC); await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); const replyMessage = `${parentMessage} reply`; await ThreadScreen.postMessage(replyMessage); @@ -134,12 +145,12 @@ describe('Smoke Test - Threads', () => { const {postListPostItemPreHeaderText} = ThreadScreen.getPostListPostItem(parentPost.id, parentMessage); await expect(postListPostItemPreHeaderText).toHaveText(savedText); - // # Go back to global threads screen, open thread options for thread, tap on save option, and tap on thread + // # Go back to global threads screen, open thread options for thread, tap on unsave option, and tap on thread await ThreadScreen.back(); await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id); - await wait(timeouts.ONE_SEC); + await waitFor(ThreadOptionsScreen.unsaveThreadOption).toBeVisible().withTimeout(timeouts.TEN_SEC); await ThreadOptionsScreen.unsaveThreadOption.tap(); - await wait(timeouts.ONE_SEC); + await waitFor(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible().withTimeout(timeouts.TEN_SEC); await GlobalThreadsScreen.getThreadItem(parentPost.id).tap(); // * Verify saved text is not displayed on the post pre-header @@ -148,7 +159,9 @@ describe('Smoke Test - Threads', () => { // # Go back to global threads screen, open thread options for thread, tap on open in channel option, and jump to recent messages await ThreadScreen.back(); await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id); + await waitFor(ThreadOptionsScreen.openInChannelOption).toBeVisible().withTimeout(timeouts.TEN_SEC); await ThreadOptionsScreen.openInChannelOption.tap(); + await waitFor(PermalinkScreen.permalinkScreen).toBeVisible().withTimeout(timeouts.TEN_SEC); await PermalinkScreen.jumpToRecentMessages(); // * Verify on channel screen and thread is displayed diff --git a/detox/e2e/test/products/channels/teams/invite_people.e2e.ts b/detox/e2e/test/products/channels/teams/invite_people.e2e.ts index 91b67f5ad..11aa7aff0 100644 --- a/detox/e2e/test/products/channels/teams/invite_people.e2e.ts +++ b/detox/e2e/test/products/channels/teams/invite_people.e2e.ts @@ -19,7 +19,7 @@ import { LoginScreen, ServerScreen, } from '@support/ui/screen'; -import {timeouts} from '@support/utils'; +import {timeouts, wait} from '@support/utils'; import {expect} from 'detox'; describe('Teams - Invite', () => { @@ -114,6 +114,7 @@ describe('Teams - Invite', () => { // # Send invitation await Invite.sendButton.tap(); + await wait(timeouts.TWO_SEC); // * Validate summary report sent await waitFor(Invite.screenSummary).toBeVisible().withTimeout(timeouts.TEN_SEC); @@ -141,6 +142,7 @@ describe('Teams - Invite', () => { // # Send invitation await Invite.sendButton.tap(); + await wait(timeouts.TWO_SEC); // * Validate summary report sent await waitFor(Invite.screenSummary).toBeVisible().withTimeout(timeouts.TEN_SEC); @@ -166,6 +168,7 @@ describe('Teams - Invite', () => { // # Send invitation await Invite.sendButton.tap(); + await wait(timeouts.TWO_SEC); // * Validate summary report not sent await expect(Invite.screenSummary).toBeVisible(); @@ -203,6 +206,7 @@ describe('Teams - Invite', () => { // # Send invitation await Invite.sendButton.tap(); + await wait(timeouts.TWO_SEC); // * Validate summary waitFor(Invite.screenSummary).toBeVisible(); diff --git a/detox/e2e/test/products/channels/threads/follow_and_unfollow_thread.e2e.ts b/detox/e2e/test/products/channels/threads/follow_and_unfollow_thread.e2e.ts index 100c69cb9..25ccb0085 100644 --- a/detox/e2e/test/products/channels/threads/follow_and_unfollow_thread.e2e.ts +++ b/detox/e2e/test/products/channels/threads/follow_and_unfollow_thread.e2e.ts @@ -27,7 +27,7 @@ import { ThreadScreen, } from '@support/ui/screen'; import {getRandomId, timeouts, wait} from '@support/utils'; -import {expect} from 'detox'; +import {expect, waitFor} from 'detox'; describe('Threads - Follow and Unfollow Thread', () => { const serverOneDisplayName = 'Server 1'; @@ -58,23 +58,27 @@ describe('Threads - Follow and Unfollow Thread', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); await ThreadScreen.postMessage(`${parentMessage} reply`); - await wait(timeouts.ONE_SEC); // * Verify thread is followed by user by default via thread navigation await expect(ThreadScreen.followingButton).toBeVisible(); // # Unfollow thread via thread navigation await ThreadScreen.followingButton.tap(); + await wait(timeouts.TWO_SEC); // * Verify thread is not followed by user via thread navigation await expect(ThreadScreen.followButton).toBeVisible(); // # Follow thread via thread navigation - await wait(timeouts.TWO_SEC); await ThreadScreen.followButton.tap(); + await wait(timeouts.TWO_SEC); // * Verify thread is followed by user via thread navigation await expect(ThreadScreen.followingButton).toBeVisible(); @@ -89,10 +93,10 @@ describe('Threads - Follow and Unfollow Thread', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); await ThreadScreen.postMessage(`${parentMessage} reply`); - await wait(timeouts.ONE_SEC); await ThreadScreen.back(); // * Verify thread is followed by user by default via post footer @@ -101,13 +105,14 @@ describe('Threads - Follow and Unfollow Thread', () => { // # Unfollow thread via post footer await postListPostItemFooterFollowingButton.tap(); + await wait(timeouts.TWO_SEC); // * Verify thread is not followed by user via post footer await expect(postListPostItemFooterFollowButton).toBeVisible(); // # Follow thread via post footer - await wait(timeouts.TWO_SEC); await postListPostItemFooterFollowButton.tap(); + await wait(timeouts.TWO_SEC); // * Verify thread is followed by user via post footer await expect(postListPostItemFooterFollowingButton).toBeVisible(); @@ -121,6 +126,7 @@ describe('Threads - Follow and Unfollow Thread', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); await ThreadScreen.postMessage(`${parentMessage} reply`); @@ -134,6 +140,7 @@ describe('Threads - Follow and Unfollow Thread', () => { // # Unfollow thread via post options await waitFor(PostOptionsScreen.followingThreadOption).toBeVisible().withTimeout(timeouts.TWO_SEC); await PostOptionsScreen.followingThreadOption.tap(); + await wait(timeouts.TWO_SEC); // * Verify thread is not followed by user via post footer const {postListPostItemFooterFollowButton, postListPostItemFooterFollowingButton} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); @@ -146,8 +153,8 @@ describe('Threads - Follow and Unfollow Thread', () => { await expect(PostOptionsScreen.followThreadOption).toBeVisible(); // # Tap on follow thread option - await wait(timeouts.TWO_SEC); await PostOptionsScreen.followThreadOption.tap(); + await wait(timeouts.TWO_SEC); // * Verify thread is followed by user via post footer await waitFor(postListPostItemFooterFollowingButton).toBeVisible().withTimeout(timeouts.TWO_SEC); @@ -168,6 +175,7 @@ describe('Threads - Follow and Unfollow Thread', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); const replyMessage = `${parentMessage} reply`; @@ -188,14 +196,9 @@ describe('Threads - Follow and Unfollow Thread', () => { // # Tap on unfollow thread option await ThreadOptionsScreen.followingThreadOption.tap(); + await wait(timeouts.TWO_SEC); // * Verify thread is not displayed anymore in all your threads section - await wait(timeouts.ONE_SEC); await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).not.toBeVisible(); - - await wait(timeouts.FOUR_SEC); - - // # Go back to channel list screen - await GlobalThreadsScreen.back(); }); }); diff --git a/detox/e2e/test/products/channels/threads/global_threads.e2e.ts b/detox/e2e/test/products/channels/threads/global_threads.e2e.ts index 1e3637555..4a9e29054 100644 --- a/detox/e2e/test/products/channels/threads/global_threads.e2e.ts +++ b/detox/e2e/test/products/channels/threads/global_threads.e2e.ts @@ -24,7 +24,7 @@ import { ServerScreen, ThreadScreen, } from '@support/ui/screen'; -import {getRandomId, timeouts} from '@support/utils'; +import {getRandomId, timeouts, wait} from '@support/utils'; import {expect} from 'detox'; describe('Threads - Global Threads', () => { @@ -72,10 +72,13 @@ describe('Threads - Global Threads', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); + const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); const replyMessage = `${parentMessage} reply`; await ThreadScreen.postMessage(replyMessage); + await wait(timeouts.TWO_SEC); const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage); @@ -117,9 +120,11 @@ describe('Threads - Global Threads', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await wait(timeouts.TWO_SEC); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); await ThreadScreen.postMessage(`${parentMessage} reply`); + await wait(timeouts.TWO_SEC); await ThreadScreen.followingButton.tap(); // * Verify thread is not followed by the current user @@ -149,6 +154,7 @@ describe('Threads - Global Threads', () => { await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); const replyMessage = `${parentMessage} reply`; await ThreadScreen.postMessage(replyMessage); + await wait(timeouts.TWO_SEC); const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage); @@ -188,6 +194,7 @@ describe('Threads - Global Threads', () => { await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); await ThreadScreen.postMessage(`${parentMessage} reply`); + await wait(timeouts.TWO_SEC); await ThreadScreen.followingButton.tap(); // * Verify thread is not followed by the current user diff --git a/detox/e2e/test/products/channels/threads/open_thread_in_channel.e2e.ts b/detox/e2e/test/products/channels/threads/open_thread_in_channel.e2e.ts index 86467ea62..06b2bf002 100644 --- a/detox/e2e/test/products/channels/threads/open_thread_in_channel.e2e.ts +++ b/detox/e2e/test/products/channels/threads/open_thread_in_channel.e2e.ts @@ -27,7 +27,7 @@ import { ThreadScreen, } from '@support/ui/screen'; import {getRandomId, timeouts, wait} from '@support/utils'; -import {expect} from 'detox'; +import {expect, waitFor} from 'detox'; describe('Threads - Open Thread in Channel', () => { const serverOneDisplayName = 'Server 1'; @@ -58,7 +58,12 @@ describe('Threads - Open Thread in Channel', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); + await ChannelScreen.dismissScheduledPostTooltip(); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); const replyMessage = `${parentMessage} reply`; await ThreadScreen.postMessage(replyMessage); @@ -81,8 +86,9 @@ describe('Threads - Open Thread in Channel', () => { // * Verify on channel screen and thread is displayed await ChannelScreen.toBeVisible(); - const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); - await expect(postListPostItem).toBeVisible(); + await ChannelScreen.dismissScheduledPostTooltip(); + const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); + await expect(channelPostItem).toBeVisible(); // # Go back to channel list screen await ChannelScreen.back(); @@ -94,7 +100,11 @@ describe('Threads - Open Thread in Channel', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); const replyMessage = `${parentMessage} reply`; await ThreadScreen.postMessage(replyMessage); @@ -122,8 +132,9 @@ describe('Threads - Open Thread in Channel', () => { // * Verify on channel screen and thread is displayed await ChannelScreen.toBeVisible(); - const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); - await expect(postListPostItem).toBeVisible(); + await ChannelScreen.dismissScheduledPostTooltip(); + const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); + await expect(channelPostItem).toBeVisible(); // # Go back to channel list screen await ChannelScreen.back(); diff --git a/detox/e2e/test/products/channels/threads/reply_to_thread.e2e.ts b/detox/e2e/test/products/channels/threads/reply_to_thread.e2e.ts index 60d547a8a..272c44386 100644 --- a/detox/e2e/test/products/channels/threads/reply_to_thread.e2e.ts +++ b/detox/e2e/test/products/channels/threads/reply_to_thread.e2e.ts @@ -25,7 +25,7 @@ import { ThreadOptionsScreen, ThreadScreen, } from '@support/ui/screen'; -import {getRandomId} from '@support/utils'; +import {getRandomId, timeouts} from '@support/utils'; import {expect} from 'detox'; describe('Threads - Reply to Thread', () => { @@ -57,10 +57,16 @@ describe('Threads - Reply to Thread', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); + const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.TEN_SEC); + await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); const replyMessage = `${parentMessage} reply`; await ThreadScreen.postMessage(replyMessage); + await ChannelScreen.dismissKeyboard(); await ThreadScreen.back(); await ChannelScreen.back(); await GlobalThreadsScreen.open(); @@ -78,11 +84,12 @@ describe('Threads - Reply to Thread', () => { // # Add new reply to thread const newReplyMessage = `${parentMessage} new reply`; await ThreadScreen.postMessage(newReplyMessage); + await ChannelScreen.dismissKeyboard(); // * Verify new reply is posted const {post: newReplyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); - const {postListPostItem} = ThreadScreen.getPostListPostItem(newReplyPost.id, newReplyMessage); - await expect(postListPostItem).toBeVisible(); + const {postListPostItem: newReplyPostItem} = ThreadScreen.getPostListPostItem(newReplyPost.id, newReplyMessage); + await expect(newReplyPostItem).toBeVisible(); // # Go back to channel list screen await ThreadScreen.back(); @@ -94,10 +101,15 @@ describe('Threads - Reply to Thread', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.TEN_SEC); await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); const replyMessage = `${parentMessage} reply`; await ThreadScreen.postMessage(replyMessage); + await ChannelScreen.dismissKeyboard(); + await ThreadScreen.back(); await ChannelScreen.back(); await GlobalThreadsScreen.open(); @@ -114,11 +126,12 @@ describe('Threads - Reply to Thread', () => { // # Add new reply to thread const newReplyMessage = `${parentMessage} new reply`; await ThreadScreen.postMessage(newReplyMessage); + await ChannelScreen.dismissKeyboard(); // * Verify new reply is posted const {post: newReplyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); - const {postListPostItem} = ThreadScreen.getPostListPostItem(newReplyPost.id, newReplyMessage); - await expect(postListPostItem).toBeVisible(); + const {postListPostItem: newReplyPostItem} = ThreadScreen.getPostListPostItem(newReplyPost.id, newReplyMessage); + await expect(newReplyPostItem).toBeVisible(); // # Go back to channel list screen await ThreadScreen.back(); diff --git a/detox/e2e/test/products/channels/threads/save_and_unsave_thread.e2e.ts b/detox/e2e/test/products/channels/threads/save_and_unsave_thread.e2e.ts index 5841784a6..3e8069de9 100644 --- a/detox/e2e/test/products/channels/threads/save_and_unsave_thread.e2e.ts +++ b/detox/e2e/test/products/channels/threads/save_and_unsave_thread.e2e.ts @@ -25,8 +25,8 @@ import { ThreadOptionsScreen, ThreadScreen, } from '@support/ui/screen'; -import {getRandomId} from '@support/utils'; -import {expect} from 'detox'; +import {getRandomId, timeouts} from '@support/utils'; +import {expect, waitFor} from 'detox'; describe('Threads - Save and Unsave Thread', () => { const serverOneDisplayName = 'Server 1'; @@ -58,10 +58,16 @@ describe('Threads - Save and Unsave Thread', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); + const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage); + await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC); + await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); const replyMessage = `${parentMessage} reply`; await ThreadScreen.postMessage(replyMessage); + await ChannelScreen.dismissKeyboard(); await ThreadScreen.back(); await ChannelScreen.back(); await GlobalThreadsScreen.open(); @@ -97,10 +103,12 @@ describe('Threads - Save and Unsave Thread', () => { const parentMessage = `Message ${getRandomId()}`; await ChannelScreen.open(channelsCategory, testChannel.name); await ChannelScreen.postMessage(parentMessage); + await ChannelScreen.dismissKeyboard(); const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage); const replyMessage = `${parentMessage} reply`; await ThreadScreen.postMessage(replyMessage); + await ChannelScreen.dismissKeyboard(); await ThreadScreen.back(); await ChannelScreen.back(); await GlobalThreadsScreen.open(); diff --git a/detox/e2e/test/products/playbooks/playbooks-e2e.ts b/detox/e2e/test/products/playbooks/playbooks-e2e.ts index 91b25b553..c13b79e81 100644 --- a/detox/e2e/test/products/playbooks/playbooks-e2e.ts +++ b/detox/e2e/test/products/playbooks/playbooks-e2e.ts @@ -8,7 +8,6 @@ import {ServerScreen, LoginScreen, ChannelScreen, ChannelListScreen, ThreadScree describe('Playbooks - Basic', () => { const serverOneDisplayName = 'Server 1'; const channelsCategory = 'channels'; - let testUser: any; let testTeam: any; let testChannel: any; diff --git a/detox/e2e/test/products/playbooks/playbooks.ts b/detox/e2e/test/products/playbooks/playbooks.ts index 6a2965456..0778565d5 100644 --- a/detox/e2e/test/products/playbooks/playbooks.ts +++ b/detox/e2e/test/products/playbooks/playbooks.ts @@ -8,7 +8,6 @@ import {ServerScreen, LoginScreen, ChannelScreen, ChannelListScreen, ThreadScree describe.skip('Playbooks - Basic', () => { const serverOneDisplayName = 'Server 1'; const channelsCategory = 'channels'; - let testUser: any; let testTeam: any; let testChannel: any; diff --git a/detox/e2e/test/setup.ts b/detox/e2e/test/setup.ts index 43e7248de..a918b230e 100644 --- a/detox/e2e/test/setup.ts +++ b/detox/e2e/test/setup.ts @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. /* eslint-disable no-await-in-loop, no-console */ +import {cleanupAdbReversePorts, resetAdbServer} from '@support/adb_utils'; import {ClaudePromptHandler} from '@support/pilot/ClaudePromptHandler'; import {Plugin, System, User} from '@support/server_api'; import {siteOneUrl} from '@support/test_config'; @@ -56,8 +57,6 @@ export async function launchAppWithRetry(): Promise { }); } - // Verify app is connected by executing a simple command - await device.reloadReactNative(); console.info(`✅ App launched successfully on attempt ${attempt}`); return; // Success, exit the function @@ -67,14 +66,27 @@ export async function launchAppWithRetry(): Promise { // If this is the last attempt, don't wait if (attempt < MAX_RETRY_ATTEMPTS) { + // Check if this is an ADB port binding issue (Android-specific, common in CI) + const errorMessage = (error as Error).message; + const isAdbPortError = errorMessage.includes('cannot bind listener') || + errorMessage.includes('Address already in use') || + errorMessage.includes('adb'); + + // Only attempt ADB cleanup if we detect an ADB-related error + // These functions are safe no-ops on iOS + if (isAdbPortError) { + console.warn('Detected ADB-related error, attempting cleanup...'); + await cleanupAdbReversePorts(); + + // On second retry with port issues, also reset ADB server + if (attempt >= 2) { + console.warn('Multiple failures detected, attempting ADB server reset...'); + await resetAdbServer(); + } + } + console.warn(`Waiting ${RETRY_DELAY}ms before retrying...`); await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY)); - - // Force a new instance on retry - if (!isFirstLaunch && attempt > 1) { - console.warn('Forcing new instance for next attempt'); - isFirstLaunch = true; - } } } } @@ -97,8 +109,15 @@ async function initializeClaudePromptHandler(): Promise { } beforeAll(async () => { + // Reset flag to ensure each test file starts with a clean app launch + isFirstLaunch = true; + await initializeClaudePromptHandler(); + // Clean up any stale ADB reverse port mappings from previous runs + // This is crucial in CI where crashed tests may leave stale connections + await cleanupAdbReversePorts(); + // Login as sysadmin and reset server configuration await System.apiCheckSystemHealth(siteOneUrl); await User.apiAdminLogin(siteOneUrl); @@ -111,6 +130,13 @@ afterAll(async () => { try { await device.terminateApp(); } catch (error) { - console.error('Error terminating app:', error); + console.error('[Teardown] Error terminating app:', error); + } + + // Clean up ADB connections after tests complete + try { + await cleanupAdbReversePorts(); + } catch (error) { + console.error('[Teardown] Error cleaning up ADB ports:', error); } });