部落格 RSS Feed

更好的 Cordova 外掛程式執行方式
作者:John M. Wargo
2018 年 2 月 2 日

Cordova 開發人員有多種方式可以測試和偵錯他們的 Cordova 應用程式。對於功能測試,開發人員會使用模擬器、模擬器和實體裝置。裝置可以在內部部署,也可以使用許多可用的雲端產品。您甚至可以使用出色的工具來偵錯您的應用程式,例如 Chrome 和 Safari 的 Web 應用程式偵錯功能,以及 Microsoft 的 Visual Studio Code Apache Cordova 擴充功能的出色偵錯功能。

對於偵錯外掛程式或偵錯使用 Cordova 外掛程式的應用程式,情況還不算太糟。對於大多數外掛程式,我想任何實體裝置都具有使用外掛程式所需的任何功能,除非外掛程式需要某些外部硬體裝置或具有並非每個裝置都具備的其他要求。對於幾個核心 Cordova 外掛程式,裝置模擬器和模擬器會公開一些功能,使測試人員能夠模擬攝影機、加速計、指南針和其他裝置端功能(儘管令人驚訝的是,早期的 iOS 模擬器不支援攝影機模擬)。

當要執行外掛程式的所有功能時,特別是模擬錯誤情況以便您可以了解應用程式的反應時,事情就變得複雜了。開發人員經常發現自己為了模擬場景而修改外掛程式程式碼,或者在測試期間手動變更外掛程式的行為。在許多情況下,開發人員必須手動在外掛程式中強制產生錯誤狀況,以便驗證應用程式內的錯誤檢查。我沒有寫過很多 Cordova 外掛程式,但在我做過的一點工作中,我希望有更好的方法。好吧,事實證明確實有。

很多很多年前,一家名為 Tiny Hippos 的小公司建立了一個名為 Ripple Emulator 的瀏覽器型模擬器,適用於許多行動裝置。Ripple 在許多方面都很有趣,但就本文的範圍而言,其中一個有趣的功能是它為許多核心 Cordova 外掛程式實作了模擬器。當您在一個窗格中執行 Cordova 應用程式時,會開啟一個單獨的窗格,其中包含用於控制模擬環境許多方面的選項,如下圖所示。

Figure 1

好吧,長話短說,Research In Motion (BlackBerry) 的人購買了 Tiny Hippos,並維護 Ripple 一段時間,然後最終將其作為育成專案貢獻給 Apache 基金會。包括 Adobe、Microsoft 等許多公司都參與其中,但該專案從未真正起飛,也從未成為穩定的產品。它實際上從未脫離測試階段。

無論如何,快轉到今天,您會發現 Microsoft 接管了 Ripple 專案並重建了它。我們保留了一些原始程式碼(一些外掛程式模擬面板和支援公用程式函式),重寫了一些部分,建立了一些新程式碼,然後將其作為一個名為 Cordova Simulate 的開源專案發布 (https://github.com/Microsoft/cordova-simulate)。我們選擇這種方法而不是投資 Ripple,因為

  • 我們定期收到有關 Ripple 啟動的問題報告,以及 Visual Studio 無法成功連線到它的問題,因此我們希望有一個更簡單的架構來呈現 Cordova Web 應用程式(一個單獨的瀏覽器視窗,僅託管該應用程式,沒有其他)。
  • 偵錯 Ripple 是一項挑戰,因為您實際上是在偵錯應用程式和 Ripple 本身(您必須鑽研 Ripple UI 的 HTML 以找到託管的 Cordova Web 應用程式,並且在偵錯時,Ripple 的 JavaScript 程式碼很容易成為您堆疊的一部分)。
  • 由於 Ripple 和 Cordova Web 應用程式在同一個瀏覽器視窗中呈現,如果 Cordova Web 應用程式因任何原因凍結,Ripple 也會凍結(或者,在不太嚴重的情況下,當 Cordova Web 應用程式忙碌時,Ripple UI 可能看起來沒有回應)。

Cordova Simulate 解決了這些問題

  • 應用程式視窗不託管模擬 UI,僅託管應用程式(以及一小段程式碼以啟用與模擬 UI 的通訊)。這解決了上面確定的前兩個問題。
  • 模擬 UI 在單獨的視窗中呈現,所有通訊都是非同步的,因此沒有回應的應用程式不會干擾模擬 UI。這解決了第三個問題。

Cordova Simulate 的另一個推動目標是我們希望使其具有可延伸性;允許外掛程式定義它們自己的模擬功能(您很快就會了解)。

關於我之前關於 Ripple 從未脫離測試階段的觀點,Cordova Simulate 不僅是一個完整的、穩健的且已發布的解決方案,它甚至還是多個 Microsoft 產品(包括商業和開源)的一部分。它捆綁在 Visual Studio 2017 中的 Visual Studio Tools for Apache Cordova (TACO - http://taco.visualstudio.com/) 中。它也包含在 Visual Studio Code 的 Apache Cordova 擴充功能中 (https://github.com/Microsoft/vscode-cordova)。

這對您來說為什麼重要?好吧,讓我解釋一下…

透過 Cordova Simulate,您現在可以存取完整的解決方案,以針對核心 Cordova 外掛程式的模擬版本測試您的應用程式。這消除了修改程式碼以模擬外掛程式回應的需求,因為 Cordova Simulate 會為您處理。

注意:如果您剛剛讀完上一段,並且對自己說「那又如何?」請稍等一下。

對您來說最重要的好處是,您也可以為每個自訂外掛程式新增對 Cordova Simulate 的支援。然後,當您在外掛程式開發期間執行外掛程式時,或當您的客戶使用您的外掛程式時,您可以使用標準方式來模擬外掛程式的功能(包括錯誤情境)。當第三方提供者將對 Cordova Simulate 的支援新增到他們的外掛程式時,開發人員可以突然使用各種外掛程式準確地測試其應用程式的每個方面。

讓我向您展示如何安裝和使用 Cordova Simulate,然後我將向您展示如何為自訂外掛程式新增對 Cordova Simulate 的支援。如果您正在使用其他人的外掛程式,並且他們的外掛程式中沒有對 Cordova Simulate 的支援,請自行新增並提交提取請求,這並不難。

安裝 Cordova Simulate

正如我之前提到的,Visual Studio Tools for Apache Cordova 和 Visual Studio Code 的 Cordova 擴充功能都包含 Cordova Simulate,因此無需遵循額外的安裝說明 - 只需安裝這些工具,您就會擁有您需要的東西。

您也可以從命令列或第三方 IDE 呼叫 Cordova Simulate。若要為這些情境安裝 Cordova Simulate,請開啟終端視窗,然後執行以下命令

npm install -g cordova-simulate

就這樣,僅此而已。當該程序完成時,您現在可以隨意使用 simulate 命令來啟動 Cordova Simulate。

啟動 Cordova Simulate

以下章節說明如何以不同方式啟動 Cordova Simulate。

Visual Studio

如果您在 Visual Studio 和 TACO 中執行應用程式,只需選擇 在瀏覽器中模擬 選項,應用程式就會啟動 Cordova Simulate。Cordova Simulate 將會開啟 Chrome 瀏覽器並執行 Cordova 應用程式的 Web 應用程式部分。Cordova Simulate 也會在 Visual Studio 中開啟一個模擬器控制視窗(使用 Internet Explorer,這並不奇怪)。您會在 Chrome 視窗中與應用程式互動,並使用模擬器控制視窗來模擬外掛程式中的方法和屬性。我稍後會向您展示如何操作。

Visual Studio Code

如果您正在使用 Visual Studio Code,請前往 偵錯 索引標籤,啟用 Cordova 偵錯,然後執行 在瀏覽器中模擬 Android在瀏覽器中模擬 iOS 選項。Cordova Simulate 將會開啟 Chrome 瀏覽器並執行 Cordova 應用程式的 Web 應用程式部分。Cordova Simulate 也會在 Visual Studio Code 內開啟一個模擬器控制視窗。您會在 Chrome 視窗中與應用程式互動,並使用模擬器控制視窗來模擬外掛程式中的方法和屬性。我稍後會向您展示如何操作。

命令列

若要從命令列啟動 Cordova Simulate,請開啟終端視窗,導覽至 Cordova 專案資料夾,然後執行以下命令

simulate \[platform\]

Platform 指的是其中一個支援的目標平台(例如:androidiosbrowser)。例如,若要模擬 Cordova 應用程式的 Android 版本,您可以使用以下命令

simulate android

Cordova Simulate 將會開啟 Chrome 瀏覽器,其中有兩個索引標籤,一個執行 Cordova 應用程式 Web 應用程式,另一個顯示下圖所示的模擬器控制視窗。

Figure 2

依預設,Cordova Simulate 會載入一組預設的外掛程式模擬器

  • 由於 HTML 本身支援地理位置,Cordova Simulate 會自動載入地理位置模擬器窗格。
  • 事件模擬器窗格會自動載入,以便開發人員根據需要觸發 Cordova 和裝置事件
  • 「持續執行呼叫」窗格提供對不包含 Cordova Simulate 支援的第三方外掛程式的基本模擬器支援。
  • 裝置模擬器會自動載入,讓您可以變更用於呈現 Web 應用程式的目標裝置。

如果我已將其他 Cordova 核心外掛程式新增到我的專案,Cordova Simulate 將會為每個外掛程式載入模擬器(如果有的話)。

此時,您可以根據需要與您的應用程式互動,並切換到模擬器控制視窗以調整外掛程式屬性和方法呼叫結果。例如,如果您的 Cordova 應用程式使用地理位置外掛程式來追蹤裝置的位置,則變更地理位置模擬器窗格中的任何值將會導致應用程式下次呼叫 navigator.geolocation.getCurrentPosition 時接收您輸入到窗格中的更新值。

為您的外掛程式新增 Cordova Simulate 支援

好了,現在是時候向您展示如何為您自己的外掛程式新增 Cordova Simulate 支援了。首先,您必須將 simulation 資料夾新增到外掛程式的 src 資料夾(因此是 src/simulation/)。在該資料夾中,您將根據外掛程式的需求建立一個或多個以下檔案

  • app-host-clobbers.js
  • app-host-handlers.js
  • app-host.js
  • sim-host-dialogs.html
  • sim-host-handlers.js
  • sim-host-panels.html
  • sim-host.js

對於我的簡單範例,我只需要三個檔案,但請參閱 https://github.com/Microsoft/cordova-simulate 的 Cordova Simulate 文件以了解有關這些檔案的詳細資訊。

我在範例中使用的外掛程式是我多年前為我的其中一本 Cordova 書籍建立的簡單電信業者外掛程式:https://www.npmjs.com/package/johnwargo-cordova-plugin-carrier。它會公開兩種方法

  • getCarrierName:一種非同步方法,可擷取執行應用程式的裝置的目前電信業者。
  • getCountryCode:一種非同步方法,可擷取執行應用程式的裝置的國家/地區碼

在執行這個外掛程式時,我需要能夠根據不同的電信業者和國家代碼值來驗證我的應用程式碼。為了模擬這個情況,我需要一個 HTML 面板,提供多個電信業者和國家代碼的選擇。為此,我在外掛程式專案的 src/simulation/ 資料夾中建立了一個 sim-host.panels.html 檔案。該檔案建立一個簡單的面板,其中包含兩個下拉式選單,提供一些國家和電信業者值。

<cordova-panel id="johnwargo-cordova-plugin-carrier" caption="Carrier" 
  spoken-text="Carrier">
    <cordova-panel-row>
        Select carrier and country code options from the drop-down lists below.
    </cordova-panel-row>
    <cordova-panel-row>
        <cordova-label label="Carrier Name" spoken="carrier name"></cordova-label>
        <cordova-combo spoken-text="Simulated value for wireless carrier" 
           id="carrier-select" style="width:auto; min-width:0; display:inline;">
            <option value="AT&T" selected="selected">AT&T</option>
            <option value="Sprint">Sprint</option>
            <option value="T-Mobile">T-Mobile</option>
            <option value="US Cellular">US Cellular</option>
            <option value="Verizon">Verizon</option>
        </cordova-combo>
    </cordova-panel-row>
    <cordova-panel-row>
        <cordova-label label="Country Code" spoken="country code"></cordova-label>
        <cordova-combo spoken-text="Simulated value for country code" 
          id="country-code-select" style="width:auto; min-width:0; display:inline;">
            <option value="CA">Canada</option>
            <option value="MX">Mexico</option>
            <option value="US" selected="selected">United States</option>
        </cordova-combo>
    </cordova-panel-row>
    <cordova-panel-row>
      Enable the error checkbox to execute the error callback instead of the success callback on plugin API calls.
    </cordova-panel-row>
    <cordova-checkbox id="is-error" spoken="">Error condition</cordova-checkbox>
</cordova-panel>

從程式碼中可以看到,該面板使用了 Cordova Simulate 支援的特殊 HTML 元素類型。大多數核心 Cordova 外掛程式的模擬器程式碼都包含在 Cordova Simulate Github 儲存庫中,因此您可以在那裡找到可用的 UI 元素的使用範例。

接下來,我將一個 sim-host.js 檔案新增到該資料夾中。這個檔案不是必要的,但它提供了一個初始化模擬外掛程式的機會,我使用它在使用者於模擬面板中進行變更時更新主控台。這不是至關重要的,但在我建立模擬時,它對於釐清發生的事情很有用。

module.exports = {
    initialize: function() {
        //Get access to the carrier selection drop-down
        var carrierSel = document.getElementById('carrier-select');
        //Add a change event listener to the field
        carrierSel.addEventListener('change', carrierChange);
        //Get access to the country code selection drop-down
        var ccSel = document.getElementById('country-code-select');
        //Add a change event listener to the field
        ccSel.addEventListener('change', ccChange); 

        function carrierChange() {
            console.log("Carrier selection changed to " + this.value);
        }
 
        function ccChange() {
            console.log("Country code selection changed to " + this.value);
        }
    }
};

如果您研究核心 Cordova 外掛程式中模擬器功能的原始碼,您會看到它們中的許多都使用程式碼中剛顯示的變更事件來更新外掛程式所公開物件的屬性。當開發人員在模擬器面板中變更值時,變更事件會註冊變更並更新本地物件。然後,當 Cordova 應用程式存取其中一個屬性時,程式碼會從本地物件傳回值。

最後,我將一個 sim-host-handlers.js 檔案新增到專案中。這個檔案中的程式碼會覆寫模擬環境中外掛程式的 cordova.exec 呼叫。在這裡,我匯出外掛程式支援的原生方法,並從模擬器面板中提取選取的值,然後將它們傳回給呼叫的應用程式。

module.exports = function(messages) {
    return {
        carrier: {
            getCarrierName: function(successCallback, errorCallback) {
                console.log("Cordova-Simulate: getCarrierName invoked");
                //Get access to the carrier selection drop-down
                var carrierSel = document.getElementById('carrier-select');
                //Pull the value from the selected item
                var selValue = carrierSel.options[carrierSel.selectedIndex].value;
                console.log('Simulating carrier: ' + selValue);
                //And return it to the calling method
                successCallback(selValue);
            },
            getCountryCode: function(successCallback, errorCallback) {
                console.log("Cordova-Simulate: getCountryCode invoked");
                //Get access to the country code selection drop-down
                var ccSel = document.getElementById('country-code-select');
                //Pull the value from the selected item
                var selValue = ccSel.options[ccSel.selectedIndex].value;
                console.log('Simulating country code: ' + selValue);
                //And return it to the calling method
                successCallback(selValue);
            }
        }
    };
};

這是一個您可以做到的非常簡單的範例,請務必查看核心 Cordova 外掛程式中包含的模擬器功能的原始碼,以獲取更複雜的範例。

現在,當您建立使用此外掛程式的 Cordova 應用程式,然後執行 Cordova Simulate 時,您會在 Cordova Simulate Simulator Control 視窗上看到以下面板。

Figure 3

對面板進行適當的變更,然後從 Cordova 應用程式呼叫相關的 API,以查看選取的結果。

如果您思考一下我們目前所做的事情,我們只處理了成功的情境 - 變更模擬環境中外掛程式的行為,以便我們可以使用不同的 API 結果來測試應用程式。大多數外掛程式也會報告錯誤狀況,為了正確地執行外掛程式或使用該外掛程式的應用程式,您必須也能夠模擬錯誤狀況。讓我來示範如何做到這一點。

首先,我在 sim-host.panels.html 檔案中新增了一列。

<cordova-panel-row>
  Enable the error checkbox to execute the error callback instead of the success callback on plugin API calls.
</cordova-panel-row>
<cordova-checkbox id="is-error" spoken="">Error condition</cordova-checkbox>

這會在面板中新增一個錯誤核取方塊,讓開發人員可以從每個外掛程式 API 呼叫中傳回錯誤。更新後的 sim-host.panels.html 檔案如下所示。

<cordova-panel id="johnwargo-cordova-plugin-carrier" caption="Carrier" 
  spoken-text="Carrier">
    <cordova-panel-row>
        Select carrier and country code options from the drop-down lists below.
    </cordova-panel-row>
    <cordova-panel-row>
        <cordova-label label="Carrier Name" spoken="carrier name"></cordova-label>
        <cordova-combo spoken-text="Simulated value for wireless carrier" 
           id="carrier-select" style="width:auto; min-width:0; display:inline;">
            <option value="AT&T" selected="selected">AT&T</option>
            <option value="Sprint">Sprint</option>
            <option value="T-Mobile">T-Mobile</option>
            <option value="US Cellular">US Cellular</option>
            <option value="Verizon">Verizon</option>
        </cordova-combo>
    </cordova-panel-row>
    <cordova-panel-row>
        <cordova-label label="Country Code" spoken="country code"></cordova-label>
        <cordova-combo spoken-text="Simulated value for country code" 
          id="country-code-select" style="width:auto; min-width:0; display:inline;">
            <option value="CA">Canada</option>
            <option value="MX">Mexico</option>
            <option value="US" selected="selected">United States</option>
        </cordova-combo>
    </cordova-panel-row>
    <cordova-panel-row>
      Enable the error checkbox to execute the error callback instead of the success callback on plugin API calls.
    </cordova-panel-row>
    <cordova-checkbox id="is-error" spoken="">Error condition</cordova-checkbox>
</cordova-panel>

現在,在 sim-host-handlers.js 檔案中,我在檔案中公開的每個方法中新增了對核取方塊狀態的檢查。對於我的外掛程式,當核取方塊被勾選時,該方法會呼叫錯誤回呼函式,傳回一個包含簡單錯誤訊息和代碼的虛擬 JSON 物件。對於您的外掛程式,您可能需要擴展這個功能,以便您可以模擬不同的錯誤狀況。

module.exports = function(messages) {
	return {
		carrier: {
			getCarrierName: function(successCallback, errorCallback) {
				console.log("Cordova-Simulate: getCarrierName invoked");
				if (document.getElementById('is-error').checked) {
					console.error("Error condition enabled");
					errorCallback({ code: 1, msg: "Simulated error condition" });
				} else {
					//Get access to the carrier selection drop-down
					var carrierSel = document.getElementById('carrier-select');
					//Pull the value from the selected item
					var selValue = carrierSel.options[carrierSel.selectedIndex].value;
					console.log('Simulating carrier: ' + selValue);
					//And return it to the calling method
					successCallback(selValue);
				}
			},
			getCountryCode: function(successCallback, errorCallback) {
				if (document.getElementById('is-error').checked) {
					console.error("Error condition enabled");
					errorCallback({ code: 2, msg: "Simulated error condition" });
				} else {
					console.log("Cordova-Simulate: getCountryCode invoked");
					//Get access to the country code selection drop-down
					var ccSel = document.getElementById('country-code-select');
					//Pull the value from the selected item
					var selValue = ccSel.options[ccSel.selectedIndex].value;
					console.log('Simulating country code: ' + selValue);
					//And return it to the calling method
					successCallback(selValue);
				}
			}
		}
	};
};

現在,當我執行 Cordova simulate 時,我會看到如下所示略有不同的面板。

Figure 4

就是這樣,這就是在您自己的外掛程式中啟用模擬的全部內容。希望您可以看到,Cordova Simulate 為開發人員(外掛程式開發人員和在其應用程式中使用這些開發人員外掛程式的開發人員)提供了一種更簡單的方式來執行外掛程式並測試使用該外掛程式的應用程式。

Cordova Simulate 是 Microsoft 的一個開源專案,但我們很樂意讓您協助增強它並修復出現的問題。請在 Github 上追蹤該專案,並了解如何協助增強該專案。