【UiPath Studio】ExcelでWEB操作をカスタマイズ：　UiPathコード実装機能を活用してパラメータ化したWEBテストを実装しました。

# はじめに
これまでUiPath Studioのコード実装系の機能を何点か紹介しています。
当記事はその中のいくつかの記事を統合したアイデアを試した実験的な実装のご紹介です。

### 結論としては、

* コード実装１つ（後述にて紹介）を用意し、
* 複数のWEB画面を共通的に操作することができました。

実験的に実装した対象はデモサイトの以下２つです。

https://uibank.uipath.com/

https://www.rpachallenge.com/?lang=JA

まずは２サイトのみの確認ですが、この２つのサイトはそれぞれ特徴が異なるため、この２つを共通のコードで処理できたことには価値があると感じてます。

### 前提となる機能をご紹介している記事です。

特に今回の仕組みの前提として、対象のWEB画面をオブジェクトリポジトリとして作成済です。
これにより、WEB画面の多用さや、セレクターの精度などをオブジェクトリポジトリに依存する思想となっています。

https://qiita.com/moripo/items/b81538b57532f78127ee

https://qiita.com/moripo/items/c9c5f80ef7bd0a2cc3e6

https://qiita.com/moripo/items/7cd9b820c7be09dcc149


:::note
当記事の実装コードは、UiPath Studio v23.10で作成＆動作確認をしています。

UiPath Studio v24.10では機能追加がされています。同じ概念の実装は可能なはずですが、細かい修正や調整が必要となる可能性があります。
:::

それでは実験した実装内容をご紹介します。


# パラメータとして用意したExcel

パラメータとして用意したExcelを参照していただければわかると思いますが、コードを共通化する一方で、処理の多用さはExcelパラメータとして表現する設計思想です。

### ★ Excel列と目的の説明

| 列名 | 目的 |
| --- | --- |
| No | 管理のためだけの番号です。処理には使っていません。 |
| 画面名 | オブジェクトリポジトリと一致させた画面名です。 |
| 要素名 | オブジェクトリポジトリと一致させた要素名です。 |
| 操作 | 後述のコード内のCASE文の条件と一致させた操作名です。 |
| 値 | 操作に応じて、入力や判定に必要な値を入力します。 |
| シミュレート | シミュレート操作かWindow Messageとしての操作かを指定します。 |

### Uibankの操作用のパラメータです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/d100c11a-f372-68b1-b350-56f30f017d65.png)

### RPA Challengeの操作用のパラメータです。
RPA Challengeサイトからダウンロードできる入力情報を、今回用意したフォーマットに転記しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/9eda233a-b12c-dbf1-e8c2-5d5874df5cf4.png)

RPA Challengeは最後にスコアが表示されるので、スクリーンショットを撮るような仕組みも用意してみました。
今回は不要でしたが、実験的にいくつかの操作も用意しています。
処理を一時的に止める **delay** や画面を明示的に閉じるための **close** も定義しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/d5bd9fa7-1803-516c-1d1b-805d1f1b9b17.png)




# 実験的に実装したコード

:::note 
補足：　実装したプロジェクトの名前は「Code実装の検証」であるため、1行目の参照`using Code実装の検証.ObjectRepository;`や`namespace Code実装の検証`という記載があります。
:::

```TestByExcel.cs
using Code実装の検証.ObjectRepository;
using System;
using System.Collections.Generic;
using System.Data;
using UiPath.CodedWorkflows;
using UiPath.Core;
using UiPath.Core.Activities.Storage;
using UiPath.Orchestrator.Client.Models;
using UiPath.Testing;
using UiPath.Testing.Activities.TestData;
using UiPath.Testing.Activities.TestDataQueues.Enums;
using UiPath.Testing.Enums;
using UiPath.UIAutomationNext.API.Contracts;
using UiPath.UIAutomationNext.API.Models;
using UiPath.UIAutomationNext.Enums;

namespace Code実装の検証
{
 
        
    /***
     * 操作をまとめて定義するクラス
     */
    public static class OperationUitl {
            /**
         * ～Opitionは各操作に応じたオプション指定です。エラー前に待つのが嫌なのでゼロ指定した後、少し調整しています。
         * 画面の描画時間に応じてカスタマイズが必要です。
         */
        public static ClickOptions click => new ClickOptions() {
            DelayBefore = 0,
            DelayAfter = 0,
            Timeout = 0
        };

        public static GetTextOptions getText => new GetTextOptions() {
            DelayBefore = 0,
            DelayAfter = 0,
            Timeout = 1
        };
               
        /**
         * シミュレート入力で画面を開くオプション指定
         */
        public static TargetAppOptions simulate => new TargetAppOptions() {
            InteractionMode = NInteractionMode.Simulate,
            OpenMode = NAppOpenMode.IfNotOpen,
            AttachMode = NAppAttachMode.SingleWindow
        };
        
        /**
         * WindowMessage入力で画面を開くオプション指定
         */
        public static TargetAppOptions winMsg => new TargetAppOptions() {
            InteractionMode = NInteractionMode.WindowMessages,
            OpenMode = NAppOpenMode.IfNotOpen,
            AttachMode = NAppAttachMode.SingleWindow
        };
        
    }
    
  
   public class TestByExcel : CodedWorkflow
    {
        [TestCase]
         public void Execute(){

             IDictionary<string, object> returnVal =RunWorkflow("readExcel.xaml", 
                 new Dictionary<string, object>(){
                                          { "sheetName", "rpachallenge"} });
             System.Data.DataTable dt = (System.Data.DataTable)returnVal["OutDt"];
             
             var result = "";         
             foreach (System.Data.DataRow row in dt.Rows ) {     

                 if("contains".Equals(row["操作"].ToString())) {                   
                     testing.VerifyExpression(result.Contains(row["値"].ToString()));
                 } else {
                    result = 操作(row["画面名"].ToString(),row["操作"].ToString(),row["要素名"].ToString(),row["値"].ToString(),Convert.ToBoolean(row["シミュレート"]) );

                 }
                 
             }
         }
            
        
        
         public string 操作(String screen, String operation, String element, String value, bool isSimulate)
        {

            UiTargetApp uiScreen ;
            if (isSimulate) {
                uiScreen = uiAutomation.Open(screen, OperationUitl.simulate);
            } else {
                uiScreen = uiAutomation.Open(screen, OperationUitl.winMsg);
            }
                
            switch(operation) {
            case "click":
                uiScreen.Click(element ,OperationUitl.click);
                break;
            case "input":
                uiScreen.TypeInto( element,  value );
                break;
            case "get":
                return uiScreen.GetText(element , OperationUitl.getText);
                break;
            case "close":
                uiScreen.Dispose();
                Log( "close all." , LogLevel.Trace);
                RunWorkflow("closeAll.xaml");
                break;
            case "screenshot":
                Log( "screenshot." , LogLevel.Trace);
                RunWorkflow("screenshot.xaml");
                break;
            case "delay":
                System.Threading.Thread.Sleep(int.Parse( value ));
                break;
            }
            
            return "";
        }
    }
}
```

# 実装の概要説明

### 操作オプション用のクラス

実装の上部にstaticクラスとして、利用頻度の高いオプションなどの指定オブジェクトを共通的に用意しています。

Excelの１行ごとのループで繰り返し実行する想定をしているため、マーカー部分の`OpenMode = NAppOpenMode.IfNotOpen`のように「既にブラウザーが開いていれば開かない」指定をしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/ada2dfb6-570f-7350-8cab-06587e4d9947.png)
また、`NInteractionMode.Simulate`と`NInteractionMode.WindowMessages`の２つの入力モードに対応したオプションを用意しています。

### WEB操作用の関数

Excelの１行ごとにこの「操作」関数が呼ばれています。
❶ 入力モードをExcelの指定に応じて振り分けています。（シミュレート操作ができないケースに対応）
❷ Excelで指定された操作の文字列に対応したCASE文で処理を振り分けています。
❸ コード実装で対応が難しい処理（調査等も含め生産性が低くなる処理）は従来のXAML実装で対応しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/f13aefc7-1882-294b-8bee-6f63c3551711.png)

:::note
❸のように、困ったところでUiPath従来の仕組みとシームレスに繋げることができることが UiPath Studio を利用するコード実装のメリットだと感じています。

これにより効率化をしつつも、実装不可能な対象システムはほとんど無いという状況を作れそうです。
:::

### テストとしての検証
テストとしての検証としてはContains関数を使って、取得した文字列が対象に含まれるか、をチェックするようにしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/008e8542-4e14-3cd7-f5b9-83d8e6f5a295.png)

:::note
この仕様は要件に応じたカスタマイズの余地がありますが、まずは実験として実装しています。

※期待結果として検証をパスすると下図のログが記録されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/1526815e-d21d-0be1-dc85-36b4666a563f.png)
:::

### テスト処理の実行

この処理は「コード化されたテストケース」として実装しています。
テスト処理の実行自体は下図のExecute関数が実行されます。
パラメータにExcelのシート名を与えることで、対象のWEB操作（≒Excelパラメータ）を指定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/ace848a3-ab43-a0cd-af05-090e9d54e25b.png)
Excelシートの内容をDataTableとして返すだけのXAMLを前処理として実装し呼び出しています。

:::note
実験的な実装なのでベタ書きにしていますが、外部からシート名を与えることで、より実用的になるはずです。
:::


# 課題

当仕組みは課題もあります。

大きな課題としては、Excelをパラメータとした繰り返し処理であり都度ブラウザーにアタッチしているため、非効率な処理となっている点があります。

この点については、Excelのパラメータの与え方を工夫することと、「操作」関数でのパラレル処理等を実装することで、多少の改善は見込めます。

#### 参考までに、RPA Challengeは1分近く掛かっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/ff408c5f-32d3-7101-cf2d-34a8c8c26d24.png)
早い実装だと10秒程度で終わる例もあるため、速度面において大きな課題があると言えます。


:::note
当課題は構想時点から想定していたものでした。

Excel1行ごとの処理を1行ごとではなく、複数行によるバッチ化することでも根本的な解決ができそうなので、追って試していきます。
（改善例はまたご紹介できれば…と思います）
:::


### :star: 追記：　課題解決版の記事も公開しました。
こちらもあわせてご確認ください。

https://qiita.com/moripo/items/634fbfe80ec9aef278b2

# おわりに

### 個人的な感想
実は、このアイデアは数年前に個人的に実装したSelenium＋Javaの仕組みから発想したものでした。
この仕組みはプライベート時間を１週間ほど削って実装し当時の現場に提案したのですが、機能の使い方の煩雑さや、その後の維持をできる要員がいなかったこと、…等々により浸透しませんでした。


Selenium＋Javaでゴリゴリ実装した仕組みよりも進歩している点としては、以下を考えています。

- UiPathのセレクター技術をベースにしているため安定していることが期待できる。
- 万が一、事前にコード化した処理で処理できない部分は、従来のローコード実装を呼び出す等の対策が打てる。
- コード実装は極小化しているため、維持の要員に要求されるスキルセットも多くない。


当仕組みの実装自体は、２～３時間です。UiPath Studio＋コード実装により、生産性が上がったことを実感しました。
当記事の内容、ご紹介した機能に興味を持っていただけると幸いです。