iOS & Android: Migrate 29 Channel Settings tests from Rainforest to Detox (#9313)
This commit is contained in:
parent
121980c7e5
commit
95d50125e4
44 changed files with 2088 additions and 1112 deletions
10
.github/workflows/e2e-ios-template.yml
vendored
10
.github/workflows/e2e-ios-template.yml
vendored
|
|
@ -115,7 +115,7 @@ jobs:
|
||||||
id: generate-specs
|
id: generate-specs
|
||||||
uses: ./.github/actions/generate-specs
|
uses: ./.github/actions/generate-specs
|
||||||
with:
|
with:
|
||||||
parallelism: 5
|
parallelism: 10
|
||||||
search_path: detox/e2e/test
|
search_path: detox/e2e/test
|
||||||
device_name: ${{ env.DEVICE_NAME }}
|
device_name: ${{ env.DEVICE_NAME }}
|
||||||
device_os_version: ${{ env.DEVICE_OS_VERSION }}
|
device_os_version: ${{ env.DEVICE_OS_VERSION }}
|
||||||
|
|
@ -244,8 +244,8 @@ jobs:
|
||||||
# Open Simulator.app in background to speed up UI rendering
|
# Open Simulator.app in background to speed up UI rendering
|
||||||
open -a Simulator --args -CurrentDeviceUDID "$SIMULATOR_ID"
|
open -a Simulator --args -CurrentDeviceUDID "$SIMULATOR_ID"
|
||||||
|
|
||||||
# Give Simulator.app time to fully initialize UI
|
# Give Simulator.app time to fully initialize UI (iOS 26.2 needs more time)
|
||||||
sleep 3
|
sleep 5
|
||||||
|
|
||||||
echo "SIMULATOR_ID=$SIMULATOR_ID" >> $GITHUB_ENV
|
echo "SIMULATOR_ID=$SIMULATOR_ID" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
|
@ -319,9 +319,9 @@ jobs:
|
||||||
tail -30 metro.log
|
tail -30 metro.log
|
||||||
else
|
else
|
||||||
echo "Metro is serving on http://localhost:8081"
|
echo "Metro is serving on http://localhost:8081"
|
||||||
# Give Metro extra time to fully initialize for iOS 26.2
|
# Give Metro extra time to fully initialize for iOS 26.2 (React Native bridge needs more time)
|
||||||
echo "Allowing Metro to fully stabilize..."
|
echo "Allowing Metro to fully stabilize..."
|
||||||
sleep 3
|
sleep 8
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd detox
|
cd detox
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,10 @@
|
||||||
"behavior": {
|
"behavior": {
|
||||||
"init": {
|
"init": {
|
||||||
"reinstallApp": false,
|
"reinstallApp": false,
|
||||||
"launchApp": true
|
"launchApp": false
|
||||||
},
|
},
|
||||||
"cleanup": {
|
"cleanup": {
|
||||||
"shutdownDevice": true
|
"shutdownDevice": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"session": {
|
"session": {
|
||||||
|
|
|
||||||
|
|
@ -181,4 +181,3 @@ To run Playbooks tests:
|
||||||
For complete documentation, see:
|
For complete documentation, see:
|
||||||
- [Wix Pilot Technical Overview](https://wix-pilot.com/docs/guides/technical-overview)
|
- [Wix Pilot Technical Overview](https://wix-pilot.com/docs/guides/technical-overview)
|
||||||
- [Pilot Best Practices Guide](https://wix-pilot.com/docs/guides/pilot-best-practices)
|
- [Pilot Best Practices Guide](https://wix-pilot.com/docs/guides/pilot-best-practices)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const shard = process.env.CI_NODE_INDEX ? process.env.CI_NODE_INDEX : '';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
setupFilesAfterEnv: ['./test/setup.ts'],
|
setupFilesAfterEnv: ['./test/setup.ts'],
|
||||||
maxWorkers: 1,
|
maxWorkers: process.env.CI ? 1 : 2,
|
||||||
testSequencer: './custom_sequencer.js',
|
testSequencer: './custom_sequencer.js',
|
||||||
testTimeout: process.env.LOW_BANDWIDTH_MODE === 'true' ? 240000 : 180000,
|
testTimeout: process.env.LOW_BANDWIDTH_MODE === 'true' ? 240000 : 180000,
|
||||||
rootDir: '.',
|
rootDir: '.',
|
||||||
|
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
// 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<void> {
|
|
||||||
// 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<void> {
|
|
||||||
// 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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -40,6 +40,7 @@ class Alert {
|
||||||
// alert buttons
|
// alert buttons
|
||||||
cancelButton = isAndroid() ? element(by.text('CANCEL')) : element(by.label('Cancel')).atIndex(1);
|
cancelButton = isAndroid() ? element(by.text('CANCEL')) : element(by.label('Cancel')).atIndex(1);
|
||||||
confirmButton = isAndroid() ? element(by.text('CONFIRM')) : element(by.label('Confirm')).atIndex(1);
|
confirmButton = isAndroid() ? element(by.text('CONFIRM')) : element(by.label('Confirm')).atIndex(1);
|
||||||
|
doneButton = isAndroid() ? element(by.text('DONE')) : element(by.label('Done')).atIndex(1);
|
||||||
deleteButton = isAndroid() ? element(by.text('DELETE')) : element(by.label('Delete')).atIndex(0);
|
deleteButton = isAndroid() ? element(by.text('DELETE')) : element(by.label('Delete')).atIndex(0);
|
||||||
deleteScheduledMessageButton = isAndroid() ? element(by.text('DELETE')) : element(by.label('Delete')).atIndex(1);
|
deleteScheduledMessageButton = isAndroid() ? element(by.text('DELETE')) : element(by.label('Delete')).atIndex(1);
|
||||||
leaveButton = isAndroid() ? element(by.text('LEAVE')) : element(by.label('Leave')).atIndex(0);
|
leaveButton = isAndroid() ? element(by.text('LEAVE')) : element(by.label('Leave')).atIndex(0);
|
||||||
|
|
|
||||||
77
detox/e2e/support/ui/screen/add_members.ts
Normal file
77
detox/e2e/support/ui/screen/add_members.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {ProfilePicture} from '@support/ui/component';
|
||||||
|
import {timeouts, wait} from '@support/utils';
|
||||||
|
import {expect} from 'detox';
|
||||||
|
|
||||||
|
class AddMembersScreen {
|
||||||
|
testID = {
|
||||||
|
addMembersScreen: 'add_members.screen',
|
||||||
|
searchInput: 'add_members.search_bar.search.input',
|
||||||
|
userList: 'add_members.user_list',
|
||||||
|
userItemPrefix: 'add_members.user_list.user_item.',
|
||||||
|
addButton: 'add_members.add.button',
|
||||||
|
addChannelMembersButton: 'add_members.selected.start.button',
|
||||||
|
backButton: 'screen.back.button',
|
||||||
|
tutorialTooltip: 'tutorial_highlight',
|
||||||
|
};
|
||||||
|
|
||||||
|
addChannelMembersButton = element(by.id(this.testID.addChannelMembersButton));
|
||||||
|
addMembersScreen = element(by.id(this.testID.addMembersScreen));
|
||||||
|
searchInput = element(by.id(this.testID.searchInput));
|
||||||
|
userList = element(by.id(this.testID.userList));
|
||||||
|
addButton = element(by.id(this.testID.addButton));
|
||||||
|
backButton = element(by.id(this.testID.backButton));
|
||||||
|
tutorialTooltip = element(by.id(this.testID.tutorialTooltip));
|
||||||
|
|
||||||
|
getUserItem = (userId: string) => {
|
||||||
|
return element(by.id(`${this.testID.userItemPrefix}${userId}.${userId}`));
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserItemProfilePicture = (userId: string) => {
|
||||||
|
return element(ProfilePicture.getProfilePictureItemMatcher(this.testID.userItemPrefix, userId));
|
||||||
|
};
|
||||||
|
|
||||||
|
getUserItemDisplayName = (userId: string) => {
|
||||||
|
return element(by.id(`${this.testID.userItemPrefix}${userId}.${userId}.display_name`));
|
||||||
|
};
|
||||||
|
|
||||||
|
toBeVisible = async () => {
|
||||||
|
await waitFor(this.searchInput).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||||
|
return this.addMembersScreen;
|
||||||
|
};
|
||||||
|
|
||||||
|
close = async () => {
|
||||||
|
await this.backButton.tap();
|
||||||
|
await expect(this.addMembersScreen).not.toBeVisible();
|
||||||
|
};
|
||||||
|
|
||||||
|
dismissTutorial = async () => {
|
||||||
|
try {
|
||||||
|
const tutorialText = element(by.text('Long-press on an item to view a user\'s profile'));
|
||||||
|
await waitFor(tutorialText).toBeVisible().withTimeout(timeouts.TWO_SEC);
|
||||||
|
await tutorialText.tap();
|
||||||
|
await wait(timeouts.HALF_SEC);
|
||||||
|
} catch {
|
||||||
|
// Tutorial not visible, continue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
searchAndAddUser = async (username: string, userId: string) => {
|
||||||
|
await this.searchInput.typeText(`${username}\n`);
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
const userItem = this.getUserItem(userId);
|
||||||
|
await waitFor(userItem).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||||
|
await userItem.tap();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await waitFor(this.addChannelMembersButton).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||||
|
await this.addChannelMembersButton.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMembersScreen = new AddMembersScreen();
|
||||||
|
export default addMembersScreen;
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
PostOptionsScreen,
|
PostOptionsScreen,
|
||||||
ThreadScreen,
|
ThreadScreen,
|
||||||
} from '@support/ui/screen';
|
} from '@support/ui/screen';
|
||||||
import {isAndroid, isIos, timeouts, wait} from '@support/utils';
|
import {isIos, timeouts, wait} from '@support/utils';
|
||||||
import {expect} from 'detox';
|
import {expect} from 'detox';
|
||||||
|
|
||||||
class ChannelScreen {
|
class ChannelScreen {
|
||||||
|
|
@ -153,13 +153,13 @@ class ChannelScreen {
|
||||||
dismissScheduledPostTooltip = async () => {
|
dismissScheduledPostTooltip = async () => {
|
||||||
// Try to close scheduled post tooltip if it exists (try both regular and admin account versions)
|
// Try to close scheduled post tooltip if it exists (try both regular and admin account versions)
|
||||||
try {
|
try {
|
||||||
await waitFor(this.scheduledPostTooltipCloseButton).toBeVisible().withTimeout(timeouts.ONE_SEC);
|
await waitFor(this.scheduledPostTooltipCloseButton).toBeVisible().withTimeout(timeouts.FOUR_SEC);
|
||||||
await this.scheduledPostTooltipCloseButton.tap();
|
await this.scheduledPostTooltipCloseButton.tap();
|
||||||
await wait(timeouts.HALF_SEC);
|
await wait(timeouts.HALF_SEC);
|
||||||
} catch {
|
} catch {
|
||||||
// Try admin account version
|
// Try admin account version
|
||||||
try {
|
try {
|
||||||
await waitFor(this.scheduledPostTooltipCloseButtonAdminAccount).toBeVisible().withTimeout(timeouts.ONE_SEC);
|
await waitFor(this.scheduledPostTooltipCloseButtonAdminAccount).toBeVisible().withTimeout(timeouts.FOUR_SEC);
|
||||||
await this.scheduledPostTooltipCloseButtonAdminAccount.tap();
|
await this.scheduledPostTooltipCloseButtonAdminAccount.tap();
|
||||||
await wait(timeouts.HALF_SEC);
|
await wait(timeouts.HALF_SEC);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -204,23 +204,6 @@ 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) => {
|
openPostOptionsFor = async (postId: string, text: string) => {
|
||||||
const {postListPostItem} = this.getPostListPostItem(postId, text);
|
const {postListPostItem} = this.getPostListPostItem(postId, text);
|
||||||
await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||||
|
|
@ -243,7 +226,7 @@ class ChannelScreen {
|
||||||
// # Post message
|
// # Post message
|
||||||
await this.postInput.tap();
|
await this.postInput.tap();
|
||||||
await this.postInput.clearText();
|
await this.postInput.clearText();
|
||||||
await this.postInput.typeText(message);
|
await this.postInput.replaceText(`${message}\n`);
|
||||||
await this.tapSendButton();
|
await this.tapSendButton();
|
||||||
await wait(timeouts.TWO_SEC);
|
await wait(timeouts.TWO_SEC);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,58 @@ class ChannelInfoScreen {
|
||||||
unarchivePublicChannel = async ({confirm = true} = {}) => {
|
unarchivePublicChannel = async ({confirm = true} = {}) => {
|
||||||
await this.unarchiveChannel(Alert.unarchivePublicChannelTitle, {confirm});
|
await this.unarchiveChannel(Alert.unarchivePublicChannelTitle, {confirm});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
copyChannelHeader = async (headerText: string) => {
|
||||||
|
// Long press on header text
|
||||||
|
await element(by.text(headerText)).longPress();
|
||||||
|
|
||||||
|
// Wait for bottom sheet
|
||||||
|
await waitFor(element(by.id('channel_info.extra.header.bottom_sheet.copy_header_text'))).
|
||||||
|
toBeVisible().
|
||||||
|
withTimeout(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// Tap copy option (actual copy action)
|
||||||
|
await element(by.id('channel_info.extra.header.bottom_sheet.copy_header_text')).tap();
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelCopyChannelHeader = async (headerText: string) => {
|
||||||
|
// Long press on header text
|
||||||
|
await element(by.text(headerText)).longPress();
|
||||||
|
|
||||||
|
// Wait for bottom sheet
|
||||||
|
await waitFor(element(by.id('channel_info.extra.header.bottom_sheet.copy_header_text'))).
|
||||||
|
toBeVisible().
|
||||||
|
withTimeout(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// Cancel
|
||||||
|
await element(by.id('channel_info.extra.header.bottom_sheet.cancel')).tap();
|
||||||
|
};
|
||||||
|
|
||||||
|
copyChannelPurpose = async (purposeText: string) => {
|
||||||
|
// Long press on purpose text
|
||||||
|
await element(by.text(purposeText)).longPress();
|
||||||
|
|
||||||
|
// Wait for bottom sheet
|
||||||
|
await waitFor(element(by.id('channel_info.title.public_private.bottom_sheet.copy_purpose'))).
|
||||||
|
toBeVisible().
|
||||||
|
withTimeout(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// Tap copy option
|
||||||
|
await element(by.id('channel_info.title.public_private.bottom_sheet.copy_purpose')).tap();
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelCopyChannelPurpose = async (purposeText: string) => {
|
||||||
|
// Long press on purpose text
|
||||||
|
await element(by.text(purposeText)).longPress();
|
||||||
|
|
||||||
|
// Wait for bottom sheet
|
||||||
|
await waitFor(element(by.id('channel_info.title.public_private.bottom_sheet.copy_purpose'))).
|
||||||
|
toBeVisible().
|
||||||
|
withTimeout(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// Cancel
|
||||||
|
await element(by.id('channel_info.title.public_private.bottom_sheet.cancel')).tap();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const channelInfoScreen = new ChannelInfoScreen();
|
const channelInfoScreen = new ChannelInfoScreen();
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ class CreateDirectMessageScreen {
|
||||||
await ChannelListScreen.headerPlusButton.tap();
|
await ChannelListScreen.headerPlusButton.tap();
|
||||||
await wait(timeouts.ONE_SEC);
|
await wait(timeouts.ONE_SEC);
|
||||||
await ChannelListScreen.openDirectMessageItem.tap();
|
await ChannelListScreen.openDirectMessageItem.tap();
|
||||||
await wait(timeouts.TEN_SEC);
|
await wait(timeouts.FOUR_SEC);
|
||||||
return this.toBeVisible();
|
return this.toBeVisible();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import AboutScreen from './about';
|
import AboutScreen from './about';
|
||||||
import AccountScreen from './account';
|
import AccountScreen from './account';
|
||||||
|
import AddMembersScreen from './add_members';
|
||||||
import AdvancedSettingsScreen from './advanced_settings';
|
import AdvancedSettingsScreen from './advanced_settings';
|
||||||
import AutoResponderNotificationSettingsScreen from './auto_responder_notification_settings';
|
import AutoResponderNotificationSettingsScreen from './auto_responder_notification_settings';
|
||||||
import BrowseChannelsScreen from './browse_channels';
|
import BrowseChannelsScreen from './browse_channels';
|
||||||
|
|
@ -55,6 +56,7 @@ import UserProfileScreen from './user_profile';
|
||||||
export {
|
export {
|
||||||
AboutScreen,
|
AboutScreen,
|
||||||
AccountScreen,
|
AccountScreen,
|
||||||
|
AddMembersScreen,
|
||||||
AdvancedSettingsScreen,
|
AdvancedSettingsScreen,
|
||||||
AutoResponderNotificationSettingsScreen,
|
AutoResponderNotificationSettingsScreen,
|
||||||
BrowseChannelsScreen,
|
BrowseChannelsScreen,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {serverOneUrl} from '@support/test_config';
|
||||||
import {ChannelListScreen, ServerScreen} from '@support/ui/screen';
|
import {ChannelListScreen, ServerScreen} from '@support/ui/screen';
|
||||||
import {isAndroid, retryWithReload, timeouts, wait} from '@support/utils';
|
import {isAndroid, retryWithReload, timeouts, wait} from '@support/utils';
|
||||||
import {expect} from 'detox';
|
import {expect} from 'detox';
|
||||||
|
|
@ -79,7 +80,15 @@ class LoginScreen {
|
||||||
while (attempt < maxAttempts) {
|
while (attempt < maxAttempts) {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await retryWithReload(() => this.loginWithRetryIfStuck(user));
|
await retryWithReload(
|
||||||
|
async () => {
|
||||||
|
return this.loginWithRetryIfStuck(user);
|
||||||
|
},
|
||||||
|
3, // retries
|
||||||
|
ServerScreen,
|
||||||
|
serverOneUrl, // serverUrl - reconnect after reload
|
||||||
|
'Server 1', // serverDisplayName
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastError = error;
|
lastError = error;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {ProfilePicture} from '@support/ui/component';
|
import {Alert, ProfilePicture} from '@support/ui/component';
|
||||||
import {ChannelInfoScreen} from '@support/ui/screen';
|
import {ChannelInfoScreen} from '@support/ui/screen';
|
||||||
import {isIos, timeouts, wait} from '@support/utils';
|
import {isIos, timeouts, wait} from '@support/utils';
|
||||||
import {expect, waitFor} from 'detox';
|
import {expect, waitFor} from 'detox';
|
||||||
|
|
@ -10,20 +10,28 @@ class ManageChannelMembersScreen {
|
||||||
testID = {
|
testID = {
|
||||||
backButton: 'screen.back.button',
|
backButton: 'screen.back.button',
|
||||||
manageMembersScreen: 'manage_members.screen',
|
manageMembersScreen: 'manage_members.screen',
|
||||||
|
channelMembersScreen: 'channel_members.screen',
|
||||||
manageDoneButton: 'manage_members.button', // Same button, text changes between "Manage" and "Done"
|
manageDoneButton: 'manage_members.button', // Same button, text changes between "Manage" and "Done"
|
||||||
searchBar: 'manage_members.search_bar',
|
searchBar: 'manage_members.search_bar',
|
||||||
|
searchInput: 'manage_members.search_bar.search.input',
|
||||||
userList: 'manage_members.user_list',
|
userList: 'manage_members.user_list',
|
||||||
userItemPrefix: 'create_direct_message.user_list.user_item.',
|
userItemPrefix: 'manage_members.user_list.user_item.',
|
||||||
|
removeButton: 'channel.remove_member',
|
||||||
notice: 'manage_members.notice',
|
notice: 'manage_members.notice',
|
||||||
tutorialHighlight: 'tutorial_highlight',
|
tutorialHighlight: 'tutorial_highlight',
|
||||||
tutorialSwipeLeft: 'tutorial_swipe_left',
|
tutorialSwipeLeft: 'tutorial_swipe_left',
|
||||||
|
gmMemberSectionList: 'manage_members.user_list.section_list',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
gmMemberSectionList = element(by.id(this.testID.gmMemberSectionList));
|
||||||
manageMembersScreen = element(by.id(this.testID.manageMembersScreen));
|
manageMembersScreen = element(by.id(this.testID.manageMembersScreen));
|
||||||
|
channelMembersScreen = element(by.id(this.testID.channelMembersScreen));
|
||||||
manageButton = element(by.id(this.testID.manageDoneButton));
|
manageButton = element(by.id(this.testID.manageDoneButton));
|
||||||
doneButton = element(by.id(this.testID.manageDoneButton)); // Same element as manageButton, different text
|
doneButton = element(by.id(this.testID.manageDoneButton)); // Same element as manageButton, different text
|
||||||
searchBar = element(by.id(this.testID.searchBar));
|
searchBar = element(by.id(this.testID.searchBar));
|
||||||
|
searchInput = element(by.id(this.testID.searchInput));
|
||||||
userList = element(by.id(this.testID.userList));
|
userList = element(by.id(this.testID.userList));
|
||||||
|
removeButton = element(by.id(this.testID.removeButton));
|
||||||
notice = element(by.id(this.testID.notice));
|
notice = element(by.id(this.testID.notice));
|
||||||
tutorialHighlight = element(by.id(this.testID.tutorialHighlight));
|
tutorialHighlight = element(by.id(this.testID.tutorialHighlight));
|
||||||
tutorialSwipeLeft = element(by.id(this.testID.tutorialSwipeLeft));
|
tutorialSwipeLeft = element(by.id(this.testID.tutorialSwipeLeft));
|
||||||
|
|
@ -91,6 +99,26 @@ class ManageChannelMembersScreen {
|
||||||
console.log('Tutorial element not visible, skipping action:');
|
console.log('Tutorial element not visible, skipping action:');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
searchAndRemoveUser = async (username: string, userId: string) => {
|
||||||
|
await expect(this.searchInput).toBeVisible();
|
||||||
|
await this.searchInput.typeText(`${username}`);
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
const userItem = this.getUserItem(userId);
|
||||||
|
await waitFor(userItem).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||||
|
await userItem.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
await expect(this.removeButton).toBeVisible();
|
||||||
|
await this.removeButton.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
await Alert.removeButton.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
await device.pressBack();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const manageChannelMembersScreen = new ManageChannelMembersScreen();
|
const manageChannelMembersScreen = new ManageChannelMembersScreen();
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ class ServerScreen {
|
||||||
|
|
||||||
// The bridge can be busy during login transition, use waitFor without idle check
|
// The bridge can be busy during login transition, use waitFor without idle check
|
||||||
const timeout = isAndroid() ? timeouts.ONE_MIN : timeouts.HALF_MIN;
|
const timeout = isAndroid() ? timeouts.ONE_MIN : timeouts.HALF_MIN;
|
||||||
await waitFor(this.usernameInput).toBeVisible().withTimeout(timeout);
|
await waitForElementToBeVisible(this.usernameInput, timeout, timeouts.ONE_SEC);
|
||||||
};
|
};
|
||||||
|
|
||||||
close = async () => {
|
close = async () => {
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ class ThreadScreen {
|
||||||
postMessage = async (message: string) => {
|
postMessage = async (message: string) => {
|
||||||
// # Post message
|
// # Post message
|
||||||
await this.postInput.tap();
|
await this.postInput.tap();
|
||||||
await this.postInput.replaceText(message);
|
await this.postInput.replaceText(`${message}\n`);
|
||||||
await this.tapSendButton();
|
await this.tapSendButton();
|
||||||
|
|
||||||
// # Wait for message to be rendered
|
// # Wait for message to be rendered
|
||||||
|
|
|
||||||
|
|
@ -24,58 +24,6 @@ export async function waitForLoadingSpinner(testID: string, timeout = 10000): Pr
|
||||||
await waitFor(element(by.id(testID))).not.toBeVisible().withTimeout(timeout);
|
await waitFor(element(by.id(testID))).not.toBeVisible().withTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Safely dismiss keyboard with retry logic
|
|
||||||
* Prevents keyboard input session invalidation errors
|
|
||||||
* @param maxAttempts - Maximum number of dismissal attempts
|
|
||||||
*/
|
|
||||||
export async function dismissKeyboardSafely(maxAttempts = 3): Promise<void> {
|
|
||||||
/* eslint-disable no-await-in-loop, no-console */
|
|
||||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
||||||
try {
|
|
||||||
await device.pressBack(); // Works on both iOS (dismisses keyboard) and Android
|
|
||||||
// Wait a bit for keyboard to dismiss
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
if (attempt === maxAttempts) {
|
|
||||||
// On final attempt, log warning but don't throw to prevent test failures
|
|
||||||
console.warn(`[dismissKeyboardSafely] Failed to dismiss keyboard after ${maxAttempts} attempts:`, error);
|
|
||||||
} else {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* eslint-enable no-await-in-loop, no-console */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform text input with keyboard state verification
|
|
||||||
* Ensures text input session is valid before typing
|
|
||||||
* @param element - Detox element to type into
|
|
||||||
* @param text - Text to type
|
|
||||||
*/
|
|
||||||
export async function typeTextSafely(elementToType: Detox.IndexableNativeElement, text: string): Promise<void> {
|
|
||||||
try {
|
|
||||||
// Tap element to ensure it's focused
|
|
||||||
await elementToType.tap();
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
||||||
|
|
||||||
// Type text
|
|
||||||
await elementToType.typeText(text);
|
|
||||||
} catch (error) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn('[typeTextSafely] Text input failed, retrying:', error);
|
|
||||||
|
|
||||||
// Retry once with dismissal and refocus
|
|
||||||
await dismissKeyboardSafely();
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
||||||
await elementToType.tap();
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
||||||
await elementToType.typeText(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retry an element visibility check with exponential backoff
|
* Retry an element visibility check with exponential backoff
|
||||||
* Helps handle race conditions during navigation and UI transitions
|
* Helps handle race conditions during navigation and UI transitions
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,18 @@ export const timeouts = {
|
||||||
* Retry a function with reload
|
* Retry a function with reload
|
||||||
* @param {function} func - function to retry
|
* @param {function} func - function to retry
|
||||||
* @param {number} retries - number of retries
|
* @param {number} retries - number of retries
|
||||||
|
* @param {string} serverUrl - optional server URL to reconnect after reload
|
||||||
|
* @param {string} serverDisplayName - optional server display name to reconnect after reload
|
||||||
* @return {Promise<void>} - promise that resolves when the function succeeds
|
* @return {Promise<void>} - promise that resolves when the function succeeds
|
||||||
* @throws {Error} - if the function fails after the specified number of retries
|
* @throws {Error} - if the function fails after the specified number of retries
|
||||||
*/
|
*/
|
||||||
export async function retryWithReload(func: () => Promise<void>, retries: number = 2): Promise<void> {
|
export async function retryWithReload(
|
||||||
|
func: () => Promise<void>,
|
||||||
|
retries: number = 2,
|
||||||
|
ServerScreen: any,
|
||||||
|
serverUrl?: string,
|
||||||
|
serverDisplayName?: string,
|
||||||
|
): Promise<void> {
|
||||||
for (let attempt = 0; attempt <= retries; attempt++) {
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
|
@ -97,7 +105,14 @@ export async function retryWithReload(func: () => Promise<void>, retries: number
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await device.reloadReactNative();
|
await device.reloadReactNative();
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await new Promise((res) => setTimeout(res, 3000));
|
await new Promise((res) => setTimeout(res, 10000));
|
||||||
|
|
||||||
|
// If server connection details provided, reconnect after reload
|
||||||
|
if (serverUrl && serverDisplayName) {
|
||||||
|
// Dynamically import to avoid circular dependencies
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await ServerScreen.connectToServer(serverUrl, serverDisplayName);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -394,7 +394,6 @@ describe('Account - Custom Status', () => {
|
||||||
await ChannelListScreen.open();
|
await ChannelListScreen.open();
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(messageText);
|
await ChannelScreen.postMessage(messageText);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem, postListPostItemHeaderDisplayName} =
|
const {postListPostItem, postListPostItemHeaderDisplayName} =
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ describe('Autocomplete - Edit Post', () => {
|
||||||
const message = `Messsage ${getRandomId()}`;
|
const message = `Messsage ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, channel.name);
|
await ChannelScreen.open(channelsCategory, channel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, channel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, channel.id);
|
||||||
await ChannelScreen.openPostOptionsFor(post.id, message);
|
await ChannelScreen.openPostOptionsFor(post.id, message);
|
||||||
await EditPostScreen.open();
|
await EditPostScreen.open();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
// *******************************************************************
|
||||||
|
// - [#] indicates a test step (e.g. # Go to a screen)
|
||||||
|
// - [*] indicates an assertion (e.g. * Check the title)
|
||||||
|
// - Use element testID when selecting an element. Create one if none.
|
||||||
|
// *******************************************************************
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Cases Included:
|
||||||
|
* - MM-T3201: RN apps Create public channel
|
||||||
|
* - MM-T3203: RN apps Create private channel
|
||||||
|
* - MM-T3199: RN apps Edit public channel
|
||||||
|
* - MM-T3206: RN apps Edit private channel
|
||||||
|
* - MM-T854: RN apps Channel can be created using 2 non-latin characters
|
||||||
|
* - MM-T867: RN apps Copying channel header text
|
||||||
|
* - MM-T865: RN apps Copying channel purpose text
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Channel, Setup} from '@support/server_api';
|
||||||
|
import {
|
||||||
|
serverOneUrl,
|
||||||
|
siteOneUrl,
|
||||||
|
} from '@support/test_config';
|
||||||
|
import {
|
||||||
|
ChannelInfoScreen,
|
||||||
|
ChannelListScreen,
|
||||||
|
ChannelScreen,
|
||||||
|
CreateOrEditChannelScreen,
|
||||||
|
LoginScreen,
|
||||||
|
HomeScreen,
|
||||||
|
ServerScreen,
|
||||||
|
} from '@support/ui/screen';
|
||||||
|
import {getRandomId, timeouts, wait} from '@support/utils';
|
||||||
|
import {expect} from 'detox';
|
||||||
|
|
||||||
|
describe('Channels', () => {
|
||||||
|
const serverOneDisplayName = 'Server 1';
|
||||||
|
const channelsCategory = 'channels';
|
||||||
|
let testUser: any;
|
||||||
|
let testTeam: any;
|
||||||
|
let publicChannelName: string;
|
||||||
|
let publicChannelDisplayName: string;
|
||||||
|
let privateChannelName: string;
|
||||||
|
let privateChannelDisplayName: string;
|
||||||
|
let channelWithMetadata: any;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const {user, team} = await Setup.apiInit(siteOneUrl);
|
||||||
|
testUser = user;
|
||||||
|
testTeam = team;
|
||||||
|
|
||||||
|
// Create a channel with header and purpose for copy tests
|
||||||
|
const {channel: metadataChannel} = await Channel.apiCreateChannel(siteOneUrl, {
|
||||||
|
teamId: testTeam.id,
|
||||||
|
name: `channel-metadata-${getRandomId()}`,
|
||||||
|
displayName: `Channel Metadata ${getRandomId()}`,
|
||||||
|
type: 'O',
|
||||||
|
header: 'This is test header',
|
||||||
|
purpose: 'Test purpose for copying',
|
||||||
|
});
|
||||||
|
channelWithMetadata = metadataChannel;
|
||||||
|
|
||||||
|
await wait(timeouts.THREE_SEC);
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, channelWithMetadata.id);
|
||||||
|
await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName);
|
||||||
|
await LoginScreen.login(testUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await HomeScreen.logout();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3201 - RN apps Create public channel', async () => {
|
||||||
|
publicChannelName = `channel-${getRandomId()}`;
|
||||||
|
publicChannelDisplayName = publicChannelName.replace(/-/g, ' ');
|
||||||
|
const channelPurpose = 'This is a test purpose for the channel';
|
||||||
|
const channelHeader = ':taco:';
|
||||||
|
|
||||||
|
await ChannelListScreen.headerPlusButton.tap();
|
||||||
|
await ChannelListScreen.createNewChannelItem.tap();
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.toBeVisible();
|
||||||
|
await expect(CreateOrEditChannelScreen.displayNameInput).toBeVisible();
|
||||||
|
await expect(CreateOrEditChannelScreen.createButton).toBeVisible();
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.displayNameInput.replaceText(publicChannelDisplayName);
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.purposeInput.tap();
|
||||||
|
await CreateOrEditChannelScreen.purposeInput.replaceText(channelPurpose);
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.headerInput.tap();
|
||||||
|
await CreateOrEditChannelScreen.headerInput.replaceText(channelHeader);
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.createButton.tap();
|
||||||
|
await ChannelScreen.dismissScheduledPostTooltip();
|
||||||
|
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
await expect(ChannelScreen.headerTitle).toHaveText(publicChannelDisplayName);
|
||||||
|
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await expect(ChannelInfoScreen.publicPrivateTitleDisplayName).toHaveText(publicChannelDisplayName);
|
||||||
|
await expect(ChannelInfoScreen.publicPrivateTitlePurpose).toHaveText(channelPurpose);
|
||||||
|
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3203 - RN apps Create private channel', async () => {
|
||||||
|
privateChannelName = `private-channel-${getRandomId()}`;
|
||||||
|
privateChannelDisplayName = privateChannelName.replace(/-/g, ' ');
|
||||||
|
const channelPurpose = 'This is a private test channel purpose';
|
||||||
|
const channelHeader = 'Private channel header';
|
||||||
|
|
||||||
|
await ChannelListScreen.headerPlusButton.tap();
|
||||||
|
await ChannelListScreen.createNewChannelItem.tap();
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.toBeVisible();
|
||||||
|
await expect(CreateOrEditChannelScreen.displayNameInput).toBeVisible();
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.toggleMakePrivateOn();
|
||||||
|
await expect(CreateOrEditChannelScreen.makePrivateToggledOn).toBeVisible();
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.displayNameInput.replaceText(privateChannelDisplayName);
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.purposeInput.tap();
|
||||||
|
await CreateOrEditChannelScreen.purposeInput.replaceText(channelPurpose);
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.headerInput.tap();
|
||||||
|
await CreateOrEditChannelScreen.headerInput.replaceText(channelHeader);
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.createButton.tap();
|
||||||
|
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
await expect(ChannelScreen.headerTitle).toHaveText(privateChannelDisplayName);
|
||||||
|
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await expect(ChannelInfoScreen.publicPrivateTitleDisplayName).toHaveText(privateChannelDisplayName);
|
||||||
|
await expect(ChannelInfoScreen.publicPrivateTitlePurpose).toHaveText(channelPurpose);
|
||||||
|
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3199 - RN apps Edit public channel', async () => {
|
||||||
|
await ChannelScreen.open(channelsCategory, publicChannelName);
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await CreateOrEditChannelScreen.openEditChannel();
|
||||||
|
await expect(CreateOrEditChannelScreen.saveButton).toBeVisible();
|
||||||
|
|
||||||
|
const updatedDisplayName = publicChannelDisplayName + ' edited';
|
||||||
|
await CreateOrEditChannelScreen.displayNameInput.clearText();
|
||||||
|
await CreateOrEditChannelScreen.displayNameInput.replaceText(updatedDisplayName);
|
||||||
|
|
||||||
|
const purposeText = 'Updated purpose for this channel';
|
||||||
|
await CreateOrEditChannelScreen.purposeInput.tap();
|
||||||
|
await CreateOrEditChannelScreen.purposeInput.clearText();
|
||||||
|
await CreateOrEditChannelScreen.purposeInput.replaceText(purposeText);
|
||||||
|
|
||||||
|
const headerLine1 = 'First line of header';
|
||||||
|
await CreateOrEditChannelScreen.headerInput.tap();
|
||||||
|
await CreateOrEditChannelScreen.headerInput.clearText();
|
||||||
|
await CreateOrEditChannelScreen.headerInput.replaceText(headerLine1 + '\n');
|
||||||
|
|
||||||
|
const headerLine2 = 'Second line of header';
|
||||||
|
await CreateOrEditChannelScreen.headerInput.replaceText(headerLine2);
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.saveButton.tap();
|
||||||
|
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
await ChannelInfoScreen.toBeVisible();
|
||||||
|
await expect(ChannelInfoScreen.publicPrivateTitleDisplayName).toHaveText(updatedDisplayName);
|
||||||
|
await expect(ChannelInfoScreen.publicPrivateTitlePurpose).toHaveText(purposeText);
|
||||||
|
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3206 - RN apps Edit private channel', async () => {
|
||||||
|
await ChannelScreen.open(channelsCategory, privateChannelName);
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await CreateOrEditChannelScreen.openEditChannel();
|
||||||
|
await expect(CreateOrEditChannelScreen.saveButton).toBeVisible();
|
||||||
|
|
||||||
|
const updatedDisplayName = privateChannelDisplayName + ' edited';
|
||||||
|
await CreateOrEditChannelScreen.displayNameInput.clearText();
|
||||||
|
await CreateOrEditChannelScreen.displayNameInput.replaceText(updatedDisplayName);
|
||||||
|
|
||||||
|
const purposeText = 'Updated purpose for private channel';
|
||||||
|
await CreateOrEditChannelScreen.purposeInput.tap();
|
||||||
|
await CreateOrEditChannelScreen.purposeInput.clearText();
|
||||||
|
await CreateOrEditChannelScreen.purposeInput.replaceText(purposeText);
|
||||||
|
|
||||||
|
const headerLine1 = 'First line of header';
|
||||||
|
await CreateOrEditChannelScreen.headerInput.tap();
|
||||||
|
await CreateOrEditChannelScreen.headerInput.clearText();
|
||||||
|
await CreateOrEditChannelScreen.headerInput.replaceText(headerLine1 + '\n');
|
||||||
|
|
||||||
|
const headerLine2 = 'Second line of header';
|
||||||
|
await CreateOrEditChannelScreen.headerInput.replaceText(headerLine2);
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.saveButton.tap();
|
||||||
|
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
await ChannelInfoScreen.toBeVisible();
|
||||||
|
await expect(ChannelInfoScreen.publicPrivateTitleDisplayName).toHaveText(updatedDisplayName);
|
||||||
|
await expect(ChannelInfoScreen.publicPrivateTitlePurpose).toHaveText(purposeText);
|
||||||
|
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T854 - RN apps Channel can be created using 2 non-latin characters', async () => {
|
||||||
|
await ChannelListScreen.headerPlusButton.tap();
|
||||||
|
await ChannelListScreen.createNewChannelItem.tap();
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.toBeVisible();
|
||||||
|
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
await CreateOrEditChannelScreen.toBeVisible();
|
||||||
|
|
||||||
|
const nonLatinChannelName = 'ÁÜ';
|
||||||
|
await CreateOrEditChannelScreen.displayNameInput.replaceText(nonLatinChannelName);
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await CreateOrEditChannelScreen.createButton.tap();
|
||||||
|
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
await ChannelScreen.dismissScheduledPostTooltip();
|
||||||
|
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
await expect(ChannelScreen.headerTitle).toHaveText(nonLatinChannelName);
|
||||||
|
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T867 - RN apps Copying channel header text', async () => {
|
||||||
|
// # Navigate to the channel with metadata
|
||||||
|
await ChannelScreen.open(channelsCategory, channelWithMetadata.name);
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
// * Verify header is visible
|
||||||
|
await expect(ChannelInfoScreen.extraHeader).toBeVisible();
|
||||||
|
|
||||||
|
// # Test long-press and cancel flow
|
||||||
|
await ChannelInfoScreen.cancelCopyChannelHeader(channelWithMetadata.header);
|
||||||
|
|
||||||
|
// * Verify we're still on channel info screen
|
||||||
|
await expect(ChannelInfoScreen.channelInfoScreen).toBeVisible();
|
||||||
|
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T865 - RN apps Copying channel purpose text', async () => {
|
||||||
|
// # Navigate to the channel with metadata
|
||||||
|
await ChannelScreen.open(channelsCategory, channelWithMetadata.name);
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
// * Verify purpose is visible
|
||||||
|
await expect(ChannelInfoScreen.publicPrivateTitlePurpose).toBeVisible();
|
||||||
|
|
||||||
|
// # Test long-press and cancel flow
|
||||||
|
await ChannelInfoScreen.cancelCopyChannelPurpose(channelWithMetadata.purpose);
|
||||||
|
|
||||||
|
// * Verify we're still on channel info screen
|
||||||
|
await expect(ChannelInfoScreen.channelInfoScreen).toBeVisible();
|
||||||
|
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
// *******************************************************************
|
||||||
|
// - [#] indicates a test step (e.g. # Go to a screen)
|
||||||
|
// - [*] indicates an assertion (e.g. * Check the title)
|
||||||
|
// - Use element testID when selecting an element. Create one if none.
|
||||||
|
// *******************************************************************
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Cases Included:
|
||||||
|
* - MM-T3200: RN apps Join existing channel
|
||||||
|
* - MM-T3202: RN apps Leave public channel
|
||||||
|
* - MM-T3207: RN apps Leave private channel
|
||||||
|
* - MM-T3188: RN apps Search for public channel Cancel search join channel
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Channel, Setup} from '@support/server_api';
|
||||||
|
import {
|
||||||
|
serverOneUrl,
|
||||||
|
siteOneUrl,
|
||||||
|
} from '@support/test_config';
|
||||||
|
import {Alert} from '@support/ui/component';
|
||||||
|
import {
|
||||||
|
BrowseChannelsScreen,
|
||||||
|
ChannelInfoScreen,
|
||||||
|
ChannelListScreen,
|
||||||
|
ChannelScreen,
|
||||||
|
LoginScreen,
|
||||||
|
ServerScreen,
|
||||||
|
HomeScreen,
|
||||||
|
} from '@support/ui/screen';
|
||||||
|
import {timeouts, wait} from '@support/utils';
|
||||||
|
import {expect} from 'detox';
|
||||||
|
|
||||||
|
describe('Channels', () => {
|
||||||
|
const serverOneDisplayName = 'Server 1';
|
||||||
|
const channelsCategory = 'channels';
|
||||||
|
let testUser: any;
|
||||||
|
let testChannel: any;
|
||||||
|
let privateChannel: any;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const {user, channel, team} = await Setup.apiInit(siteOneUrl);
|
||||||
|
testUser = user;
|
||||||
|
testChannel = channel;
|
||||||
|
|
||||||
|
const {channel: privateChannelData} = await Channel.apiCreateChannel(siteOneUrl, {type: 'P', prefix: 'channel', teamId: team.id});
|
||||||
|
privateChannel = privateChannelData;
|
||||||
|
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, privateChannel.id);
|
||||||
|
await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName);
|
||||||
|
await LoginScreen.login(testUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await HomeScreen.logout();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3200 - RN apps Join existing channel', async () => {
|
||||||
|
|
||||||
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
const joinMessage = `${testUser.username} joined the channel`;
|
||||||
|
await waitFor(element(by.text(joinMessage).withAncestor(by.id('post_list')))).
|
||||||
|
toBeVisible();
|
||||||
|
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3202 - RN apps Leave public channel', async () => {
|
||||||
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await ChannelInfoScreen.scrollView.scrollTo('bottom');
|
||||||
|
await expect(ChannelInfoScreen.leaveChannelOption).toBeVisible();
|
||||||
|
await ChannelInfoScreen.leaveChannelOption.tap();
|
||||||
|
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
const leaveAlertTitle = 'Leave channel';
|
||||||
|
await expect(element(by.text(leaveAlertTitle))).toBeVisible();
|
||||||
|
await expect(element(by.text(`Are you sure you want to leave the public channel ${testChannel.display_name}? You can always rejoin.`))).toBeVisible();
|
||||||
|
|
||||||
|
await Alert.leaveButton.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
await ChannelListScreen.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3207 - RN apps Leave private channel', async () => {
|
||||||
|
|
||||||
|
await ChannelScreen.open(channelsCategory, privateChannel.name);
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await ChannelInfoScreen.scrollView.scrollTo('bottom');
|
||||||
|
|
||||||
|
await expect(ChannelInfoScreen.leaveChannelOption).toBeVisible();
|
||||||
|
await ChannelInfoScreen.leaveChannelOption.tap();
|
||||||
|
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
await expect(element(by.text('Leave channel'))).toBeVisible();
|
||||||
|
await expect(element(by.text(`Are you sure you want to leave the private channel ${privateChannel.display_name}? You cannot rejoin the channel unless you're invited again.`))).toBeVisible();
|
||||||
|
await Alert.leaveButton.tap();
|
||||||
|
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
await ChannelListScreen.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3188 - RN apps Search for public channel Cancel search join channel', async () => {
|
||||||
|
const channelName = testChannel.name;
|
||||||
|
|
||||||
|
await BrowseChannelsScreen.open();
|
||||||
|
|
||||||
|
const searchTerm = channelName.substring(0, 4);
|
||||||
|
await BrowseChannelsScreen.searchInput.typeText(searchTerm);
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
await expect(BrowseChannelsScreen.getChannelItemDisplayName(testChannel.name)).toHaveText(testChannel.display_name);
|
||||||
|
await BrowseChannelsScreen.getChannelItem(testChannel.name).multiTap(2);
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
await expect(ChannelScreen.headerTitle).toHaveText(testChannel.display_name);
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,332 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
// *******************************************************************
|
||||||
|
// - [#] indicates a test step (e.g. # Go to a screen)
|
||||||
|
// - [*] indicates an assertion (e.g. * Check the title)
|
||||||
|
// - Use element testID when selecting an element. Create one if none.
|
||||||
|
// *******************************************************************
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Cases Included:
|
||||||
|
* - MM-T3195: RN apps Add members to channel
|
||||||
|
* - MM-T856: Add existing users to public channel from drop-down Add Members
|
||||||
|
* - MM-T3196: RN apps Manage members in channel
|
||||||
|
* - MM-T3204: RN apps Add user to private channel
|
||||||
|
* - MM-T3205: RN apps Remove user from private channel
|
||||||
|
* - MM-T878: RN apps View Members in GM
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Channel, Setup, Team, User} from '@support/server_api';
|
||||||
|
import {
|
||||||
|
serverOneUrl,
|
||||||
|
siteOneUrl,
|
||||||
|
} from '@support/test_config';
|
||||||
|
import {
|
||||||
|
AddMembersScreen,
|
||||||
|
ChannelInfoScreen,
|
||||||
|
ChannelScreen,
|
||||||
|
CreateDirectMessageScreen,
|
||||||
|
HomeScreen,
|
||||||
|
LoginScreen,
|
||||||
|
ManageChannelMembersScreen,
|
||||||
|
ServerScreen,
|
||||||
|
} from '@support/ui/screen';
|
||||||
|
import {isAndroid, timeouts, wait} from '@support/utils';
|
||||||
|
import {expect} from 'detox';
|
||||||
|
|
||||||
|
describe('Channels', () => {
|
||||||
|
const serverOneDisplayName = 'Server 1';
|
||||||
|
const channelsCategory = 'channels';
|
||||||
|
|
||||||
|
// Base setup (shared across all tests)
|
||||||
|
let testUser: any;
|
||||||
|
let testTeam: any;
|
||||||
|
let testChannel: any;
|
||||||
|
|
||||||
|
// Test-specific data
|
||||||
|
let addMemberUser: any; // For MM-T3195
|
||||||
|
let user2: any; // For MM-T856
|
||||||
|
let memberUser: any; // For MM-T3196
|
||||||
|
let privateChannel1: any; // For MM-T3204
|
||||||
|
let privUser: any; // For MM-T3204
|
||||||
|
let privateChannel2: any; // For MM-T3205
|
||||||
|
let removeMeUser: any; // For MM-T3205
|
||||||
|
let gmUser1: any; // For MM-T878
|
||||||
|
let gmUser2: any; // For MM-T878
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
// 1. Base setup (shared across all tests)
|
||||||
|
const {user, team, channel} = await Setup.apiInit(siteOneUrl);
|
||||||
|
testUser = user;
|
||||||
|
testTeam = team;
|
||||||
|
testChannel = channel;
|
||||||
|
|
||||||
|
// 2. Test 1 (MM-T3195): User for adding to channel
|
||||||
|
const {user: newUser1} = await User.apiCreateUser(siteOneUrl, {prefix: 'addmember'});
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, newUser1.id, testTeam.id);
|
||||||
|
addMemberUser = newUser1;
|
||||||
|
|
||||||
|
// 3. Test 2 (MM-T856): Another user for adding to channel
|
||||||
|
const {user: newUser2} = await User.apiCreateUser(siteOneUrl, {prefix: 'user2'});
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, newUser2.id, testTeam.id);
|
||||||
|
user2 = newUser2;
|
||||||
|
|
||||||
|
// 4. Test 3 (MM-T3196): User already in channel for removal
|
||||||
|
const {user: newUser3} = await User.apiCreateUser(siteOneUrl, {prefix: 'member'});
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, newUser3.id, testTeam.id);
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, newUser3.id, testChannel.id);
|
||||||
|
memberUser = newUser3;
|
||||||
|
|
||||||
|
// 5. Test 4 (MM-T3204): Private channel + user to add
|
||||||
|
const {channel: privChan1} = await Channel.apiCreateChannel(siteOneUrl, {
|
||||||
|
teamId: testTeam.id,
|
||||||
|
type: 'P',
|
||||||
|
});
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, privChan1.id);
|
||||||
|
privateChannel1 = privChan1;
|
||||||
|
|
||||||
|
const {user: newUser4} = await User.apiCreateUser(siteOneUrl, {prefix: 'privuser'});
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, newUser4.id, testTeam.id);
|
||||||
|
privUser = newUser4;
|
||||||
|
|
||||||
|
// 6. Test 5 (MM-T3205): Private channel + user already in it for removal
|
||||||
|
const {channel: privChan2} = await Channel.apiCreateChannel(siteOneUrl, {
|
||||||
|
teamId: testTeam.id,
|
||||||
|
type: 'P',
|
||||||
|
});
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, privChan2.id);
|
||||||
|
privateChannel2 = privChan2;
|
||||||
|
|
||||||
|
const {user: newUser5} = await User.apiCreateUser(siteOneUrl, {prefix: 'removeme'});
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, newUser5.id, testTeam.id);
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, newUser5.id, privChan2.id);
|
||||||
|
removeMeUser = newUser5;
|
||||||
|
|
||||||
|
// 7. Test 6 (MM-T878): Two users for GM creation
|
||||||
|
const {user: gmUserOne} = await User.apiCreateUser(siteOneUrl, {prefix: 'gmuser1'});
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
const {user: gmUserTwo} = await User.apiCreateUser(siteOneUrl, {prefix: 'gmuser2'});
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, gmUserOne.id, testTeam.id);
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, gmUserTwo.id, testTeam.id);
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
gmUser1 = gmUserOne;
|
||||||
|
gmUser2 = gmUserTwo;
|
||||||
|
|
||||||
|
// 8. Login once with test user
|
||||||
|
await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName);
|
||||||
|
await LoginScreen.login(testUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
// # Log out
|
||||||
|
await HomeScreen.logout();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3195 - RN apps Add members to channel', async () => {
|
||||||
|
// # Use pre-created user
|
||||||
|
const newUser = addMemberUser;
|
||||||
|
|
||||||
|
// # Open default test channel
|
||||||
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
|
|
||||||
|
// # Open channel info and tap add members
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await expect(ChannelInfoScreen.addMembersAction).toBeVisible();
|
||||||
|
await ChannelInfoScreen.addMembersAction.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// # Dismiss tutorial if present
|
||||||
|
await AddMembersScreen.dismissTutorial();
|
||||||
|
await AddMembersScreen.toBeVisible();
|
||||||
|
|
||||||
|
// # Search and add user
|
||||||
|
await AddMembersScreen.searchAndAddUser(newUser.username, newUser.id);
|
||||||
|
|
||||||
|
// * Verify user added system message appears
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
const systemMessage = `${newUser.username} added to the channel by ${testUser.username}`;
|
||||||
|
await waitFor(element(by.text(systemMessage).withAncestor(by.id('post_list')))).
|
||||||
|
toBeVisible();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T856 - Add existing users to public channel from drop-down Add Members', async () => {
|
||||||
|
// # Use pre-created user
|
||||||
|
const newUser = user2;
|
||||||
|
|
||||||
|
// # Open default test channel
|
||||||
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
|
|
||||||
|
// # Open channel info and tap add members
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await expect(ChannelInfoScreen.addMembersAction).toBeVisible();
|
||||||
|
await ChannelInfoScreen.addMembersAction.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// # Dismiss tutorial if present and search and add user
|
||||||
|
await AddMembersScreen.dismissTutorial();
|
||||||
|
await AddMembersScreen.toBeVisible();
|
||||||
|
await AddMembersScreen.searchAndAddUser(newUser.username, newUser.id);
|
||||||
|
|
||||||
|
// * Verify user added system message appears
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
const systemMessage = `${newUser.username} added to the channel by ${testUser.username}`;
|
||||||
|
await waitFor(element(by.text(systemMessage).withAncestor(by.id('post_list')))).
|
||||||
|
toBeVisible();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3196 - RN apps Manage members in channel', async () => {
|
||||||
|
// # Use pre-created user (already in channel)
|
||||||
|
const removedUser = memberUser;
|
||||||
|
|
||||||
|
// # Open default test channel
|
||||||
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
|
|
||||||
|
// # Open channel info and tap members option
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await expect(ChannelInfoScreen.membersOption).toBeVisible();
|
||||||
|
await ChannelInfoScreen.membersOption.tap();
|
||||||
|
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
await element(by.text(isAndroid()? 'MANAGE': 'Manage')).tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// # Search and remove user
|
||||||
|
await ManageChannelMembersScreen.searchAndRemoveUser(removedUser.username, removedUser.id);
|
||||||
|
|
||||||
|
// * Verify user removed system message appears
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
const systemMessage = `${removedUser.username} was removed from the channel`;
|
||||||
|
await waitFor(element(by.text(systemMessage).withAncestor(by.id('post_list')))).
|
||||||
|
toBeVisible();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3204 - RN apps Add user to private channel', async () => {
|
||||||
|
// # Use pre-created private channel and user
|
||||||
|
const privateChannel = privateChannel1;
|
||||||
|
const newUser = privUser;
|
||||||
|
|
||||||
|
// # Open private channel
|
||||||
|
await ChannelScreen.open(channelsCategory, privateChannel.name);
|
||||||
|
|
||||||
|
// # Open channel info and tap add members
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await expect(ChannelInfoScreen.addMembersAction).toBeVisible();
|
||||||
|
await ChannelInfoScreen.addMembersAction.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// # Dismiss tutorial if present and search and add user
|
||||||
|
await AddMembersScreen.dismissTutorial();
|
||||||
|
await AddMembersScreen.toBeVisible();
|
||||||
|
await AddMembersScreen.searchAndAddUser(newUser.username, newUser.id);
|
||||||
|
|
||||||
|
// * Verify user added system message appears
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
const systemMessage = `${newUser.username} added to the channel by ${testUser.username}`;
|
||||||
|
await waitFor(element(by.text(systemMessage).withAncestor(by.id('post_list')))).
|
||||||
|
toBeVisible();
|
||||||
|
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3205 - RN apps Remove user from private channel', async () => {
|
||||||
|
// # Use pre-created private channel and user (already in channel)
|
||||||
|
const privateChannel = privateChannel2;
|
||||||
|
const removedUser = removeMeUser;
|
||||||
|
|
||||||
|
// # Open private channel
|
||||||
|
await ChannelScreen.open(channelsCategory, privateChannel.name);
|
||||||
|
|
||||||
|
// # Open channel info and tap members option
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await expect(ChannelInfoScreen.membersOption).toBeVisible();
|
||||||
|
await ChannelInfoScreen.membersOption.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
await element(by.text(isAndroid()? 'MANAGE': 'Manage')).tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// # Search and remove user
|
||||||
|
await ManageChannelMembersScreen.searchAndRemoveUser(removedUser.username, removedUser.id);
|
||||||
|
|
||||||
|
// * Verify user removed system message appears
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
const systemMessage = `${removedUser.username} was removed from the channel`;
|
||||||
|
await waitFor(element(by.text(systemMessage).withAncestor(by.id('post_list')))).
|
||||||
|
toBeVisible();
|
||||||
|
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T878 - RN apps View Members in GM', async () => {
|
||||||
|
|
||||||
|
await CreateDirectMessageScreen.open();
|
||||||
|
await CreateDirectMessageScreen.searchInput.replaceText(`${gmUser1.username}`);
|
||||||
|
await CreateDirectMessageScreen.searchInput.tapReturnKey();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
await CreateDirectMessageScreen.getUserItem(gmUser1.id).tap();
|
||||||
|
|
||||||
|
// * Verify the first new user is selected
|
||||||
|
await expect(CreateDirectMessageScreen.getSelectedDMUserDisplayName(gmUser1.id)).toBeVisible();
|
||||||
|
|
||||||
|
// # Search for the second new user and tap on the second new user item
|
||||||
|
await CreateDirectMessageScreen.searchInput.replaceText(`${gmUser2.username}`);
|
||||||
|
await CreateDirectMessageScreen.searchInput.tapReturnKey();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
await CreateDirectMessageScreen.getUserItem(gmUser2.id).tap();
|
||||||
|
|
||||||
|
// * Verify the second new user is selected
|
||||||
|
await expect(CreateDirectMessageScreen.getSelectedDMUserDisplayName(gmUser2.id)).toBeVisible();
|
||||||
|
|
||||||
|
// # Tap on start button
|
||||||
|
await CreateDirectMessageScreen.startButton.tap();
|
||||||
|
await ChannelScreen.dismissScheduledPostTooltip();
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
|
||||||
|
// # Open channel info and tap members option
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await expect(ChannelInfoScreen.membersOption).toBeVisible();
|
||||||
|
await ChannelInfoScreen.membersOption.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// * Verify members list is visible
|
||||||
|
await expect(ManageChannelMembersScreen.gmMemberSectionList).toBeVisible();
|
||||||
|
|
||||||
|
// # Go back
|
||||||
|
await ManageChannelMembersScreen.backButton.tap();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
// *******************************************************************
|
||||||
|
// - [#] indicates a test step (e.g. # Go to a screen)
|
||||||
|
// - [*] indicates an assertion (e.g. * Check the title)
|
||||||
|
// - Use element testID when selecting an element. Create one if none.
|
||||||
|
// *******************************************************************
|
||||||
|
|
||||||
|
import {Channel, Setup} from '@support/server_api';
|
||||||
|
import {
|
||||||
|
serverOneUrl,
|
||||||
|
siteOneUrl,
|
||||||
|
} from '@support/test_config';
|
||||||
|
import {
|
||||||
|
ChannelInfoScreen,
|
||||||
|
ChannelListScreen,
|
||||||
|
ChannelScreen,
|
||||||
|
FindChannelsScreen,
|
||||||
|
LoginScreen,
|
||||||
|
ServerScreen,
|
||||||
|
HomeScreen,
|
||||||
|
} from '@support/ui/screen';
|
||||||
|
import {getRandomId, timeouts, wait} from '@support/utils';
|
||||||
|
import {expect} from 'detox';
|
||||||
|
|
||||||
|
describe('Channels', () => {
|
||||||
|
const serverOneDisplayName = 'Server 1';
|
||||||
|
const channelsCategory = 'channels';
|
||||||
|
const favoritesCategory = 'favorites';
|
||||||
|
let testUser: any;
|
||||||
|
let testTeam: any;
|
||||||
|
let testChannel: any;
|
||||||
|
let archiveChannel: any;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const {user, channel, team} = await Setup.apiInit(siteOneUrl);
|
||||||
|
testUser = user;
|
||||||
|
testChannel = channel;
|
||||||
|
testTeam = team;
|
||||||
|
|
||||||
|
// Create a separate channel for archiving test
|
||||||
|
const {channel: archiveCh} = await Channel.apiCreateChannel(siteOneUrl, {
|
||||||
|
teamId: testTeam.id,
|
||||||
|
name: `archive-test-${getRandomId()}`,
|
||||||
|
displayName: `Archive Test ${getRandomId()}`,
|
||||||
|
type: 'O',
|
||||||
|
});
|
||||||
|
archiveChannel = archiveCh;
|
||||||
|
|
||||||
|
await wait(timeouts.THREE_SEC);
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, archiveChannel.id);
|
||||||
|
await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName);
|
||||||
|
await LoginScreen.login(testUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await HomeScreen.logout();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3189 - RN apps Display channel list', async () => {
|
||||||
|
await expect(ChannelListScreen.channelListScreen).toBeVisible();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3190 - RN apps Display Channel Info', async () => {
|
||||||
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
await expect(ChannelInfoScreen.channelInfoScreen).toBeVisible();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3191 - RN apps Change channel', async () => {
|
||||||
|
|
||||||
|
await FindChannelsScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
const searchTerm = testChannel.name.substring(0, 3);
|
||||||
|
await FindChannelsScreen.searchInput.typeText(searchTerm);
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
await expect(FindChannelsScreen.getFilteredChannelItem(testChannel.name)).toBeVisible();
|
||||||
|
await FindChannelsScreen.getFilteredChannelItem(testChannel.name).tap();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await ChannelScreen.toBeVisible();
|
||||||
|
await expect(ChannelScreen.headerTitle).toHaveText(testChannel.display_name);
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T850 - RN apps Favorite a channel', async () => {
|
||||||
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
|
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await expect(ChannelInfoScreen.favoriteAction).toBeVisible();
|
||||||
|
await ChannelInfoScreen.favoriteAction.tap();
|
||||||
|
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
await expect(ChannelInfoScreen.unfavoriteAction).toBeVisible();
|
||||||
|
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
|
||||||
|
// * Verify channel is listed under favorites category
|
||||||
|
await expect(ChannelListScreen.getChannelItemDisplayName(favoritesCategory, testChannel.name)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3192 - RN apps Un-favorite a channel', async () => {
|
||||||
|
await ChannelScreen.open(favoritesCategory, testChannel.name);
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await expect(ChannelInfoScreen.unfavoriteAction).toBeVisible();
|
||||||
|
|
||||||
|
await ChannelInfoScreen.unfavoriteAction.tap();
|
||||||
|
|
||||||
|
await wait(timeouts.THREE_SEC);
|
||||||
|
await expect(ChannelInfoScreen.favoriteAction).toBeVisible();
|
||||||
|
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
|
||||||
|
await expect(ChannelListScreen.getChannelItemDisplayName(channelsCategory, testChannel.name)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3197 - RN apps Archive public or private channel', async () => {
|
||||||
|
// # Navigate to the archive channel
|
||||||
|
await ChannelScreen.open(channelsCategory, archiveChannel.name);
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
// # Scroll to bottom to reveal archive option
|
||||||
|
await ChannelInfoScreen.scrollView.scrollTo('bottom');
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
// # Archive the channel
|
||||||
|
await ChannelInfoScreen.archivePublicChannel({confirm: true});
|
||||||
|
|
||||||
|
// * Verify channel info screen is closed
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
await expect(ChannelInfoScreen.channelInfoScreen).not.toBeVisible();
|
||||||
|
|
||||||
|
// * Verify we're back at channel list
|
||||||
|
await ChannelListScreen.toBeVisible();
|
||||||
|
|
||||||
|
// * Verify archived channel is not visible in the list
|
||||||
|
await expect(ChannelListScreen.getChannelItemDisplayName(channelsCategory, archiveChannel.name)).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
// *******************************************************************
|
||||||
|
// - [#] indicates a test step (e.g. # Go to a screen)
|
||||||
|
// - [*] indicates an assertion (e.g. * Check the title)
|
||||||
|
// - Use element testID when selecting an element. Create one if none.
|
||||||
|
// *******************************************************************
|
||||||
|
|
||||||
|
import {Setup} from '@support/server_api';
|
||||||
|
import {
|
||||||
|
serverOneUrl,
|
||||||
|
siteOneUrl,
|
||||||
|
} from '@support/test_config';
|
||||||
|
import {
|
||||||
|
ChannelInfoScreen,
|
||||||
|
ChannelScreen,
|
||||||
|
LoginScreen,
|
||||||
|
ServerScreen,
|
||||||
|
HomeScreen,
|
||||||
|
} from '@support/ui/screen';
|
||||||
|
import {timeouts, wait} from '@support/utils';
|
||||||
|
import {expect} from 'detox';
|
||||||
|
|
||||||
|
describe('Channels', () => {
|
||||||
|
const serverOneDisplayName = 'Server 1';
|
||||||
|
const channelsCategory = 'channels';
|
||||||
|
let testUser: any;
|
||||||
|
let testChannel: any;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const {user, channel} = await Setup.apiInit(siteOneUrl);
|
||||||
|
testUser = user;
|
||||||
|
testChannel = channel;
|
||||||
|
|
||||||
|
await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName);
|
||||||
|
await LoginScreen.login(testUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await HomeScreen.logout();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T3198 - Channel notifications Mobile Push', async () => {
|
||||||
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
|
|
||||||
|
await ChannelInfoScreen.open();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await expect(ChannelInfoScreen.notificationPreferenceOption).toBeVisible();
|
||||||
|
await ChannelInfoScreen.notificationPreferenceOption.tap();
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
const notificationSettingsScreen = element(by.id('push_notification_settings.screen'));
|
||||||
|
await expect(notificationSettingsScreen).toBeVisible();
|
||||||
|
|
||||||
|
const backButton = element(by.id('screen.back.button'));
|
||||||
|
await backButton.tap();
|
||||||
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
||||||
|
await ChannelInfoScreen.close();
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
// *******************************************************************
|
||||||
|
// - [#] indicates a test step (e.g. # Go to a screen)
|
||||||
|
// - [*] indicates an assertion (e.g. * Check the title)
|
||||||
|
// - Use element testID when selecting an element. Create one if none.
|
||||||
|
// *******************************************************************
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test Cases Included:
|
||||||
|
* - MM-T858: Combined joinleave messages in public channel
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Channel, Post, Setup, Team, User} from '@support/server_api';
|
||||||
|
import {
|
||||||
|
serverOneUrl,
|
||||||
|
siteOneUrl,
|
||||||
|
} from '@support/test_config';
|
||||||
|
import {
|
||||||
|
ChannelScreen,
|
||||||
|
LoginScreen,
|
||||||
|
ServerScreen,
|
||||||
|
HomeScreen,
|
||||||
|
} from '@support/ui/screen';
|
||||||
|
import {timeouts, wait} from '@support/utils';
|
||||||
|
import {expect} from 'detox';
|
||||||
|
|
||||||
|
describe('Channels', () => {
|
||||||
|
const serverOneDisplayName = 'Server 1';
|
||||||
|
const channelsCategory = 'channels';
|
||||||
|
let testUser: any;
|
||||||
|
let testTeam: any;
|
||||||
|
let testChannel: any;
|
||||||
|
let user1: any;
|
||||||
|
let user5: any;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const {user, channel, team} = await Setup.apiInit(siteOneUrl);
|
||||||
|
testUser = user;
|
||||||
|
testTeam = team;
|
||||||
|
testChannel = channel;
|
||||||
|
|
||||||
|
({user: user1} = await User.apiCreateUser(siteOneUrl, {prefix: 'user1'}));
|
||||||
|
const {user: user2} = await User.apiCreateUser(siteOneUrl, {prefix: 'user2'});
|
||||||
|
const {user: user3} = await User.apiCreateUser(siteOneUrl, {prefix: 'user3'});
|
||||||
|
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, user1.id, testTeam.id);
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, user2.id, testTeam.id);
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, user3.id, testTeam.id);
|
||||||
|
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, user1.id, testChannel.id);
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, user2.id, testChannel.id);
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, user3.id, testChannel.id);
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
await Channel.apiRemoveUserFromChannel(siteOneUrl, testChannel.id, user1.id);
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
const {user: user4} = await User.apiCreateUser(siteOneUrl, {prefix: 'user4'});
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, user4.id, testTeam.id);
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, user4.id, testChannel.id);
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
await Post.apiCreatePost(siteOneUrl, {
|
||||||
|
channelId: testChannel.id,
|
||||||
|
message: 'Test message to interrupt system messages',
|
||||||
|
});
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
({user: user5} = await User.apiCreateUser(siteOneUrl, {prefix: 'user5'}));
|
||||||
|
await Team.apiAddUserToTeam(siteOneUrl, user5.id, testTeam.id);
|
||||||
|
await Channel.apiAddUserToChannel(siteOneUrl, user5.id, testChannel.id);
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName);
|
||||||
|
await LoginScreen.login(testUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await HomeScreen.logout();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-T858 - Combined joinleave messages in public channel', async () => {
|
||||||
|
// Verify the posts via API to ensure correct structure
|
||||||
|
const {posts} = await Post.apiGetPostsInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
||||||
|
// Verify we have the expected post types (no expect needed)
|
||||||
|
const hasJoinPost = posts.some((p: { type: string }) => p.type === 'system_join_channel');
|
||||||
|
const hasAddPosts = posts.filter((p: { type: string }) => p.type === 'system_add_to_channel').length >= 4;
|
||||||
|
const hasRemovePost = posts.some((p: { type: string }) => p.type === 'system_remove_from_channel');
|
||||||
|
const hasRegularPost = posts.some((p: { message: string }) => p.message === 'Test message to interrupt system messages');
|
||||||
|
|
||||||
|
if (!hasJoinPost || !hasAddPosts || !hasRemovePost || !hasRegularPost) {
|
||||||
|
throw new Error(`Missing expected posts. Join: ${hasJoinPost}, Adds: ${hasAddPosts}, Remove: ${hasRemovePost}, Regular: ${hasRegularPost}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now verify the UI displays these messages correctly
|
||||||
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
||||||
|
// Verify the regular message is visible
|
||||||
|
await expect(element(by.text('Test message to interrupt system messages'))).toBeVisible();
|
||||||
|
|
||||||
|
await ChannelScreen.back();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -93,7 +93,6 @@ describe('Channels - Channel Post List', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open('channels', testChannel.name);
|
await ChannelScreen.open('channels', testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify message is added to post list
|
// * Verify message is added to post list
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import {
|
||||||
LoginScreen,
|
LoginScreen,
|
||||||
ServerScreen,
|
ServerScreen,
|
||||||
} from '@support/ui/screen';
|
} from '@support/ui/screen';
|
||||||
import channelScreen from '@support/ui/screen/channel';
|
|
||||||
import {timeouts, wait} from '@support/utils';
|
import {timeouts, wait} from '@support/utils';
|
||||||
import {expect, waitFor} from 'detox';
|
import {expect, waitFor} from 'detox';
|
||||||
|
|
||||||
|
|
@ -62,7 +61,6 @@ describe('Messaging - Channel Link', () => {
|
||||||
await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id);
|
await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id);
|
||||||
const channelLink = `${serverOneUrl}/${testTeam.name}/channels/${targetChannel.name}`;
|
const channelLink = `${serverOneUrl}/${testTeam.name}/channels/${targetChannel.name}`;
|
||||||
await ChannelScreen.postMessage(channelLink);
|
await ChannelScreen.postMessage(channelLink);
|
||||||
await channelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// # Tap on channel link
|
// # Tap on channel link
|
||||||
await element(by.text(channelLink)).tap();
|
await element(by.text(channelLink)).tap();
|
||||||
|
|
@ -82,7 +80,6 @@ describe('Messaging - Channel Link', () => {
|
||||||
await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id);
|
await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id);
|
||||||
const channelLink = `${serverOneUrl}/${testTeam.name}/channels/${targetChannel.name}`;
|
const channelLink = `${serverOneUrl}/${testTeam.name}/channels/${targetChannel.name}`;
|
||||||
await ChannelScreen.postMessage(channelLink);
|
await ChannelScreen.postMessage(channelLink);
|
||||||
await channelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// # Wait for keyboard to dismiss and message to be visible
|
// # Wait for keyboard to dismiss and message to be visible
|
||||||
await wait(timeouts.TWO_SEC);
|
await wait(timeouts.TWO_SEC);
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,6 @@ describe('Messaging - Emojis and Reactions', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
await ChannelScreen.openPostOptionsFor(post.id, message);
|
await ChannelScreen.openPostOptionsFor(post.id, message);
|
||||||
|
|
||||||
|
|
@ -105,7 +104,6 @@ describe('Messaging - Emojis and Reactions', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
await ChannelScreen.openPostOptionsFor(post.id, message);
|
await ChannelScreen.openPostOptionsFor(post.id, message);
|
||||||
await EmojiPickerScreen.open();
|
await EmojiPickerScreen.open();
|
||||||
|
|
@ -140,7 +138,6 @@ describe('Messaging - Emojis and Reactions', () => {
|
||||||
const message = 'brown fox :fox_face: lazy dog :dog:';
|
const message = 'brown fox :fox_face: lazy dog :dog:';
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify message is posted with emojis
|
// * Verify message is posted with emojis
|
||||||
const resolvedMessage = 'brown fox 🦊 lazy dog 🐶';
|
const resolvedMessage = 'brown fox 🦊 lazy dog 🐶';
|
||||||
|
|
@ -174,7 +171,6 @@ describe('Messaging - Emojis and Reactions', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const searchTerm = 'blahblahblahblah';
|
const searchTerm = 'blahblahblahblah';
|
||||||
await ChannelScreen.openPostOptionsFor(post.id, message);
|
await ChannelScreen.openPostOptionsFor(post.id, message);
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,6 @@ describe('Messaging - Follow and Unfollow Message', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// # Wait for keyboard to dismiss and message to be visible
|
// # Wait for keyboard to dismiss and message to be visible
|
||||||
await wait(timeouts.ONE_SEC);
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
@ -106,7 +105,6 @@ describe('Messaging - Follow and Unfollow Message', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// # Wait for keyboard to dismiss and message to be visible
|
// # Wait for keyboard to dismiss and message to be visible
|
||||||
await wait(timeouts.ONE_SEC);
|
await wait(timeouts.ONE_SEC);
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ describe('Messaging - Message Delete', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify message is added to post list
|
// * Verify message is added to post list
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -78,7 +77,6 @@ describe('Messaging - Message Delete', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify message is added to post list
|
// * Verify message is added to post list
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -100,7 +98,6 @@ describe('Messaging - Message Delete', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem: parentPostListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, message);
|
const {postListPostItem: parentPostListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, message);
|
||||||
|
|
@ -114,7 +111,6 @@ describe('Messaging - Message Delete', () => {
|
||||||
// # Post a reply, open post options for the reply message, tap delete option and confirm
|
// # Post a reply, open post options for the reply message, tap delete option and confirm
|
||||||
const replyMessage = `${message} reply`;
|
const replyMessage = `${message} reply`;
|
||||||
await ThreadScreen.postMessage(replyMessage);
|
await ThreadScreen.postMessage(replyMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage);
|
const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage);
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ describe('Messaging - Message Edit', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify message is added to post list
|
// * Verify message is added to post list
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -95,7 +94,6 @@ describe('Messaging - Message Edit', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify message is added to post list
|
// * Verify message is added to post list
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -129,7 +127,6 @@ describe('Messaging - Message Edit', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem: parentPostListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, message);
|
const {postListPostItem: parentPostListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, message);
|
||||||
|
|
||||||
|
|
@ -143,7 +140,6 @@ describe('Messaging - Message Edit', () => {
|
||||||
// # Post a reply, open post options for the reply message and tap edit option
|
// # Post a reply, open post options for the reply message and tap edit option
|
||||||
const replyMessage = `${message} reply`;
|
const replyMessage = `${message} reply`;
|
||||||
await ThreadScreen.postMessage(replyMessage);
|
await ThreadScreen.postMessage(replyMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage);
|
const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@ describe('Messaging - Message Reply', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify message is added to post list
|
// * Verify message is added to post list
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -80,7 +79,6 @@ describe('Messaging - Message Reply', () => {
|
||||||
// # Reply to parent post
|
// # Reply to parent post
|
||||||
const replyMessage = `${message} reply`;
|
const replyMessage = `${message} reply`;
|
||||||
await ThreadScreen.postMessage(replyMessage);
|
await ThreadScreen.postMessage(replyMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify reply message is posted
|
// * Verify reply message is posted
|
||||||
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -96,7 +94,6 @@ describe('Messaging - Message Reply', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify message is added to post list
|
// * Verify message is added to post list
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -118,7 +115,6 @@ describe('Messaging - Message Reply', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message);
|
const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message);
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ describe('Messaging - Pin and Unpin Message', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify message is posted
|
// * Verify message is posted
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -90,7 +89,7 @@ describe('Messaging - Pin and Unpin Message', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message);
|
const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message);
|
||||||
await expect(postListPostItem).toBeVisible();
|
await expect(postListPostItem).toBeVisible();
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ describe('Messaging - Save and Unsave Message', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify message is posted
|
// * Verify message is posted
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -90,7 +89,6 @@ describe('Messaging - Save and Unsave Message', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message);
|
const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message);
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,6 @@ describe('Search - Pinned Messages', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(post.id, message);
|
const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(post.id, message);
|
||||||
|
|
@ -115,7 +114,6 @@ describe('Search - Pinned Messages', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post: pinnedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: pinnedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(pinnedPost.id, message);
|
const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(pinnedPost.id, message);
|
||||||
|
|
@ -156,7 +154,6 @@ describe('Search - Pinned Messages', () => {
|
||||||
// # Post a reply
|
// # Post a reply
|
||||||
const replyMessage = `${updatedMessage} reply`;
|
const replyMessage = `${updatedMessage} reply`;
|
||||||
await ThreadScreen.postMessage(replyMessage);
|
await ThreadScreen.postMessage(replyMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify reply is posted
|
// * Verify reply is posted
|
||||||
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -189,7 +186,6 @@ describe('Search - Pinned Messages', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post: pinnedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: pinnedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(pinnedPost.id, message);
|
const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(pinnedPost.id, message);
|
||||||
|
|
@ -223,7 +219,6 @@ describe('Search - Pinned Messages', () => {
|
||||||
const message = `Message ${getRandomId()}`;
|
const message = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(message);
|
await ChannelScreen.postMessage(message);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post: pinnedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: pinnedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(pinnedPost.id, message);
|
const {postListPostItem: channelPostItem} = ChannelScreen.getPostListPostItem(pinnedPost.id, message);
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ describe('Threads - Follow and Unfollow Thread', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
||||||
await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC);
|
await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC);
|
||||||
|
|
@ -93,7 +92,6 @@ describe('Threads - Follow and Unfollow Thread', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
||||||
await ThreadScreen.postMessage(`${parentMessage} reply`);
|
await ThreadScreen.postMessage(`${parentMessage} reply`);
|
||||||
|
|
@ -126,7 +124,6 @@ describe('Threads - Follow and Unfollow Thread', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
||||||
await ThreadScreen.postMessage(`${parentMessage} reply`);
|
await ThreadScreen.postMessage(`${parentMessage} reply`);
|
||||||
|
|
@ -175,7 +172,6 @@ describe('Threads - Follow and Unfollow Thread', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
||||||
const replyMessage = `${parentMessage} reply`;
|
const replyMessage = `${parentMessage} reply`;
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,6 @@ describe('Threads - Global Threads', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ describe('Threads - Open Thread in Channel', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
await ChannelScreen.dismissScheduledPostTooltip();
|
await ChannelScreen.dismissScheduledPostTooltip();
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
||||||
|
|
@ -100,7 +99,6 @@ describe('Threads - Open Thread in Channel', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
||||||
await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC);
|
await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.FOUR_SEC);
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ describe('Threads - Reply to Thread', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
||||||
|
|
@ -66,7 +65,6 @@ describe('Threads - Reply to Thread', () => {
|
||||||
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
||||||
const replyMessage = `${parentMessage} reply`;
|
const replyMessage = `${parentMessage} reply`;
|
||||||
await ThreadScreen.postMessage(replyMessage);
|
await ThreadScreen.postMessage(replyMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
await ThreadScreen.back();
|
await ThreadScreen.back();
|
||||||
await ChannelScreen.back();
|
await ChannelScreen.back();
|
||||||
await GlobalThreadsScreen.open();
|
await GlobalThreadsScreen.open();
|
||||||
|
|
@ -84,7 +82,6 @@ describe('Threads - Reply to Thread', () => {
|
||||||
// # Add new reply to thread
|
// # Add new reply to thread
|
||||||
const newReplyMessage = `${parentMessage} new reply`;
|
const newReplyMessage = `${parentMessage} new reply`;
|
||||||
await ThreadScreen.postMessage(newReplyMessage);
|
await ThreadScreen.postMessage(newReplyMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify new reply is posted
|
// * Verify new reply is posted
|
||||||
const {post: newReplyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: newReplyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
@ -101,14 +98,12 @@ describe('Threads - Reply to Thread', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
||||||
await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
await waitFor(postListPostItem).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||||
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
||||||
const replyMessage = `${parentMessage} reply`;
|
const replyMessage = `${parentMessage} reply`;
|
||||||
await ThreadScreen.postMessage(replyMessage);
|
await ThreadScreen.postMessage(replyMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
await ThreadScreen.back();
|
await ThreadScreen.back();
|
||||||
await ChannelScreen.back();
|
await ChannelScreen.back();
|
||||||
|
|
@ -126,7 +121,6 @@ describe('Threads - Reply to Thread', () => {
|
||||||
// # Add new reply to thread
|
// # Add new reply to thread
|
||||||
const newReplyMessage = `${parentMessage} new reply`;
|
const newReplyMessage = `${parentMessage} new reply`;
|
||||||
await ThreadScreen.postMessage(newReplyMessage);
|
await ThreadScreen.postMessage(newReplyMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
// * Verify new reply is posted
|
// * Verify new reply is posted
|
||||||
const {post: newReplyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: newReplyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ describe('Threads - Save and Unsave Thread', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
const {postListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
|
||||||
|
|
@ -67,7 +66,6 @@ describe('Threads - Save and Unsave Thread', () => {
|
||||||
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
||||||
const replyMessage = `${parentMessage} reply`;
|
const replyMessage = `${parentMessage} reply`;
|
||||||
await ThreadScreen.postMessage(replyMessage);
|
await ThreadScreen.postMessage(replyMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
await ThreadScreen.back();
|
await ThreadScreen.back();
|
||||||
await ChannelScreen.back();
|
await ChannelScreen.back();
|
||||||
await GlobalThreadsScreen.open();
|
await GlobalThreadsScreen.open();
|
||||||
|
|
@ -103,12 +101,10 @@ describe('Threads - Save and Unsave Thread', () => {
|
||||||
const parentMessage = `Message ${getRandomId()}`;
|
const parentMessage = `Message ${getRandomId()}`;
|
||||||
await ChannelScreen.open(channelsCategory, testChannel.name);
|
await ChannelScreen.open(channelsCategory, testChannel.name);
|
||||||
await ChannelScreen.postMessage(parentMessage);
|
await ChannelScreen.postMessage(parentMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
|
||||||
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
|
||||||
const replyMessage = `${parentMessage} reply`;
|
const replyMessage = `${parentMessage} reply`;
|
||||||
await ThreadScreen.postMessage(replyMessage);
|
await ThreadScreen.postMessage(replyMessage);
|
||||||
await ChannelScreen.dismissKeyboard();
|
|
||||||
await ThreadScreen.back();
|
await ThreadScreen.back();
|
||||||
await ChannelScreen.back();
|
await ChannelScreen.back();
|
||||||
await GlobalThreadsScreen.open();
|
await GlobalThreadsScreen.open();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
/* eslint-disable no-await-in-loop, no-console */
|
/* eslint-disable no-await-in-loop, no-console */
|
||||||
|
|
||||||
import {cleanupAdbReversePorts, resetAdbServer} from '@support/adb_utils';
|
|
||||||
import {ClaudePromptHandler} from '@support/pilot/ClaudePromptHandler';
|
import {ClaudePromptHandler} from '@support/pilot/ClaudePromptHandler';
|
||||||
import {Plugin, System, User} from '@support/server_api';
|
import {Plugin, System, User} from '@support/server_api';
|
||||||
import {siteOneUrl} from '@support/test_config';
|
import {siteOneUrl} from '@support/test_config';
|
||||||
|
|
@ -111,40 +110,19 @@ export async function launchAppWithRetry(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info(`✅ App launched successfully on attempt ${attempt}`);
|
console.info(`✅ App launched successfully on attempt ${attempt}`);
|
||||||
return; // Success, exit the function
|
return;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastError = error;
|
lastError = error;
|
||||||
console.warn(`❌ App launch failed on attempt ${attempt}/${MAX_RETRY_ATTEMPTS}: ${(error as Error).message}`);
|
console.warn(`❌ App launch failed on attempt ${attempt}/${MAX_RETRY_ATTEMPTS}: ${(error as Error).message}`);
|
||||||
|
|
||||||
// If this is the last attempt, don't wait
|
|
||||||
if (attempt < MAX_RETRY_ATTEMPTS) {
|
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...`);
|
console.warn(`Waiting ${RETRY_DELAY}ms before retrying...`);
|
||||||
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
|
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get here, all attempts failed
|
|
||||||
throw new Error(`Failed to launch app after ${MAX_RETRY_ATTEMPTS} attempts. Last error: ${(lastError as Error).message}`);
|
throw new Error(`Failed to launch app after ${MAX_RETRY_ATTEMPTS} attempts. Last error: ${(lastError as Error).message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,28 +131,20 @@ export async function launchAppWithRetry(): Promise<void> {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function initializeClaudePromptHandler(): Promise<void> {
|
async function initializeClaudePromptHandler(): Promise<void> {
|
||||||
if (process.env.ANTHROPIC_API_KEY) {
|
try {
|
||||||
|
if (!process.env.ANTHROPIC_API_KEY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const promptHandler = new ClaudePromptHandler(process.env.ANTHROPIC_API_KEY);
|
const promptHandler = new ClaudePromptHandler(process.env.ANTHROPIC_API_KEY);
|
||||||
pilot.init(promptHandler);
|
pilot.init(promptHandler);
|
||||||
} else {
|
} catch (e) {
|
||||||
console.info('To use ClaudePromptHandler, please set the ANTHROPIC_API_KEY environment variable.');
|
console.warn('Claude init failed, continuing without AI:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
// Reset flag to ensure each test file starts with a clean app launch
|
// Reset flag to ensure each test file starts with a clean app launch
|
||||||
isFirstLaunch = true;
|
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);
|
|
||||||
await Plugin.apiDisableNonPrepackagedPlugins(siteOneUrl);
|
|
||||||
await launchAppWithRetry();
|
await launchAppWithRetry();
|
||||||
|
|
||||||
// Verify Detox connection is healthy after app launch
|
// Verify Detox connection is healthy after app launch
|
||||||
|
|
@ -182,28 +152,10 @@ beforeAll(async () => {
|
||||||
|
|
||||||
// Wait for app to be fully ready (database initialized, bridge ready)
|
// Wait for app to be fully ready (database initialized, bridge ready)
|
||||||
await waitForAppReady();
|
await waitForAppReady();
|
||||||
});
|
await initializeClaudePromptHandler();
|
||||||
|
|
||||||
// Add this to speed up test cleanup
|
// Login as sysadmin and reset server configuration
|
||||||
afterAll(async () => {
|
await System.apiCheckSystemHealth(siteOneUrl);
|
||||||
try {
|
await User.apiAdminLogin(siteOneUrl);
|
||||||
// Dismiss keyboard if visible to prevent session invalidation issues
|
await Plugin.apiDisableNonPrepackagedPlugins(siteOneUrl);
|
||||||
try {
|
|
||||||
await device.sendToHome();
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[Teardown] Could not send app to home:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
await device.terminateApp();
|
|
||||||
} catch (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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
1637
detox/package-lock.json
generated
1637
detox/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,8 +4,8 @@
|
||||||
"repository": "git@github.com:mattermost/mattermost-mobile.git",
|
"repository": "git@github.com:mattermost/mattermost-mobile.git",
|
||||||
"author": "Mattermost, Inc.",
|
"author": "Mattermost, Inc.",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-s3": "3.712.0",
|
"@aws-sdk/client-s3": "3.937.0",
|
||||||
"@aws-sdk/lib-storage": "3.712.0",
|
"@aws-sdk/lib-storage": "3.937.0",
|
||||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
"@babel/plugin-proposal-class-properties": "7.18.6",
|
||||||
"@babel/plugin-transform-modules-commonjs": "7.26.3",
|
"@babel/plugin-transform-modules-commonjs": "7.26.3",
|
||||||
"@babel/plugin-transform-runtime": "7.25.9",
|
"@babel/plugin-transform-runtime": "7.25.9",
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
"babel-plugin-module-resolver": "5.0.2",
|
"babel-plugin-module-resolver": "5.0.2",
|
||||||
"client-oauth2": "4.3.3",
|
"client-oauth2": "4.3.3",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "4.3.1",
|
||||||
"detox": "20.46.3",
|
"detox": "20.47.0",
|
||||||
"dotenv": "17.2.1",
|
"dotenv": "17.2.1",
|
||||||
"form-data": "4.0.1",
|
"form-data": "4.0.1",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
|
|
@ -48,11 +48,11 @@
|
||||||
"e2e:android-inject-settings": "node inject-detox-settings.js",
|
"e2e:android-inject-settings": "node inject-detox-settings.js",
|
||||||
"e2e:android-create-emulator": "./create_android_emulator.sh",
|
"e2e:android-create-emulator": "./create_android_emulator.sh",
|
||||||
"e2e:android-build": "detox build -c android.emu.debug",
|
"e2e:android-build": "detox build -c android.emu.debug",
|
||||||
"e2e:android-test": "detox test -c android.emu.debug --reuse --record-logs failing --take-screenshots failing",
|
"e2e:android-test": "detox test -c android.emu.debug --record-logs failing --take-screenshots failing",
|
||||||
"e2e:android-build-release": "detox build -c android.emu.release",
|
"e2e:android-build-release": "detox build -c android.emu.release",
|
||||||
"e2e:android-test-release": "detox test -c android.emu.release --record-logs failing --take-screenshots failing",
|
"e2e:android-test-release": "detox test -c android.emu.release --record-logs failing --take-screenshots failing",
|
||||||
"e2e:ios-build": "IOS=true detox build -c ios.sim.debug",
|
"e2e:ios-build": "IOS=true detox build -c ios.sim.debug",
|
||||||
"e2e:ios-test": "IOS=true detox test -c ios.sim.debug --reuse --record-logs failing --take-screenshots failing",
|
"e2e:ios-test": "IOS=true detox test -c ios.sim.debug --record-logs failing --take-screenshots failing",
|
||||||
"e2e:ios-build-release": "detox build -c ios.sim.release",
|
"e2e:ios-build-release": "detox build -c ios.sim.release",
|
||||||
"e2e:ios-test-release": "IOS=true detox test -c ios.sim.release --record-logs failing --take-screenshots failing",
|
"e2e:ios-test-release": "IOS=true detox test -c ios.sim.release --record-logs failing --take-screenshots failing",
|
||||||
"e2e:ios-disable-autofill": "cd utils && node disable_ios_autofill.js",
|
"e2e:ios-disable-autofill": "cd utils && node disable_ios_autofill.js",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue