構建基於 iOS 模擬器的前端除錯方案

雲音樂前端團隊發表於2020-06-24

封面

作者:imyzf

本文將為大家介紹自動化控制 iOS 模擬器的原理,為開發基於 iOS 模擬器的前端除錯方案提供幫助。

我們在開發 iOS App 內的前端頁面時,有一個很大的痛點,頁面無法使用 Safari Inspector 等工具除錯。遇到了問題,我們只能想辦法加 vConsole,或者注入 Weinre,或者盲改,實在不行就找客戶端同學手動打包除錯,總之排查問題的路途非常艱難。

在參考了 RN 和 Weex 等跨平臺框架的開發工具後,我們發現使用模擬器除錯是解決該問題的很好方法,我們將前端頁面放到模擬器的 App 中執行,蘋果就不會對其有限制,允許我們使用 Safari Inspector 除錯了。

Safari Inspector 是和 Chrome Devtools 類似的除錯工具,由 Safari 瀏覽器自帶,支援以下功能:

Safari Inspector 功能

  • 檢查頁面元素
  • 檢視網路請求
  • 斷點除錯
  • 儲存管理(Local Storage,Cookies 等)
  • ……

這些功能是 vConsole、Weinre 等工具無法比擬的,可以幫助我們快速定位問題。

基於這些原理,我們內部已經開發了一款工具,部分功能視訊可以點此預覽。但由於該工具和內部業務耦合較深,目前暫無開源計劃。

前提條件

介紹這套方案之前,我們需要了解一下方案的前提條件:

  • 裝有 macOS 和 Xcode 的電腦:由於蘋果的限制,模擬器和 Xcode 只能在 macOS 上執行。Xcode 直接在 App Store 中安裝即可,十分簡單,無需其他操作。
  • 為模擬器構建的 App 包:由於模擬器是基於 x86 架構的,需要客戶端開發同學提供為模擬器構建的包,和在手機上安裝的包會有所不同。
  • 支援 URL Scheme 喚起的 App:承載前端頁面的 App 必須支援用協議喚起並開啟頁面,才能用工具實現自動化,否則只能在 App 內手動點選相關鏈路開啟頁面。

總體流程

整體流程圖

我們的模擬器除錯方案整體流程如上圖所示:

  1. 獲取裝置列表,提供給使用者選擇
  2. 檢查模擬器狀態,如果沒有啟動,就啟動該模擬器
  3. 檢查是否安裝對應的 App,如果沒有安裝,就下載安裝包進行安裝
  4. 啟動 App,並開啟需要除錯的頁面
  5. 根據頁面型別,使用對應的工具進行除錯(例如 Safari Inspector)

核心工具

我們在實現本方案時,主要基於以下工具:

  • xcrun:Xcode 提供了一個命令列工具xcrun對開發相關的功能進行控制,是一系列工具的集合。
  • simctlxcrun提供了一個子命令simctl用於控制模擬器,提供了模擬器的啟動、關閉、安裝應用、開啟 URL 等功能。可以通過直接執行xcrun simctl檢視幫助文件。
  • node-simctl:由 Appium 提供的simctl 工具的 JS 封裝。由於前端的方案一般都是基於 node.js 開發的,所以可以使用 node-simctl 包更方便地控制模擬器。不過由於node-simctl只提供了部分功能的封裝,我們依然需要手動呼叫xcrun命令來實現更多功能。

模擬器控制

在本方案中,最重要的部分就是對模擬器的控制。

前期準備

使用者通過 App Store 安裝完 Xcode 後,第一次執行需要同意蘋果的許可協議,然後自動安裝一些元件,之後才可以正常使用。為了提高易用性,我們希望自動處理這個過程,而不是告訴使用者,安裝 Xcode 後要採取一些操作。

首先我們可以嘗試執行一次 xcrun simctl命令,如果使用者第一次執行,錯誤資訊中會提醒使用者手動執行xcodebuild -license接受許可,所以我們可以在錯誤資訊中搜尋xcodebuild -license字串,如果有找到,就自動動執行xcodebuild -license accept命令,幫助使用者自動接受許可。這裡要注意的是,執行該命令需要 root 許可權,可以使用sudo-prompt等包提權執行命令。

第一次執行

獲取裝置列表

我們可以直接使用 node-simctl 的getDevices()函式獲取本地安裝的所有裝置列表,比呼叫命令列更方便,可以直接獲取到一個物件,不需要自己解析,物件部分結構如下:

{
    '13.4': [
        {
            sdk: '13.4',
            dataPath: '/Users/xx/Library/Developer/CoreSimulator/Devices/xxx/data',
            logPath: '/Users/xx/Library/Logs/xxx',
            udid: 'C1AA9736-XXX-YYY-ZZZ-2A4A674B6B21',
            isAvailable: true,
            deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro-Max',
            state: 'Shutdown',
            name: 'iPhone 11 Pro Max',
            platform: 'iOS'
        }
    ]
]

這裡不僅包含了 iPhone,還有 Apple Watch 和 Apple TV 等裝置,我們可以遍歷返回結果,通過name欄位進行過濾,因為一般我們只需要在 iPhone 中進行除錯。

啟動裝置

首先我們要判斷裝置是否已經啟動,我們可以通過 xcrun simctl bootstatus ${deviceId}命令獲取裝置狀態(這裡的 deviceId 即上面獲取裝置列表得到的udid),但是如果裝置沒有啟動,這個命令會一直等待,不會退出,所以我們可以通過這個特徵,基於命令是否超時(例如 1000ms 未返回結果)來判斷裝置是否啟動。

接下來,就可以直接用xcrun instruments -w ${deviceId}命令,啟動對應的裝置了。

程式碼示例:

let status = '';
try {
    status = execSync(
        `xcrun simctl bootstatus ${deviceId}`,
        { timeout: 1000 }
    );
} catch (error) {
    // 如果模擬器未啟動,會一直等待,然後超時 kill,丟擲一個 ETIMEDOUT 異常
    if (error.code !== 'ETIMEDOUT') {
        throw error
    }
}
// 檢查是否啟動
if (status.indexOf('Device already booted') < 0) {
    console.log('正在啟動模擬器……')
    execSync(`xcrun instruments -w ${deviceId}`)
}

安裝 App

模擬器的安裝包是一個以.app為結尾命名的資料夾,和 macOS 應用類似,而不是 iPhone 真機上安裝使用的.ipa包。所以安裝包需要先用zip等工具進行打包上傳到伺服器,安裝前下載到本地解壓,使用 node-simctl 的installApp()方法進行安裝。

App 檢查和啟動

對於使用者是否安裝了 App,其實是在通過分析喚起 App 的錯誤資訊來判斷的。如果 App 未安裝,會在喚起的時候會報錯,錯誤資訊中包含了domain=NSOSStatusErrorDomain字串,表示 App 沒有安裝,這個時候我們去呼叫上面的安裝流程即可。

NSOSStatusErrorDomain

整個流程中最重要的一步是如何將我們的頁面在 App 中開啟,實際上很簡單,只需要 App 本身支援類似 cloudmusic://open?url=xxx這樣的 URL Scheme 即可。我們通過 node-simctl 的openUrl()方法直接呼叫 scheme,模擬器便會幫我們啟動關聯的 App,然後需要 App 根據接收到的 Scheme 引數,幫我們開啟需要除錯的頁面。

程式碼示例:

try {
    await simctl.openUrl(deviceId, url)
} catch (error) {
    // 沒有安裝 App,開啟協議會報 NSOSStatusErrorDomain
    if (error.message.indexOf('domain=NSOSStatusErrorDomain') >= 0) {
        await simctl.installApp(deviceId, appPath)
        await simctl.openUrl(deviceId, url)
    } else {
        throw error
    }
}

啟動偵錯程式

在模擬器中開啟除錯頁面以後,對於 RN 頁面,我們可以用 React Native Debugger 等工具除錯。對於 H5 頁面,我們可以從 Safari 選單中開啟 Inspector除錯(如果沒有“開發”選單,請在 Safari 偏好設定 - 高階 - 選中在選單欄中線顯示“開發”選單)。

Safari 開發選單

當然這一步也可以實現自動化,需要藉助 Apple Script 搜尋 Safari 選單中的關鍵字並模擬點選,有點複雜,並且隨著系統升級可能會失效,可以參考網上的一些討論

方案擴充套件

至此,我們已經瞭解瞭如何控制模擬器,實現最基本的功能,但是我們還可以對方案進行擴充套件實現,提高易用性。

接入 CI 服務

客戶端會定期釋出新版本,加入新的功能,所以我們也需要保持除錯用的包為較新版本。一般客戶端團隊都會搭建自己的 CI 服務(例如 Jenkins)進行打包,所以我們可以進行接入,自動下載和安裝最新的包。甚至我們可以拉取 CI 伺服器上的包列表,實現安裝歷史版本,迴歸除錯一些功能。

需要注意的是,客戶端團隊一般只針對 ARM 架構打包,所以需要在 CI 上新增 x86 構建目標,構建產物才能成功在模擬器上執行。

多 App 支援

隨著公司業務範圍的擴充,我們可能需要在多個 App 內除錯頁面,通過指定以下兩點,可以實現多 App 的適配:

  1. URL Scheme:通過指定不同的 Scheme,可以在不同的 App 中開啟頁面
  2. Bundle ID:類似com.netease.cloudmusic這樣的字串,是 App 的唯一標識,可以通過這個 ID 來進行 App 的啟動、終止、解除安裝等操作

總結

到此為止,我們介紹了構建一套基於 iOS 模擬器的前端除錯方案的基本原理,基於以上內容,我們可以結合 commander 和 inquirer 開發出一套 CLI 工具,也可以結合 Electron 開發一套 GUI 工具,為開發提效。如果你有更多的想法或者相關經驗,也歡迎在評論區與我們交流~

本文釋出自 網易雲音樂前端團隊,文章未經授權禁止任何形式的轉載。我們一直在招人,如果你恰好準備換工作,又恰好喜歡雲音樂,那就 加入我們

相關文章