@Mobile • Test App End-2-End triggered by gre on ref live-12636 #17881
Workflow file for this run
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: "@Mobile • Test App End-2-End" | |
run-name: "@Mobile • Test App End-2-End triggered by ${{ inputs.login || github.actor }} ${{ format('on ref {0}', github.ref_name) }}" | |
on: | |
push: | |
branches: | |
- main | |
- develop | |
- release | |
- hotfix | |
workflow_dispatch: | |
inputs: | |
ref: | |
description: | | |
If you run this manually, and want to run on a PR, the correct ref should be refs/pull/{PR_NUMBER}/merge to | |
have the "normal" scenario involving checking out a merge commit between your branch and the base branch. | |
If you want to run only on a branch or specific commit, you can use either the sha or the branch name instead (prefer the first verion for PRs). | |
login: | |
description: The GitHub username that triggered the workflow | |
required: false | |
base_ref: | |
description: The base branch to merge the head into when checking out the code | |
required: false | |
# Uncomment to have log-level: trace on detox run and build | |
# (cf: apps/ledger-live-mobile/detox.config.js) | |
# env: | |
# DEBUG_DETOX: true | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.ref_name != 'develop' && github.ref || github.run_id }} | |
cancel-in-progress: true | |
permissions: | |
id-token: write | |
contents: read | |
jobs: | |
detox-tests-ios: | |
name: "Ledger Live Mobile - iOS Detox Tests" | |
runs-on: [m1, ARM64] | |
env: | |
NODE_OPTIONS: "--max-old-space-size=7168" | |
LANG: en_US.UTF-8 | |
LANGUAGE: en_US.UTF-8 | |
LC_ALL: en_US.UTF-8 | |
outputs: | |
status: ${{ steps.detox.outcome }} | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ inputs.ref || github.sha }} | |
- name: setup caches | |
id: caches | |
uses: LedgerHQ/ledger-live/tools/actions/composites/setup-caches@develop | |
with: | |
skip-pod-cache: "false" | |
skip-turbo-cache: "false" | |
accountId: ${{ secrets.AWS_ACCOUNT_ID_PROD }} | |
roleName: ${{ secrets.AWS_CACHE_ROLE_NAME }} | |
region: ${{ secrets.AWS_CACHE_REGION }} | |
turbo-server-token: ${{ secrets.TURBOREPO_SERVER_TOKEN }} | |
- name: Cache LLM pods | |
uses: actions/cache@v3 | |
with: | |
path: | | |
apps/ledger-live-mobile/ios/Pods | |
~/Library/Caches/CocoaPods | |
~/.cocoapods | |
key: ${{ runner.os }}-pods-${{ hashFiles('apps/ledger-live-mobile/ios/Podfile.lock') }} | |
- name: Configure AWS credentials | |
uses: aws-actions/configure-aws-credentials@v3 | |
id: aws | |
with: | |
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID_PROD }}:role/${{ secrets.AWS_CACHE_ROLE_NAME }} | |
aws-region: ${{ secrets.AWS_CACHE_REGION }} | |
- name: cache detox build | |
uses: tespkg/actions-cache@v1 | |
if: steps.aws.conclusion == 'success' | |
id: detox-build | |
with: | |
path: ${{ github.workspace }}/apps/ledger-live-mobile/ios/build/Build/Products/Release-iphonesimulator | |
key: ${{ runner.os }}-detox-${{ hashFiles('apps/ledger-live-mobile/ios/Podfile.lock', 'apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj') }} | |
restore-keys: | | |
${{ runner.os }}-detox- | |
accessKey: ${{ env.AWS_ACCESS_KEY_ID }} | |
secretKey: ${{ env.AWS_SECRET_ACCESS_KEY }} | |
sessionToken: ${{ env.AWS_SESSION_TOKEN}} | |
bucket: ll-gha-s3-cache | |
region: ${{ secrets.AWS_CACHE_REGION }} | |
use-fallback: false | |
- name: install dependencies | |
run: | | |
pnpm i --filter="live-mobile..." --filter="ledger-live" --filter="@ledgerhq/dummy-*-app..." --no-frozen-lockfile --unsafe-perm | |
- name: Build dependencies | |
run: | | |
pnpm build:llm:deps --api="http://127.0.0.1:${{ steps.caches.outputs.port }}" --token="${{ secrets.TURBOREPO_SERVER_TOKEN }}" --team="foo" | |
- name: Build Dummy Live SDK and Dummy Wallet API apps for testing | |
run: | | |
pnpm build:dummy-apps | |
shell: bash | |
- name: Create iOS simulator | |
id: simulator | |
run: | | |
ID=$(xcrun simctl create "iPhone 14" "iPhone 14") | |
echo "id=$ID" >> $GITHUB_OUTPUT | |
- name: Build iOS app for Detox test run | |
if: steps.detox-build.outputs.cache-hit != 'true' | |
run: pnpm mobile e2e:ci -p ios -b | |
- name: Build JS Bundle app for Detox test run | |
if: steps.detox-build.outputs.cache-hit == 'true' | |
run: pnpm mobile e2e:ci -p ios --bundle | |
- name: Test iOS app | |
id: detox | |
timeout-minutes: 75 | |
run: pnpm mobile e2e:ci -p ios -t | |
- name: Delete iOS simulator | |
if: ${{ always() && steps.simulator.outputs.id }} | |
run: | | |
xcrun simctl delete ${{ steps.simulator.outputs.id }} | |
- name: Generate single file Allure report | |
if: ${{ !cancelled() || steps.detox.outcome == 'cancelled' }} | |
run: pnpm dlx allure-commandline generate apps/ledger-live-mobile/artifacts --single-file | |
- name: Upload Allure single file report | |
uses: actions/upload-artifact@v4 | |
if: ${{ !cancelled() || steps.detox.outcome == 'cancelled' }} | |
with: | |
name: ios-allure-report | |
path: allure-report/index.html | |
- name: Upload test artifacts | |
uses: actions/upload-artifact@v4 | |
if: ${{ !cancelled() || steps.detox.outcome == 'cancelled' }} | |
with: | |
name: "ios-test-artifacts" | |
path: apps/ledger-live-mobile/artifacts | |
allure-report-ios: | |
name: "Allure Reports Export on Server" | |
runs-on: [ledger-live-medium] | |
if: ${{ !cancelled() && github.ref_name == 'develop' }} | |
needs: [detox-tests-ios] | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ (github.event_name == 'workflow_dispatch' && (inputs.ref || github.ref_name)) || github.sha }} | |
- uses: LedgerHQ/ledger-live/tools/actions/composites/upload-allure-report@develop | |
with: | |
platform: ios | |
login: ${{ vars.ALLURE_USERNAME }} | |
password: ${{ secrets.ALLURE_LEDGER_LIVE_PASSWORD }} | |
path: ios-test-artifacts | |
detox-tests-android: | |
name: "Ledger Live Mobile - Android Detox Tests" | |
runs-on: [ledger-live-linux-8CPU-32RAM] | |
env: | |
NODE_OPTIONS: "--max-old-space-size=7168" | |
LANG: en_US.UTF-8 | |
LANGUAGE: en_US.UTF-8 | |
LC_ALL: en_US.UTF-8 | |
AVD_API: 32 | |
AVD_ARCH: x86_64 | |
AVD_PROFILE: pixel_6_pro | |
AVD_TARGET: google_apis | |
AVD_NAME: "Pixel_6_Pro_API_32" | |
AVD_CORES: 4 | |
AVD_RAM_SIZE: 4096M | |
AVD_OPTIONS: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none | |
outputs: | |
status: ${{ steps.detox.outcome }} | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ inputs.ref || github.sha }} | |
- name: Setup the toolchain | |
id: toolchain | |
uses: LedgerHQ/ledger-live/tools/actions/composites/setup-toolchain@develop | |
with: | |
skip-turbo-cache: "false" | |
accountId: ${{ secrets.AWS_ACCOUNT_ID_PROD }} | |
roleName: ${{ secrets.AWS_CACHE_ROLE_NAME }} | |
region: ${{ secrets.AWS_CACHE_REGION }} | |
turbo-server-token: ${{ secrets.TURBOREPO_SERVER_TOKEN }} | |
- name: setup JDK 17 | |
uses: actions/setup-java@v3 | |
with: | |
distribution: "zulu" | |
java-version: "17" | |
cache: "gradle" | |
- name: setup Android SDK | |
uses: android-actions/[email protected] | |
- name: Gradle cache | |
uses: gradle/gradle-build-action@v2 | |
# https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ | |
- name: Enable KVM group perms | |
run: | | |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules | |
sudo udevadm control --reload-rules | |
sudo udevadm trigger --name-match=kvm | |
- name: Fix emulator directory permissions | |
continue-on-error: true | |
run: sudo chown -R $(whoami):$(id -ng) /usr/local/lib/android/sdk/emulator/ | |
- name: Install dependencies | |
run: | | |
pnpm i --filter="live-mobile..." --filter="ledger-live" --filter="@ledgerhq/dummy-*-app..." --no-frozen-lockfile --unsafe-perm | |
- name: Build dependencies | |
run: | | |
pnpm build:llm:deps --api="http://127.0.0.1:${{ steps.toolchain.outputs.port }}" --token="${{ secrets.TURBOREPO_SERVER_TOKEN }}" --team="foo" | |
- name: Build Dummy Live SDK and Dummy Wallet API apps for testing | |
run: | | |
pnpm build:dummy-apps --api="http://127.0.0.1:${{ steps.toolchain.outputs.port }}" --token="${{ secrets.TURBOREPO_SERVER_TOKEN }}" --team="foo" | |
shell: bash | |
- name: Build Android app for Detox test run | |
run: | | |
pnpm mobile e2e:ci -p android -b | |
- name: cache android emulator | |
timeout-minutes: 5 | |
uses: tespkg/actions-cache@v1 | |
id: detox-avd | |
continue-on-error: true | |
with: | |
path: | | |
~/.android/avd/* | |
~/.android/adb* | |
/usr/local/lib/android/sdk/system-images/android-${{ env.AVD_API }}/${{ env.AVD_TARGET }}/${{ env.AVD_ARCH }}/* | |
/usr/local/lib/android/sdk/emulator/* | |
key: ${{ runner.os }}-detox-avd-${{ env.AVD_NAME }}-${{ env.AVD_PROFILE }}-${{ env.AVD_TARGET }}-${{ env.AVD_API }}-${{ env.AVD_ARCH }} | |
accessKey: ${{ env.AWS_ACCESS_KEY_ID }} | |
secretKey: ${{ env.AWS_SECRET_ACCESS_KEY }} | |
sessionToken: ${{ env.AWS_SESSION_TOKEN }} | |
bucket: ll-gha-s3-cache | |
region: ${{ secrets.AWS_CACHE_REGION }} | |
use-fallback: false | |
- name: create AVD and generate snapshot for caching | |
if: steps.detox-avd.outputs.cache-hit != 'true' | |
uses: reactivecircus/android-emulator-runner@v2 | |
with: | |
api-level: ${{ env.AVD_API }} | |
arch: ${{ env.AVD_ARCH }} | |
profile: ${{ env.AVD_PROFILE }} | |
target: ${{ env.AVD_TARGET }} | |
avd-name: ${{ env.AVD_NAME }} | |
force-avd-creation: true | |
cores: ${{ env.AVD_CORES }} | |
ram-size: ${{ env.AVD_RAM_SIZE }} | |
disable-linux-hw-accel: false | |
emulator-options: ${{ env.AVD_OPTIONS }} | |
script: ./tools/scripts/wait_emulator_idle.sh | |
- name: Run Android Tests | |
id: detox | |
run: pnpm mobile e2e:ci -p android -t | |
timeout-minutes: 45 | |
env: | |
DETOX_INSTALL_TIMEOUT: 120000 | |
- name: Generate single file Allure report | |
if: ${{ !cancelled() || steps.detox.outcome == 'cancelled' }} | |
run: pnpm dlx allure-commandline generate apps/ledger-live-mobile/artifacts --single-file | |
- name: Upload Allure single file report | |
uses: actions/upload-artifact@v4 | |
if: ${{ !cancelled() || steps.detox.outcome == 'cancelled' }} | |
with: | |
name: android-allure-report | |
path: allure-report/index.html | |
- name: Upload test artifacts | |
uses: actions/upload-artifact@v4 | |
if: ${{ !cancelled() || steps.detox.outcome == 'cancelled' }} | |
with: | |
name: "android-test-artifacts" | |
path: apps/ledger-live-mobile/artifacts/ | |
allure-report-android: | |
name: "Allure Reports Export on Server" | |
runs-on: [ledger-live-medium] | |
if: ${{ !cancelled() && github.ref_name == 'develop' }} | |
needs: [detox-tests-android] | |
steps: | |
- name: checkout | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ (github.event_name == 'workflow_dispatch' && (inputs.ref || github.ref_name)) || github.sha }} | |
- uses: LedgerHQ/ledger-live/tools/actions/composites/upload-allure-report@develop | |
with: | |
platform: android | |
login: ${{ vars.ALLURE_USERNAME }} | |
password: ${{ secrets.ALLURE_LEDGER_LIVE_PASSWORD }} | |
path: android-test-artifacts | |
report: | |
needs: [detox-tests-android, detox-tests-ios] | |
runs-on: ubuntu-latest | |
if: ${{ !cancelled() && github.event_name == 'workflow_dispatch' }} | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ inputs.ref || github.sha }} | |
- uses: actions/github-script@v6 | |
name: prepare status | |
id: status | |
with: | |
script: | | |
const fs = require("fs"); | |
const [ owner, repo ] = "${{ github.repository }}".split("/"); | |
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRunAttempt, { | |
owner, | |
repo, | |
run_id: "${{ github.run_id }}", | |
attempt_number: "${{ github.run_attempt }}", | |
}); | |
const findJobUrl = os => | |
jobs.find(job => job.name == `Ledger Live Mobile - ${os} Detox Tests`)?.html_url; | |
const keys = { | |
ios: { | |
symbol: "🍏", | |
name: "iOS", | |
jobUrl: findJobUrl("iOS") | |
}, | |
android: { | |
symbol: "🤖", | |
name: "Android", | |
jobUrl: findJobUrl("Android") | |
}, | |
}; | |
const report = { | |
ios: { | |
pass: ${{ needs.detox-tests-ios.outputs.status == 'success' }}, | |
status: "${{ needs.detox-tests-ios.outputs.status }}", | |
}, | |
android: { | |
pass: ${{ needs.detox-tests-android.outputs.status == 'success'}}, | |
status: "${{ needs.detox-tests-android.outputs.status }}", | |
}, | |
}; | |
let summary = `### Detox Tests | |
` | |
summary += `|` | |
const reportKeys = Object.keys(report); | |
const detoxSuccess = Object.entries(report).every(([os, values]) => !!values.pass); | |
reportKeys.forEach((k) => { | |
summary += ` [${keys[k].symbol} ${keys[k].name}](${keys[k].jobUrl}) |`; | |
}); | |
summary += ` | |
|`; | |
for (let i = 0; i < reportKeys.length; i++) { | |
summary += ` :--: |`; | |
} | |
summary += ` | |
|`; | |
Object.entries(report).forEach(([os, values]) => { | |
summary += ` ${values.pass ? "✅" : "❌"} (${values.status}) |`; | |
}); | |
const output = { | |
summary | |
}; | |
fs.writeFileSync("summary.json", JSON.stringify(output), "utf-8"); | |
- uses: actions/upload-artifact@v4 | |
name: upload summary | |
with: | |
name: summary.json | |
path: ${{ github.workspace }}/summary.json | |
report-on-slack: | |
runs-on: ubuntu-latest | |
needs: [detox-tests-android, detox-tests-ios] | |
if: ${{ failure() && github.event_name == 'push' }} | |
steps: | |
- name: format message | |
uses: actions/github-script@v6 | |
id: message | |
with: | |
script: | | |
const fs = require("fs"); | |
const text = `❌ 🍏 Detox tests failed ❌`; | |
const iOSResult = [ | |
{ | |
"type": "header", | |
"text": { | |
"type": "plain_text", | |
"text": `❌ iOS Detox tests failed ❌`, | |
"emoji": true | |
} | |
}, | |
{ | |
"type": "divider" | |
}, | |
{ | |
"type": "section", | |
"text": { | |
"type": "mrkdwn", | |
"text": `😵 Build Failed | |
` | |
} | |
} | |
]; | |
const androidResult = [ | |
{ | |
"type": "header", | |
"text": { | |
"type": "plain_text", | |
"text": `❌ Android Detox tests failed ❌`, | |
"emoji": true | |
} | |
}, | |
{ | |
"type": "divider" | |
}, | |
{ | |
"type": "section", | |
"text": { | |
"type": "mrkdwn", | |
"text": `😵 Build Failed | |
` | |
} | |
} | |
]; | |
const infoBlock = [ | |
{ | |
"type": "divider" | |
}, | |
{ | |
"type": "section", | |
"text": { | |
"type": "mrkdwn", | |
"text": `<https://github.com/LedgerHQ/ledger-live/actions/runs/${{ github.run_id }}|Workflow run> for more informations`, | |
} | |
} | |
]; | |
const blocks = [] | |
.concat(${{ needs.detox-tests-ios.outputs.status == 'success' }} ? [] : iOSResult) | |
.concat(${{ needs.detox-tests-android.outputs.status == 'success' }} ? [] : androidResult) | |
.concat(infoBlock); | |
const result = { | |
text, | |
blocks | |
}; | |
fs.writeFileSync(`./payload-slack-content.json`, JSON.stringify(result, null, 2)); | |
- name: post to a Slack channel | |
id: slack | |
uses: slackapi/[email protected] | |
with: | |
channel-id: "CTMQ0S5SB" | |
payload-file-path: "./payload-slack-content.json" | |
env: | |
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }} | |
- name: post to a Slack channel | |
if: contains(fromJson('["develop", "main"]'), github.ref_name) | |
uses: slackapi/[email protected] | |
with: | |
channel-id: "C05FKJ7DFAP" | |
payload-file-path: "./payload-slack-content.json" | |
env: | |
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }} |