Test Mobile E2E #8
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: "Test Mobile E2E" | |
on: | |
schedule: | |
- cron: "0 3 * * 1-5" | |
workflow_call: | |
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). | |
type: string | |
required: false | |
login: | |
description: The GitHub username that triggered the workflow | |
type: string | |
required: false | |
base_ref: | |
description: The base branch to merge the head into when checking out the code | |
type: string | |
required: false | |
export_to_xray: | |
description: Send tests results to Xray | |
required: false | |
type: boolean | |
default: false | |
test_execution_android: | |
description: "[Android] Test Execution ticket ID. Ex: 'B2CQA-2461'" | |
required: false | |
type: string | |
test_execution_ios: | |
description: "[iOS] Test Execution ticket ID. Ex: 'B2CQA-2461'" | |
required: false | |
type: string | |
speculos_tests: | |
description: Run Speculos tests | |
required: false | |
type: boolean | |
default: false | |
slack_notif: | |
description: "Send notification to Slack?" | |
required: false | |
type: boolean | |
default: false | |
# Uncomment to have log-level: trace on detox run and build | |
# (cf: apps/ledger-live-mobile/detox.config.js) | |
# env: | |
# DEBUG_DETOX: true | |
permissions: | |
id-token: write | |
contents: read | |
env: | |
SPECULOS_IMAGE_TAG: ghcr.io/ledgerhq/speculos:0.11 | |
COINAPPS: ${{ github.workspace }}/coin-apps | |
SPECULOS_RUN: ${{ inputs.speculos_tests || github.event_name == 'schedule' }} | |
jobs: | |
detox-tests-ios: | |
name: "LLM - iOS Detox Tests" | |
runs-on: [m1, ARM64] | |
if: ${{ !inputs.speculos_tests && github.event_name != 'schedule' }} | |
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: Configure AWS credentials | |
uses: aws-actions/configure-aws-credentials@v4 | |
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 }} | |
- uses: nick-fields/retry@v3 | |
name: install dependencies | |
with: | |
max_attempts: 2 | |
timeout_minutes: 15 | |
command: pnpm i --filter="live-mobile..." --filter="ledger-live" --filter="@ledgerhq/dummy-*-app..." --no-frozen-lockfile --unsafe-perm | |
new_command_on_retry: rm -rf ~/.cocoapods/ && pnpm clean && pnpm i --filter="live-mobile..." --filter="ledger-live" --filter="@ledgerhq/dummy-*-app..." --no-frozen-lockfile --unsafe-perm | |
- 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', 'apps/ledger-live-mobile/detox.config.js', 'apps/ledger-live-mobile/.env.mock') }} | |
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: 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: Setup Speculos image and Coin Apps | |
if: ${{ env.SPECULOS_RUN == 'true' }} | |
uses: LedgerHQ/ledger-live/tools/actions/composites/setup-speculos_image@develop | |
with: | |
coinapps_path: ${{ env.COINAPPS }} | |
speculos_tag: ${{ env.SPECULOS_IMAGE_TAG }} | |
bot_id: ${{ secrets.GH_BOT_APP_ID }} | |
bot_key: ${{ secrets.GH_BOT_PRIVATE_KEY }} | |
- name: Test iOS app | |
id: detox | |
timeout-minutes: 75 | |
run: pnpm mobile e2e:ci -p ios -t $([[ "$INPUT_SPECULOS" == "true" ]] && printf %s '--speculos') | |
env: | |
SEED: ${{ secrets.SEED_QAA_B2C }} | |
INPUT_SPECULOS: ${{ env.SPECULOS_RUN }} | |
- name: Delete iOS simulator | |
if: ${{ always() && steps.simulator.outputs.id }} | |
run: | | |
xcrun simctl delete ${{ steps.simulator.outputs.id }} | |
- 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: ${{ always() && (inputs.slack_notif || github.event_name == 'push') }} | |
needs: [detox-tests-ios] | |
outputs: | |
report-url: ${{ steps.upload.outputs.report-url }} | |
result: ${{ steps.summary.outputs.test_result }} | |
status: ${{ needs.detox-tests-ios.outputs.status }} | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event_name == 'pull_request') && (inputs.ref || github.ref_name)) || github.sha }} | |
- uses: LedgerHQ/ledger-live/tools/actions/composites/upload-allure-report@develop | |
id: upload | |
with: | |
platform: ios | |
login: ${{ vars.ALLURE_USERNAME }} | |
password: ${{ secrets.ALLURE_LEDGER_LIVE_PASSWORD }} | |
path: ios-test-artifacts | |
- name: Get summary | |
id: summary | |
if: ${{ !cancelled() }} | |
uses: LedgerHQ/ledger-live/tools/actions/composites/get-allure-summary@develop | |
with: | |
allure-results-path: ios-test-artifacts | |
platform: iOS | |
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 }} | |
strategy: | |
fail-fast: false | |
matrix: | |
shardIndex: [1, 2, 3] | |
shardTotal: [3, 1] | |
exclude: | |
- shardIndex: ${{ (github.event.inputs.speculos_tests == 'false' || github.event_name == 'push') && '2' }} | |
- shardIndex: ${{ (github.event.inputs.speculos_tests == 'false' || github.event_name == 'push') && '3' }} | |
- shardTotal: ${{ (github.event.inputs.speculos_tests == 'false' || github.event_name == 'push') && '3' }} | |
- shardTotal: ${{ github.event.inputs.speculos_tests != 'false' && github.event_name != 'push' && '1' }} | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ inputs.ref || github.sha }} | |
- name: Setup the caches | |
uses: LedgerHQ/ledger-live/tools/actions/composites/setup-caches@develop | |
id: setup-caches | |
with: | |
install-proto: true | |
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.setup-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 --api="http://127.0.0.1:${{ steps.setup-caches.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: Setup Speculos image and Coin Apps | |
if: ${{ env.SPECULOS_RUN == 'true' }} | |
uses: LedgerHQ/ledger-live/tools/actions/composites/setup-speculos_image@develop | |
with: | |
coinapps_path: ${{ env.COINAPPS }} | |
speculos_tag: ${{ env.SPECULOS_IMAGE_TAG }} | |
bot_id: ${{ secrets.GH_BOT_APP_ID }} | |
bot_key: ${{ secrets.GH_BOT_PRIVATE_KEY }} | |
- name: Run Android Tests | |
id: detox | |
run: pnpm mobile e2e:ci -p android -t $([[ "$INPUT_SPECULOS" == "true" ]] && printf %s '--speculos') --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} | |
timeout-minutes: ${{ env.SPECULOS_RUN && 120 || 45 }} | |
env: | |
DETOX_INSTALL_TIMEOUT: 120000 | |
SEED: ${{ secrets.SEED_QAA_B2C }} | |
INPUT_SPECULOS: ${{ env.SPECULOS_RUN }} | |
- name: Upload test artifacts | |
uses: actions/upload-artifact@v4 | |
if: ${{ !cancelled() || steps.detox.outcome == 'cancelled' }} | |
with: | |
name: "android-test-artifacts-${{ matrix.shardIndex }}" | |
path: apps/ledger-live-mobile/artifacts/ | |
allure-report-android: | |
name: "Allure Reports Export on Server" | |
runs-on: [ledger-live-medium] | |
if: ${{ always() && (inputs.slack_notif || github.event_name == 'push' || github.event_name == 'schedule') }} | |
outputs: | |
report-url: ${{ steps.upload.outputs.report-url }} | |
result: ${{ steps.summary.outputs.test_result }} | |
status: ${{ needs.detox-tests-android.outputs.status }} | |
needs: [detox-tests-android] | |
steps: | |
- name: checkout | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ ((github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event_name == 'pull_request') && (inputs.ref || github.ref_name)) || github.sha }} | |
- uses: LedgerHQ/ledger-live/tools/actions/composites/upload-allure-report@develop | |
id: upload | |
with: | |
platform: android | |
login: ${{ vars.ALLURE_USERNAME }} | |
password: ${{ secrets.ALLURE_LEDGER_LIVE_PASSWORD }} | |
path: android-test-artifacts | |
- name: Get summary | |
id: summary | |
if: ${{ !cancelled() }} | |
uses: LedgerHQ/ledger-live/tools/actions/composites/get-allure-summary@develop | |
with: | |
allure-results-path: android-test-artifacts | |
platform: android | |
upload-to-xray: | |
name: "Upload to Xray" | |
runs-on: [ledger-live-medium] | |
strategy: | |
matrix: | |
platform: | |
- android | |
- ios | |
fail-fast: false | |
env: | |
XRAY_CLIENT_ID: ${{ secrets.XRAY_CLIENT_ID }} | |
XRAY_CLIENT_SECRET: ${{ secrets.XRAY_CLIENT_SECRET }} | |
XRAY_API_URL: https://xray.cloud.getxray.app/api/v2 | |
JIRA_URL: https://ledgerhq.atlassian.net/browse | |
TEST_EXECUTION: ${{ matrix.platform == 'android' && inputs.test_execution_android || inputs.test_execution_ios }} | |
needs: [detox-tests-android, detox-tests-ios] | |
if: ${{ !cancelled() && inputs.export_to_xray }} | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
ref: ${{ inputs.ref || github.sha }} | |
- name: Download Allure Results | |
uses: actions/download-artifact@v4 | |
with: | |
path: "artifacts-${{ matrix.platform }}" | |
name: ${{ matrix.platform }}-test-artifacts | |
- name: Format Xray results | |
run: apps/ledger-live-mobile/e2e/xray.formater.sh artifacts-${{ matrix.platform }} ${{ matrix.platform }} ${{ env.TEST_EXECUTION}} | |
- name: Upload aggregated xray results | |
uses: actions/upload-artifact@v4 | |
with: | |
retention-days: 1 | |
name: xray-reports-${{ matrix.platform }} | |
path: "artifacts-${{ matrix.platform }}/xray_report.json" | |
- name: Authenticate to Xray | |
id: authenticate | |
run: | | |
response=$(curl -H "Content-Type: application/json" -X POST --data '{"client_id": "${{ env.XRAY_CLIENT_ID }}", "client_secret": "${{ env.XRAY_CLIENT_SECRET }}"}' ${{ env.XRAY_API_URL }}/authenticate) | |
echo "xray_token=$response" >> $GITHUB_OUTPUT | |
- name: Publish report on Xray | |
id: publish-xray | |
run: | | |
response=$(curl -H "Content-Type: application/json" \ | |
-H "Authorization: Bearer ${{ steps.authenticate.outputs.xray_token }}" \ | |
-X POST \ | |
--data @artifacts-${{ matrix.platform }}/xray_report.json \ | |
${{ env.XRAY_API_URL }}/import/execution) | |
key=$(echo $response | jq -r '.key') | |
echo "xray_key=$key" >> $GITHUB_OUTPUT | |
- name: Write Xray report link in summary | |
shell: bash | |
run: echo "::notice title=${{ matrix.platform }} Xray report URL::${{ env.JIRA_URL }}/${{ steps.publish-xray.outputs.xray_key }}" | |
report-on-slack: | |
runs-on: ubuntu-22.04 | |
needs: [allure-report-android, allure-report-ios] | |
if: ${{ (failure() && github.event_name == 'push') || (always() && (inputs.slack_notif || github.event_name == 'schedule')) }} | |
env: | |
IOS_STATUS: ${{ needs.allure-report-ios.outputs.status }} | |
IOS_REPORT_URL: ${{ needs.allure-report-ios.outputs.report-url }} | |
ANDROID_STATUS: ${{ needs.allure-report-android.outputs.status }} | |
ANDROID_REPORT_URL: ${{ needs.allure-report-android.outputs.report-url }} | |
steps: | |
- name: format message | |
uses: actions/github-script@v7 | |
id: message | |
with: | |
script: | | |
const fs = require("fs"); | |
const text = "Ledger Live Mobile E2E tests finished"; | |
const header = [ | |
{ | |
"type": "header", | |
"text": { | |
"type": "plain_text", | |
"text": process.env.SPECULOS_RUN == 'false' | |
? "Ledger Live Mobile Mocked Tests on ${{ github.ref_name }}" | |
: ":ledger-logo: Ledger Live Mobile E2E nightly tests results on ${{ github.ref_name }}", | |
"emoji": true | |
} | |
}, | |
{ | |
"type": "divider" | |
} | |
]; | |
const iOSResult = [ | |
{ | |
"type": "section", | |
"text": { | |
"type": "mrkdwn", | |
"text": `- 🍏 iOS: ${process.env.IOS_STATUS !== 'success' ? '❌' : '✅'} ${{ needs.allure-report-ios.outputs.result || 'No test results' }}` | |
} | |
} | |
]; | |
const androidResult = [ | |
{ | |
"type": "section", | |
"text": { | |
"type": "mrkdwn", | |
"text": `- 🤖 Android: ${process.env.ANDROID_STATUS !== 'success' ? '❌' : '✅'} ${{ needs.allure-report-android.outputs.result || 'No test results' }}` | |
} | |
} | |
]; | |
const iOSInfo = [ | |
{ | |
"type": "mrkdwn", | |
"text": process.env.IOS_REPORT_URL ? `*Allure Report iOS*\n<${process.env.IOS_REPORT_URL}|Allure Report iOS>` : '*Allure Report iOS*\nNo Allure Report' | |
} | |
]; | |
const androidInfo = [ | |
{ | |
"type": "mrkdwn", | |
"text": process.env.ANDROID_REPORT_URL ? `*Allure Report Android*\n<${process.env.ANDROID_REPORT_URL}|Allure Report Android>` : '*Allure Report Android*\nNo Allure Report' | |
} | |
]; | |
const infoFields = [] | |
.concat(${{ env.SPECULOS_RUN == 'true' }} ? [] : iOSInfo) | |
.concat(androidInfo) | |
.concat([ | |
{ | |
"type": "mrkdwn", | |
"text": `*Workflow*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Workflow run>` | |
} | |
]); | |
const infoBlock = [ | |
{ | |
"type": "divider" | |
}, | |
{ | |
"type": "section", | |
"fields": infoFields | |
} | |
]; | |
const blocks = [] | |
.concat(header) | |
.concat(${{ env.SPECULOS_RUN == 'true' }} ? [] : iOSResult) | |
.concat(androidResult) | |
.concat(infoBlock); | |
const result = process.env.SPECULOS_RUN === 'false' | |
? { | |
text, | |
blocks, | |
} | |
: { | |
attachments: [ | |
{ | |
color: process.env.ANDROID_STATUS !== 'success' | |
? "#FF333C" | |
: "#33FF39", | |
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: ${{ github.event_name == 'push' && contains(fromJson('["develop", "main"]'), github.ref_name) || github.event_name == 'schedule' }} | |
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 }} |