From 6124233fa8a372dc7c271d9f9d93aa0f2fcdc534 Mon Sep 17 00:00:00 2001 From: Wiktor Sieprawski Date: Wed, 17 Apr 2024 13:58:54 +0200 Subject: [PATCH 1/3] Chore/GitHub hosted detox runners (#2428) * chore: check xcrun presence * fix: correct yaml syntax * chore: boot simulator * chore: run tests * fix: change node version * chore: increase timeout * chore: boot simulator after installing deps * chore: use node 18.19.0 * fix: use node 18.12.1 * fix: install ci dependencies * fix: correct bootstrap scope * fix: scope syntax * fix: bootstrap syntax * fix: bootstrap syntax * fix: force lfs install * chore: install Detox CLI * fix: use iOS SDK 17.1 * test: increase init delay * test: significantly increase initial delay * chore: dump debug info * chore: continue on test run error * chore: skip logs dumping * fix: correct screenshot cmd * fix: change simulator uuid * fix: correct starting metro * chore: increase timeouts * chore: try to establish ws conn in intervals * chore: dynamically set simulator udid * chore: cleanup * fix: correct getting udid syntax * chore: temporarily switch to push trigger * chore: cleanup * chore: adjust intervals * fix: uploading screenshot * chore: change trigger condition * chore: debug run condition * chore: debug trigger condition * chore: restore previous run conditions * chore: android pipeline draft * chore: temporarily skip ios workflow * chore: list android emulators * chore: debug sdk home * chore: list avdmanager * chore: check docker presence * chore: check java * chore: use proper cmdline-tools to list avd * chore: create and boot avd * chore: install sdk image * fix: correct sdkmanager path * fix: auto accept sdk prompts * chore: list qemu * fix: resign qemu binary * fix: enter qemu loc * chore: debug entitlements * chore: change qemu path * chore: add verbose to qemu signing * chore: cleanup android workflow * chore: change ios workflow trigger * chore: restore self-hosted android detox * fix: force pull lfs --- .github/workflows/e2e-android-self.yml | 44 ++++++++++ .github/workflows/e2e-android.yml | 82 ++++++++++++++----- .github/workflows/e2e-ios.yml | 62 +++++++++++--- packages/mobile/.detoxrc.js | 2 +- packages/mobile/e2e/utils/consts/timeouts.js | 2 +- packages/mobile/ios/Podfile | 2 +- packages/mobile/ios/Podfile.lock | 4 +- .../ios/Quiet.xcodeproj/project.pbxproj | 4 +- packages/mobile/ios/Quiet/AppDelegate.m | 13 +-- packages/mobile/ios/Quiet/Info.plist | 16 ++-- .../startConnection/startConnection.saga.ts | 3 + 11 files changed, 183 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/e2e-android-self.yml diff --git a/.github/workflows/e2e-android-self.yml b/.github/workflows/e2e-android-self.yml new file mode 100644 index 0000000000..113d6e1d6f --- /dev/null +++ b/.github/workflows/e2e-android-self.yml @@ -0,0 +1,44 @@ +name: Detox E2E Android (self-hosted) + +on: + push: + paths: + - packages/mobile/** + - packages/backend/** + - packages/state-manager/** + - .github/workflows/e2e-android-self.yml + +jobs: + detox-android-self-hosted: + timeout-minutes: 10 + runs-on: [self-hosted, macOS, ARM64, android] + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + npm i + npm run lerna bootstrap --scope @quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/mobile,backend-bundle + + - name: Pull binaries + run: | + git lfs install --force + git lfs pull + + - name: Pass local config + run : | + cat << EOF >> packages/mobile/android/local.properties + ndk.path=/Users/quiet/Library/Android/sdk/ndk/25.1.8937393 + EOF + + - name: Build Detox + run: | + export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home + cd packages/mobile + detox build -c android.emu.debug.ci + + - name: Run basic tests + run: | + cd packages/mobile + detox test starter -c android.emu.debug.ci \ No newline at end of file diff --git a/.github/workflows/e2e-android.yml b/.github/workflows/e2e-android.yml index 4481bdc54d..08a9ee19de 100644 --- a/.github/workflows/e2e-android.yml +++ b/.github/workflows/e2e-android.yml @@ -1,40 +1,70 @@ -name: E2E Android +name: Detox E2E Android -on: - push: - paths: - - packages/mobile/** - - packages/backend/** - - packages/state-manager/** - - .github/workflows/e2e-android.yml +on: workflow_dispatch jobs: detox-android: - timeout-minutes: 10 - runs-on: [self-hosted, macOS, ARM64, android] + timeout-minutes: 25 + runs-on: [macos-latest-xlarge] steps: - uses: actions/checkout@v4 + + - uses: actions/setup-node@master + with: + node-version: 18.12.1 - name: Install dependencies run: | - npm i - npm run lerna bootstrap --scope @quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/mobile,backend-bundle + npm ci + npm run lerna bootstrap -- --scope=\'{@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/mobile,backend-bundle}\' - name: Pull binaries run: | - git lfs install + git lfs install --force git lfs pull - - name: Pass local config - run : | - cat << EOF >> packages/mobile/android/local.properties - ndk.path=/Users/quiet/Library/Android/sdk/ndk/25.1.8937393 - EOF + # see: https://stackoverflow.com/questions/67264212/android-emulator-crash-when-start-hvf-error-hv-error + - name: Create qemu entitlements + run: | + { + echo '' + echo '' + echo '' + echo '' + echo ' com.apple.security.hypervisor' + echo ' ' + echo '' + echo '' + } >> $ANDROID_HOME/emulator/qemu/darwin-aarch64/entitlements.xml + + - name: Re-sign qemu binary + run: | + cd $ANDROID_HOME/emulator/qemu/darwin-aarch64 + codesign -s - --entitlements entitlements.xml --force qemu-system-aarch64 --verbose + + - name: Install SDK image + run: yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install 'system-images;android-34;google_apis;arm64-v8a' + + - name: Create AVD + run: $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd -n emulator_ci -k 'system-images;android-34;google_apis;arm64-v8a' -d 'pixel_7' + + - name: Boot AVD + run: $ANDROID_HOME/emulator/emulator -avd emulator_ci + + - name: Install pm2 + run: npm install pm2@latest -g + + - name: Start metro + run: | + cd packages/mobile + pm2 --name METRO start npm -- start + + - name: Install Detox CLI + run: npm install detox-cli --global - name: Build Detox run: | - export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home cd packages/mobile detox build -c android.emu.debug.ci @@ -42,3 +72,17 @@ jobs: run: | cd packages/mobile detox test starter -c android.emu.debug.ci + + - name: Stop metro + run: pm2 stop METRO + + - name: Take screenshot + if: always() + run: | # TODO + + - name: Upload screenshot + if: always() + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: screenshot.png + path: screenshot.png diff --git a/.github/workflows/e2e-ios.yml b/.github/workflows/e2e-ios.yml index 34ad277d79..40e4a43dc7 100644 --- a/.github/workflows/e2e-ios.yml +++ b/.github/workflows/e2e-ios.yml @@ -1,28 +1,27 @@ -name: E2E iOS +name: Detox E2E iOS -on: - push: - paths: - - packages/mobile/** - - packages/backend/** - - packages/state-manager/** +on: workflow_dispatch jobs: detox-ios: - timeout-minutes: 10 - runs-on: [self-hosted, macOS, ARM64, iOS] + timeout-minutes: 25 + runs-on: [macos-latest-xlarge] steps: - uses: actions/checkout@v4 + + - uses: actions/setup-node@master + with: + node-version: 18.12.1 - name: Install dependencies run: | - npm i - npm run lerna bootstrap --scope @quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/mobile,backend-bundle + npm ci + npm run lerna bootstrap -- --scope=\'{@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/mobile,backend-bundle}\' - name: Pull binaries run: | - git lfs install + git lfs install --force git lfs pull - name: Install pods @@ -30,6 +29,30 @@ jobs: cd packages/mobile/ios pod install + - name: List simulator devices + run: xcrun simctl list devices + + - name: Boot simulator + run: | + UDID=$(xcrun simctl list devices | grep 'iPhone 15 (' | awk -F '[()]' '{print $2}' | awk 'NR==2') + xcrun simctl boot "$UDID" + + - name: Install pm2 + run: npm install pm2@latest -g + + - name: Start metro + run: | + cd packages/mobile + pm2 --name METRO start npm -- start + + - name: Install Detox CLI + run: npm install detox-cli --global + + - name: Install applesimutils + run: | + brew tap wix/brew + brew install applesimutils + - name: Build Detox run: | cd packages/mobile @@ -39,3 +62,18 @@ jobs: run: | cd packages/mobile detox test starter -c ios.sim.debug.ci + + - name: Stop metro + run: pm2 stop METRO + + - name: Take screenshot + if: always() + run: | + /usr/bin/xcrun simctl io booted screenshot screenshot.png + + - name: Upload screenshot + if: always() + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: screenshot.png + path: screenshot.png diff --git a/packages/mobile/.detoxrc.js b/packages/mobile/.detoxrc.js index a03e86f3a9..8ac7d9d0ce 100644 --- a/packages/mobile/.detoxrc.js +++ b/packages/mobile/.detoxrc.js @@ -77,7 +77,7 @@ module.exports = { emulator_ci: { type: 'android.emulator', device: { - avdName: 'Pixel_7_API_31', + avdName: 'emulator_ci', }, }, }, diff --git a/packages/mobile/e2e/utils/consts/timeouts.js b/packages/mobile/e2e/utils/consts/timeouts.js index f4bcf26440..9bdfda887e 100644 --- a/packages/mobile/e2e/utils/consts/timeouts.js +++ b/packages/mobile/e2e/utils/consts/timeouts.js @@ -1,3 +1,3 @@ export const BASIC = 5000 export const LONG = 20000 -export const STARTUP = 90000 +export const STARTUP = 120000 diff --git a/packages/mobile/ios/Podfile b/packages/mobile/ios/Podfile index 43c87c263b..de8795aada 100644 --- a/packages/mobile/ios/Podfile +++ b/packages/mobile/ios/Podfile @@ -1,6 +1,6 @@ require_relative '../node_modules/react-native/scripts/react_native_pods' -platform :ios, '17.4' +platform :ios, '17.1' target 'Quiet' do config = use_native_modules! diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock index e550809b7d..01c21f974e 100644 --- a/packages/mobile/ios/Podfile.lock +++ b/packages/mobile/ios/Podfile.lock @@ -1393,6 +1393,6 @@ SPEC CHECKSUMS: Tor: 39dc71bf048312e202608eb499ca5c74e841b503 Yoga: 13c8ef87792450193e117976337b8527b49e8c03 -PODFILE CHECKSUM: 811e75c5d23ebd5b5f3e16c6dd4bae230db730d0 +PODFILE CHECKSUM: eed49772dde039b0723324c813c83dd4c1af35f7 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/packages/mobile/ios/Quiet.xcodeproj/project.pbxproj b/packages/mobile/ios/Quiet.xcodeproj/project.pbxproj index 489dc64d1f..c101495008 100644 --- a/packages/mobile/ios/Quiet.xcodeproj/project.pbxproj +++ b/packages/mobile/ios/Quiet.xcodeproj/project.pbxproj @@ -5300,7 +5300,7 @@ INFOPLIST_FILE = Quiet/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Quiet; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; - IPHONEOS_DEPLOYMENT_TARGET = 17.4; + IPHONEOS_DEPLOYMENT_TARGET = 17.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5396,7 +5396,7 @@ INFOPLIST_FILE = Quiet/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Quiet; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; - IPHONEOS_DEPLOYMENT_TARGET = 17.4; + IPHONEOS_DEPLOYMENT_TARGET = 17.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/packages/mobile/ios/Quiet/AppDelegate.m b/packages/mobile/ios/Quiet/AppDelegate.m index cb92136270..ee2c2c74ac 100644 --- a/packages/mobile/ios/Quiet/AppDelegate.m +++ b/packages/mobile/ios/Quiet/AppDelegate.m @@ -51,11 +51,14 @@ - (void) initWebsocketConnection { * Delay used below can't cause any race condition as websocket won't connect until data server starts listening anyway. */ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ - NSTimeInterval delayInSeconds = 5; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { - [[self.bridge moduleForName:@"CommunicationModule"] sendDataPortWithPort:self.dataPort socketIOSecret:self.socketIOSecret]; - }); + NSArray *intervals = @[@5, @15, @30, @60, @90]; + for (NSNumber *interval in intervals) { + NSTimeInterval delayInSeconds = [interval doubleValue]; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { + [[self.bridge moduleForName:@"CommunicationModule"] sendDataPortWithPort:self.dataPort socketIOSecret:self.socketIOSecret]; + }); + } }); } diff --git a/packages/mobile/ios/Quiet/Info.plist b/packages/mobile/ios/Quiet/Info.plist index 51846dee44..1ba9e97f4a 100644 --- a/packages/mobile/ios/Quiet/Info.plist +++ b/packages/mobile/ios/Quiet/Info.plist @@ -36,26 +36,26 @@ CFBundleVersion 371 ITSAppUsesNonExemptEncryption - + LSRequiresIPhoneOS - + NSAppTransportSecurity NSAllowsArbitraryLoads - + NSAllowsLocalNetworking - + NSExceptionDomains localhost NSExceptionAllowsInsecureHTTPLoads - + NSLocationWhenInUseUsageDescription - + UIAppFonts Rubik-Black.ttf @@ -74,7 +74,7 @@ Rubik-SemiBoldItalic.ttf UIBackgroundModes - + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -88,6 +88,6 @@ UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance - + diff --git a/packages/mobile/src/store/init/startConnection/startConnection.saga.ts b/packages/mobile/src/store/init/startConnection/startConnection.saga.ts index 5a4d63ce1b..6cf1f49ef3 100644 --- a/packages/mobile/src/store/init/startConnection/startConnection.saga.ts +++ b/packages/mobile/src/store/init/startConnection/startConnection.saga.ts @@ -10,6 +10,9 @@ import { eventChannel } from 'redux-saga' export function* startConnectionSaga( action: PayloadAction['payload']> ): Generator { + const isAlreadyConnected = yield* select(initSelectors.isWebsocketConnected) + if (isAlreadyConnected) return + const { dataPort, socketIOSecret } = action.payload console.log('WEBSOCKET', 'Entered start connection saga', dataPort) From db587a436061917f7cb95807f33e5d758e940cee Mon Sep 17 00:00:00 2001 From: Isla Koenigsknecht Date: Wed, 17 Apr 2024 17:39:51 -0400 Subject: [PATCH 2/3] Chore(1977): Fix e2e tests on windows (#2454) * Fix windows E2E tests * Reorder tests and try to wait for multiple client test case to finish --- .github/workflows/e2e-crossplatform.yml | 1 + .github/workflows/e2e-win.yml | 23 ++++++++++--------- packages/e2e-tests/src/selectors.ts | 4 ++-- .../src/tests/invitationLink.test.ts | 5 +++- .../src/tests/multipleClients.test.ts | 2 +- packages/e2e-tests/src/utils.ts | 6 ++--- 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.github/workflows/e2e-crossplatform.yml b/.github/workflows/e2e-crossplatform.yml index b45d5caddb..e5e629a763 100644 --- a/.github/workflows/e2e-crossplatform.yml +++ b/.github/workflows/e2e-crossplatform.yml @@ -8,6 +8,7 @@ on: - packages/state-manager/** - packages/identity/** - packages/common/** + - packages/e2e-tests/** jobs: mac: diff --git a/.github/workflows/e2e-win.yml b/.github/workflows/e2e-win.yml index 3c6b32f20e..541f73edb7 100644 --- a/.github/workflows/e2e-win.yml +++ b/.github/workflows/e2e-win.yml @@ -92,15 +92,7 @@ jobs: max_attempts: 3 shell: bash command: cd packages/e2e-tests && npm run test oneClient.test.ts - - - name: Run multiple clients test - uses: nick-fields/retry@14672906e672a08bd6eeb15720e9ed3ce869cdd4 # v2.9.0 - with: - timeout_minutes: 30 - max_attempts: 3 - shell: bash - command: cd packages/e2e-tests && npm run test multipleClients.test.ts - + - name: Run user profile test uses: nick-fields/retry@v2 with: @@ -108,11 +100,20 @@ jobs: max_attempts: 3 shell: bash command: cd packages/e2e-tests && npm run test userProfile.test.ts - + - name: Run invitation link test - Includes 2 separate application clients uses: nick-fields/retry@14672906e672a08bd6eeb15720e9ed3ce869cdd4 # v2.9.0 with: - timeout_minutes: 25 + timeout_minutes: 10 max_attempts: 3 shell: bash command: cd packages/e2e-tests && npm run test invitationLink.test.ts + + + - name: Run multiple clients test + uses: nick-fields/retry@14672906e672a08bd6eeb15720e9ed3ce869cdd4 # v2.9.0 + with: + timeout_minutes: 30 + max_attempts: 3 + shell: bash + command: cd packages/e2e-tests && npm run test multipleClients.test.ts \ No newline at end of file diff --git a/packages/e2e-tests/src/selectors.ts b/packages/e2e-tests/src/selectors.ts index bcd637b221..4ecbfdb7f9 100644 --- a/packages/e2e-tests/src/selectors.ts +++ b/packages/e2e-tests/src/selectors.ts @@ -51,12 +51,12 @@ export class App { console.log('App closed', this.buildSetup.dataDir) } - async cleanup() { + async cleanup(force: boolean = false) { console.log(`Performing app cleanup`, this.buildSetup.dataDir) if (this.isOpened) { throw new Error(`App with dataDir ${this.buildSetup.dataDir} is still open, close before cleaning up!`) } - this.buildSetup.clearDataDir() + this.buildSetup.clearDataDir(force) } get saveStateButton() { diff --git a/packages/e2e-tests/src/tests/invitationLink.test.ts b/packages/e2e-tests/src/tests/invitationLink.test.ts index 472780f041..321bc77489 100644 --- a/packages/e2e-tests/src/tests/invitationLink.test.ts +++ b/packages/e2e-tests/src/tests/invitationLink.test.ts @@ -26,6 +26,9 @@ describe('New user joins using invitation link while having app opened', () => { beforeAll(async () => { ownerApp = new App() guestApp = new App({ defaultDataDir: true }) + if (process.platform === 'win32') { + await guestApp.cleanup(true) + } }) beforeEach(async () => { @@ -143,7 +146,7 @@ describe('New user joins using invitation link while having app opened', () => { const copiedCode = url.hash.substring(1) expect(() => parseInvitationCode(copiedCode)).not.toThrow() const data = parseInvitationCode(copiedCode) - const commandFull = `${command[process.platform as SupportedPlatformDesktop]} "${composeInvitationDeepUrl(data)}"` + const commandFull = `${command[process.platform as SupportedPlatformDesktop]} ${process.platform === 'win32' ? '""' : ''} "${composeInvitationDeepUrl(data)}"` console.log(`Calling ${commandFull}`) execSync(commandFull) console.log('Guest opened invitation link') diff --git a/packages/e2e-tests/src/tests/multipleClients.test.ts b/packages/e2e-tests/src/tests/multipleClients.test.ts index 59ad62e03a..41a3806992 100644 --- a/packages/e2e-tests/src/tests/multipleClients.test.ts +++ b/packages/e2e-tests/src/tests/multipleClients.test.ts @@ -341,7 +341,7 @@ describe('Multiple Clients', () => { await new Promise(resolve => setTimeout(() => { resolve() - }, 2000) + }, 20000) ) const channels = await sidebarOwner.getChannelList() expect(channels.length).toEqual(2) diff --git a/packages/e2e-tests/src/utils.ts b/packages/e2e-tests/src/utils.ts index cb38d7a977..01eadb33a4 100644 --- a/packages/e2e-tests/src/utils.ts +++ b/packages/e2e-tests/src/utils.ts @@ -53,9 +53,9 @@ export class BuildSetup { } private getBinaryLocation() { - console.log('filename', this.fileName) switch (process.platform) { case 'linux': + console.log('filename', this.fileName) return `${__dirname}/../Quiet/${this.fileName ? this.fileName : BuildSetup.getEnvFileName()}` case 'win32': return `${process.env.LOCALAPPDATA}\\Programs\\@quietdesktop\\Quiet.exe` @@ -233,8 +233,8 @@ export class BuildSetup { await this.driver?.close() } - public clearDataDir() { - if (process.env.IS_CI === 'true') { + public clearDataDir(force: boolean = false) { + if (process.env.IS_CI === 'true' && !force) { console.warn('Not deleting data directory because we are running in CI') return } From b8ef7450c8a3364ed99e6240e2add0c7a73ea271 Mon Sep 17 00:00:00 2001 From: Wiktor Sieprawski Date: Fri, 19 Apr 2024 11:59:32 +0200 Subject: [PATCH 3/3] fix: postpone restore connection saga (#2462) --- .../startConnection/restoreConnection/restoreConnection.saga.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mobile/src/store/init/startConnection/restoreConnection/restoreConnection.saga.ts b/packages/mobile/src/store/init/startConnection/restoreConnection/restoreConnection.saga.ts index 7b7195b2be..3a7c001360 100644 --- a/packages/mobile/src/store/init/startConnection/restoreConnection/restoreConnection.saga.ts +++ b/packages/mobile/src/store/init/startConnection/restoreConnection/restoreConnection.saga.ts @@ -2,7 +2,7 @@ import { delay, put, select } from 'typed-redux-saga' import { initSelectors } from '../../init.selectors' import { initActions } from '../../init.slice' -const WEBSOCKET_CONNECTION_DELAY = 5000 +const WEBSOCKET_CONNECTION_DELAY = 15000 export function* restoreConnectionSaga(): Generator { // Give the worker time to init websocket connection