Xamarin.UITest Tips for Xamarin.Forms Controls

## 0. はじめに

この記事は、[Xamarin その1 Advent Calendar 2017 - Qiita](https://qiita.com/advent-calendar/2017/xamarin) の 23日目のエントリーです。12月23日は、私の誕生日でして、昨年に引き続き今年も23日目を担当させていただき、嬉しく思います。よろしくお願いいたします。

本記事は、Xamarin.UITest における Tips 集です。Xamarin で提供される個別のコントロールに対するテストコード方法について調査・検証した結果をまとめています。

## 1. Xamarin UITest の基本

### ```AutomationId``` 属性

UI テストでは、ある特定のコントロールに対して操作を行う必要があります。Xamarin.Forms のコントロールには、UI テスト時にコントロールを識別するための仕組みとして、```AutomationId``` 属性が提供されています。UI テストで操作する必要のあるコントロールには、```AutomationId``` 属性を付与するようにしましょう。以下に ```Button``` コントロールに AutomationId を付与した XAML の例を記載します。

```xml
<Button AutomationId="ButtonDemoPage.Button" Command="{Binding ClickCommand}" Text="Click"/>
```

この ```AutomadionId``` は、各プラットフォームで異なるフィールドに割り当てられます。

|Platform|フィールド名|
|---|---|
|iOS|id|
|Android|label|

Repl (read-eval-print-loop) を利用して、各プラットフォームの UI ツリーを確認すると上の表のように ```AutomationId``` が割り当てられている様子がわかります。

- iOS の場合

![Automation-Id-iOS](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/671aa1d6e6ccaf5d3283221017090faeb53d8449/AutomationId-iOS.png)

- Android の場合

![AutomationId-Android](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/671aa1d6e6ccaf5d3283221017090faeb53d8449/AutomationId-Android.png)

### ```IApp``` インターフェース

アプリケーションに対する操作（タップ、スワイプなどの操作）は、```IApp``` インターフェースが提供するメソッド経由で行います。以下に主なメソッドを記載します。

|メソッド|概要|
|---|---|
|Query|指定した条件に該当する UI 要素を検索する|
|Tap|UI 要素をタップする|
|Screenshot|スクリーンショットを撮影する|
|EnterText|テキストを入力する|
|ScrollUp|上にスクロールする|
|ScrollDown|下にスクロールする|
|Flash|当該 UI 要素を点滅させる|

先ほどの ```Button``` コントロールをタップしたい場合は、Xamarin.UITest では以下のように記述します。

```csharp
app.Tap(x => x.Marked("ButtonDemoPage.Button"));
```

### ```AppQuery``` クラス

名前から想像できるように、アプリケーションの UI ツリーに対するクエリを組み立てるためのメソッドを提供するクラスです。

|メソッド|概要|
|---|---|
|Class|引数に指定した型にマッチした UI コントロールを取得する|
|Marked|引数で指定した識別子やテキスト値にマッチするコントロールを取得する|
|Child|コントロールの子供を取得する|
|Id|指定した Id に該当するコントロールを取得する|
|Index|UI コントロール配列から当該インデックスの要素を取得する|
|Invoke|ネイティブコントロールのメソッド・プロパティを呼び出す|

先ほどの ```Button``` コントロールをタップする例 ```app.Tap(x => x.Marked("ButtonDemoPage.Button"))``` の意味は、"ButtonDemoPage.Button" という識別子で Mark されたコントロールを取得して、タップするという意味になります。

## 2. Xamarin.UITest Tips for Xamarin.Forms Controls

ここからは、Xamairn.Forms で提供される各コントロールの Xamarin.UITest の Tips になります。

### ```Label```

![Label](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/ec90aa1c98dfa0ddc284c614a74188885f2fd1cc/Label.png)

ラベルの表示が正しいかを Xamarin.UITest で確認をする方法を記載します。XAML 上で以下のように ```Label``` が定義されているとします。

```xml
<Label AutomationId="LabelDemoPage.Label" Text="Welcome to Xamarin.Forms!"/>
```

```Label``` コントロールは、iOS では ```UILabel``` クラス、Android では ```TextView``` クラスのオブジェクトになります。このとき、Xamarin UI Test で Label を取得するには、以下のようにコードを記載します。

```csharp
app.Query(x => x.Marked("Welcome to Xamarin.Forms!"));
```

### ```Image```

![Image](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/ce89d527d3dbe65248792e14984ef2030848a4bd/Image.png)

```Image``` コントロールを Xamairn.UITest で取得する方法について記載します。XAML 上に以下のように ```Image``` コントロールが定義されているものとします。

```xml
<Image AutomationId="ImageDemoPage.Image"
       Source="https://www.xamarin.com/content/images/pages/branding/assets/xamagon.png"/>
```

以下のコードに記載するように、```AutomationId``` を利用して、```Image``` コントロールを取得することができます。

```csharp
app.Query(x => x.Marked("ImageDemoPage.Image"));
```

### ```BoxView```

![BoxView](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/ce89d527d3dbe65248792e14984ef2030848a4bd/BoxView.png)

ここでは、BoxView コントロールをタップする例を記載します。以下のように XAML 上で BoxView が定義されているとします。

```xml
<BoxView x:Name="MyBoxView" AutomationId="FormsGallery.BoxViewDemoPage.BoxView"
         Color="Blue" WidthRequest="150" HeightRequest="150">
    <BoxView.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding TapCommand}"/>
    </BoxView.GestureRecognizers>
</BoxView>
```

この BoxView を Xamarin.UITest でタップするには、以下のようにコードを記述します。

```csharp
app.Tap(x => x.Marked("FormsGallery.BoxViewDemoPage.BoxView"));
```

### ```Button```

![Button](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/6da747bbf3d9db40561c77a0e9ad526dacb4d8e0/Button.png)

ボタンをタップするシナリオを Xamarin.UITest で実現する方法を記載します。なお、XAML 上では以下のようにボタンが実装されているとします。

```xml
<Button AutomationId="ButtonDemoPage.Button"
        Command="{Binding ClickCommand}"
        Text="Click"/>
```

Xamarin.UITest でボタンをタップするには ```IApp.Tap``` メソッドを利用します。

```csharp
app.Tap(x => x.Marked("ButtonDemoPage.Button"));
```

### ```SearchBar```

![SearchBar](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/6da747bbf3d9db40561c77a0e9ad526dacb4d8e0/SearchBar.png)

SearchBar で検索条件の入力・検索を Xamarin.UITest で実現する方法を記載します。なお、XAML 上では、以下のように SearchBar が実装されているとします。

```xml
<SearchBar AutomationId="SearchBar"
           x:Name="SampleSearchBar"
           Text="{Binding SearchWord}"
           SearchCommand="{Binding SearchCommand}"
           SearchCommandParameter="{Binding SearchWord}"/>
```

検索条件を入力し、検索処理を実行するには、```IApp.EnterText``` メソッドで条件を入力後、 ```IApp.PressEnter``` メソッドを利用します。

```csharp
app.EnterText(x => x.Marked("SearchBar"), "Title");
app.PressEnter();
```

検索条件をクリアするには、```IApp.CelarText``` メソッドを実行後、 ```IApp.DissmissKeyboard``` メソッドでキーボードを消します。

```csharp
app.ClearText(x => x.Marked(mark));
app.DismissKeyboard();
```

検索処理をキャンセルする場合は、Android と iOS で異なる処理を記述する必要があります。

```csharp
// SearchBar のキャンセルボタンを押す
if (platform == Platform.Android)
{
    app.Tap(x => x.Id("search_close_btn"));
}
else if (platform == Platform.iOS)
{
    app.Tap(x => x.Marked("Cancel"));
}
app.DismissKeyboard();
```

### ```Slider```

![Slider](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/7196e98e5986d9502fa5144bcdde777128828609/Slider.png)

Slider の値を設定・取得するシナリオを Xamarin.UITest で実現する方法について記載します。以下のように XAML 上に Slider が定義されているとします。

```xml
<Slider AutomationId="Slider"
        Minimum="0"
        Maximum="100"
        Value="{Binding Value}"/>
```

- 値を設定するには、```IApp.SetSliderValue``` メソッドを利用します
- 値を取得するには、ネイティブコントロールのメソッドを ```AppQuery.Invoke``` で呼び出します
  - Android
      - ```getProgress``` メソッド
  - iOS
      - ```UISlider``` クラスの ```value``` プロパティ

```csharp
string marked = "SliderDemoPage.Slider";
if(platform == Platform.Android)
{
    var progress = 500.0;
    app.SetSliderValue(marked, progress);
    var actual = Convert.ToDouble(app.Query(x => x.Marked(marked).Invoke("getProgress"))[0]);
}
else if(platform == Platform.iOS)
{
    var progress = 50;
    app.SetSliderValue(marked, progress);
    var actual = Convert.ToInt32(app.Query(x => x.Class("UISlider").Invoke("value"))[0]);
}
```

### ```Stepper```

![Stepper](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/9a13b6a61746e964bc0c0cd6a89c5fbb36438e69/Stepper.png)

Stepper の値の増減を Xamarin.UITest で実現する方法を記載します。XAML 上に以下のように ```Stepper``` に定義されているものとします。

```xml
<Stepper Minimum="0" Maximum="100" Value="{Binding Value}"/>
```

```AutomationId``` を利用して ```Stepper``` の値を増減させたいところですが、```AutomationId``` を利用して ```Stepper``` の増減ボタンを押すことができません。理由は、```AutomationId``` が、Android では、```LinearLayout``` に付与され、iOS では ```UIStepper``` に付与されており、値を増減させるボタンに付与されていないためです。

- ```Stepper``` を Repl の tree コマンド表示した図（Android）

![Stepper-Repl-Android](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/e981a8904fdf754ac869610684a5c90ce703fdf8/Stepper-Repl-Android.png)

- ```Stepper``` を Repl の tree コマンド表示した図（iOS）

![Stepper-Repl-iOS](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/e981a8904fdf754ac869610684a5c90ce703fdf8/Stepper-Repl-iOS.png)

従って、各プラットフォームでいかに説明するように Xamarin.UITest のコードを記述する必要があります。

#### Android の場合

Android の場合は、```Stepper``` のボタンは、ボタンのテキストが、"+" または "-" となるため、当該のボタンをタップするコードを記述します。

```csharp
app.Tap(x => x.Class("android.widget.Button").Text("+")); // increment
app.Tap(x => x.Class("android.widget.Button").Text("-")); // decrement
```

#### iOS の場合

iOS の場合は、```Stepper``` のボタンは、ボタンのテキストが、"Increment" または "Decrement" なので、当該のボタンをタップするコードを記述します。

```csharp
app.Tap(x => x.Marked("Increment")); // increment
app.Tap(x => x.Marked("Decrement")); // decrement
```

### ```Switch```

![Switch](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/9a13b6a61746e964bc0c0cd6a89c5fbb36438e69/Switch.png)

```Switch``` の ON / OFF を切り替える操作と ```Switch``` の ON / OFF 状態を取得するシナリオを、Xamarin.UITest で実現する方法について記載します。以下のように XAML 上で ```Switch``` が定義されているとします。

```xml
<Switch AutomationId="SwitchDemoPage.Switch" IsToggled="{Binding Value}"/>
```

```Switch``` の ON / OFF を切り替えるには、```IApp.Tap``` メソッドで ```AutomationId``` を指定して、```Switch``` をタップします。```Switch``` の ON / OFF の状態を取得するには、ネイティブのメソッド・フィールドを ```AppQuery.Invoke``` で呼び出して取得する必要があります。

以下に各プラットフォームで利用すべきネイティブのメソッド・フィールドについて記載します。

|プラットフォーム|ネイティブコントロール|メソッド・フィールド|補足|
|---|---|---|---|
|Android|Android.Widget.Switch|```isChecked``` メソッドを利用する||
|iOS|UISwitch|```isOn``` フィールドの値を取得する|ON の時には 1、OFF の時には 0 が返される|

Xamarin.UITest のコードは、以下のようになります。

```csharp
string marked = "SwitchDemoPage.Switch";
app.Tap(x => x.Marked(marked)); // Tap Switch control

if (platform == Platform.Android)
{
    var result = (bool)app.Query(x => x.Marked(marked).Invoke("isChecked"))[0];
}
else if(platform == Platform.iOS)
{
    // If switch is ON, get 1.
    // If switch is OFF, get 0.
    var result = Convert.ToInt32(app.Query(x => x.Marked(marked).Invoke("isOn")));
}
```

### ```DatePicker```

![DatePicker](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/bba77270f871940997bbc16540502bc42c67ebbf/DatePicker.png)

```DatePicker``` で日付を選択するシナリオを Xamarin.UITest で実現する方法について記載します。XAML 上で以下のように ```DatePicker``` が定義されているものとします。

```xml
<DatePicker AutomationId="DatePickerDemoPage.DatePicker" Format="D"/>
```

```DatePicker``` での日付の選択を Xamarin.UITest で実現するには、以下のステップで処理を実装する必要があります。

- ```DatePicker``` をタップし、日付選択の Picker を起動する
  - ```IApp.Tap``` メソッドに ```AutomationId``` を指定してタップ処理を実行します
  - ```DatePicker``` が表示されるまで待ちます
- 日付を変更する
  - Android の場合
      - ```DatePicker``` は ```Android.Widget.DatePicker``` のオブジェクトに変換されるので、```AppQuery.Invoke```メソッドを利用して ```Android.Widget.DatePicker``` クラスの ```updateDate``` メソッドを呼び出す
  - iOS の場合
      - ```UIPickerTableView``` の年、月、日に該当するそれぞれの列に対して、以下の操作を行う必要があります
        - ```IApp.ScrollDownTo``` / ```IApp.ScrollUpTo``` メソッドを利用してスクロールさせる
        - スクロール後、当該列のテキストをタップして、値を確定する
- Picker を閉じる
  - Android の場合
      - ```Android.Widget.DatePicker``` の OK ボタンは、"button1" という Id が付与されているため、Id から OK ボタンのコントロールを取得して、```IApp.Tap``` メソッドを実行する
  - iOS の場合
      - "Done" とマークされたボタンをタップします

Xamarin.UITest のコードは以下のようになります。テスト対象のデバイスの言語は en-US を想定しています。

```csharp
var date = new DateTime(2018, 12, 23);
CultureInfo enus = new CultureInfo("en-US");
var month = enus.DateTimeFormat.GetMonthName(date.Month);
var expected = date.ToString("D", enus);
var actual = string.Empty;

var mark = "DatePickerDemoPage.DatePicker";
if (platform == Platform.Android)
{
    app.Tap(x => x.Marked(mark));
    app.WaitForElement(x => x.Class("DatePicker"));
    app.Query(x => x.Class("DatePicker").Invoke("updateDate", date.Year, date.Month -1, date.Day));
    app.Tap(x => x.Id("button1")); //Ok Button in DatePicker Dialogue
}
else if(platform == Platform.iOS)
{
    app.Tap(x => x.Id(mark));
    app.WaitForElement(x => x.Class("UIPickerView"));

    // Scroll DatePicker items
    var iOSTableViewClass = "UIPickerTableView";
    app.ScrollDownTo(z => z.Marked(month), x => x.Class(iOSTableViewClass).Index(0), timeout: TimeSpan.FromSeconds(30), strategy: ScrollStrategy.Auto);
    app.Tap(x => x.Text(month));

    app.ScrollDownTo(z => z.Marked(date.Day.ToString()), x => x.Class(iOSTableViewClass).Index(3), timeout: TimeSpan.FromSeconds(30), strategy: ScrollStrategy.Auto);
    app.Tap(x => x.Text(date.Day.ToString()));

    app.ScrollDownTo(z => z.Marked(date.Year.ToString()), x => x.Class(iOSTableViewClass).Index(6), timeout: TimeSpan.FromSeconds(30), strategy: ScrollStrategy.Auto);
    app.Tap(x => x.Text(date.Year.ToString()));

    app.Tap(x => x.Marked("Done")); // Tap button marked with "Done".
}
```

iOS の日付の変更ですが、```UIDatePicker``` の ```selectRow()``` メソッドで値の設定はできるのですが、"Done" ボタンをタップした後に、```DatePicker``` の日付の表示が変更できませんでした。いろいろと試行錯誤をした結果、ここに記載している方法にたどり着きました。```selectRow()``` メソッドを利用した方法でも日付の変更ができるよという情報があれば、教えていただけると幸いです。

### ```TimePicker```

![TimePicker](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/bba77270f871940997bbc16540502bc42c67ebbf/TimePicker.png)

```TimePicker``` で時刻を選択するシナリオを、Xamarin.UITest で実現する方法について記載します。XAML 上で以下のように ```TimePicker``` が定義されているものとします。

```xml
<TimePicker AutomationId="TimePickerDemoPage.TimePicker" Format="T"/>
```

```TimePicker``` での時刻の選択するシナリオの流れは、```DatePicker``` の流れとほぼ同じです。

- ```TimePicker``` をタップし、日付選択の Picker を起動する
  - ```IApp.Tap``` メソッドに ```AutomationId``` を指定してタップ処理を実行する
  - ```TimePicker``` が表示されるまで待ちます
- 時刻を変更する
  - Android の場合
      - ```TimePicker``` は "timePicker" という Id が付与された ```Android.Widget.TimePicker``` のオブジェクトに変換されるので、```AppQuery.Invoke```メソッドを利用して ```Android.Widget.TimePicker``` クラスの ```setHour``` メソッド、```setMinute``` を呼び出す
  - iOS の場合
      - ```UIPickerTableView``` の時間、分、AM/PM に該当するそれぞれの列に対して、以下の操作を行う必要がある
        - ```IApp.ScrollDownTo``` / ```IApp.ScrollUpTo``` メソッドを利用してスクロールさせる
        - スクロール後、当該列のテキストをタップして、値を確定する
- Picker を閉じる
  - Android の場合
      - ```Android.Widget.TimePicker``` の OK ボタンは、"button1" という Id が付与されているので、`Id から OK ボタンのコントロールを取得して、```IApp.Tap``` メソッドを実行する
  - iOS の場合
      - "Done" とマークされたボタンをタップする

```csharp
var hour = 11;
var minutes = 59;
var meridian = "PM";
var mark = "TimePickerDemoPage.TimePicker";

if (platform == Platform.Android)
{
    app.Tap(x => x.Marked(mark));
    app.WaitForElement(x => x.Id("timePicker"));

    app.Query(x => x.Id("timePicker").Invoke("setHour", hour + 12));
    app.Query(x => x.Id("timePicker").Invoke("setMinute", minutes));

    app.Tap(x => x.Id("button1")); //Ok Button in DatePicker Dialogue
}
else if(platform == Platform.iOS)
{
    app.Tap(x => x.Id(mark));
    app.WaitForElement(x => x.Class("UIPickerView"));

    app.ScrollDownTo(z => z.Marked(hour.ToString()), x => x.Class(iOSTableViewClass).Index(0), timeout: TimeSpan.FromSeconds(30), strategy: ScrollStrategy.Auto);
    app.Tap(x => x.Text(hour.ToString()));

    app.ScrollDownTo(z => z.Marked(minutes.ToString()), x => x.Class(iOSTableViewClass).Index(3), timeout: TimeSpan.FromSeconds(30), strategy: ScrollStrategy.Auto);
    app.Tap(x => x.Text(minutes.ToString()));

    app.ScrollDownTo(z => z.Marked(meridian), x => x.Class(iOSTableViewClass).Index(6), timeout: TimeSpan.FromSeconds(30), strategy: ScrollStrategy.Auto);
    app.Tap(x => x.Text(meridian));

    app.Tap(x => x.Marked("Done")); // Tap "Done" button.
}
```

### ```Entry```

![Entry](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/1fd89d681790a3cbfecfd8fa0a83d9e6031122ce/Entry.png)

```Entry``` のテキスト入力・クリアを行うシナリオを、Xamarin.UITest で実現する方法について記載します。以下のように ```Entry``` が XAML 上に定義されているとします。

```xml
<Entry  AutomationId="EntryDemoPage.MailEntry" Placeholder="Enter email address"/>
```

テキストの入力には ```IApp.EnterText``` メソッドを利用し、テキストのクリアには ```IApp.CelarText``` メソッドを利用します。

```csharp
var mailId = "EntryDemoPage.MailEntry";

//EnterText
app.EnterText(x => x.Marked(mailId), "someone@somewhere.local");
app.DismissKeyboard();

//ClearText
app.ClearText(x => x.Marked(mailId));
app.DismissKeyboard();
```

### ```Editor```

![Editor](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/31f0b8ec3de9cd8578356de2c1821713fc452085/Editor.png)

```Editor``` の UI テストは、```Entry``` と同様の方法で実装できます。以下のような XAML が定義されているとします。

```xml
<Editor AutomationId="EditorDemoPage.Editor"/>
```

テキストの入力・クリアは以下のように実装することができます。

```csharp
var marked = "EditorDemoPage.Editor";

//EnterText
app.EnterText(x => x.Marked(marked), "very long text");
app.DismissKeyboard();

//ClearText
app.ClearText(x => x.Marked(marked));
app.DismissKeyboard();
```

### ```ActivityIndicator```

![ActivityIndicator](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/1fd89d681790a3cbfecfd8fa0a83d9e6031122ce/ActivityIndicator.png)

以下の二つのシナリオを Xamarin.UITest で実現する方法について記載します。

- ```ActivityIndicator``` が表示・非表示になるまで待つ
- インジケーターの表示・非表示を切り替える

なお、```ActivityIndicator``` は XAML で以下のように定義されているものとします。

```xml
<ActivityIndicator AutomationId="ActivityIndicator" IsRunning="True"/>
```

#### ```ActivityIndicator``` が表示・非表示になるまで待つ

特定の要素の表示・非表示を待つので、```WaitForElement``` 又は ```WaitForNoElement``` メソッドを利用します。以下にサンプルを示します。

```csharp
// ActivityIndicator が表示されるまで待つ
app.WaitForElement(x => x.Marked("ActivityIndicator"), timeout: TimeSpan.FromSeconds(30));

// ActivityIndicator が非表示になるまで待つ
app.WaitForNoElement(x => x.Marked("ActivityIndicator"), timeout: TimeSpan.FromSeconds(30));
```

#### インジケーターの表示・非表示を切り替える

インジケーターの表示・非表示を切り替えるには、```ActivityIndicator``` が、ネイティブではどのようにレンダリングされているかを知っておく必要があります。以下に Android, iOS それぞれのプラットフォームについて、```ActivityIndicator``` がレンダリングされるコントロール名、インジケーターの表示・非表示を切り替えるメソッドについて記載します。

|プラットフォーム|ネイティブコントロール|表示・非表示に関連するネイティブメソッド|
|---|---|---|
|Android|Android.Widget.ProgressBar|```setVisibility``` メソッド|
|iOS|UIActivityIndicatorView|```stopAnimating``` メソッド|

従って、Xamarin UITest でインジケーターの表示・非表示を切り替えるには、上記のネイティブメソッドを ```AppQuery.Invoke``` を利用して呼び出します。

```csharp
if(platform == Platform.Android)
{
    app.Query(x => x.Marked(mark).Invoke("setVisibility", 8));
    app.WaitForNoElement(x => x.Marked(mark), timeout: TimeSpan.FromSeconds(30));
}
else if(platform == Platform.iOS)
{
    app.Query(x => x.Marked(mark).Invoke("stopAnimating"));
    app.WaitForNoElement(x => x.Marked(mark), timeout: TimeSpan.FromSeconds(30));
}
```

### ```ProgressBar```

![ProgressBar](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/31f0b8ec3de9cd8578356de2c1821713fc452085/ProgressBar.png)

ProgressBar の進捗状況を設定・取得するシナリオを Xamarin UITest で実現する方法について記載します。以下のように ProgressBar が XAML 上に定義されているとします。

```xml
<ProgressBar AutomationId="ProgressBarDemoPage.ProgressBar"
             VerticalOptions="CenterAndExpand"/>
```

ProgressBar の進捗状況の取得・設定を行うためには、ProgressBar がネイティブコントロールに対して、```AppQuery.Invoke``` を実行する必要があります。以下に各プラットフォームにおけるコントロール、進捗状況を取得・設定するメソッド・プロパティの対応関係を示します。

|プラットフォーム|ネイティブコントロール|メソッド・プロパティ|補足|
|---|---|---|---|
|Android|Android.Widget.ProgressBar|```setProgress``` / ```getProgress``` メソッド を利用する|0 ～ 10,000の値をとり、10,000 が 100％ に対応する|
|iOS|UIProgressView|```progress``` プロパティを利用する|0.0 ～ 1.0 の値をとり、1.0f が 100％ に対応する|

具体的なコードを見ていきます。

```csharp
if (platform == Platform.Android)
{
    // 進捗を 25% に設定する
    app.Query(x => x.Class("ProgressBar").Invoke("setProgress", 2500));

    // 進捗を取得する
    long progress = (long)app.Query(x => x.Class("ProgressBar").Invoke("getProgress"))[0];
}
else if (platform == Platform.iOS)
{
    // 進捗を 50% に設定する
    app.Query(x => x.Class("UIProgressView").Invoke("setProgress:animated", 0.5));

    // 進捗を取得する
    var progress = app.Query(x => x.Class("UIProgressView").Invoke("progress"))[0];

    // Convert をかけているのは、long 型でデータが返される場合があるため（値が 0 の時など）
    double value = System.Convert.ToDouble(progress);
}
```

iOS で進捗を取得するときに、long 型でデータが返されることがあるため、```Convert``` クラスを利用して値を変換するなどの処理を行っておく必要があります。

### ```Picker```

![Picker](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/1fd89d681790a3cbfecfd8fa0a83d9e6031122ce/Picker.png)

Picker の項目を選択するシナリオを Xamarin.UITest で実現する方法について記載します。以下のように Picker が XAML 上に定義されているとします。

```xml
<Picker AutomationId="PickerDemoPage.Picker"
        Title="Color"
        ItemsSource="{Binding PickerItems}"
        SelectedItem="{Binding SelectedColor}"/>
```

UI Test では以下の ３つのステップを実現する必要があります。

- Picker をタップして選択項目のリストを表示する
  - AutomationId を付与したコントロールを ```IApp.Tap``` メソッドでタップする
- 選択対象となる項目が表示されるまで Picker をスクロールさせる
  - ```IApp.ScrollDownTo``` 又は ```IApp.ScrollUpTo``` メソッドを利用して、タップ対象の項目が表示されるまでスクロールさせる
- 選択対象の項目をタップする
  - 対象の項目を ```IApp.Tap``` メソッドを利用してタップする
- iOS の場合は、最後に "Done" ボタンをタップする

```csharp
// Picker をタップする
var marked = "PickerDemoPage.Picker";
app.Tap(x => x.Marked(marked));

if (platform == Platform.Android)
{
    app.ScrollDownTo(z => z.Marked("Yellow"),
                        x => x.Id("select_dialog_listview"),
                        timeout: TimeSpan.FromSeconds(30),
                        strategy: ScrollStrategy.Auto);
    app.Tap(x => x.Marked("Yellow"));
}
else if(platform == Platform.iOS)
{
    app.ScrollUpTo(z => z.Marked("Aqua"),
                    x => x.Class("UIPickerTableView").Index(0),
                    timeout: DefaultTimeout,
                    strategy: ScrollStrategy.Auto);
    app.Tap(x => x.Marked("Aqua"));
    app.Tap(x => x.Marked("Done")); // Done をタップする
}
```

### ```ListView```

![ListView](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/1fd89d681790a3cbfecfd8fa0a83d9e6031122ce/ListView.png)

```ListView``` の項目を選択するシナリオを Xamarin.UITest で実現する方法について記載します。XAML 上で以下のように ```ListView``` が定義されているものとします。```DataTemplate``` 内のコントロールで、UI Test で取得するコントロールには、```AutomationId``` を付与するようにしましょう。

```xml
<ListView AutomationId="ListViewDemoPage.ListView"
            ItemsSource="{Binding People}"
            IsPullToRefreshEnabled="True"
            RefreshCommand="{Binding RefleshCommand}"
            IsRefreshing="{Binding IsRefleshing, Mode=TwoWay}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell AutomationId="ListViewDemoPage.ViewCell">
                <StackLayout Orientation="Horizontal" Margin="1,1">
                    <BoxView  Color="{Binding FavoriteColor}"/>
                    <StackLayout Orientation="Vertical">
                        <Label AutomationId="ListViewDemoPage.NameLabel"
                                Text="{Binding Name}"/>
                        <Label AutomationId="ListViewDemoPage.BirthDayLabel"
                                Text="{Binding Birthday, StringFormat='{0:yyyy-MM-dd}'}"/>
                    </StackLayout>
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
```

- 画面上に表示されているアイテムは、```IApp.Query``` メソッドを利用して、 ```AutomationId``` から取得することができます
- 画面上に表示されていないアイテム（リストの最後尾のアイテムなど）を取得したい場合は、```IApp.ScrollDownTo``` 等を利用して画面をスクロール後、アイテムの取得を行う必要があります

以下に Xamarin.UITest のサンプルコードを示します。

```csharp
var mark = "ListViewDemoPage.ListView";
var nameLabelMark = "ListViewDemoPage.NameLabel";

app.ScrollDownTo(z => z.Marked(nameLabelMark).Text("Yvonne"), x => x.Marked(mark), timeout: TimeSpan.FromSeconds(30), strategy: ScrollStrategy.Auto);

var nameLabels = app.Query(x => x.Marked(nameLabelMark));
var birthdayLabels = app.Query(x => x.Marked("ListViewDemoPage.BirthDayLabel"));
```

#### Pull To Refresh が実装された ListView の UI Test

```ListView``` を引っ張って更新するシナリオを Xamarin.UITest で実現したい場合の補足説明を記載します。具体的には、以下の二つの操作を実装する方法について説明します。

- ```ListView``` を引っ張って更新する
- ```ListView``` が更新中かどうかを判定する

```ListView``` を引っ張って更新するには、```ListView``` をドラッグすればよいことになります。ここでは、```ListView``` の**最初のセルの中心座標**から、**```ListView``` の中心座標**までをドラッグすることで実現します。

![ListView-PullToRefresh](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/0ad6331f6f75bf0e167f5297dd96b2eeddecf9b2/ListView-PullToRefresh.png)

以下に Xamarin.UITest のサンプルを示します。

```csharp
AppResult firstCellInList = null;
if(platform == Platform.Android)
{
    firstCellInList = app.Query(x => x.Class("ViewCellRenderer_ViewCellContainer").Index(0)).FirstOrDefault();
}
else if (platform == Platform.iOS)
{
    firstCellInList = app.Query(x => x.Class("Xamarin_Forms_Platform_iOS_ViewCellRenderer_ViewTableCell")).FirstOrDefault();
}

var firstCenterX = firstCellInList.Rect.CenterX;
var firstCenterY = firstCellInList.Rect.CenterY;

var listview = app.Query(x => x.Marked(marked))[0];
var listviewCenterX = listview.Rect.CenterX;
var listviewCenterY = listview.Rect.CenterY;

app.DragCoordinates(firstCenterX, firstCenterY, listviewCenterX, listviewCenterY);
```

```ListView``` が更新中かどうかを判定する方法は、Android、iOS それぞれのネイティブの知識が必要になります。

|Platform|ネイティブコントロール|判定方法|
|---|---|---|
|Android|android.support.v4.widget.SwipeRefreshLayout|```isRefreshing``` メソッドを利用する|
|iOS|UIRefreshControl|コントロールが存在するかどうかで判定する|

判定を行う処理のサンプルを以下に示します。

```csharp
var isRefreshing = false;
if (platform == Platform.Android)
{
    isRefreshing = (bool)app.Query(x => x.Class("SwipeRefreshLayout").Invoke("isRefreshing")).First();
}
else(platform == Platform.iOS)
{
    isRefreshing = (bool)app.Query(x => x.Class("UIRefreshControl")).Any();
}
```

この判定処理を定期的にチェックすることで、```ListView``` が更新中かどうかを判定することができます。
以下のように判定処理をプロパティ化し、判定処理を一定時間リトライするメソッドを実装し、```WaitForIndicatorToDisappear(3, 10)``` のように待ちの処理を実装するとよいでしょう。

```csharp
public bool RefreshIndicatorIsDisplayed
{
    get
    {
        if (platform == Platform.Android)
            return (bool)app.Query(x => x.Class("SwipeRefreshLayout").Invoke("isRefreshing")).First();

        if (platform == Platform.iOS)
            return (bool)app.Query(x => x.Class("UIRefreshControl")).Any();

        throw new Exception("NotSupportedPlatform");
    }
}

public void WaitForIndicatorToDisappear(int retryCount = 3, int waitSeconds = 10)
{
    int counter = 0;
    while (RefreshIndicatorIsDisplayed)
    {
        Thread.Sleep(waitSeconds * 1000);
        counter++;

        if (counter >= retryCount)
            throw new Exception($"待ち時間 {waitSeconds * retryCount} をオーバーしました");
    }
}
```

### ```TableView```

![TableView](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/b12a4fefde7aea89066afc26cb5ad10674320eaa/TableView.png)

```TableView``` 内のセルを操作するシナリオを、Xamarin.UITest で実現する方法について記載します。以下のように XAML 上に ```TableView``` が定義されているものとします。

```xml
<TableView  AutomationId="TableViewFormDemoPage.TableView" Intent="Form">
    <TableSection Title="Table Section">
        <TextCell AutomationId="TextCell" Detail="With Detail Text"  Text="Text Cell"/>
        <ImageCell AutomationId="ImageCell" Text="Image Cell"
                   ImageSource="https://www.xamarin.com/content/images/pages/branding/assets/xamagon.png"/>
        <SwitchCell AutomationId="SwitchCell" Text="Switch Cell" IsEnabled="False"/>
        <EntryCell AutomationId="EntryCell" Label="Entry Cell" Placeholder="Type text here"/>
        <ViewCell AutomationId="ViewCell">
            <StackLayout>
                <Label AutomationId="ViewCell.Label" Text="A ViewCell can be anything you want!"/>
            </StackLayout>
        </ViewCell>
    </TableSection>
</TableView>
```

```TableView``` コントロールの取得は、```AutomationId``` を指定して取得することができます。しかしながら、```TableView``` 内のセルnには、XAML で ```AutomationId``` を付与してもネイティブのオブジェクトツリーには反映されません。従って、```TableView``` のコントロールを取得した後に、```Child``` メソッドを駆使しながら、テスト対象のコントロールを取得することになります。

以下、Xamairn.Forms で提供されている標準のセルコントロールについて、Xamarin.UITest ではどのように実装したらよいかについて記載します。

#### ```TextCell```

```TextCell``` を Repl で評価すると、各プラットフォームで以下のように変換されていることがわかります。

- Android の場合

```text
[ConditionalFocusLayout]
[TextCellRenderer_TextCellView > LinearLayout]
    [TextView] text: "Text Cell"
    [TextView] text: "With Detail Text"
[View]
```

- iOS の場合

```text
[Xamarin_Forms_Platform_iOS_CellTableViewCell] text: "Text Cell"
  [UITableViewCellContentView]
    [UITableViewLabel] label: "Text Cell",  text: "Text Cell"
    [UITableViewLabel] label: "With Detail Text",  text: "With Detail Text"
  [_UITableViewCellSeparatorView]
  [_UITableViewCellSeparatorView]
  [UITableTextAccessibilityElement] label: "Text Cell, With Detail Text"
```

```TextCell``` コントロールを取得するには、表示されている文字列を指定してコントロールを取得します。

```csharp
// TextCell
var textCellText = app.Query(x => x.Marked("Text Cell"));
var textCellDetail = app.Query(x => x.Marked("With Detail Text"));
```

#### ```EntryCell```

```EntryCell``` のテキストの入力・クリアを行うシナリオを、Xamarin.UITest で実現する方法について記載します。

Android の場合、```EntryCell``` を Repl で評価すると以下のようなツリー構造になっていることがわかります。

```text
[ConditionalFocusLayout]
[EntryCellView]
    [TextView] text: "Entry Cell"
    [EntryCellEditText]
[View]
```

Android の場合、入力用のテキストボックスは、```EntryCellView``` クラスの子オブジェクトの ```EntryCellEditText``` オブジェクトであることがわかります。従って、```EntryCell``` でテキストを入力するには以下の方針でテキストを入力する必要があります。

- ```AppQuery.Class``` メソッドで ```EntryCellView``` クラスのオブジェクトを取得し、その子オブジェクトを取得する
  - 子オブジェクトを取得する方法は、以下の二通りの方法がある
    - ```AppQuery.Child``` メソッドでインデックスを指定する
    - ```AppQuery.Child``` メソッドでクラス名（EntryCellEditText）を指定する
- テキストの入力は ```IApp.EnterText``` メソッド、テキストのクリアは ```IApp.ClearText``` メソッドを利用する

iOS の場合、```EntryCell``` を Repl で評価すると以下のようなツリー構造になっていることがわかります。

```text
[Xamarin_Forms_Platform_iOS_EntryCellRenderer_EntryCellTableViewCell] text: "Entry Cell"
  [UITableViewCellContentView]
    [UITextField]
      [UITextFieldLabel] label: "Type text here",  text: "Type text here"
      [_UITextFieldContentView > UITextSelectionView]
      [UIAccessibilityTextFieldElement] text: "Type text here"
    [UITableViewLabel] label: "Entry Cell",  text: "Entry Cell"
  [_UITableViewCellSeparatorView]
  [UITableTextAccessibilityElement] label: "Entry Cell"
  [UIAccessibilityElementMockView] text: "Type text here"
    [UIAccessibilityTextFieldElement] text: "Type text here"
```

iOS の場合、入力用のテキストボックスは、```Xamarin_Forms_Platform_iOS_EntryCellRenderer_EntryCellTableViewCell``` クラスの孫（```UITableViewCellContentView``` クラスの子供）となっているので、Xamarin.UITest では、以下の方針でテキストの入力・クリアの処理を実装する必要があります。

- ```AppQuery.Class``` メソッドで ```Xamarin_Forms_Platform_iOS_EntryCellRenderer_EntryCellTableViewCell``` を取得し、その最初の子供を取得する
  - 子オブジェクトは、```Child``` メソッドで取得する
    - インデックス指定か、クラス名（```UITableViewCellContentView```）を指定する
  - その子供の最初のコントロールを ```Child``` メソッドで取得する
    - インデックス指定か、クラス名
- テキストの入力は ```IApp.EnterText``` メソッド、テキストのクリアは ```IApp.ClearText``` メソッドを利用する

以下に、サンプルコードを示します。

```csharp
// Enter text.
var inputText = "This is text for test.";
if(platform == Platform.Android)
{
    app.EnterText(x => x.Class("EntryCellView").Child(1), inputText);
    app.EnterText(x => x.Class("EntryCellView").Child("EntryCellEditText"), inputText);

    app.DismissKeyboard();

}
else if (platform == Platform.iOS)
{
    app.EnterText(x => x.Class("Xamarin_Forms_Platform_iOS_EntryCellRenderer_EntryCellTableViewCell").Child(0).Child(0), inputText);
    app.EnterText(x => x.Class("Xamarin_Forms_Platform_iOS_EntryCellRenderer_EntryCellTableViewCell").Child("UITableViewCellContentView").Child("UITextField"), inputText);
    app.DismissKeyboard();
}

// Clear text.
if (platform == Platform.Android)
{
    app.ClearText(x => x.Class("EntryCellView").Child(1));
}
else if (platform == Platform.iOS)
{
    app.ClearText(x => x.Class("Xamarin_Forms_Platform_iOS_EntryCellRenderer_EntryCellTableViewCell").Child(0).Child(0));
}
```

#### ```SwitchCell```

```SwitchCell``` の ```Switch``` の ON / OFF を切り替えるシナリオを、Xamarin.UITest で実現する方法について記載します。

Android の場合、```SwitchCell``` を Repl で評価すると、次のようなツリー構造になっています。従って、```SwitchCellView``` クラスの子供の ```Switch``` クラスのオブジェクトに対して、```Invoke``` メソッドを呼び出してやればよいことになります。

```text
[ConditionalFocusLayout]
[SwitchCellView]
    [LinearLayout]
    [TextView] text: "Switch Cell"
    [Switch]
[View]
```

iOS の場合も Repl で評価すると以下のようなツリー構造になっているため、```Xamarin_Forms_Platform_iOS_CellTableViewCell``` クラスの子供の ```UISwitch``` クラスのオブジェクトに対して、```Invoke``` メソッドを使用して、ON / OFF を切り替えればよいことになります。

```text
[Xamarin_Forms_Platform_iOS_CellTableViewCell] text: "Switch Cell"
  [UITableViewCellContentView]
    [UITableViewLabel] label: "Switch Cell",  text: "Switch Cell"
  [UISwitch > UISwitchModernVisualElement] label: "Switch Cell",  text: "0"
    [UIView > UIView]
    [UIView > UIView]
    [UIView > UIImageView]
    [UIImageView]
  [_UITableViewCellSeparatorView]
  [UITableTextAccessibilityElement] label: "Switch Cell",  text: "0"
  [UIAccessibilityElementMockView] label: "Switch Cell",  text: "0"
```

以下に、サンプルコードを記載します。

```csharp
// SwitchCell
if (platform == Platform.Android)
{
    app.Query(x => x.Class("SwitchCellView").Class("Switch").Invoke("setChecked", true));
}
else if (platform == Platform.iOS)
{
    app.Query(x => x.Class("Xamarin_Forms_Platform_iOS_CellTableViewCell").Class("UISwitch").Invoke("setOn:animated", true));
}
```

#### ```ImageCell```

```ImageCell``` に配置された Image を Xamarin.UITest で取得する場合を考えます。

Android の場合のオブジェクトツリーは以下のようになるため、```TextCellRenderer_TextCellView``` クラスの子供の ```ImageView`` オブジェクトを取得することになります。

```text
[ConditionalFocusLayout]
[TextCellRenderer_TextCellView]
    [ImageView]
    [LinearLayout]
    [TextView] text: "Image Cell"
[View]
```

iOS の場合のオブジェクト構造は以下のようなツリー構造となるため、```Xamarin_Forms_Platform_iOS_CellTableViewCell``` クラスの孫となる ```UIImageView``` オブジェクトを取得することになります。

```text
[Xamarin_Forms_Platform_iOS_CellTableViewCell] text: "Image Cell"
  [UITableViewCellContentView]
    [UITableViewLabel] label: "Image Cell",  text: "Image Cell"
    [UIImageView]
  [_UITableViewCellSeparatorView]
  [UITableTextAccessibilityElement] label: "Image Cell"
```

以下にサンプルコードを記載します。

```csharp
// ImageCell
if (platform == Platform.Android)
{
    app.Query(x => x.Class("TextCellRenderer_TextCellView").Class("ImageView"));
}
else if(platform == Platform.iOS)
{
    app.Query(x => x.Class("Xamarin_Forms_Platform_iOS_CellTableViewCell").Child("UITableViewCellContentView").Class("UIImageView"));
}
```

#### ```ViewCell```

```ViewCell``` の Xamarin.UITest のコードは、セル内に配置したコントロールに依存します。ここで記載している各コントロールのテストコードの書き方を参考にしてください。

### ```Frame```

![Frame](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/27960182d320a0cc1d2b010e10f9f091e14340d0/Frame.png)

```Frame``` コントロールを Xamarin.UITest で取得する方法について記載します。XAML 上に以下のように ```Frame``` が定義されているものとします。

```xml
<Frame AutomationId="FrameDemoPage.Frame"
       OutlineColor = "Black"
       VerticalOptions = "CenterAndExpand">
    <Label Text = "I've been framed!"/>
</Frame>
```

```AutomationId``` を指定して以下のようなコードで、```Frame``` コントロールを取得することができます。

```csharp
app.Query(x => x.Marked("FrameDemoPage.Frame"));
```

### ```NavigationPage```

```NavigationPage``` において、前の画面に戻る場合は、```IApp.Back``` メソッドを利用しましょう。

```csharp
app.Back();
```

### ```MasterDetailPage```

![MasterDetailPage](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/1fd89d681790a3cbfecfd8fa0a83d9e6031122ce/MasterDetail.png)

以下のシナリオを Xamarin.UITest で実現方法について記載します。

- ハンバーガーメニューをタップする
- メニューのアイテムを選択する

#### ハンバーガーメニューをタップする

ハンバーガーメニューをタップするには、ネイティブでどのようなコントロールに変換されているかを知っておく必要があります。

```xml
<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                  xmlns:local="clr-namespace:FormsGallery"
                  x:Class="FormsGallery.MasterDetailPageDemoPage"
                  Title="Master Detail"
                  IsPresented="{Binding IsPresented}">
    <MasterDetailPage.Master>
        <local:ColorListPage/>
    </MasterDetailPage.Master>
    <MasterDetailPage.Detail>
        <NavigationPage>
            <x:Arguments>
                <local:NamedColorPage Title="Detail"/>
            </x:Arguments>
        </NavigationPage>
    </MasterDetailPage.Detail>
</MasterDetailPage>
```

- Android の場合

Repl で UI のツリー構造を調べると、ハンバーガーメニューは、ネイティブでは、```AppCompatImageButton``` の "OK" ボタンに変換されていることがわかります。

```text
[Toolbar] id: "toolbar"
    [AppCompatTextView] text: "Detail"
    [AppCompatImageButton] label: "OK"
```

- iOS の場合

Repl で UI のツリー構造を調べると、ハンバーガーメニューは、ネイティブでは label にアイコン名が付与されたボタンに変換されていることがわかります。

```text
[UINavigationBar] id: "Detail"
[_UIBarBackground > UIImageView]
[_UINavigationBarContentView]
    [_UIButtonBarStackView]
    [_UIButtonBarButton > ... > UIImageView] label: "hamburger"
    [UILabel] label: "Detail",  text: "Detail"
[_UIButtonBarButton > ... > UIImageView] label: "hamburger"
```

従って、ハンバーガーメニューをタップするには、各プラットフォームで以下のように Xamarin.UITest のコードを記述する必要があります。

|プラットフォーム|タップする方法|補足|
|---|---|---|
|Android|```AppCompatImageButton``` ボタンで、ボタンのテキストが "OK" のボタンを ```IApp.Tap``` メソッドでタップする||
|iOS|メニューの label の値でコントロールを取得し、 ```IApp.Tap``` メソッドをタップする|label は、Title の値。ただし、Icon を指定したときはアイコン名（拡張子を除く）|

以下にハンバーガーメニューをタップするコードのサンプルを示します。

```csharp
if(platform == Platform.Android)
{
    app.Tap(x => x.Class("AppCompatImageButton").Marked("OK"));
}
else if (platform == Platform.iOS)
{
    app.Tap(x => x.Marked("hamburger")); // Tap control marked with icon name
}
```

#### メニューの項目を選択する

メニューの項目を選択は、```ListView``` のリストから項目を選択する方法と同じアプローチで実現することができます。

- 選択対象となる項目が表示されるまで、```IApp.ScrollDownTo``` / ```IApp.ScrollUpTo``` メソッドを利用してスクロールする
- 選択対象となる項目を ```IApp.Tap``` メソッドを利用してタップする- メニューのページ

以下のような ```ListView``` が XAML 上に定義されており、この ```ListView``` に表示される項目をタップする場合を考えます。

```xml
<ListView AutomationId="ColorListPage.ListView" ItemsSource="{Binding ColorList}">
    <ListView.Behaviors>
        <behaviorsPack:SelectedItemBehavior Command="{Binding SelectedColorCommand}"/>
    </ListView.Behaviors>
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <StackLayout VerticalOptions="Center">
                    <Label  AutomationId="ColorListPage.ColorLabel" Text="{Binding Name}" Margin="5,0,0,0"/>
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
```

```ListView``` をスクロールし、項目を選択するサンプルコードは、以下のようになります。

```csharp
app.ScrollDownTo(x => x.Marked("ColorListPage.ColorLabel").Text("Yellow"), x => x.Marked("ColorListPage.ListView"), timeout: TimeSpan.FromSeconds(30), strategy: ScrollStrategy.Auto);
app.Tap(x => x.Marked("ColorListPage.ColorLabel").Text("Yellow"));
```

### ```TabbedPage```

![TabbedPage](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/31f0b8ec3de9cd8578356de2c1821713fc452085/TabbedPage.png)

```TabbedPage``` 内のページを切り替える操作を、Xamarin.UITest で実現する方法について記載します。XAML 上で、以下のように ```TabbedPage``` の子ページが定義されているとします。

```xml
<TabbedPage.Children>
    <local:Page1 Title="Tab1"/>
    <local:Page2 Title="Tab2"/>
    <local:Page3 Title="Tab3"/>
</TabbedPage.Children>
```

UITest で Tab2 のコンテンツに切り替えるには、以下のように ```AppQuery.Marked``` メソッドで Tab のTitle をコンテンツを取得し、```IApp.Tap``` メソッドを利用してタップ操作を実現することができます。

```csharp
app.Tap(x => x.Marked("Tab2"));
```

### ```CarouselPage```

![CarouselPage](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/1fd89d681790a3cbfecfd8fa0a83d9e6031122ce/CarouselPage.png)

```CarouselPage``` 内に定義されたコンテンツをスワイプ操作で切り替えるシナリオを、Xamarin.UITest で実現する方法について記載します。```IApp``` インタフェースには、スワイプ操作を実現するメソッドが提供されていますので、これを利用することができます。

|メソッド|説明|
|---|---|
|```SwipeRightToLeft```|右から左へスワイプする|
|```SwipeLeftToRight```|左から右へスワイプする|

右から左にスワイプ操作を行うコードの例を以下に記載します。

```csharp
app.SwipeRightToLeft(); // Swipe
```

### ```ToolbarItems```

![ToolbarItems](https://gist.githubusercontent.com/tafuji/cc3e3ee960a3194cfe7a0abee5d3663f/raw/31f0b8ec3de9cd8578356de2c1821713fc452085/ToolbarItem.png)

```ToolbarItems``` コントロールのアイテムをタップするシナリオを、Xamairn.UITest で実現する方法について記載します。以下のように XAML 上に ```ToolBarItems``` が定義されているものとします。

```xml
<ContentPage.ToolbarItems>
    <ToolbarItem AutomationId="ToolbarItemDemoPage.Item1"
                    Text="ToolBar1"
                    Command="{Binding ClickCommand}"
                    CommandParameter="ToolBar1"/>
    <ToolbarItem AutomationId="ToolbarItemDemoPage.Item2"
                    Text="ToolBar2"
                    Command="{Binding ClickCommand}"
                    CommandParameter="ToolBar2"/>
</ContentPage.ToolbarItems>
```

```ToolBarItem``` はネイティブでは以下のコントロールに変換されます。

|プラットフォーム|ネイティブコントロール|
|---|---|
|Android|Android.Widget.Button|AutomationId は付与されない|
|iOS|UIBarButtonItem|```AutomationId``` は ```Id``` プロパティに付与される|

したがって、各プラットフォームで ```IApp.Tap``` に受け渡すパラメータが異なります。

- Android
  - ボタンのテキストの文字列を ```AppQuery.Marked``` に渡してコントロールを取得し、```IApp.Tap```メソッドでタップします
- iOS
  - ```AutomationId``` を ```AppQuery.Id``` に渡してコントロールを取得し、```IApp.Tap``` メソッドでタップします

サンプルコードを以下に示します。

```csharp
if (platform == Platform.Android)
{
    app.Tap(x => x.Marked("ToolBar1"));
    app.Tap(x => x.Marked("ToolBar2"));
}
else if(platform == Platform.iOS)
{
    app.Tap(x => x.Id("ToolbarItemDemoPage.Item1"));
    app.Tap(x => x.Id("ToolbarItemDemoPage.Item2"));
}
```

## 3. おわりに

最後まで、みていただきありがとうございました。以下のコントロールに関する Tips は、時間の都合でここに掲載できなかったのですが、いずれアップデートできればと考えています。

- ```WebView```
- ```Map```

## 4. ソースコード

記事の動作検証に利用したソースコードは、Github に公開しています。

- [FormsGalleryUITest](https://github.com/tafuji/FormsGalleryUITest)