Appiumの設定システム
Appium 2は設定ファイルをサポートしています。設定ファイルは、コマンドライン引数と(ほぼ)1対1の対応を持つことを目的としています。エンドユーザーは、Appium 2に設定ファイル、CLI引数、またはその両方を提供できます(引数が設定ファイルよりも優先されます)。
このドキュメントでは、設定システムの仕組みについて技術的な概要を説明します。これはAppiumのコントリビューターを対象としていますが、システムの基本的な機能についても説明します。
設定ファイルの読み込み¶
設定ファイルは、スキーマに対して検証できるJSON、JavaScript、またはYAMLファイルです。デフォルトでは、このファイルの名前は.appiumrc.{json,js,yaml,yml}
となり、appium
に依存するプロジェクトのルートにある必要があります。他のファイル名と場所は、--config <file>
フラグでサポートされています。明らかな理由により、config
引数は設定ファイル内では許可されていません。
別のファイルの代わりに、設定はプロジェクトのpackage.json
にappiumConfig
プロパティを使用して埋め込むことができます。例:
Appiumサーバーがappium
実行可能ファイルを介して開始されると、lib/main.js
のinit
関数はlib/config-file.js
を呼び出して、設定ファイルとpackage.json
をロードまたは検索します。
注意
設定が見つからなくてもエラーにはなりません!
lilconfig
パッケージは、検索とロード機能を提供します。検索パスの詳細については、そのドキュメントを参照してください。さらに、Appiumはyaml
パッケージを介してYAMLで記述された設定ファイルをサポートしています。
設定ファイルが見つかり、正常に検証された場合、その結果はデフォルトと追加のCLI引数のセットとマージされます。CLI引数は設定ファイルよりも優先され、設定ファイルはデフォルトよりも優先されます。
検証¶
同じシステムが、設定ファイルとコマンドライン引数の検証の両方に使用されます。
ajv
パッケージは検証を提供します。もちろん、ajv
で何かを検証するには、スキーマを提供する必要があります。
ベーススキーマは、lib/schema/appium-config-schema.js
によってエクスポートされるJSON Schema Draft-7に準拠したオブジェクトです。このスキーマは、Appiumにネイティブな設定を定義し、サーバーとしての動作のみを対象としています。他の機能(例:plugin
またはdriver
サブコマンド)の設定は定義しません。
警告
このファイルはベーススキーマであることに注意してください。これは非常に重要になります。
このファイルはJSONファイルではありません。なぜなら、a) JSONは人間が扱うのが面倒で、b) 特に@jlippsによって嫌われており、c) ajv
はJSONファイルではなくオブジェクトを受け入れるからです。
設定ファイルがどのように検証されるかを説明する方が簡単なので、そこから始めます。
設定ファイルの検証¶
設定ファイルが見つかると(lib/config-file.js
)、設定ファイルの内容をlib/schema/schema.js
からエクスポートされたvalidate
関数に渡して呼び出します。次に、これはajv
に、Appiumが提供したスキーマに対してデータを検証するように要求します。
設定ファイルが無効な場合、ユーザーに表示されるエラーが生成されます。最後に、init
関数はこれらのエラーを検出し、それらを表示し、プロセスが終了します。
これが理解できたことを願います。なぜなら、これは簡単な部分だからです。
CLI引数の検証¶
前述したように、同じシステムが設定ファイルとCLI引数の両方の検証に使用されます。
完全に判断するわけではありませんが、AppiumはCLI引数解析にargparse
を使用しています。このパッケージや同様のパッケージは、コマンドラインのNode.jsスクリプトが受け入れる引数を定義するためのAPIを提供し、最終的にユーザーが指定した引数のオブジェクト表現を返します。
スキーマが設定ファイルで許可されているものを定義するのと同様に、コマンドラインで許可されているものも定義します。
スキーマによるCLI引数の定義¶
CLI引数は、その値を検証する前に定義する必要があります。
JSONスキーマはCLI引数を定義するのに自然な適合ではありません。動作させるためのグリースが必要ですが、アダプターといくつかのカスタムメタデータを使用することで、十分に機能させることができます。
lib/cli/parser.js
には、argparse
のArgumentParser
をラップするものが存在します。それは(お待ちかねの)... ArgParser
と呼ばれています。ラッパーが存在するのは、argparse
でカスタムなことを行っているためですが、スキーマとは直接関係ありません。
ArgParser
インスタンスが作成され、そのparseArgs()
メソッドが生のCLI引数で呼び出されます。受け入れられる引数の定義は、lib/cli/args.js
の一部に由来します。ここでは、server
サブコマンドでの使用を意図していないすべての引数(例:driver
サブコマンドとそのサブコマンド)がハードコードされています。args.js
には、lib/schema/cli-args.js
のtoParserArgs
を呼び出す関数getServerArgs()
も含まれています。lib/schema/cli-args.js
は、argparse
とスキーマの間の「アダプター」レイヤーと見なすことができます。
toParserArgs
は、lib/schema/schema.js
によってエクスポートされるflattenSchema
関数を使用して、スキーマをキー/値表現に「圧縮」します。次に、toParserArgs
は各キー/値ペアを反復処理し、最終的にArgParser
に引き渡すのに適したArgumentOption
オブジェクトに「変換」します。
このアダプター(cli-args.js
)は、ほとんどの混乱が隠されている場所です。このねずみ穴をもう少し詳しく調べてみましょう。
CLIとスキーマの不一致¶
変換アルゴリズム(lib/schema/cli-args.js
の関数subSchemaToArgDef
を参照)は、ほとんどがハックと特別なケースを関数にきれいに詰め込んだものです。argparse
からJSONスキーマにきれいにマッピングされないものには、以下が含まれますが、これらに限定されません。
- スキーマは、スキーマで「
--foo=<value>
の値をbar
というプロパティに格納する」ことをネイティブに表現できません(これはArgumentOption['dest']
プロパティに対応します)。 - スキーマはエイリアスをネイティブに表現できません。例:
--verbose
は-v
にもなります。 - スキーマの
enum
は複数の型に限定されていませんが、argparse
の同等のArgumentOption['choices']
プロパティは限定されています。 - スキーマは
argparse
の「アクション」の概念について知りません(Appiumは現在カスタムアクションを使用していないことに注意してください。使用していましたが、再び使用する可能性があります)。 argparse
には、email
、hostname
、ipv4
、uri
などのネイティブ型はなく、スキーマにはあります。- スキーマ検証は検証のみを行い、翻訳、変換、または強制(ほとんど)を実行しません。
argparse
ではこれが可能です。 - スキーマでは、何らかの理由で
null
型が許可されています。CLIでnull
を渡したことがありますか? argparse
はプリミティブ以外のものを理解していません。オブジェクト、配列などはなく、特定の型の配列もありません。
上記のすべてのケースとその他のケースは、アダプターによって処理されます。
警告
アダプターで行われた一部の決定は、コイントスによって行われました。何かがそうなっている理由に興味がある場合は、何らかの何かをしなければならなかった可能性が高いです。
型を処理することについて、より詳しく見てみましょう。
ajv
による引数の型¶
argparse
は、その API を介して、コンシューマーがさまざまな引数の型(文字列、数値、ブールフラグなど)を定義できますが、Appium はこれらの組み込み型をほとんど使用しません。なぜでしょうか?理由は以下のとおりです。
- 引数の型は、スキーマで定義済みであるため、すでにわかっています。
ajv
は、スキーマに対する検証を提供します。- スキーマを使用すると、
argparse
がネイティブに提供できるよりも、型、許可される値などをより表現豊かに定義できます。 - スキーマの表現力により、より適切なエラーメッセージを表示できます。
そのため、アダプターは argparse
の組み込み型を避け(ArgumentOption['type']
の許可された文字列値を参照)、代わりに関数を type
として提供する機能を悪用します。例外は、type
を持たず、代わりに action: 'store_true'
を持つブールフラグです。なぜこうなっているのかは、永遠にわからないかもしれません。
関数としての型¶
type
が関数の場合、その関数は検証と(必要に応じて)型変換の両方を行います。では、これらの関数は何でしょうか?
注: プロパティの型が
boolean
の場合、ArgumentOption
からtype
は省略され(したがって関数ではありません)、代わりにstore_true
のaction
プロパティが提供されます。ええ、これは奇妙です。なぜそうなのかはわかりません。
ええと…それはスキーマによって異なります。しかし、一般的に言って、スキーマ内のキーワードに対応する関数のパイプラインを作成します。port
引数の例を見てみましょう。appium
を実行しているユーザーがバインドできるポートを OS に問い合わせる代わりに、この引数は 1 から 65535 の整数であると想定されます。これは、パイプラインに結合する 2 つの関数であることがわかります。
- 可能であれば、値を整数に変換します。
process.argv
のすべての値は文字列であるため、数値が必要な場合は型変換が必要です。 ajv
を使用して、port
のスキーマに対して整数を検証します。スキーマを使用すると、minimum
およびmaximum
キーワードを使用して範囲を定義できます。詳細については、以下を参照してください。
構成ファイルの検証と同様に、エラーが検出された場合、Appium はエンドユーザーに適切に通知し、プロセスはヘルプテキスト付きで終了します。
プリミティブ型ではない引数の場合、物事はそれほど簡単ではありません。
トランスフォーマー¶
argparse
が配列を理解していないことを覚えていますか?値を表現する最も人間工学的な方法が、実際には配列である場合はどうでしょうか?
Appium は、構成ファイルでは受け入れることができるにもかかわらず、CLI で配列を受け入れることができません。しかし、Appium はカンマ区切りの文字列(CSV「行」)を受け入れることができます。または、区切り文字で区切られたリストを含むファイルを指す文字列のファイルパスを受け入れることができます。いずれにせよ、値が引数パーサーから出力されるまでに、配列になっている必要があります。
上記のように、JSON スキーマのネイティブ機能はこれを表現できません。ただし、Appium が検出して適切に処理できるカスタムキーワードを定義することは可能です。そこで、Appium はそれを行います。
この場合、カスタムキーワード appiumCliTransformer
が ajv
に登録されます。appiumCliTransformer
の値は(この記事の執筆時点では)csv
または json
になります。ベーススキーマファイル appium-config-schema.js
で、Appium はこの動作が必要な場合は appiumCliTransformer: 'csv'
を使用します。
注意
型が array
であるスキーマで定義されたすべてのプロパティは、自動的に csv
トランスフォーマーを使用します。同様に、型が object
であるプロパティは json
トランスフォーマーを使用します。array
が json
トランスフォーマーを使用したい場合もあるかもしれませんが、それ以外の場合、array
または object
型のプロパティに appiumCliTransformer
キーワードが存在することは必ずしも必要ではありません。
アダプター(アダプターを覚えていますか?)は、「CSV トランスフォーマー」を含むパイプライン関数を作成し(トランスフォーマーは lib/schema/cli-transformers.js
で定義されています)、この関数を argparse
に渡される ArgumentOption
の type
プロパティとして使用します。この場合、スキーマの type: 'array'
は無視されます。
注意
構成ファイルは、Appium が期待するものを正確に定義できるため、値の複雑な変換を実行する必要はありません。したがって、Appium は構成ファイルの値の後処理を行いません。
この特別な処理を必要としないプロパティは、検証に ajv
を直接使用します。これがどのように機能するかには説明が必要なので、次に説明します。
ajv
による個々の引数の検証¶
JSON スキーマについて考えるとき、「この JSON ファイルがあり、それをスキーマに対して検証したい」と考えがちです。それは有効であり、実際、Appium は構成ファイルでまさにそれを行います。ただし、Appium は引数を検証するときにこれを行いません。
注意
実装中、私はすべての引数を構成ファイルのようなデータ構造にまとめて、一度にすべて検証しようと考えました。それは可能だったと思いますが、CLI 引数のオブジェクトはフラットなキー/値構造であり、スキーマはそうではないため、面倒だと思いました。
代わりに、Appium はスキーマ内の特定のプロパティに対して値を検証します。これを行うために、CLI 引数定義とその対応するプロパティ間のマッピングを維持します。マッピング自体は、引数の一意の識別子をキーとし、ArgSpec
(lib/schema/arg-spec.js
) オブジェクトを値とする Map
です。
ArgSpec
オブジェクトは、次のメタデータを格納します。
プロパティ名 | 説明 |
---|---|
name |
スキーマ内のプロパティ名に対応する引数の正規名。 |
extType? |
該当する場合は driver または plugin |
extName? |
該当する場合は拡張機能名 |
ref |
スキーマ内のプロパティの計算された $id |
arg |
先頭のダッシュなしで、CLI で受け入れられる引数 |
dest |
解析された引数オブジェクト (argparse の parse_args() によって返される) のプロパティ名 |
defaultValue? |
該当する場合は、スキーマの default キーワードの値 |
スキーマが確定すると、既知のすべての引数の ArgSpec
オブジェクトで Map
が入力されます。
したがって、アダプターが引数の type
の関数のパイプラインを作成するとき、すでに引数の ArgSpec
を持っています。validate(value, ref)
(lib/schema/schema.js
内) を呼び出す関数を作成します。ここで、value
はユーザーが提供したものであり、ref
は ArgSpec
の ref
プロパティです。概念としては、ajv
はそれが知っている任意の ref
を使用して検証できます。スキーマ内の各プロパティは、定義されているかどうかに関係なく、この ref
で参照できます。視覚化を支援するために、スキーマが次のようであるとします。
foo
の ref
は my-schema.json#/properties/foo
になります。Ajv
インスタンスがこの my-schema.json
について知っていると仮定すると、検証関数を取得するために、getSchema(ref)
メソッド (schema
プロパティを持ちますが、誤称です) を呼び出すことができます。schema.js
の validate(value, ref)
は、この検証関数を呼び出します。
注意
スキーマ仕様では、スキーマの作成者は、これをオーバーライドするために明示的な $id
キーワードを提供できると述べていますが、現時点では Appium はこれをサポートしていません。必要な場合、拡張機能の作成者は、カスタム $id
なしで $ref
を慎重に使用する必要があります。ただし、拡張機能がこれを必要とするほど複雑なスキーマを持つことはほとんどありません。Appium 自体でさえ、独自のプロパティを定義するために $ref
を使用していません。
次に、Appium がスキーマをロードする方法を見てみましょう。これは実際には、引数の検証前に行われます。
スキーマのロード¶
拡張機能はしばらく無視して、ベーススキーマから始めましょう。
lib/schema/schema.js
モジュールを最初にインポートすると、AppiumSchema
のインスタンスが作成されます。これはシングルトンであり、そのメソッドはモジュールからエクスポートされます (すべてがインスタンスにバインドされています)。
コンストラクターはほとんど何もしません。Ajv
インスタンスをインスタンス化し、Appium のカスタムキーワードで構成し、ajv-formats モジュールを介して format
キーワードのサポートを追加します。
それ以外の場合、AppiumSchema
インスタンスは、その finalize()
メソッド (finalizeSchema()
としてエクスポート) が呼び出されるまで、Ajv
インスタンスと対話しません。このメソッドが呼び出されると、「これ以上スキーマを追加するつもりはないので、ArgSpec
オブジェクトを作成して、ajv
にスキーマを登録してください」ということになります。
いつ確定が行われるのでしょうか? ええと、
appium
実行可能ファイルが起動すると、APPIUM_HOME
内で拡張機能を確認して構成します (適当に説明)。- その後に初めて引数について考え始めます。
ArgParser
をインスタンス化します。これにより(思い出してください)、アダプターが実行されてスキーマが引数に変換されます。 - 確定はここで行われます—パーサーを作成するときです。引数の検証関数を作成するために、スキーマを
ajv
に登録する必要があります。 - その後、Appium は
ArgParser
を使用して引数を解析します。 - 最後に、返されたオブジェクトで何を行うかを決定します。
拡張機能がない場合でも、finalize()
は Appium ベーススキーマ (appium-config-schema.js
) について知っており、それを登録するだけです。ただし、上記の手順 1. は多くの作業を行っているため、拡張機能がどのように関与するかを見てみましょう。
拡張機能のサポート¶
このシステムの設計目標の 1 つは次のとおりです。
拡張機能は、Appium にカスタム CLI 引数を登録できる必要があり、ユーザーは他の引数と同じように使用できる必要があります。.
以前、Appium 2 はこの方法で引数 (--driverArgs
を介して) を受け入れていましたが、検証は手動で行われ、拡張機能の実装者はカスタム API を使用する必要がありました。また、ユーザーはコマンドラインで JSON 文字列を構成としてぎこちなく渡す必要がありました。さらに、これらの引数に関するコンテキストヘルプ (--help
経由) は存在しませんでした。
現在、オプションのスキーマを提供することにより、ドライバーまたはプラグインは Appium に CLI 引数と構成ファイルスキーマを登録できます。
スキーマを登録するには、拡張機能は package.json
内に appium.schema
プロパティを提供する必要があります。値はスキーマ自体でも、スキーマへのパスでも構いません。後者の場合、スキーマはJSONまたはCommonJSモジュールである必要があります(現時点ではESMはサポートされておらず、YAMLもサポートされていません)。
このスキーマ内の任意のプロパティについて、そのプロパティは --<拡張機能タイプ>-<拡張機能名>-<プロパティ名>
の形式のCLI引数として表示されます。たとえば、fake
ドライバーがプロパティ foo
を提供する場合、引数は --driver-fake-foo
となり、他のCLI引数と同様に appium server --help
に表示されます。
設定ファイル内の対応するプロパティは server.<拡張機能タイプ>.<拡張機能名>.<プロパティ名>
になります。例:
上記の命名規則により、ある拡張機能タイプが別の拡張機能タイプと名前の競合を起こす問題を回避できます。
注意
拡張機能は appiumCliAliases
を介してエイリアスを提供できますが、拡張機能からのすべての引数には --<拡張機能タイプ>-<拡張機能名>-
がプレフィックスとして付与されるため、「短い」フラグは許可されていません。拡張機能名と引数名は、Lodashのルールに従って、CLI用にケバブケースに変換されます。
スキーマオブジェクトはAppiumのベーススキーマによく似ていますが、トップレベルのプロパティのみを持ちます(ネストされたプロパティは現在サポートされていません)。例:
{
"title": "my rad schema for the cowabunga driver",
"type": "object",
"properties": {
"fizz": {
"type": "string",
"default": "buzz",
"$comment": "corresponds to CLI --driver-cowabunga-fizz"
}
}
}
ユーザーの設定ファイルに記述すると、これは server.driver.cowabunga.fizz
プロパティになります。
拡張機能がロードされると、schema
プロパティが検証され、スキーマは AppiumSchema
に登録されます(finalize()
が呼び出されるまで、Ajv
には登録されません)。
ファイナライズ中、登録された各スキーマは Ajv
インスタンスに追加されます。スキーマには、拡張機能のタイプと名前に基づいて $id
が割り当てられます(これにより、拡張機能が提供するものがすべて上書きされます)。スキーマには、additionalProperties: false
キーワードを介して、不明な引数が許可されないように強制されます。
内部的には、ベーススキーマには driver
と plugin
プロパティがあり、これらはオブジェクトです。ファイナライズされると、拡張機能の名前に対応するプロパティがそれぞれに追加され、このプロパティの値は、拡張機能スキーマ内のプロパティの $id
への参照になります。たとえば、server.driver
プロパティは次のようになります。
これが「ベース」スキーマと呼ばれる理由です。拡張機能がスキーマを提供すると、ベーススキーマは変更されます。拡張機能スキーマは別々に保持されますが、参照は、最終的に ajv
に追加される前にスキーマに追加されます。これは、Ajv
インスタンスが、認識しているスキーマからの参照を、認識している任意のスキーマに理解できるためです。
注意
これにより、Appiumとインストールされた拡張機能の完全な静的スキーマを提供することが不可能になります(2021年11月5日現在)。静的な .json
スキーマはベースから生成されます(Gulpタスク経由)が、拡張機能スキーマは含まれていません。静的スキーマには、Appium以外にも用途があります。たとえば、IDEは、この方法で設定ファイルのコンテキストエラーチェックを提供できます。これを解決しましょうか?
ベーススキーマ内の特定の引数の参照IDを検索する方法と同様に、拡張機能からの引数の検証もまったく同じ方法で行われます。cowabunga
ドライバーのスキーマIDが driver-cowabunga.json
の場合、fizz
プロパティは、ajv
に登録された任意のスキーマから driver-cowabunga.json#/properties/fizz
を介して参照できます。「ベース」スキーマの引数は、代わりに appium.json#properties/
で始まります。
開発環境のサポート¶
開発フロー中、ベーススキーマを維持するためにいくつかの追加タスクが自動化されています。
- トランスパイル後のステップとして、
lib/appium-config.schema.json
が以下から生成されます。 lib/schema/appium-config-schema.js
(Babelによって生成されたCJS版に加えて)。- このファイルはバージョン管理されています。このステップで、最終的に
build/lib/appium-config.schema.json
にコピーされます。プリコミットフック(- ルートモノレポの
scripts/generate-schema-declarations.js
を参照)は、 - 上記のJSONファイルから
types/appium-config-schema.d.ts
を生成します。types/types.d.ts
の型 - はこのファイルに依存します。このファイルはバージョン管理されています。
カスタムキーワードリファレンス¶
キーワードは lib/schema/keywords.js
で定義されています。
appiumCliAliases
: スキーマがエイリアスを表現できるようにします(例:CLI引数は--verbose
または-v
にできます)。これは文字列の配列です。3文字未満の文字列は、ダブルダッシュ(--
)ではなく、単一のダッシュ(-
)で始まります。拡張機能によって提供される引数はすべて、--<拡張機能タイプ>-<拡張機能名>-
プレフィックスを持つ必要があるため、ダブルダッシュで始まることに注意してください。appiumCliDest
: スキーマがargprase
後の引数オブジェクトでカスタムプロパティ名を指定できるようにします。設定されていない場合、これはキャメルケースの文字列になります。appiumCliDescription
: スキーマがコマンドラインに表示されるときの引数の説明を上書きできるようにします。これはappiumCliTransformer
(またはarray
/object
型のプロパティ) と組み合わせて使用すると便利です。CLIを使用するユーザーが提供できるものと、設定ファイルを使用するユーザーが提供できるものには大きな違いがあるためです。appiumCliTransformer
: 現在はcsv
またはjson
のいずれかを選択できます。これらは値を後処理するカスタム関数です。設定ファイルのロードと検証時には使用されませんが、考え方は、設定ファイルが必要とするものを使用したときに得られるオブジェクトと同じになるようにすることです(例:文字列の配列)。csv
はカンマ区切りの文字列とCSVファイル用です。json
は生のJSON文字列と.json
ファイル用です。appiumCliIgnore
:true
の場合、このプロパティをCLIでサポートしません。appiumDeprecated
:true
の場合、プロパティは「非推奨」と見なされ、ユーザーにそのように表示されます(例:--help
出力)。JSONスキーマドラフト2019-09では、新しいキーワードdeprecated
が導入されており、このメタスキーマにアップグレードする場合は、代わりにこれを使用する必要があります。その際、appiumDeprecated
自体をdeprecated
とマークする必要があります。