TestCafe（３）テストのAPI２（テストの流れとページ情報の参照）

## 記事の概要
　TestCafeについての記述の3回目です。
　今回はテストで何ができるかの概要の確認の為にテスト内容の記述について書いてます。ハンドブック的に使おうと思っているので詳しく知りたい場合はAPIのドキュメントを参照してください。
　APIの説明をを自分なりにかいつまんで書いているので、動作確認はしていません。間違ってたら教えてください。
　なんか、書いてたらAPIの日本語化＋補足してるみたいになってしまいました。

## テストの概要
　テストの流れはtestメソッドの呼び出しで初期のページを読み込んだ後は基本的に以下のような感じかと思います。
　1. 画面のエレメントや情報を取得する。（今回の記事）
　2. 画面を操作する（入力、クリック、ドラッグなど）
　3. アサーション（入力などのチェック）
　
　そのほか、特殊な処理として以下のようなことができるようです。（必要になれば調べますが現状はできることリストとしての覚書です）
・HTTPリクエストを補足して、ログを出したり仮のレスポンスを返したりすることができる。
・認証処理。ベーシック認証やWindows認証を実行させる
・テストのポーズ
・テストのデバッグ
・iフレームの対応
・ブラウザコンソールメッセージへのアクセス

## 画面のエレメントや情報を取得する
### １．ブラウザやプラットフォーム情報の取得
　ブラウザやOSの情報を取得します。「all」を利用した場合など、ブラウザに応じて処理を分けたい場合に重宝します。テスト関数の引数のテストコントロール「t」のプロパティー「t.browser」の子プロパティから以下の内容が取得できます。

| プロパティ| 型 | 概要 | 例 |
| :---------- | :---------- | :---------- | :---------- |
| alias | 文字列 | テスト中のブラウザのエリアス | firefox:headless |
| name | 文字列 | ブラウザの名称 | Chrome |
| version | 文字列 | バージョン | 77.0.3865.120 |
| platform | 文字列 | 実行環境の種類 | desktop、mobile、tablet、otherのいずれか |
| headless | 真偽値 | ヘッドレスモードかどうか | true:ヘッドレスモード<br />false:ヘッドレスではない |
| os | オブジェクト | OSの名前（nameプロパティ）とバージョン（versionプロパティ） | { name: 'macOS', version: '10.15.1' } |
| engine | オブジェクト | ブラウザエンジンの名前（nameプロパティ）とバージョン（versionプロパティ） | { name: 'Gecko', version: '20100101' } |
| userAgent | 文字列 | ユーザーエージェント | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/77.0.3865.120 Safari/537.36 |
| prettyUserAgent | 文字列 | フォーマットされたブラウザとOSの名前とバージョン |Chrome 77.0.3865.75 / macOS 10.14.0 |

　以下のように条件分岐させるときなどに使います。(サンプルは「fixture.beforeEach」で行っていますが、「test」でも同じ様に使います)

```
fixture `My fixture`
    .page `https://example.com`
    .beforeEach(async t => {
        if (t.browser.engine.name === 'Blink')
            return;

        // ...
    });
```

### ２．セレクタ（テストするWebページのエレメントのハンドルみたいなもの）
　クライアントページのエレメントに関する情報は以下のようなセレクタで取得します。これで取得したオブジェクトを利用して画面を操作したり、エレメントの情報を取得して条件分岐やアサーションを実施します。
　以下はそのコンストラクタです。

```
　Selector( init [, options] )
```

| 引数 | 型 | 概要 |
| :---------- | :---------- | :---------- |
| init | 関数,文字列,Selector,Snapshot,Promise | 対象を特定するための関数、文字列、他のセレクタ、スナップショット(\*)、プロミス(\*)など |
| options | オブジェクト | オプション |

#### 引数[init]

　セレクタの基本的な検索条件（もしくは方法）で以下のものがあります。

| 種類 | 概要 | サンプル |
| :-- | :-- | :-- |
| 文字列 | CSSのセレクタ文字列 | onst usernameInput = Selector('#username'); |
| 関数 | クライアントサイドで実行させるDOMのアクセス関数 | const element = Selector(() => { <br />return  document.getElementById('targetId');< br />});<br />※この例では[id=targetId]のエレメントを取得しているが、基本的にはWebページのDOMから検索したエレメントを返せばよい |
| セレクタ | ほかに作成したセレクタ<br />他のセレクタを設定してさらにオプションを追加する様な場合に利用 | const ctaButton = Selector('.cta-button');<br /> Selector(ctaButton, { visibilityCheck: true }); |
| スナップショット | 他のセレクタから取得した全ての属性と一致するものを取得。取得した属性の一部を変更してから利用することもできるみたい | const topMenuSnapshot = await Selector('#top-menu')();<br />const visibleTopMenu = Selector(topMenuSnapshot, { visibilityCheck: true }); |
| プロミス | 引数のあるセレクタで引数を指定したもの？ | const elementWithIdOrClassName = Selector(value => { return document.getElementById(value) ｜｜ document.getElementsByClassName(value); });<br /> const submitButton = Selector(elementWithIdOrClassName('main-element')); }); |

　注意点としてはSelectorで取得したオブジェクトはあくまでTestCafeのオブジェクトであり、**決してクライアントページのエレメントオブジェクトではない**ことに注意してください。（最初そう思ってたので取得したセレクタのプロパティを取ろうとしたら取れなかったという自分の失敗）

#### 引数[options]
　セレクタの取得に対する以下のオプションが指定できます。

| オプション | 概要 |
| :---------- | :---------- |
| boundTestRun | Node.jsのコールバックで利用する場合のオプションの様です。このあたりは今回は調べてません。 |
| dependencies | Webページ内で実行する関数を定義した場合、テストスクリプト内の変数や関数を利用できるように依存情報として設定する。（APIのこれのサンプルで「customId」と書かれているのはソースの中では「persistentId 」の記述ミスだと思う） |
| timeout | エレメントの取得に失敗するまでのタイムアウトを設定します。設定するとデフォルトの値（3000）やコマンドオプションや定義ファイルに記述できますが、ここで設定するとこの処理のみその値を利用します。 |
| visibilityCheck | visibleのコンポーネントを指定します。無ければエラーになります。ただし「count」等のメソッドではこのオプションは無視されるみたいです。 | | 

#### セレクタのフィルタ
　上で取得したセレクタは複数のエレメントを指していることがあり、一つまたは少数に特定するために以下のフィルタが利用できます。

| メソッド | 概要 | 例 |
| :-- | :-- | :-- |
| nth(index) | index番目<br />負の値の場合-1が最後尾で前に進む | Selector('ul').nth(2);<br />Selector('div').nth(-1); |
| withText(text) or withText(re) | 内容にtextで与えた文字を含めたもの。<br />正規表現（\で括ったリテラル）の場合、正規表現に一致するもの | Selector('label').withText('foo');<br />Selector('div').withText(/a[b-e]/); |
| withExactText(text) | テキストが完全一致するもの | Selector('.container').withExactText('foo');<br /> この場合、「foobar」や「Foo」は一致しない |
withAttribute(attrName [, attrValue]) | 属性があるかどうか、値が指定されれば値まで一致するもの。<br />属性名も値も正規表現が使える | Selector('div').withAttribute('myAttr');// 属性に「myAttr」があるもの<br /> Selector('div').withAttribute('attrName', 'foo');// 属性に「attrName='foo'」があるもの<br />Selector('ul').withAttribute(/[123]z/, /a[0-9]/);// 正規表現の利用|　
| filterVisible() | 表示されているもの。<br />「display:none」や「visibility:hidden」、幅や高さが0のものは対象外 | Selector('div').filterVisible(); |　
| filterHidden() | 表示されていないもの。「filterVisible()」の反対 | Selector('label').filterHidden(); |　
| filter(cssSelector) | cssセレクタが一致するもの | Selector('li').filter('.someClass') |　
| filter(filterFn, dependencies) | Webページ内で実行する関数を指定してフィルターする。<br /> 「dependencies」にはセレクタのコンストラクタと同じように依存性を設定する。| const isNodeOk = (node, idx) => { /*...*/ };<br />Selector('ul').filter((node, idx) => {<br />    return isNodeOk(node, idx);<br />}, { isNodeOk }); |　

#### セレクタからの相対セレクタの取得 ###
　セレクタからの相対的な位置（親、子、同一階層）のセレクタの取得。

| メソッド | 概要 |
| :-- | :-- |
| find(cssSelector)<br />find(filterFn, dependencies)  | 子 |
| parent()<br />parent(index)<br />parent(cssSelector)<br />parent(filterFn, dependencies) | 親 |
| child()<br />child(index)<br />child(cssSelector)<br />child(filterFn, dependencies) | 直下 |
| sibling()<br />sibling(index)<br />sibling(cssSelector)<br />sibling(filterFn, dependencies) | 同一階層（兄弟） |
| nextSibling()<br />nextSibling(index)<br />nextSibling(cssSelector)<br />nextSibling(filterFn, dependencies) | 自身以降の同一階層 |
| prevSibling()<br />prevSibling(index)<br /> prevSibling(cssSelector)<br />prevSibling(filterFn, dependencies) | 自身以前の同一階層 |

　引数無の場合は全てのエレメントのセレクタになります。　
　引数がある場合は以下のようになります。

| 引数 | 型 | 概要 |
| :-- | :-- | :-- |
| index | 数値 | 取得したリストのindex番目のセレクタで0が先頭。負の場合は最後尾からの位置で-1が最後尾。ただし「prevSibling」のみリストは逆順 |
| cssSelector | 文字列 | cssセレクタが一致するもののみを抽出 |
| filterFn,dependencies | 関数、オブジェクト | 取得した配列に対してフィルタする関数を指定する。テストスクリプト内の変数や関数を参照する場合は「dependencies」に記述する |

　引数で関数を利用する場合は、その関数は以下の書式になります。（サンプルは「prevSibling」の場合）

```
　Selector('section').prevSibling((node, idx, originNode) => {
　　　　// ここにフィルター処理を記述
});
```
引数は以下の通りです。

| 引数 | 型 | 概要 |
| :-- | :-- | :-- |
| node | オブジェクト | 取得したリストの個々 |
| idx | 数値 | 配列内での先頭からの位置、0始まり |
| originNode | オブジェクト | 関数を実行している元のオブジェクト |

#### セレクタの属性やメソッド
　セレクタは次回掲載予定の画面の操作に使えるほか、以下の属性やメソッドを利用して条件分岐やアサーションに利用します。ドキュメントではセレクタのエレメントの有無や個数と属性取得が別の章になっていますが、ここではまとめて一覧します。
　全てのセレクタに対するものと、エレメントを指すセレクタのみしか使用できないものがあります。

| 種別 | 対象 | 記述 | 属性 | 概要 |
| :-- | :-- | :-- | :-- | :-- |
| メンバ | 全て | exist | 真偽値 | 対象のエレメントの存在。 |
| メンバ | 全て | count | 数値 | エレメントの個数 |
| メンバ | 全て | childElementCount | 数値 | 子エレメントの個数 |
| メンバ | 全て | childNodeCount | 数値 | 子ノードの個数 |
| メンバ | 全て | hasChildElements | 真偽値 | 子エレメントの存在。 |
| メンバ | 全て | hasChildNodes | 真偽値  | 子ノードの存在。 |
| メンバ | 全て | nodeType | 列挙 | DOMのノードの種類 |
| メンバ | 全て | textContent | 文字列 | ノード内の文字列 |
| メソッド | 全て | hasClass(className) | 真偽値 | 引数「className」を継承しているかどうか |
| メンバ | エレメント | attributes | オブジェクト | エレメントの属性値を{name:value,...}形式で返す。<br />後述のメソッド「getAttribute」で個々に取得できる  |
| メンバ | エレメント  | boundingClientRect | オブジェクト  | ビューポートに対する相対のエレメントの位置と大きさ。<br />left、ight、bottom、top、width、heightが取得できます。<br />後述のメソッド「getBoundingClientRectProperty」で個々に取得できる  | 
| メンバ  | chekubox or radiobuttonエレメント  | checked | 真偽値 | チェックされているかどうか | 
| メンバ | エレメント | classNames　| 文字列リスト | classに設定されているリスト |
| メンバ | エレメント | clientHeight | 数値 | エレメントの高さ。paddingは含むが、スクロールバー、罫線、マージンは含まない。<br />DOMのエレメントの「clientHeight」属性 |
| メンバ | エレメント | clientLeft | 数値 | エレメントの左<br />DOMのエレメントの「clientLeft」属性 |
| メンバ | エレメント | clientTop | 数値 | エレメントの上<br />DOMのエレメントの「clientTop」 属性|
| メンバ | エレメント | clientWidth | 数値 | エレメントの幅。paddingは含むが、スクロールバー、罫線、マージンは含まない。<br />DOMのエレメントの「clientWidth」属性 |
| メンバ | エレメント | focused | 真偽値 | フォーカスされているかどうか |
| メンバ | エレメント | id | 文字列 | エレメントのID<br />DOMのエレメントの「id」属性 |
| メンバ | エレメント | innerText | 文字列 | 表示されるテキスト<br />DOMのエレメントの「innerText 」属性 |
| メンバ | エレメント | namespaceURI | 文字列 | ネームスペースのURI.ネームスペースが無い場合はnullになる<br />DOMのエレメントの「Element.namespaceURI」 |
| メンバ | エレメント | offsetHeight | 数値  | エレメントのpaddingと罫線を含む高さ<br />DOMのエレメントの「offsetHeight 」属性 |
| メンバ | エレメント | offsetLeft | 数値  | エレメントの親の左端からのエレメントの左端までのピクセル数<br />DOMのエレメントの「offsetLeft  」属性  |
| メンバ | エレメント | offsetTop | 数値  | エレメントの親の上端からのエレメントの上端までのピクセル数<br />DOMのエレメントの「offsetTop 」属性 |
| メンバ | エレメント | offsetWidth | 数値  | エレメントのpaddingと罫線を含む幅<br />DOMのエレメントの「offsetWidth」属性  |
| メンバ | Optionエレメント | selected | 真偽値 | optionタグで選択されているものがあるかどうか |
| メンバ | Optionエレメント | selectedIndex | 数値 | optionタグで選択されている要素のインデックス |
| メンバ  | エレメント | scrollHeight | 数値 | スクロールして表示されていない部分を含むエレメントの高さ |
| メンバ  | エレメント | scrollLeft | 数値 | 横方向のスクロール量 |
| メンバ | エレメント | scrollTop | 数値 | 縦方向のスクロール量 |
| メンバ | エレメント | scrollWidth | 数値 | スクロールして表示されていない部分を含むエレメントの幅 |
| メンバ | エレメント | style | オブジェクト | エレメントに適用されている最終的なcssの情報<br />後述のメソッド「getStyleProperty」でも取得できる |
| メンバ | エレメント | tagName | 文字列 | エレメントのタグ名 |
| メンバ | inputエレメント | value | 文字列  | inputタグのvalue|
| メンバ | エレメント | visible | 真偽値 | エレメントのvisible状態。display:noneやvisibility:hiddenではなく幅や高さも0でない場合true。それ以外はfalse |
| メソッド | エレメント   | getStyleProperty(propertyName) | オブジェクト | 「propertyName」名のcssプロパティーを取得する |
| メソッド  | エレメント   | getAttribute(attributeName) | 文字列 | 「attributeName」名の属性の値を取得する |
| メソッド  | エレメント   | getBoundingClientRectProperty(propertyName) | 数値 | エレメントのビューポートに対する相対のエレメントの位置と大きさの「propertyName」のメンバの値を取得 |
| メソッド  | エレメント   | hasAttribute(attributeName) | 真偽値 | 「attributeName」の属性が設定されているかどうか |

#### そのほかセレクタに関する記述
　そのほかセレクタについて以下の記述がありますが、ここでは説明しませんので興味があればドキュメントのAPIを見てください。（必要になった時に加筆するかも）
　・ セレクタに独自の属性を追加する
　・ Edgeに対する制限
　・ 以下のフレームワークに対する情報
　　React
　　Angular
　　AngularJS
　　Vue
　　Aurelia

### ２．ブラウザページでスクリプトを実行して情報を取得（クライアント関数）
　上記のセレクタのメンバ やメソッドでは取得できない情報をWebページから取得する場合、以下のクライアントサイドスクリプトで取得します。
　
```
ClientFunction( fn [, options] )
```

| 引数 | 型 | 概要 |
| :---------- | :---------- | :---------- |
| fn| 関数 | Webページ内で実行する処理 |
| options | オブジェクト | オプション |

#### クライアントで実行する処理
　以下はWebページでのURLを取得するサンプルです。

```
test('My Test', async t => {
    const location = await getWindowLocation();
});
```

#### クライアントで非同期処理を実行したい場合
　クライアントで非同期処理を実行したい場合、Promiseを利用して以下の様に記述し、取得したPromiseの結果に応じた処理を記述するようです。

```
const performAsyncOperation = ClientFunction(() => {
    return new Promise(resolve => {
        window.setTimeout(resolve, 500); // some async operations
    });
});
// この後、thenメソッドで結果に応じた処理を記述する
```

#### 定義せずに1回限りのクライアント処理を行う方法
　1回限りのクライアント関数を定義して実行するのが無駄に感じるので、これは結構使えそうです。即時関数みたいのものですね。

```
t.eval( fn [, options] )
```

　以下は「document.documentURI」を取得している例です。

```
test('My Test', async t => {
    const docURI = await t.eval(() => document.documentURI);
});
```

#### クライアント関数での制限
　・クライアント関数内では「async」「await」は使えません。
　・テストのスコープ外の変数は使えません。

#### その他のAPIの概要
　APIには以下の事も記述されていますが、私は使いそうにないので調べてません。必要なら知れべてください。

　・クライアント関数内で別のスクリプトをインポートする方法
　・Node.jsでのコールバックの方法