【UiPath Studio】ExcelでWEB操作をカスタマイズ： 実装したテストを高速化します。

# はじめに

UiPath Studioのコード実装機能を活用し、複数のWEBサイトのテストを共通のコードで実行しよう、という試みです。

当記事は以下の記事の実装をベースに、
並列処理の扱いを組み込むことで高速化するアイデアを試します。

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


:::note
当記事ではUiPath Studio v23.10で実装したコードを、v24.10で改修しています。

参考までに検証したプロジェクトの依存関係は下図です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/3d9a132a-2154-c527-f698-7796637daf73.png)
※前回同様、前提としてオブジェクトリポジトリ機能を活用した仕組みですので、依存関係に対象とするWEBサイトのオブジェクトリポジトリを取り込んでいます。
:::


# 結果から…。並列処理で処理時間 1/3超 の高速化に成功しました。

### 改善前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/a61fe7ca-9189-97a0-ca60-8a51044f0a53.png)

### 改善後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/6905d8ca-a646-fa59-9cb9-52fbce5524c1.png)

**約51秒 が 約15秒 で完了**しています。

仕組み上は最高速度を目指すことは不可能であるものの、
実用レベルには十分な速度は実現できたと思います。

:::note
余談ですが、前回のコードをStudio v23.10 ⇒ v24.10 で実行するだけでも 10秒弱 高速化していました。

v23.10では約60秒掛かっていた処理が、改善前の図のとおり51秒程度の結果でした。
:::


# 実装したコード

### Excelレイアウトも少し見直しました。

並列処理の指示を明確にするために専用の列を設けました。
並列指示が途切れるまでを１つの処理ブロックとしてまとめて処理する設計思想です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/f73024b5-b3fd-7476-5e70-e555e354804a.png)

:::note
設計思想として、Excelパラメータで判断するか、共通コードのロジック側で判断するか、といった判断は様々考えられます。
今回は少し考えたのちExcel側で指示する仕様としました。
:::

### コード

```TestByExcel.cs
using Code実装の検証.ObjectRepository;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
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"];
             
             List<String[]> 並列操作List =new List<String[]>();

             int i=0;
             var result = "";
             bool is並列シミュレート = false;
             foreach (System.Data.DataRow row in dt.Rows ) {
                 
                 bool 並列指示 = false;
                 try {
                    並列指示 = Convert.ToBoolean(row["並列"].ToString());
                 }catch (Exception e){
                     ;
                 }

                 // 並列指示の場合、実際の操作は次のループに任せる
                 if (並列指示) {
                    // 操作に必要な情報をString配列でListに保持しておく
                    並列操作List.Add( new String[]{row["操作"].ToString(),row["要素名"].ToString(),row["値"].ToString()} );
                    is並列シミュレート = Convert.ToBoolean(row["シミュレート"]);
                     
                 } else {
                    
                    // 並列操作Listが溜まっていれば…
                    if ( 並列操作List.Count>0 ) {
                        // バッチ操作をしてListも初期化する
                        バッチ操作(row["画面名"].ToString(), 並列操作List, is並列シミュレート);
                        並列操作List = new List<String[]>();
                    }
                    
                    // 通常（＝並列以外）の操作実装
                    if ("contains".Equals(row["操作"].ToString())) {                   
                        testing.VerifyExpression(result.Contains(row["値"].ToString()));
                    } else {
                        result = 操作(row["画面名"].ToString(),row["操作"].ToString(),row["要素名"].ToString(),row["値"].ToString(),Convert.ToBoolean(row["シミュレート"]) );                   
                    }

                 }
                 i++;
             }
         }
            
         public void バッチ操作(String screen, List<String[]> inputList , bool isSimulate)
        {
            UiTargetApp uiScreen ;
            if (isSimulate) {
                uiScreen = uiAutomation.Open(screen, OperationUitl.simulate);
            } else {
                uiScreen = uiAutomation.Open(screen, OperationUitl.winMsg);
            }           

            // 同時実行タスクの最大数を Listサイズ に設定
            ParallelOptions parallelOptions = new ParallelOptions();
            parallelOptions.MaxDegreeOfParallelism = inputList.Count;

            // 入力操作を並行で…
            Parallel.ForEach(inputList, parallelOptions, x =>
            {
                switch(x[0]) {
                case "click":
                    uiScreen.Click(x[1] ,OperationUitl.click);
                    break;
                case "input":
                    uiScreen.TypeInto( x[1],  x[2] );
                    break;
                }
            });
                         
        }
        
         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 "";
        }
    }
}
```

# 前回からの変更の概要説明

"バッチ操作" 関数を用意した点が大きな変更点です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/ab3c2aa7-3e14-e5ea-68ba-a4f705ec09c2.png)

:::note
関数名の「バッチ操作」は並列処理を実装するか決めていない時の名残りで、
バッチ的に画面アタッチ回数を減らすだけでは効果がなく、並列処理を実装した経緯によるものです。
:::

:::note
触れていませんが今回の改善によりもう一方のWEBテストであるUiBank向けのテストも退行していないことも確認しました。
:::

:::note warn
逆に言えば、UiBank, RPA Challenge以外のWEBサイトでの動作は確認していません。
対象とするシステムの特徴の幅広さに応じて、細かい修正や調整が必要となると考えられます。
:::


# おわりに

個人的に想定した課題はクリアしました。当件の実装はここまでとします。
簡単なWEBの操作であればExcelのファイルを書き換えるだけで操作が可能であろう、と考えられます。

UiPath Studioの実装者がアサインできず、テスト実装が課題になるようなユースケースはあるのでは…と想像します。そんな際に一部の機能を共通コード等でフレームワーク的に提供することで、効率化できるかもしれません。

課題解決の参考になれば幸いです。