Test Mobile E2E #47
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: "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: ${{ inputs.speculos_tests != 'false' && '2' }} | |
- shardIndex: ${{ inputs.speculos_tests != 'false' && '3' }} | |
- shardTotal: ${{ inputs.speculos_tests != 'false' && '3' }} | |
- shardTotal: ${{ inputs.speculos_tests == 'false' && '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.shard.index }}/${{ matrix.shard.total }} | |
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.shard.index }}" | |
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') }} | |
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) }} | |
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 workflowLink = `<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Workflow run> for more informations`; | |
const text = `Detox tests finished: ${workflowLink}`; | |
const iOSResult = [ | |
{ | |
"type": "header", | |
"text": { | |
"type": "plain_text", | |
"text": `${process.env.IOS_STATUS !== 'success' ? '🍏 iOS Detox tests failed ❌' : '🍏 iOS Detox tests succeeded ✅'}`, | |
"emoji": true | |
} | |
}, | |
{ | |
"type": "section", | |
"text": { | |
"type": "mrkdwn", | |
"text": `${{ needs.allure-report-ios.outputs.result || 'No test results' }}` | |
} | |
}, | |
{ | |
"type": "section", | |
"text": { | |
"type": "mrkdwn", | |
"text": process.env.IOS_REPORT_URL ? `<${process.env.IOS_REPORT_URL}|Allure Report iOS>` : 'No Allure Report' | |
} | |
} | |
]; | |
const androidResult = [ | |
{ | |
"type": "header", | |
"text": { | |
"type": "plain_text", | |
"text": `${process.env.ANDROID_STATUS !== 'success' ? '🤖 Android Detox tests failed ❌' : '🤖 Android Detox tests succeeded ✅'}`, | |
"emoji": true | |
} | |
}, | |
{ | |
"type": "section", | |
"text": { | |
"type": "mrkdwn", | |
"text": `${{ needs.allure-report-android.outputs.result || 'No test results' }}` | |
} | |
}, | |
{ | |
"type": "section", | |
"text": { | |
"type": "mrkdwn", | |
"text": process.env.ANDROID_REPORT_URL ? `<${process.env.ANDROID_REPORT_URL}|Allure Report Android>` : 'No Allure Report' | |
} | |
} | |
]; | |
const infoBlock = [ | |
{ | |
"type": "divider" | |
}, | |
{ | |
"type": "section", | |
"text": { | |
"type": "mrkdwn", | |
"text": workflowLink | |
} | |
} | |
]; | |
const result = { | |
text, | |
blocks: [...iOSResult, ...androidResult, ...infoBlock] | |
}; | |
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) }} | |
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 }} |