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とマークする必要があります。