WinActorで困った話　その１（IE編）

#今回の内容
WinActorで開発した時に遭遇した、困った話と解決方法を書いていきたいと思います。
今回はIE編です。
前回見せれなかったWinActorの実際の画面も貼っていきます。
#何が起きたのか？
##処理の内容
IEを使ったシステムに対しWinActorで、下図のシナリオを作っていました。
![シナリオ.jpg](https://qiita-image-store.s3.amazonaws.com/0/311963/959b6337-96c4-7307-5454-c419d2606e21.jpeg)
`※判定材料の取得は全部で10個ほどあるのですが、省略しています。`
この流れでひとまずは完成したのですが…
１つの問題を除いては正常に動きました。
だけど、その１つが非常に大きな問題になったのです…
##問題とは？
**遅い、とにかく遅い！**
今回のシナリオは1日1000件を超える件数に対して処理を行うのですが、作ったシナリオを実行すると**処理完了までに約4～5時間**かかったのです。
これでは日が変わると同時に動かし始めても、終わるころには日が昇り始めます…
何としても解消する必要がありました。
#原因は？
##原因その１：画面遷移
上の図で発生している画面遷移が一つ目の問題です。
IEは主流ブラウザと違い動作が遅いため、この画面移動に非常に時間がかかるのです。
移動１回が１秒だとして、１件に対し移動が２回。
`1000件×2秒＝2000秒`
移動だけで**約33分**かかる計算、処理の10分の1が画面遷移。
結構時間かかってる。
##原因その２：判定処理
詳細画面で、この行は処理を行うべきかどうかの判定をしているんですが、画面情報を色々と参照する必要があって1件辺り約8秒かかってました。
`1000件×8秒＝16000秒`
処理判定だけで**約260分**かかる計算に、そりゃ遅いわ…
##解決策
処理速度の向上のためには原因１と２を解決する必要がありますが、今回作ったシナリオでは流れ的に解決不可能なので大幅に修正を加える必要があります。
なので対応として**一覧画面上で判定を行い、対象外の場合は画面遷移を行わない**という形で対応することに。
今回は一覧画面上で処理の可否判定が行える材料があったので助かりました。
#解決方法
##色による判定
処理可否判定の仕様に、一覧画面のボタンがあるセルの背景に色がついている行は無視していいというのがありました。
![一覧の特徴.jpg](https://qiita-image-store.s3.amazonaws.com/0/311963/5b81829e-190f-98fb-0dbf-e0b19cf64ea9.jpeg)
しかし何故最初からそうしなかったのか？
それはWinActorの標準ライブラリに、**HTMLソース内のクラス名やスタイルを取得するものが存在しなかった**からです。
（以前の記事で書いた通りWinActorはブラウザの画面をHTMLソースで読み込むため、色の取得は出来ません。）
そのため仕方なく詳細画面内の情報で判断を行っていたのです。
##クラス取得ライブラリの作成
というわけで上で書いた問題点を解消するため、VBScriptを使い独自ライブラリを作成することにします。

```VBScript:テーブル指定セルのクラス名有無を判定するライブラリ
ieTitle  = GetUMSWindowTitle(@ウィンドウ識別名@)
ieHWnd  = GetUMSWindowHandle(@ウィンドウ識別名@)

' 引数を解釈する
frameNum   = !frame index! + 0
tableNum   = !tag index! + 0
cname = !対象クラス名!
cellRow    = !行番号! - 1
cellColumn = !列番号! - 1

If IsNull(ieTitle) Then
  SetUMSVariable $処理結果$, "指定されたウィンドウ識別ルールに該当するウィンドウが存在しません。"
  WScript.Quit
End If

' タイトルの接尾辞を取り除く
ieTitle = Left(ieTitle, InStrRev(ieTitle, " - ") - 1)

' 指定されたページのdocumentを取得する
Set document = Nothing

Set shell = CreateObject("Shell.Application")
For i = 0 To shell.Windows.Count - 1
  Set window = shell.Windows(i)
   
  title = Empty
  On Error Resume Next
    title = window.document.title
  On Error Goto 0

  If title <> Empty Then
    If InStr(title, ieTitle) <> 0 Then
      Set document = window.document
      Exit For
    End If
  End If
Next

If document Is Nothing Then
  SetUMSVariable $処理結果$, "指定されたタイトルのページが開かれていません。"
  WScript.Quit
End If

' document内の指定された番号のframeオブジェクトを取得する
On Error Resume Next
  Set document = FindFrame(document)
On Error Goto 0

If document Is Nothing Then
  SetUMSVariable $処理結果$, "指定された番号のフレームが存在しません。"
  WScript.Quit
End If

' document内の指定された番号のtableオブジェクトを取得する
Set table = Nothing
On Error Resume Next
  Set table = (document.document.getElementsByTagName("table"))(tableNum)
On Error Goto 0

If table Is Nothing Then
  SetUMSVariable $処理結果$, "指定された番号の表が存在しません。"
  WScript.Quit
End If

' table内の指定された行番号・列番号のcellオブジェクトを取得する
Set cell = Nothing
On Error Resume Next
  Set cell = (table.rows(cellRow)).cells(cellColumn)
On Error Goto 0

If cell Is Nothing Then
  SetUMSVariable $処理結果$, "指定されたセルが存在しません。"
  WScript.Quit
End If

' 正常に処理が完了したため"OK"を返す
result = ""
If InStr(cell.outerHTML, cname) > 0 Then
  result = "true"
Else
  result = "false"
End If
SetUMSVariable $処理結果$, result


'---------------------------------------------------------------
' フレーム0を探索する
Function FindFrame(rootDoc)
  If frameNum = 0 Then
    Set FindFrame = rootDoc.frames
    Exit Function
  End If
  frameNum = frameNum - 1

  Dim i
  For i = 0 To (rootDoc.frames.Length - 1)
    Set frms = rootDoc.frames
    Set frm = frms(i)
    Set FindFrame = FindFrame2(frm)
    If Not FindFrame Is Nothing Then
      Exit Function
    End If
  Next

  Set FindFrame = Nothing
End Function


'---------------------------------------------------------------
' 子フレームを探索する
Function FindFrame2(rootFrame)
  If frameNum = 0 Then
    Set FindFrame2 = rootFrame
    Exit Function
  End If
  frameNum = frameNum  - 1

  Set frms2 = rootFrame.document.frames

  Dim j
  For j = 0 To (rootFrame.document.frames.Length - 1)
    Set frm2 = frms2(j)
    'Set FindFrame2 = FindFrame2(rootFrame.document.frames(j)) 'NG
    Set FindFrame2 = FindFrame2(frm2)
    If Not FindFrame2 Is Nothing Then
      Exit Function
    End If
  Next

  Set FindFrame2 = Nothing
End Function
'--
```

このコード自体は、WinActorに元々ある「テーブル指定セルのテキストを取得」するライブラリを改造したものです。
ライブラリの画面はこんな感じ。
![スクリプト画面.jpg](https://qiita-image-store.s3.amazonaws.com/0/311963/2743bd9f-cacd-bcc7-6798-55cb4392194f.jpeg)
大事な部分は引数の追加と、

```
' 正常に処理が完了したため"OK"を返す
result = ""
If InStr(cell.outerHTML, cname) > 0 Then
  result = "true"
Else
  result = "false"
End If
SetUMSVariable $処理結果$, result
```
この部分になります。

上記の処理で指定セルにクラス名が存在するかを判定し、存在すればTrue、しなければFalseを返却するようにしています。
もしもテーブル以外の判定でも使いたければ、似たことを行っているライブラリに上記コードを追加すればいいでしょう。

スクリプトが完成したので、早速シナリオに組み込むことにします！
#修正と結果
##シナリオ修正
ライブラリを作成したことで、シナリオは下のようになりました。
![修正後のシナリオ.jpg](https://qiita-image-store.s3.amazonaws.com/0/311963/a67a0f15-3791-bb9e-7f11-219b512f45a9.jpeg)
分岐の直前に作成したライブラリを追加し、そこの結果に対して分岐処理をかけています。
そのためライブラリ処理でFalseが返ってきた場合は、詳細画面へ行かず次の行の判定を行うようになりました。
これで詳細画面での処理が大幅に削減されるようになりました。
##結果
さて修正して実施してみた結果ですが…
処理時間：約300分⇨**約160分**
大体半分の処理時間に！やったぜ！！
#まとめ
今回はWinActorで作ったシナリオが遅い場合の対応方法の一例を記事にしてみました。
高速化を行う場合にまず考えるべきことは下の2点。

1. 画面遷移の削減
2. シナリオの削減

ブラウザ上で処理を行う場合、どうしても画面の読込を待つ必要があります。
またシナリオも画面上の値を取得したり、値に対する加工編集を行うと意外と時間がかかるので、以下にシナリオの実行回数を減らすのかも重要になってきます。
今回の修正コンセプトは、**画面遷移を減らすことで、シナリオの実行回数を減らす**だったので、このような形になりました。
このコンセプトはRPA開発では、どのような状況でも使えると思うので、常に意識するといいんじゃないでしょうか。

次回は、WinActorで困った話Chrome編を書いていきたいと思います。