跳至主要內容

原生行動應用程式跨平台 E2E 測試指南

·13 分鐘閱讀

對於那些在持續整合與持續部署 (CICD) 環境中,特別是當涉及 Android 和 iOS 原生行動應用程式時,在行動自動化測試方面遇到困難的人來說,本文是必讀之作。很難找到足夠的資源涵蓋這個特定主題。

Guide for Cross Platform E2E

在本文中,我將引導您逐步了解如何使用 GitHub Actions,為 iOS 和 Android 平台建立一個全面的免費端對端測試管線。在整個教學過程中,我們將使用我們喜愛的 WebdriverIO 框架。

挑戰

我們的挑戰是建立一個統一的管線工作流程,以便在 iOS 和 Android 平台上測試我們的原生行動應用程式。在先前的文章中,我們徹底探討了使用 GitHub Actions 和模擬器建構 Android 應用程式測試管線的過程。為了處理 Android 元件的 E2E 測試,我們將重複使用該工作流程。但是,我們仍然需要解決為 iPhone/iPad 模擬器建立單獨作業的更大挑戰。

在我的研究過程中,我發現了一個非常實用但經常被忽略的功能,即 macOS GitHub runner 預先安裝了 Xcode,這是 iPhone iOS 模擬器所需的 SDK。這個發現讓我產生了一個想法:為什麼不重複我們針對 Android 模擬器所遵循的流程,但這次是針對 iOS 模擬器?這特別令人興奮,因為 GitHub Actions 的 iOS runner 支援虛擬化,使其成為我們目的的可行選項。

此功能允許我們建構管線,而無需任何額外費用(最多 2000 分鐘)。

MacOS Runner

工作流程結構

我們的 GitHub Actions 工作流程在核心概念上與 Android 基本相同,但技術上略有差異

  • 建立模擬器
  • 安裝相依性
  • 執行測試
  • 產生報告

看起來相當簡單,但如何做到?讓我們看看

步驟 1

如先前所述,GH Actions runner 預先安裝了一系列的可用模擬器。雖然我們可以利用其中一個現有的模擬器,但這需要使用 deviceName 和它的

每次執行時隨機變更的 UUID。不過,您仍然可以使用 Shell 命令擷取相關的 UUID。

為了簡化流程並提高彈性,我們將建立自己的模擬器。由於 Xcode 已安裝,我們可以利用「xcrun」CLI。若要使用終端機從已安裝的 iOS 版本建立模擬器,只需執行以下命令

xcrun simctl create "iPhone 14 Pro" "com.apple.CoreSimulator.SimDeviceType.iPhone-14-Pro" "com.apple.CoreSimulator.SimRuntime.iOS-16-0"

執行此命令將立即建立模擬器,並隨後擷取其 UUID。

為了加強重複使用性並最佳化流程,我們可以將此命令封裝在 Shell 指令碼中。經過一些修改,我們可以確保 UUID 儲存為 GitHub Runner 中的環境變數,我們最終將在測試功能中使用它。

#!/bin/bash

# Set iPhone model and iOS version
iphone_model="${IPHONE_MODEL// /-}"
ios_version="${IOS_VERSION//./-}"
simulator_name="${iphone_model}"
simulator_udid=$(xcrun simctl create "$IPHONE_MODEL" "com.apple.CoreSimulator.SimDeviceType.$iphone_model" "com.apple.CoreSimulator.SimRuntime.iOS-$ios_version")

# Export the simulator UDID as an environment variable
export SIMULATOR_UDID="$simulator_udid"
echo "SIMULATOR_UDID=$SIMULATOR_UDID" >> $GITHUB_ENV

# Boot the simulator
xcrun simctl boot "$simulator_udid"

透過使用上述指令碼,我們可以將裝置型號和 iOS 版本作為環境變數提供,這些環境變數可以儲存在我們工作流程的環境區段中,這將建立模擬器並將其 UUID 儲存在 GITHUB_ENV 中。此 UUID 對於在我們的測試中設定所需的效能至關重要。

由於我們在 Shell 指令碼中使用 IPHONE_MODEL 和 IOS_VERSION 作為環境變數,因此我們必須在環境區段中設定它們,如下所示。

步驟 2

在上一個步驟成功建立並啟動模擬器後,驗證此過程是否順利完成以及裝置是否已完全準備就緒非常重要。

Checking booting status

為了確保測試順利開始,確認 IOS 已完全啟動至關重要。為此,我建立了一個程式碼片段,該程式碼片段會持續監控裝置的狀態,直到獲得特定輸出,表示模擬器的啟動程序已完成。

#!/bin/zsh

function wait_for_boot() {
printf "${G}==> ${BL}Waiting for the simulator to boot...${NC}\n"
start_time=$(date +%s)
spinner=( "⠹" "⠺" "⠼" "⠶" "⠦" "⠧" "⠇" "⠏" )
i=0
# Get the timeout value from the environment variable or use the default value of 60 seconds
timeout=${BOOT_TIMEOUT:-60}

while true; do
output=$(xcrun simctl bootstatus "$SIMULATOR_UDID")
echo "${output}"
if [[ $output == *"Device already booted, nothing to do."* ]]; then
printf "\e[K${G}==> \u2713 Simulator booted successfully${NC}\n"
exit 0
else
printf "${YE}==> Please wait ${spinner[$i]} ${NC}\r"
i=$(( (i+1) % 8 ))
fi

elapsed_time=$(( $(date +%s) - $start_time ))
if [[ $elapsed_time -ge $timeout ]]; then
printf "${RED}==> Timeout waiting for simulator to boot 🕛${NC}\n"
exit 1
fi

sleep 1
done
}

# Call the wait_for_boot function
wait_for_boot

步驟 3

接下來,我們將介紹執行測試所需的必要步驟和相依性。這包括安裝 Appium、XCUITest 驅動程式和必要的 Node.js 程式庫。

  "devDependencies": {
"@wdio/allure-reporter": "^8.10.4",
"@wdio/appium-service": "^8.10.5",
"@wdio/cli": "^8.10.5",
"@wdio/local-runner": "^8.10.5",
"@wdio/mocha-framework": "8.10.4",
"@wdio/spec-reporter": "8.8.7",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
},
"dependencies": {
"allure-commandline": "^2.22.1",
"appium": "2.0.0-beta.71",
"appium-uiautomator2-driver": "*",
"appium-xcuitest-driver": "*"
}

將拼圖組合起來

既然現在設定在 iOS 模擬器上執行行動自動化測試環境所需的關鍵元素已準備就緒,讓我們將它們全部包裝到 GH Actions 的單一 yaml 檔案中

name: Wdio-x-native

on:
workflow_dispatch:

env:
IPHONE_MODEL: iPhone 8
IOS_VERSION: 16.2
BOOT_TIMEOUT: 700

jobs:
ios:
runs-on: macos-13

steps:
- uses: actions/checkout@v3

- name: Export environment variables
run: |
export IPHONE_MODEL=$IPHONE_MODEL
export IOS_VERSION=$IOS_VERSION

- name: Start simulator
run: |
chmod a+x ./sscript/start_simu.sh
./sscript/start_simu.sh

- name: Install dependencies
run: |
npm i

- name: Check simulator booting status
run: |
chmod a+x ./check_simu.sh
./check_simu.sh

- name: Execute the test
run: |
npm run ios

將模擬器狀態檢查和模擬器啟動合併到單一 Shell 指令碼中是可行的。但是,我刻意將它們分開個別執行。這讓我能利用模擬器啟動所花費的時間,並安裝其餘的相依性。之後,我才能繼續檢查模擬器的狀態。同樣地,我們將對 Android 模擬器應用相同的方法(請參閱先前的文章)。

建立跨平台工作流程

現在是時候將我們先前的文章中的 Android 工作流程與 Ios 工作流程合併為一個單一工作流程,並使用矩陣策略,如下所示

name: Wdio-x-native

on:
workflow_dispatch:

env:
IPHONE_MODEL: iPhone 8
IOS_VERSION: 16.2
API_LEVEL: 32
EMULATOR_NAME: Nexus
EMULATOR_DEVICE: Nexus 5
EMULATOR_VERSION: 12
ANDROID_ARCH: x86_64
ANDROID_TARGET: google_apis
ANDROID_BUILD_TOOLS_VERSION: 34.0.0-rc4
ANDROID_SDK_PACKAGES: system-images;android-32;google_apis;x86_64 platforms;android-32 build-tools;34.0.0-rc4 platform-tools emulator
EMULATOR_TIMEOUT: 350
BOOT_TIMEOUT: 700

jobs:
ios:
runs-on:
- macos-13
strategy:
matrix:
os: [IOS]
device: [$IPHONE_MODEL]
version: [$IOS_VERSION]
steps:
- uses: actions/checkout@v3

- name: Export environment variables
run: |
export IPHONE_MODEL=$IPHONE_MODEL
export IOS_VERSION=$IOS_VERSION
# ...
# find the full workflow at the end of the article
# ...

android:
runs-on: macos-13
strategy:
matrix:
os: [Android]
emulator_name: [$EMULATOR_NAME]
steps:
- uses: actions/checkout@v3

- name: Add avdmanager and sdkmanager to system PATH
run: |
echo "$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/${{ env.ANDROID_BUILD_TOOLS_VERSION }}" >> $GITHUB_PATH

- name: Install Sdk
run: |
yes Y | sdkmanager --licenses
sdkmanager --install ${ANDROID_SDK_PACKAGES}

- name: Build emulator

# ...
# find the full workflow at the end of the article
# ...

在上面的範例中,我們將先前提及的 IOS 工作流程與我們先前的文章中所述的 Android 模擬器工作流程整合在一起。

這些是您可能需要為 Android 模擬器和 iPhone 模擬器提供的建議組態。請務必注意,deviceName、platformVersion 和 UUID 並未在我們的物件中進行硬式編碼。此彈性允許我們根據需要輕鬆地在不同版本和裝置型號之間切換。

const emulator = [{
platformName: 'android',
'appium:options': {
deviceName: process.env.CI ? process.env.EMULATOR_NAME : 'Nexus',
platformVersion: process.env.CI ? process.env.EMULATOR_VERSION : '13',
automationName: 'uiautomator2',
appPackage: 'com.wdiodemoapp',
appWaitPackage: 'com.wdiodemoapp',
appActivity: 'com.wdiodemoapp.MainActivity',
appWaitActivity: 'com.wdiodemoapp.MainActivity',
uiautomator2ServerLaunchTimeout: 200000,
uiautomator2ServerInstallTimeout: 200000,
appWaitForLaunch: true,
autoGrantPermissions: true,
adbExecTimeout: 200000,
androidInstallTimeout: 150000,
ignoreHiddenApiPolicyError: true,
noReset: true,
fullReset: false
}
}]

const simulator = [{
platformName: 'iOS',
'appium:options': {
deviceName: process.env.CI ? process.env.IPHONE_MODEL : 'Iphone-13',
platformVersion: process.env.CI ? process.env.IOS_VERSION : '15.5',
automationName: 'XCUITest',
bundleId: 'org.wdioNativeDemoApp',
app: 'iOS-Simulator-NativeDemoApp-0.4.0.app.zip',
udid: process.env.CI ? process.env.SIMULATOR_UDID : '15A098DB-B8A0-4D6A-9057-23FF1F0F0D9B',
useNewWDA: true,
usePrebuiltWDA: false,
wdaConnectionTimeout: 180000,
appWaitForLaunch: true,
noReset: true,
fullReset: false
}
}]

初始執行

好消息是,工作流程已正確設定,且 IOS 應用程式的 e2e 測試已順利執行

Initial executions

Initial executions

雖然 iPhone 模擬器的端對端測試已通過,但觀察到 Android 模擬器的測試顯示不穩定。

Initial executions

偵錯

系統 UI 崩潰

看來首次在無頭模式下執行 Android 時,偶爾會導致隨機的系統 UI 無回應問題。不幸的是,此問題阻止我們執行測試,因為 UI 系統無回應,這導致 Appium 無法與應用程式正確互動。

在檢閱 allure 報告螢幕截圖時,已確認此問題

Allure Report

這說明了為什麼終端機記錄顯示 Appium 無法找到任何元素,儘管應用程式已成功啟動。

Test Run Log

這是合理的,因為 Appium 嘗試在目前正在執行的活動(即 .systemui)上找到所需的元素,即使我們的目標應用程式在背景中啟動

連線逾時

已注意到在某些情況下,Appium 在啟動測試時遇到失敗,且所有連線重試嘗試均未成功。然而,在進行徹底調查後,發現透過 app:"./test.apk" 功能將 Apk 檔案安裝到 Android 模擬器需要異常長的時間,需要顯著延長連線逾時,以確保成功安裝,這並非最佳解決方案。

既然我們已找出問題及其根本原因,現在是時候解決和處理它們了。

解決

系統 UI 崩潰

幸運的是,我們可以利用能夠在 Android 裝置上 grep 目前正在執行的活動的優勢。此權限允許我們偵測系統 UI 或任何類似的 Android 服務是否會崩潰或正常運作。我們可以透過執行以下 adb Shell 命令來達成此目的

adb shell dumpsys window 2>/dev/null | grep -i mCurrentFocus

Android Failure

Android Failure

在我們持續的實作過程中,我們可以模擬在 Android 裝置上遇到此問題時的自然行為。具體來說,我們會持續點擊 Home 鍵,直到問題解決。一旦問題解決且 Android 系統正常運作後,我們預期會觀察到「.NexusLauncherActivity」作為目前正在執行的主要活動(其中「Nexus」代表 Android 裝置)。

為了達成這個目標,我開發了以下的 Shell Script:

#!/bin/bash

function check_current_focus() {
printf "==> Checking emulator running activity \n"
start_time=$(date +%s)
i=0
timeout=20
target="com.google.android.apps.nexuslauncher.NexusLauncherActivity"

while true; do
result=$(adb shell dumpsys window 2>/dev/null | grep -i mCurrentFocus)

if [[ $result == *"$target"* ]]; then
printf "==> Activity is okay: \n"
printf "$result\n"
break
else
adb shell input keyevent KEYCODE_HOME
printf "==> Menu button is pressed \n"
i=$(( (i+1) % 8 ))
fi

current_time=$(date +%s)
elapsed_time=$((current_time - start_time))
if [ $elapsed_time -gt $timeout ]; then
printf "==> Timeout after ${timeout} seconds elapsed 🕛.. \n"
return 1
fi
sleep 4
done
}

check_current_focus

上面顯示的函數會持續循環,如果找不到主要活動 (NexusLauncherActivity),它會發送一個 Home 鍵事件並重複此過程,直到找到或達到逾時時間為止。

連線逾時

我不會大幅延長 Appium 的連線逾時時間,而是會在另一個步驟中處理 APK 安裝以及主要活動檢查。

      - name: Install APK
run: |
adb install Android-NativeDemoApp-0.4.0.apk
chmod a+x ./mainActivityCheck.sh
./mainActivityCheck.sh

Testing Apk installation and the shell script

太棒了!我們的解決方案已成功執行,且 APK 已正確安裝。如預期,系統 UI 沒有回應,而 Shell Script 有效地管理並處理了這種情況。

優化和增強工作流程

我已經改善了工作流程的派送方式,以便更好地控制我可以在哪些平台上執行測試,無論是 iOS、Android 還是跨平台。

name: Wdio-x-native

on:
workflow_dispatch:
inputs:
e2e:
type: choice
description: Select a platform
required: true
options:
- xplatform
- ios
- android
default: xplatform

因此,我們的工作應該進行調整

name: Wdio-x-native

on:
workflow_dispatch:
inputs:
e2e:
type: choice
description: Select a platform
required: true
options:
- xplatform
- ios
- android
default: xplatform


permissions:
contents: write
pages: write
id-token: write

env:
IPHONE_MODEL: iPhone 8
IOS_VERSION: 16.2
API_LEVEL: 32
EMULATOR_NAME: Nexus
EMULATOR_DEVICE: Nexus 5
EMULATOR_VERSION: 12
ANDROID_ARCH: x86_64
ANDROID_TARGET: google_apis
ANDROID_BUILD_TOOLS_VERSION: 34.0.0-rc4
ANDROID_SDK_PACKAGES: system-images;android-32;google_apis;x86_64 platforms;android-32 build-tools;34.0.0-rc4 platform-tools emulator
EMULATOR_TIMEOUT: 350
BOOT_TIMEOUT: 700

jobs:
ios:
runs-on:
- macos-13
if: ${{ contains(github.event.inputs.e2e, 'ios') || contains(github.event.inputs.e2e, 'xplatform') }}
strategy:
matrix:
os: [IOS]
device: [$IPHONE_MODEL]
version: [$IOS_VERSION]
steps:
- uses: actions/checkout@v3
# find the full workflow at the end of the article

android:
runs-on: macos-13
if: ${{ contains(github.event.inputs.e2e, 'android') || contains(github.event.inputs.e2e, 'xplatform') }}
strategy:
matrix:
os: [Android]
emulator_name: [$EMULATOR_NAME]
steps:
- uses: actions/checkout@v3
# find the full workflow at the end of the article

Input Dispatch

最後,產生我們的報告並立即部署到 GitHub 頁面。

      - name: Generate report
if: always()
run: |
npx allure generate report/allure-results

- name: Setup Pages
if: always()
uses: actions/configure-pages@v3

- name: Upload artifact
if: always()
uses: actions/upload-pages-artifact@v1
with:
path: './allure-report'

- name: Deploy to GitHub Pages
if: always()
id: deployment
uses: actions/deploy-pages@v2

工作流程執行

Workflow execution

iOS Job Android Job

Allure report

好消息!我們的工作流程現在運作完美且表現出完全的穩定性。工作流程可以針對單一平台(例如 Android 或 iOS)觸發,也可以同時針對兩個平台並行觸發。

完整工作流程
loading...

結論

透過利用 GitHub Actions 提供的功能(它為 Android 和 iOS 都提供現成的 SDK),我們獲得了顯著的優勢。這讓我們可以構建一個高效的端對端測試管道,而無需產生任何成本或依賴行動裝置農場雲服務。儘管在真實裝置上進行測試是更理想的,特別是對於 Android 而言,但這種方法的免費性質提供了一個令人滿意的折衷方案。

在我們的討論中,我們逐步示範了如何使用 GitHub Actions 管道為原生行動應用程式建立跨平台端對端測試。我們解決了各種挑戰、障礙和問題,確保對該過程有透徹的了解。有了這些知識,您應該會發現更容易建構適合您特定需求的自訂管道。

歡迎!我可以幫您什麼嗎?

WebdriverIO AI Copilot