跳至內容

建立驅動程式

Appium 希望讓任何人能夠輕鬆開發自己的自動化驅動程式,作為 Appium 生態系的一部分。本指南將說明所涉及的內容,以及如何使用 Appium 提供的工具來完成各種驅動程式開發工作。本指南假設您 (1) 是 Appium 的熟練使用者,(2) 是 Node.js 的熟練開發人員,以及 (3) 您已閱讀並理解 驅動程式簡介

如果符合上述條件,太好了!本指南將協助您開始。

在建立您的驅動程式之前

在開始實作您的驅動程式之前,重要的是先釐清一些事項。例如,您需要知道您的驅動程式將執行什麼。它嘗試為哪個平台公開 WebDriver 自動化?

Appium 沒有神奇地賦予您自動化任何平台的能力。它所做的只是提供一組便利的工具來實作 WebDriver 協定。因此,如果您想要建立一個驅動程式,例如一個新的應用程式平台的驅動程式,您需要知道如何在沒有 Appium 的情況下自動化該平台上的應用程式。

這通常表示您需要非常熟悉特定平台的應用程式開發。而且這通常表示您將依賴平台供應商提供的工具或 SDK。

基本上,如果您無法回答這個問題:「我如何啟動、遠端觸發行為,以及從這個平台上的應用程式讀取狀態?」那麼您還沒有準備好撰寫 Appium 驅動程式。請務必進行研究,以確保前進的道路。一旦有,將其編碼並使其可用於 Appium 驅動程式應該是簡單的部分!

其他可供參考的驅動程式

建立 Appium 驅動程式最棒的事情之一是,已經有許多開放原始碼的 Appium 驅動程式可供您參考。有一個 fake-driver 範例驅動程式,除了展示本指南中描述的一些事項之外,它基本上沒有做其他事情。

當然,所有 Appium 的官方驅動程式都是開放原始碼的,並且可以在專案的 GitHub 組織中的儲存庫中取得。因此,如果您發現自己詢問:「驅動程式如何執行 X?」請閱讀這些驅動程式的程式碼!如果您遇到問題,也不要害怕向 Appium 開發人員提問;我們隨時樂意協助確保驅動程式開發體驗良好!

Appium 驅動程式的基本需求

如果您希望您的驅動程式成為有效的 Appium 驅動程式,這些是您的驅動程式必須執行的動作(或狀態)。

包含 Appium 擴充功能中繼資料的 Node.js 套件

所有 Appium 驅動程式基本上都是 Node.js 套件,因此必須具備有效的 package.json。您的驅動程式不限於 Node.js,但必須提供以 Node.js 編寫的轉接器,以便 Appium 可以載入它。

您的 package.json 必須將 appium 納入為 peerDependency。相依性版本的條件應儘可能寬鬆(除非您碰巧知道您的驅動程式只會與特定版本的 Appium 搭配使用)。例如,對於 Appium 2,這看起來會像 ^2.0.0,宣告您的驅動程式適用於任何以 2.x 開頭的 Appium 版本。

您的 package.json 必須包含一個 appium 欄位,如下所示(我們稱之為「Appium 擴充功能中繼資料」)

```json
{
  ...,
  "appium": {
    "driverName": "fake",
    "automationName": "Fake",
    "platformNames": [
      "Fake"
    ],
    "mainClass": "FakeDriver"
  },
  ...
}
```

必要的子欄位為

  • driverName:這應該是您的驅動程式的簡短名稱。
  • automationName:這應該是使用者將用於他們的 appium:automationName 功能,以告知 Appium 使用您的驅動程式的字串。
  • platformNames:這是一個陣列,其中包含一個或多個您的驅動程式視為有效的平台名稱。當使用者傳送 platformName 功能以啟動工作階段時,它必須包含在此清單中,以便您的驅動程式處理工作階段。已知的平台名稱字串包括:iOStvOSmacOSWindowsAndroid
  • mainClass:這是從您的 main 欄位匯出的命名輸出(以 CommonJS 樣式)。它必須是一個擴充 Appium BaseDriver 的類別(請見下方)。

擴充 Appium 的 BaseDriver 類別

最終,您的驅動程式會更容易撰寫,因為實作 WebDriver 協定和處理某些常見邏輯的大部分繁重工作已經由 Appium 處理了。這全部編碼為一個類別,Appium 為您匯出以供使用,稱為 BaseDriver。它從 appium/driver 匯出,因此您可以使用下列其中一種樣式匯入它,並建立您自己的類別來擴充它

import {BaseDriver} from 'appium/driver';
// or: const {BaseDriver} = require('appium/driver');

export class MyDriver extends BaseDriver {
}

讓您的驅動程式可以使用

基本上就是這樣!透過匯出驅動程式類別的 Node.js 套件,以及正確的 Appium 擴充功能中繼資料,您就擁有了一個 Appium 驅動程式!現在它不會執行任何動作,但您可以在 Appium 中載入它,使用它啟動和停止工作階段,等等...

要讓使用者可以使用,你可以透過 NPM 發布。這樣一來,你的驅動程式就可以透過 Appium CLI 安裝

appium driver install --source=npm <driver-package-on-npm>

當然,最好先測試你的驅動程式。一種查看它在 Appium 中運作方式的方法是先在本地安裝它

appium driver install --source=local /path/to/your/driver

開發你的驅動程式

如何開發你的驅動程式取決於你。不過,在 Appium 中執行你的驅動程式很方便,而且不需要進行大量的發布和安裝。最直接的方式是將最新版本的 Appium 包含為 devDependency,然後再包含你自己的驅動程式,如下所示

{
    "devDependencies": {
        ...,
        "appium": "^2.0.0",
        "your-driver": "file:.",
        ...
    }
}

現在,你可以本地執行 Appium (npm exec appiumnpx appium),而且因為你的驅動程式與它一起列為相依性,所以它會自動「安裝」並可以使用。你可以這樣設計你的 e2e 測試,或者如果你使用 Node.js 編寫它們,你可以簡單地匯入 Appium 的啟動伺服器方法,以處理在 Node 中啟動和停止 Appium 伺服器。(TODO:準備好後,參考開源驅動程式中的一個實作)。

另一種使用現有 Appium 伺服器安裝進行本地開發的方法是簡單地本地安裝你的驅動程式

appium driver install --source=local /path/to/your/driver/dev/dir

在開發期間更新你的驅動程式

當 Appium 伺服器啟動時,它會將你的驅動程式載入記憶體。在 Appium 伺服器下次啟動之前,對你的驅動程式程式碼所做的變更不會生效。僅僅啟動新的工作階段還不足以重新載入你的驅動程式程式碼。

不過,你可以將 APPIUM_RELOAD_EXTENSIONS 環境變數設定為 1,以要求 Appium 清除其模組快取,並在每次要求新的工作階段時重新載入擴充功能。這樣一來,當你對驅動程式進行程式碼變更時,就不需要重新啟動伺服器。

標準驅動程式實作構想

這些是你建立驅動程式時可能會想要做的事情。

在建構函式中設定狀態

如果你定義自己的建構函式,你將需要呼叫 super 以確保所有標準狀態都正確設定

constructor(...args) {
    super(...args);
    // now do your own thing
}

這裡的 args 參數是包含用於啟動 Appium 伺服器的所有 CLI 參數的物件。

定義並驗證已接受的能力

你可以定義自己的能力並對其進行基本驗證。使用者永遠可以傳送你未定義的能力,但如果他們傳送你已明確定義的能力,則 Appium 會驗證它們是否為正確的類型(並會檢查是否存在必要的能力)。

如果你要完全關閉能力驗證,請在建構函式中將 this.shouldValidateCaps 設定為 false

若要將驗證限制提供給 Appium,請在建構函式中將 this.desiredCapConstraints 設定為驗證物件。驗證物件可能會有點複雜。以下是 UiAutomator2 驅動程式的範例

{
  app: {
    presence: true,
    isString: true
  },
  automationName: {
    isString: true
  },
  browserName: {
    isString: true
  },
  launchTimeout: {
    isNumber: true
  },
}

開始一個工作階段並讀取能力

Appium 的 BaseDriver 已經實作 createSession 指令,所以你不需要這麼做。不過,通常需要執行你自己的啟動動作(啟動應用程式、執行一些平台程式碼,或根據你為驅動程式定義的能力執行不同的動作)。因此,你可能會覆寫 createSession。你可以透過在驅動程式中定義方法來這麼做

async createSession(jwpCaps, reqCaps, w3cCaps, otherDriverData) {
    const [sessionId, caps] = super.createSession(w3cCaps);
    // do your own stuff here
    return [sessionId, caps];
}

基於舊有原因,你的函式會收到舊式 JSON Wire Protocol 所需和必要的上限作為前兩個引數。由於舊協定不再受支援,且客戶端都已更新,因此你只能依賴 w3cCaps 參數。(有關 otherDriverData 的討論,請參閱下方關於並行驅動程式的區段)。

你會想要確保呼叫 super.createSession 以取得工作階段 ID 以及已處理的能力(請注意,能力也設定在 this.caps 上;在此處修改 caps 本機會沒有任何效果,除了變更使用者在建立工作階段回應中看到的部分)。

所以就是這樣!你可以使用你的驅動程式所需的任何啟動邏輯填寫中間區段。

結束工作階段

如果你的驅動程式需要任何清理或關閉邏輯,最好在覆寫 deleteSession 的實作時執行。

async deleteSession() {
    // do your own cleanup here
    // don't forget to call super!
    await super.deleteSession();
}

如果可能的話,在此處不擲回任何錯誤非常重要,以便工作階段清理的所有部分都能成功!

存取能力和 CLI 引數

你通常會想要讀取使用者為工作階段設定的參數,無論是以 CLI 引數或能力的形式。執行此操作最簡單的方法是存取 this.opts,它是所有選項(來自 CLI 或能力)的合併。因此,例如要存取 appium:app 能力,你可以直接取得 this.opts.app 的值。

如果你想知道某個項目是以 CLI 引數能力傳送的,你可以明確存取 this.cliArgsthis.caps 物件。

在所有情況下,當你在此處存取值時,appium: 能力前綴都會被移除,以方便使用。

實作 WebDriver 經典指令

透過在驅動程式類別中實作函式來處理 WebDriver 指令。WebDriver 通訊協定的每個成員,加上各種 Appium 擴充功能,都有對應的函式,如果你想在驅動程式中支援該指令,就必須實作該函式。查看 Appium 的 routes.js 是了解 Appium 支援哪些指令以及每個指令需要實作哪個方法的最佳方式。此檔案中的每個路由物件都會告訴你指令名稱以及你預期會收到的該指令參數。

讓我們以這個區塊為例

'/session/:sessionId/url': {
    GET: {command: 'getUrl'},
    POST: {command: 'setUrl', payloadParams: {required: ['url']}},
}

在這裡,我們看到路由 /session/:sessionId/url 已對應到兩個指令,一個是 GET 要求,另一個是 POST 要求。如果我們想要允許我們的驅動程式變更「網址」(或對我們的驅動程式來說可能代表任何意思),因此我們可以實作 setUrl 指令,並知道它會採用 url 參數

async setUrl(url) {
    // your implementation here
}

幾個注意事項:- 所有指令方法都應該是 async 函式,否則會傳回 Promise - 你不需要擔心通訊協定編碼/解碼。你會取得 JS 物件作為參數,並可以在回應中傳回 JSON 可序列化物件。Appium 會負責將其包裝在 WebDriver 通訊協定回應格式中,將其轉換為 JSON 等 - 所有基於工作階段的指令都會收到 sessionId 參數作為最後一個參數 - 所有基於元素的指令都會收到 elementId 參數作為倒數第二個參數 - 如果你的驅動程式沒有實作指令,使用者仍然可以嘗試存取該指令,並會收到 501 尚未實作 回應錯誤。

實作 WebDriver BiDi 指令

WebDriver BiDi 是 WebDriver 規格的較新版本,透過 Websockets 而不是 HTTP 實作。作為 Appium 驅動程式作者,你可以利用 Appium 的 BiDi 支援,而無需了解任何關於 BiDi 通訊協定或 Websockets 的資訊。實作 BiDi 指令的處理常式與實作 WebDriver 經典指令的處理常式相同(在上一節中說明)。你只需在你的驅動程式中定義一個具有適當名稱的方法,當客戶端要求 BiDi 指令時,就會呼叫該方法。若要查看你應該用於 BiDi 指令的特定名稱,請查看 bidi-commands.js

目前,你還需要在驅動程式執行個體上定義 doesSupportBidi 欄位,並確保它設定為 true。除非你的驅動程式以這種方式表示它支援 BiDi,否則 Appium 就不會為你的驅動程式開啟其 Websocket 伺服器,也不會設定任何處理常式。

實作元素尋找

元素尋找是一種特殊的指令實作案例。你實際上並不想覆寫 findElementfindElements,即使這些是 routes.js 中所列的內容。如果你實作此函式,Appium 會為你做很多工作

async findElOrEls(strategy, selector, mult, context) {
    // find your element here
}

以下是傳入的內容

  • strategy - 字串,正在使用的定位器策略
  • selector - 字串,選擇器
  • mult - 布林值,使用者是否要求一個元素或與選擇器匹配的所有元素
  • context - (選用) 如果已定義,將為 W3C 元素 (即,JS 物件,其中 W3C 元素識別碼為金鑰,元素 ID 為值)

您需要傳回下列其中一項

  • 單一 W3C 元素 (如上所述的物件)
  • W3C 元素陣列

請注意,您可以從 appium/support 匯入 W3C 網頁元素識別碼

import {util} from 'appium/support';
const { W3C_WEB_ELEMENT_IDENTIFIER } = util;

您如何處理元素取決於您!通常,您會保留 ID 到實際元素「物件」的快取映射,或任何等同於您平台的元素

定義有效的定位器策略

您的驅動程式可能只支援標準 WebDriver 定位器策略的子集,或可能新增自己的自訂定位器策略。若要告訴 Appium 哪些策略對您的驅動程式來說是有效的,請建立策略陣列並將其指定給 this.locatorStrategies

this.locatorStrategies = ['xpath', 'custom-strategy'];

如果使用者嘗試使用任何策略,而非允許的策略,Appium 將會擲回錯誤,這讓您可以保持元素尋找程式碼的乾淨,並只處理您知道的策略

預設情況下,有效策略清單是空的,因此,如果您的驅動程式並非只是代理到其他 WebDriver 端點,您需要定義一些。協定標準定位器策略已在此處定義 here

擲回特定於 WebDriver 的錯誤

WebDriver 規格定義了一組 錯誤碼,用於在發生錯誤時伴隨命令回應。Appium 已為每個這些碼建立錯誤類別,因此,您可以在命令中擲回適當的錯誤,它將在協定回應中對使用者執行正確的動作。若要存取這些錯誤類別,請從 appium/driver 匯入它們

import {errors} from 'appium/driver';

throw new errors.NoSuchElementError();

將訊息記錄到 Appium 日誌

當然,您隨時可以使用 console.log,但 Appium 為您提供了一個不錯的記錄器,即 this.log (它有 .info.debug.log.warn.error 方法,用於不同的記錄層級)。如果您想在驅動程式內容之外建立 Appium 記錄器 (例如,在指令碼或輔助程式檔案中),您也可以隨時建立自己的記錄器

import {logging} from 'appium/support';
const log = logging.getLogger('MyDriver');

Appium 驅動程式的更多可能性

這些是您的驅動程式可以執行的動作,以利用額外的驅動程式功能或更方便地執行其工作。

新增自訂命令列參數的架構

如果您希望您的驅動程式在 Appium 伺服器啟動時從命令列接收資料,您可以新增自訂 CLI 參數(例如伺服器管理員應該設定但不應傳遞為功能的埠)。

若要為 Appium 伺服器定義 CLI 參數(或組態屬性),您的擴充功能必須提供一個架構。在擴充功能的 package.jsonappium 屬性中,新增一個 schema 屬性。這將會 a) 成為架構本身,或 b) 成為架構檔案的路徑。

這些架構的規則

  • 架構必須符合 JSON Schema Draft-07
  • 如果 schema 屬性是架構檔案的路徑,該檔案必須採用 JSON 或 JS(CommonJS)格式。
  • 不支援自訂 $id 值。若要使用 $ref,請提供相對於架構根目錄的值,例如 /properties/foo
  • format 關鍵字的已知值可能受支援,但其他各種關鍵字可能不受支援。如果您發現需要使用的關鍵字不受支援,請 尋求支援 或發送公關!
  • 架構必須為 object 類型({"type": "object"}),其中包含 properties 關鍵字中的參數。不支援巢狀屬性。

範例

{
  "type": "object",
  "properties": {
    "test-web-server-port": {
      "type": "integer",
      "minimum": 1,
      "maximum": 65535,
      "description": "The port to use for the test web server"
    },
    "test-web-server-host": {
      "type": "string",
      "description": "The host to use for the test web server",
      "default": "sillyhost"
    }
  }
}

上述架構定義了兩個屬性,可透過 CLI 參數或組態檔案設定。如果這個擴充功能是一個驅動程式,且其名稱為「horace」,CLI 參數將分別為 --driver-horace-test-web-server-port--driver-horace-test-web-server-host。或者,使用者可以提供包含下列內容的組態檔案

{
  "server": {
    "driver": {
      "horace": {
        "test-web-server-port": 1234,
        "test-web-server-host": "localhorse"
      }
    }
  }
}

新增驅動程式指令碼

有時您可能希望您的驅動程式的使用者能夠在會話的內容之外執行指令碼(例如執行預先建置驅動程式面向的指令碼)。若要支援這一點,您可以在 Appium 擴充功能的元資料中新增一個指令碼名稱和 JS 檔案的對應。因此,假設您在專案中建立了一個指令碼,位於專案中的 scripts 目錄中,名為 driver-prebuild.js。然後,您可以新增一個像這樣的 scripts 欄位

{
    "scripts": {
        "prebuild": "./scripts/driver-prebuild.js"
    }
}

現在,假設您的驅動程式名稱為 mydriver,您的驅動程式使用者可以執行 appium driver run mydriver prebuild,您的指令碼將會執行。

代理命令至其他 WebDriver 實作

Appium 驅動程式的常見設計架構是具備 Appium 驅動程式介面的特定於平台的 WebDriver 實作。例如,Appium UiAutomator2 驅動程式會與執行於 Android 裝置上的特殊(基於 Java)伺服器進行介面。在網頁檢視模式中,它也會與 Chromedriver 進行介面。

如果您發現自己處於這種情況,那麼讓 Appium 知道您的驅動程式只會將 WebDriver 命令直接代理至另一個端點會非常容易。

首先,透過實作 canProxy 方法讓 Appium 知道您的驅動程式可以代理

canProxy() {
    return true;
}

接著,告訴 Appium 哪些 WebDriver 路由不應嘗試代理(通常會有一些您不想轉送的特定路由)

getProxyAvoidList() {
    return [
        ['POST', new RegExp('^/session/[^/]+/appium')]
    ];
}

代理避免清單應為陣列的陣列,其中每個內部陣列的第一個成員為 HTTP 方法,第二個成員為正規表示式。如果正規表示式與路由相符,則不會代理路由,而是由您的驅動程式處理。在此範例中,我們避免代理所有具有 appium 前綴的 POST 路由。

接著,我們必須設定代理本身。執行此操作的方式是使用 Appium 中稱為 JWProxy 的特殊類別。(名稱表示「JSON Wire Proxy」,與該通訊協定的舊式實作相關)。您會想要使用連線至遠端伺服器的詳細資料建立 JWProxy 物件

// import {JWProxy} from 'appium/driver';

const proxy = new JWProxy({
    server: 'remote.server',
    port: 1234,
    base: '/',
});

this.proxyReqRes = proxy.proxyReqRes.bind(proxy);
this.proxyCommand = proxy.command.bind(proxy);

在此,我們建立一個代理物件,並將其部分方法指定給 this,名稱為 proxyReqResproxyCommand。這是 Appium 使用代理所必需的,所以請不要忘記這個步驟! JWProxy 還有各種其他選項,您也可以在原始程式碼中查看。(待辦事項:將選項發布為 API 文件,並在此處建立連結)。

最後,我們需要一種方式來告訴 Appium 代理何時處於活動狀態。對於您的驅動程式,它可能總是處於活動狀態,或者可能僅在特定內容中處於活動狀態。您可以將邏輯定義為 proxyActive 的實作

proxyActive() {
    return true; // or use custom logic
}

透過這些部分,您不必重新實作已由您要代理的遠端端點實作的任何內容。Appium 會為您處理所有代理。

將 Proxy BiDi 命令代理至另一個 BiDi 實作

上述有關代理 WebDriver 命令的所有內容在概念上也適用於特別代理 BiDi 命令。為了啟用 BiDi 代理,您需要

  1. 將驅動程式實例中的 doesSupportBidi 欄位設定為 true
  2. 在驅動程式中實作 get bidiProxyUrl。這應該會傳回一個 Websocket URL,也就是您要將 BiDi 指令代理到的上游 socket 的位址。

此處的預期模式是您要在上游實作中開始一個工作階段,檢查傳回的功能中是否有 active BiDi socket(例如 webSocketUrl 功能),然後將一個內部欄位設定為該值,以便 get bidiProxyUrl 可以傳回該值。一旦所有這些都到位,Appium 將會將 BiDi 指令從客戶端直接代理到上游連線。

使用新指令擴充現有的通訊協定

您可能會發現現有的指令對您的驅動程式來說不夠用。如果您想要公開無法對應到任何現有指令的行為,您可以使用兩種方式之一建立新指令

  1. 擴充 WebDriver 通訊協定並建立客戶端側外掛程式來存取擴充功能
  2. 透過定義執行方法來覆寫執行指令碼指令

如果您想要遵循第一個途徑,您可以指示 Appium 辨識新方法並將它們新增到其允許的 HTTP 路徑和指令名稱組中。您可以透過將驅動程式類別中的 newMethodMap 靜態變數指定為與 Appium 的 routes.js 物件相同形式的物件來執行此操作。例如,以下是 FakeDriver 範例驅動程式的 newMethodMap

static newMethodMap = {
  '/session/:sessionId/fakedriver': {
    GET: {command: 'getFakeThing'},
    POST: {command: 'setFakeThing', payloadParams: {required: ['thing']}},
  },
  '/session/:sessionId/fakedriverargs': {
    GET: {command: 'getFakeDriverArgs'},
  },
};

在此範例中,我們新增了幾個新路徑和總共 3 個新指令。若要取得更多關於如何使用這種方式定義指令的範例,最好瀏覽 routes.js。現在,您只需要以與實作任何其他 Appium 指令相同的方式實作指令處理常式即可。

使用這種方式新增新指令的缺點是,使用標準 Appium 客戶端的人員不會有專門設計來鎖定這些終端點的良好客戶端側函式。因此,您需要為您想要支援的每種語言建立並釋出客戶端側外掛程式(相關說明或範例可以在相關客戶端文件找到)。

執行此操作的另一種替代方式是覆寫所有 WebDriver 客戶端都已經可以存取的指令:執行指令碼。Appium 提供了一些方便的工具讓這件事變得容易。假設您正在為稱為 soundz 的立體聲系統建立一個驅動程式,而且您想要建立一個指令來透過名稱播放歌曲。您可以公開此指令給您的使用者,讓他們呼叫類似以下內容的東西

// webdriverio example. Calling webdriverio's `executeScript` command is what trigger's Appium's
// Execute Script command handler
driver.executeScript('soundz: playSong', [{song: 'Stairway to Heaven', artist: 'Led Zeppelin'}]);

然後在您的驅動程式碼中,您可以將靜態屬性 executeMethodMap 定義為腳本名稱對應到您的驅動程式上方法的對應。它與上面所述的 newMethodMap 具有相同的基本形式。一旦定義了 executeMethodMap,您還需要實作執行腳本命令處理常式,根據 Appium 的路由對應,它被稱為 execute。實作可以呼叫單一輔助函式 this.executeMethod,它負責查看使用者傳入的腳本和引數,並將其路由到您已定義的正確自訂處理常式。以下是範例

static executeMethodMap = {
  'soundz: playSong', {
    command: 'soundzPlaySong',
    params: {required: ['song', 'artist'], optional: []},
  }
}

async soundzPlaySong(song, artist) {
  // play the song based on song and artist details
}

async execute(script, args) {
  return await this.executeMethod(script, args);
}

關於此系統的幾個注意事項:1. 透過呼叫執行腳本來傳送的引數陣列只能包含零個或一個元素。清單中的第一個項目被視為您方法的參數物件。這些參數將被剖析、驗證,然後按照 executeMethodMap 中指定的順序套用至您的覆載方法(required 參數清單中指定的順序,接著是 optional 參數清單)。也就是說,此架構假設只有一個實際引數透過執行腳本傳入(而且此引數應該是具有代表執行方法所預期的參數的鍵/值的物件)。1. Appium 沒有自動為您實作 execute(執行腳本處理常式)。例如,您可能希望僅在您不在代理模式時呼叫 executeMethod 輔助函式!1. 如果腳本名稱與定義為 executeMethodMap 中命令的腳本名稱之一不符,或者如果缺少參數,executeMethod 輔助函式將拒絕並傳回錯誤。

建置 Appium Doctor 檢查

您的使用者可以執行 appium driver doctor <driverName> 來執行安裝和健康檢查。請參閱 建置 Doctor 檢查 指南,以取得關於此功能的更多資訊。

實作 Appium 設定的處理

Appium 使用者可以透過 CLI 引數以及功能傳送參數到您的驅動程式。但在測試過程中無法變更這些參數,而且使用者有時會希望在測試過程中調整參數。Appium 有 設定 API 可供此目的使用。

若要在您自己的驅動程式中支援設定,首先在建構函式中將 this.settings 定義為適當類別的執行個體

// import {DeviceSettings} from 'appium/driver';

this.settings = new DeviceSettings();

現在,您只要呼叫 this.settings.getSettings() 就能隨時讀取使用者設定。這會傳回一個 JS 物件,其中設定名稱為金鑰,並具有對應的值。

如果您想要指定一些預設設定,或是在設定更新時執行一些程式碼,您也可以執行這兩件事。

constructor() {
  const defaults = {setting1: 'value1'};
  this.settings = new DeviceSettings(defaults, this.onSettingsUpdate.bind(this));
}

async onSettingsUpdate(key, value) {
  // do anything you want here with key and value
}

發出 BiDi 事件

透過 WebDriver BiDi 協定,客戶端可以訂閱任意事件,這些事件可以透過 BiDi socket 連線非同步傳送給客戶端。身為 Appium 驅動程式作者,您不必擔心事件訂閱。如果您想要使用特定方法名稱和酬載發出事件,只要使用內建事件發射器搭配 bidiEvent 事件即可,這非常容易。

舉例來說,假設我們的驅動程式想要定期發出 CPU 負載資訊。我們可以定義一個稱為 system.cpu 的事件,以及一個看起來像 {load: 0.97} 的酬載,以表示 97% 的 CPU 使用率。只要我們願意,我們的驅動程式就可以簡單呼叫下列程式碼(假設我們在 this.currentCpuLoad 中有目前的負載)

this.eventEmitter.emit('bidiEvent', {
  method: 'system.cpu',
  params: {load: this.currentCpuLoad},
})

現在,如果客戶端已訂閱 system.cpu 事件,則每當驅動程式發出該事件時,客戶端就會收到負載通知。

讓自己了解其他並行驅動程式正在使用的資源

假設您的驅動程式使用一些系統資源,例如埠。有幾個方法可以確保多個同時進行的階段不會使用相同的資源

  1. 讓您的使用者透過功能指定資源 ID(appium:driverPort 等)
  2. 總是使用免費資源(為每個階段尋找新的隨機埠)
  3. 讓每個驅動程式表達其正在使用的資源,然後在新的階段開始時檢查其他驅動程式目前使用的資源。

若要支援這個第三個策略,您可以在您的驅動程式中實作 get driverData,以傳回您的驅動程式目前正在使用的資源類型,例如

get driverData() {
  return {specialPort: 1234, specialFile: /path/to/file}
}

現在,當在您的驅動程式上啟動新的階段時,任何其他同時執行的驅動程式(同類型)的 driverData 回應也會包含在內,作為 createSession 方法的最後一個參數

async createSession(jwpCaps, reqCaps, w3cCaps, driverData)

您可以深入探討這個 driverData 陣列,以查看其他驅動程式正在使用哪些資源,以協助您決定您想要用於這個特定階段的資源。

警告

請小心,因為 driverData 僅在單一執行中 Appium 伺服器的階段之間傳遞。沒有任何東西可以阻止使用者執行多個 Appium 伺服器,並在每個伺服器上同時要求您的驅動程式。在這種情況下,您將無法透過 driverData 確保資源的獨立性,因此您可能會考慮使用基於檔案的鎖定機制或類似機制。

警告

另外,請注意您只會收到您的驅動程式的其他執行個體的driverData。因此,其他無關的驅動程式可能仍在使用一些系統資源。一般來說,Appium 沒有提供任何功能來確保無關的驅動程式不會互相干擾,因此驅動程式必須允許使用者指定資源位置或位址以避免衝突。

將記錄事件記錄到 Appium 事件時間軸

Appium 有 事件時序 API,允許使用者取得特定伺服器端事件(例如命令、啟動里程碑等)的時間戳記,並將其顯示在時間軸上。此功能基本上是為了讓使用者能夠內省內部事件的時間,以協助除錯或執行 Appium 驅動程式內部的分析。您可以將自己的事件新增到事件記錄

this.logEvent(name);

只要提供事件名稱,它就會在目前時間新增,並作為事件記錄的一部分提供給使用者使用。

在安全性標記後面隱藏行為

Appium 有基於功能標記的 安全性模型,允許驅動程式作者在安全性標記後面隱藏特定功能。這表示如果您有一個您認為不安全的特性,並且希望伺服器管理員選擇加入,您可以要求他們透過將其新增到--allow-insecure清單或完全關閉伺服器安全性來啟用該特性。

若要支援您自己的驅動程式中的檢查,您可以呼叫this.isFeatureEnabled(featureName)來判斷是否已啟用給定名稱的功能。或者,如果您只想短路並在未啟用該功能時擲回錯誤,您可以呼叫this.assertFeatureEnabled(featureName)

使用暫存目錄存放檔案

如果您想為您的驅動程式建立的檔案使用暫存目錄,這些檔案在電腦或伺服器重新啟動之間無需保留,您可以從this.opts.tmpDir讀取。這會從@appium/support讀取暫存目錄位置,可能會被 CLI 標記覆寫。也就是說,這比寫入您自己的暫存目錄更安全,因為這裡的位置與可能的使用者設定相容。this.opts.tmpDir是一個字串,目錄的路徑。

處理意外關機或崩潰

您的驅動程式可能會遇到無法正常繼續運作的情況。例如,它可能會偵測到某些外部服務已崩潰,且不再有任何功能。在此情況下,它可以呼叫 `this.startUnexpectedShutdown(err)`,其中包含任何詳細資料的錯誤物件,而 Appium 將嘗試在關閉工作階段前優雅地處理任何剩餘的請求。

如果您想在遇到此情況時執行一些自己的清理邏輯,您可以在呼叫 `this.startUnexpectedShutdown` 之前立即執行,或者您可以將處理常式附加到意外關機事件,並執行您的清理邏輯「跳脫框架」來說

this.onUnexpectedShutdown(handler)

handler 應為接收錯誤物件(代表意外關機的原因)的函式。