Initial commit

This commit is contained in:
Sathurshan 2024-03-20 15:42:55 +05:30
parent afe50aed7e
commit 4bb088d413
17 changed files with 363 additions and 48 deletions

View File

@ -75,6 +75,11 @@ restore_cache: &restore-gradle-cache
name: Restore gradle cache
key: android-{{ checksum "android/build.gradle" }}-{{ checksum "android/app/build.gradle" }}
install-maestro-cli: &install-maestro-cli
name: Install Maestro CLI
command: |
curl -Ls "https://get.maestro.mobile.dev" | bash
# COMMANDS
commands:
@ -362,13 +367,6 @@ commands:
working_directory: ios
- save_cache: *save-gems-cache
create-e2e-account-file:
description: "Create e2e account file"
steps:
- run:
command: |
echo $E2E_ACCOUNT | base64 --decode > ./e2e_account.ts
working_directory: e2e
version: 2.1
@ -516,8 +514,7 @@ jobs:
- run:
name: Build Android
command: |
export RUNNING_E2E_TESTS=true
yarn e2e:android-build
yarn android
- save_cache: *save-gradle-cache
- store_artifacts:
path: android/app/build/outputs/apk/experimentalPlay/release/app-experimental-play-release.apk
@ -534,7 +531,6 @@ jobs:
name: android/android-machine
resource-class: large
tag: 2022.12.1
parallelism: 4
steps:
- checkout
- attach_workspace:
@ -542,8 +538,8 @@ jobs:
- restore_cache: *restore-npm-cache-linux
- run: *install-npm-modules
- save_cache: *save-npm-cache-linux
- run: *install-maestro-cli
- run: mkdir ~/junit
- create-e2e-account-file
- android/create-avd:
avd-name: Pixel_API_31_AOSP
install: true
@ -555,14 +551,13 @@ jobs:
echo "hw.lcd.height = 2280" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
echo "hw.lcd.width = 1080" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
- run:
name: Run Detox Tests
name: Run Maestro Tests
command: |
TEST=$(circleci tests glob "e2e/tests/**/*.ts" | circleci tests split --split-by=timings)
yarn e2e:android-test $TEST
maestro test -e APP_ID=chat.rocket.reactnative -e PLATFORM=Android e2e/ --format junit
- store_artifacts:
path: artifacts
- run:
command: cp junit.xml ~/junit/
command: cp report.xml ~/junit/
when: always
- store_test_results:
path: ~/junit
@ -604,19 +599,12 @@ jobs:
- save_cache: *save-npm-cache-mac
- save_cache: *save-gems-cache
- manage-pods
- run:
name: Configure Detox
command: |
brew tap wix/brew
brew install applesimutils
- run:
name: Build
command: |
/usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ios/RocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ios/ShareRocketChatRN/Info.plist
yarn detox clean-framework-cache && yarn detox build-framework-cache
export RUNNING_E2E_TESTS=true
yarn e2e:ios-build
yarn ios
- persist_to_workspace:
root: /Users/distiller/project
paths:
@ -624,7 +612,6 @@ jobs:
e2e-test-ios:
executor: mac-env
parallelism: 5
steps:
- checkout
- attach_workspace:
@ -632,23 +619,21 @@ jobs:
- restore_cache: *restore-npm-cache-mac
- run: *install-npm-modules
- save_cache: *save-npm-cache-mac
- run: *install-maestro-cli
- run:
name: Configure Facebook IDB
command: |
brew tap facebook/fb
brew install facebook/fb/idb-companion
- run: mkdir ~/junit
- run:
name: Configure Detox
name: Run Maestro Tests
command: |
brew tap wix/brew
brew install applesimutils
yarn detox clean-framework-cache && yarn detox build-framework-cache
- create-e2e-account-file
- run:
name: Run tests
command: |
TEST=$(circleci tests glob "e2e/tests/**/*.ts" | circleci tests split --split-by=timings)
yarn e2e:ios-test $TEST
maestro test -e APP_ID=chat.rocket.reactnative -e PLATFORM=iOS e2e/ --format junit
- store_artifacts:
path: artifacts
- run:
command: cp junit.xml ~/junit/
command: cp report.xml ~/junit/
when: always
- store_test_results:
path: ~/junit

View File

@ -1,5 +1,6 @@
import React from 'react';
import { RectButton, RectButtonProps } from 'react-native-gesture-handler';
import { View } from 'react-native';
import { useTheme } from '../theme';
@ -9,20 +10,22 @@ export interface ITouchProps extends RectButtonProps {
testID?: string;
}
const Touch = React.forwardRef<RectButton, ITouchProps>(({ children, onPress, underlayColor, ...props }, ref) => {
const Touch = React.forwardRef<RectButton, ITouchProps>(({ children, onPress, underlayColor, testID, ...props }, ref) => {
const { colors } = useTheme();
return (
<RectButton
ref={ref}
onPress={onPress}
activeOpacity={1}
underlayColor={underlayColor || colors.bannerBackground}
rippleColor={colors.bannerBackground}
{...props}
>
{children}
</RectButton>
<View testID={testID}>
<RectButton
ref={ref}
onPress={onPress}
activeOpacity={1}
underlayColor={underlayColor || colors.bannerBackground}
rippleColor={colors.bannerBackground}
{...props}
>
{children}
</RectButton>
</View>
);
});

View File

@ -1,5 +1,5 @@
import React, { useContext } from 'react';
import { View } from 'react-native';
import { Alert, View } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import MessageContext from './Context';
@ -153,7 +153,7 @@ const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
disabled={(props.isInfo && !props.isThreadReply) || props.archived || props.isTemp || props.type === 'jitsi_call_started'}
style={{ backgroundColor }}
>
<View>
<View testID='message-container'>
<Message {...props} />
</View>
</Touchable>

28
e2e_maestro/data.js Normal file
View File

@ -0,0 +1,28 @@
/* eslint-disable no-undef */
const data = {
server: 'https://mobile.rocket.chat',
alternateServer: 'https://stable.rocket.chat',
adminUser: MAESTRO_ADMIN_USER,
adminPassword: MAESTRO_ADMIN_PASSWORD,
channels: {
detoxpublic: {
name: 'detox-public'
},
detoxpublicprotected: {
name: 'detox-public-protected',
joinCode: '123'
}
},
randomUser: () => {
const randomVal = output.helpers_random();
return {
username: `user${randomVal}`,
name: `user${randomVal}`, // FIXME: apply a different name
password: `Password1@${randomVal}`,
email: `mobile+${randomVal}@rocket.chat`
};
}
};
output.data = data;

View File

@ -0,0 +1,7 @@
appId: ${APP_ID}
---
- extendedWaitUntil:
visible:
id: ${'room-view-title-' + room}
optional: true
timeout: 60000

View File

@ -0,0 +1,18 @@
appId: ${APP_ID}
---
- extendedWaitUntil:
visible:
id: 'login-view'
timeout: 2000
- tapOn:
id: 'login-view-email'
- inputText: ${username}
- pressKey: enter
- tapOn:
id: 'login-view-password'
- inputText: ${password}
- pressKey: enter
- extendedWaitUntil:
visible:
id: 'rooms-list-view'
timeout: 30000

View File

@ -0,0 +1,10 @@
appId: ${APP_ID}
---
- runFlow:
file: navigateToWorkspace.yaml
- tapOn:
id: 'workspace-view-login'
- extendedWaitUntil:
visible:
id: 'login-view'
timeout: 2000

View File

@ -0,0 +1,13 @@
appId: ${APP_ID}
---
- runFlow:
file: searchRoom.yaml
env:
room: ${room}
- tapOn:
text: ${room}
index: 1
- runFlow:
file: checkRoomTitle.yaml
env:
room: ${room}

View File

@ -0,0 +1,18 @@
appId: ${APP_ID}
env:
server: ${server || output.data.server}
---
- extendedWaitUntil:
visible:
id: 'new-server-view'
timeout: 60000
- tapOn:
id: 'new-server-view-input'
- inputText: ${server}
- pressKey: enter
- extendedWaitUntil:
visible:
id: 'workspace-view'
timeout: 60000
- assertVisible:
id: 'workspace-view'

View File

@ -0,0 +1,30 @@
appId: ${APP_ID}
---
- extendedWaitUntil:
visible:
id: 'rooms-list-view'
timeout: 30000
- tapOn:
id: 'rooms-list-view-search'
- extendedWaitUntil:
visible:
id: 'rooms-list-view-search-input'
timeout: 1000
- waitForAnimationToEnd:
timeout: 500
- runFlow:
when:
true: ${nativeElementAction === 'replaceText'}
commands:
- tapOn:
id: 'rooms-list-view-search-input'
- inputText: ''
- tapOn:
id: 'rooms-list-view-search-input'
- inputText: ${room}
- evalScript: ${output.helpers_sleep(500)}
- evalScript: ${output.helpers_sleep(500)}
- extendedWaitUntil:
visible:
id: ${roomTestId || 'rooms-list-view-item-' + room}
timeout: 60000

View File

@ -0,0 +1,70 @@
/* eslint-disable no-undef */
const { server } = output.data;
const baseURL = `${server}/api/v1/`;
let headers = {
'Content-Type': 'application/json;charset=UTF-8'
};
const login = (username, password) => {
console.log(`Logging in as user ${username}`);
const response = http.post(`${baseURL}login`, {
headers: { ...headers },
body: JSON.stringify({
user: username,
password
})
});
const { authToken, userId } = json(response.body).data;
headers = { ...headers, 'X-User-Id': userId, 'X-Auth-Token': authToken };
return { authToken, userId };
};
const createRandomUser = () => {
try {
login(output.data.adminUser, output.data.adminPassword);
const user = output.data.randomUser();
console.log(`Creating user ${user.username}`);
http.post(`${baseURL}users.create`, {
headers: { ...headers },
body: JSON.stringify({
username: user.username,
name: user.name,
password: user.password,
email: user.email
})
});
return user;
} catch (error) {
console.log(JSON.stringify(error));
throw new Error('Failed to create user');
}
};
const sendMessage = (user, channel, msg, tmid) => {
console.log(`Sending message to ${channel}`);
try {
login(user.username, user.password);
const response = http.post(`${baseURL}chat.postMessage`, {
headers: { ...headers },
body: JSON.stringify({
channel,
msg,
tmid
})
});
return json(response.body).data;
} catch (infoError) {
console.log(JSON.stringify(infoError));
throw new Error('Failed to find or create private group');
}
};
output.helpers_data_setup = {
login,
createRandomUser,
sendMessage
};

View File

@ -0,0 +1,11 @@
/* eslint-disable no-undef */
const random = (length = 10) => {
let text = '';
const possible = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
for (let i = 0; i < length; i += 1) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
output.helpers_random = random;

View File

@ -0,0 +1,9 @@
/* eslint-disable no-undef */
const sleep = (time) => {
const t = new Date().getTime() + time;
while (new Date().getTime() <= t) {
// do nothing
}
};
output.helpers_sleep = sleep;

6
e2e_maestro/init.yaml Normal file
View File

@ -0,0 +1,6 @@
appId: ${APP_ID}
---
- runScript: data.js
- runScript: helpers/random.js
- runScript: helpers/sleep.js
- runScript: helpers/data_setup.js

View File

@ -0,0 +1,48 @@
appId: ${APP_ID}
name: Onboarding
onFlowStart:
- runFlow: ../../init.yaml
---
- launchApp:
clearState: true
clearKeychain: true
permissions:
android.permission.POST_NOTIFICATIONS: allow
- extendedWaitUntil:
visible:
id: 'new-server-view'
timeout: 20000
# should have onboarding screen
- assertVisible:
id: 'new-server-view'
# should enter an invalid server and get error
- tapOn:
id: 'new-server-view-input'
- inputText: 'invalidtest'
- pressKey: enter
- extendedWaitUntil:
visible:
text: 'Oops!'
optional: true
timeout: 10000
- tapOn:
text: 'OK'
optional: true
# should enter a valid server without login services and navigate to login
- launchApp:
stopApp: true
- extendedWaitUntil:
visible:
id: 'new-server-view'
timeout: 5000
- tapOn:
id: 'new-server-view-input'
- inputText: ${output.data.server}
- pressKey: enter
- extendedWaitUntil:
visible:
id: 'new-server-view'
timeout: 60000

View File

@ -0,0 +1,54 @@
appId: ${APP_ID}
name: Mark as unread
env:
name: 'markasunread'
message: 'message-mark-as-unread'
onFlowStart:
- runFlow: ../../init.yaml
- runScript: helpers/07-markasunread/init.js
---
- evalScript: ${output.helpers_sleep(5000)}
- evalScript: ${output[name].user = output.helpers_data_setup.createRandomUser()}
- evalScript: ${output[name].otherUser = output.helpers_data_setup.createRandomUser()}
- launchApp:
clearState: true
clearKeychain: true
permissions:
android.permission.POST_NOTIFICATIONS: allow
- runFlow:
file: ../../helpers/app/navigateToLogin.yaml
- runFlow:
file: ../../helpers/app/login.yaml
env:
username: ${output[name].user.username}
password: ${output[name].user.password}
- runFlow:
file: ../../helpers/app/navigateToRoom.yaml
env:
room: ${output[name].otherUser.username}
# should mark message as unread
- evalScript: ${output.helpers_data_setup.sendMessage(output[name].otherUser,'@' + output[name].user.username, message)}
- extendedWaitUntil:
visible:
text: ${message}
timeout: 30000
- evalScript: ${output.helpers_sleep(300)}
- longPressOn:
text: ${message}
- extendedWaitUntil:
visible:
id: action-sheet-handle
timeout: 3000
- swipe:
direction: UP
duration: 100
- tapOn:
text: 'Mark unread'
index: 0
- extendedWaitUntil:
visible:
id: rooms-list-view
timeout: 5000
- assertVisible:
id: ${ 'rooms-list-view-item-' + output[name].otherUser.username }

View File

@ -0,0 +1,5 @@
/* eslint-disable no-undef */
output.markasunread = {
user: null,
otherUser: null
};