続：「T-DASH」の テスト実行結果（成功/失敗）をチャットで通知できるかやってみた！

本記事は、[『テスト自動化ツールT-DASHの活用方法や事例を記事にしよう！ by T-DASH Advent Calendar 2022』](https://qiita.com/advent-calendar/2022/t-dash02)に参加しています。
よろしくお願いいたします！！
本記事は、2022/12/19を担当しております。

https://qiita.com/advent-calendar/2022/t-dash02

2022/12/4担当記事はこちら

https://qiita.com/94dango/items/e1b111bbbe3073e4d4e7

2022/12/5担当記事はこちら

https://qiita.com/94dango/items/9429fa4eec20a994cc61

[『テスト自動化ツールT-DASHの良い点・悪い点をレビューしてみた！ by T-DASH Advent Calendar 2022](https://qiita.com/advent-calendar/2022/t-dash01)』にも参加しております。

https://qiita.com/advent-calendar/2022/t-dash01

T-DASH」の使い方全般の記事はこちら

https://qiita.com/94dango/items/aa302f7bafc0598e8e68

# はじめに

本記事は、「T-DASH」の **テスト実行結果（成功/失敗）をチャットで通知する方法** について試した記事です。
前回の（2022/12/13）担当記事で挑戦した **『T-DASHのテスト実行完了をチャットで通知してみた！』の続編記事** となっております。
よろしければ合わせてお読みください。

https://qiita.com/94dango/items/1cf620e763a48365e80e

前回の記事では **「T-DASH」のテスト実行完了をLINEへ自動通知する機能** をPythonで開発しました。
無事に自動化することに成功しましたが、その際に課題が残っておりました。
その課題が、 **テスト結果（成功/失敗）の情報も知りたい** というものです。
今回は、この課題解決をテーマに記事を書いていきます。

# 要求仕様

「T-DASH」のテストシナリオの実行が完了したら、 **テスト結果の成功/失敗をチャットで通知** したい。
テスト実行結果をチャットへ自動通知する機能は前回の記事で実装済みのため、今回は **テスト結果の成功/失敗を取得する** 機能を追加したい。

:::note info
「T-DASH」のテスト実行を自動実行する方法は2022/12/5担当記事
T-DASHのテストシナリオ実行を自動化出来るかやってみた！
https://qiita.com/94dango/items/9429fa4eec20a994cc61 で紹介しています。
:::

# 調査

前回の実装で、 **テスト実行完了 = テスト結果レポートが生成される** という「T-DASH」の内部仕様を利用して、テスト実行完了をバックグラウンドで定期的に監視してチャット（LINE）に通知する手段を用いました。
この **「テスト結果レポート」 には当然、テスト結果が記録されている** わけですから、このレポートを分析し、データを収集することでテスト結果の成功/失敗の情報を入手できると考えられます。
あとは入手した結果をチャットのメッセージにセットして送信すれば実現できると思います。

◆ テスト結果レポート
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302008/57a5f7a5-6423-5d8f-3a19-7fb826f22e24.png)

上記は「テスト結果レポート」のサンプルです。
このファイルはテスト実行完了後に、 `C:\Users\{ユーザー名}\AppData\Local\Programs\T-DASH\projects\{project_name}\reports\` フォルダーに `report-YYYYMMDD-hhmmss.html` ファイル名で生成されています。
HTMLファイルで生成されているため、テキストエディターやWebブラウザーでファイルの中身を確認することが出来ます。
このレポートの上部に **テスト実行結果のサマリー（黄色にハイライト部分）** が記録されています。
この情報を文字列として抽出することが出来れば良さそうです。


# 基本仕様

基本仕様として整理していきましょう。

1. テスト実行完了後に自動生成される「テスト結果レポート」を監視し、「テスト結果レポート」のフルパスを取得する（前回記事で実装済み）
1. 取得したフルパスから **HTMLファイルを解析し、テスト実行結果のサマリーからテキスト情報を抽出** する
1. 抽出したテキスト情報を整形し、チャットのメッセージ本文にセットする
1. チャットで自動送信する（前回記事で実装済み）

今回は、 **2.～3.の部分が開発範囲** となります。
3.についてはテキストをPython上で加工してあげれば済みますね。
次の設計では、 **2.に重点を置いて** 考えていきます。

# 設計

上述の通り、「テスト結果レポート」はHTMLファイルのフォートマットで保存されいます。
HTMLファイルを解析して **必要な箇所（テスト実行結果のサマリー）** から文字列情報を取得すること出来れば良いことになります。
Pythonには嬉しいことに、Webスクレイピングを強力にアシストしてくれるライブラリ「BeautifulSoup」があります。
計画当初は、「BeautifulSoup」でローカルのHTMLファイルを開き、要素を特定（CSSセレクター）して、文字列情報を頂いてしまう戦略で進めていました。
しかし結果的にこの戦略は失敗しました。
失敗までに施行した対応は次の通りです。

## 失敗ケース①
**ローカルファイルにある「テスト結果レポート」を「BeautifulSoup」で直接解析** しようしましたが、失敗しました。
 「BeautifulSoup」でローカルのHTMLファイルのパスを指定して開くいた場合は、静的なHTMLファイルとして開きます。
「テスト結果レポート」にはJavaScriptでテスト結果をレンダリングする内部処理があるらしく、静的なHTMLファイルで開くと、次の本文が取得されます。
そのため肝心なテスト結果のデータを参照することが出来ませんでした。

```
We're sorry but template_tdash-plan-report doesn't work properly without JavaScript enabled. Please enable it to continue.
```

## 失敗ケース②
「BeautifulSoup」で直接解析がダメなら、 **requestsライブラリのGETでローカルファイルの「テスト結果レポート」にアクセス** して、取得できたレスポンスを「BeautifulSoup」のインプットソースとして利用出来ないか試してみました。
しかしこちらの方法も失敗に終わりました。
まずrequestsはHTTPサーバーと通信するものですので、そもそもローカルの静的HTMLファイルのパスをアクセス先に引数に指定してもエラーになります。

## 失敗ケース③
ならば、 **ローカルでWebサーバーを起動し「テスト結果レポート」をホスト** してしまうかと考えました。
SimpleHTTPServerを簡易的にローカルサーバーを起動する方法はわかりましたが、カレントフォルダーでない場所にある「テスト結果レポート」をホスティングして起動する方法が私にはわからず、時間がかかりそうなので断念しました。

## 失敗ケース④
次に試したがの、「テスト結果レポート（ `*report*.html` ）」からの取得はいったん諦め、 **同じフォルダーに出力される `*output*.xml` からの取得** を試みました。
しかしこちらも途中で断念しました。
**テスト実行が完了するより先に `*output*.xml` ファイルが生成されてしまい、欲しい情報の取得が出来なかった** ことが理由です。
`*output*.xml` ファイルを読み込むタイミングを調整してあげれば解決出来そうな気もしましたが、既存の実装コードから見直しが必要になりそうでしたので、今回は却下しました。

## 成功ケース
丸一日いろいろ模索した結果、かなり不格好ですが無理やり目標は達成出来ましたので、ご紹介します。
手順は次の通りです。

1. テスト実行完了後に自動生成される「テスト結果レポート」を監視する（前回記事で実装済み）
1.  **「T-DASH」のダッシュボードをSeleniumで開き、ダッシュボードのテスト結果サマリーからテキスト情報を抽出** する
1. 抽出したテキスト情報を整形し、チャットのメッセージ本文にセットする
1. チャットで自動送信する（前回記事で実装済み）


:::note info
本記事では次の環境で動作を確認しています。
・Windows11(22H2)
・T-DASH Ver.2.0.003
・Python Ver.3.10.0
・watchdog Ver.2.1.9
・requests Ver.2.28.1
・PyYAML Ver.6.0
・LINE Ver.7.14.1.2907
・selenium Ver.4.7.2
:::

:::note warn
※「T-DASH」本体が提供している機能ではなく、無理やり自動実行しています。当然サポート対象外の利用方法ですのでご承知の上、ご覧ください。
:::

# 環境構築

前回作成したプロジェクト「tdash_end_watcher」をコピーし、「tdash_end_watcher2」プロジェクトを作成して進めます。
VSCodeで開発用フォルダーを開き、インタープリターを仮想環境に切り替えます。

```terminal:terminal
>PS D:\my_python\tdash\tdash_end_watcher2> &  d:/my_python/tdash_end_watcher2/.venv/Scripts/Activate.ps1
```

selenium を pip install します。

```terminal:terminal
(.venv) PS D:\my_python\tdash\tdash_end_watcher2> py -m pip install selenium
```

```terminal:terminal
(.venv) PS D:\my_python\tdash\tdash_end_watcher2> pip list
Package            Version
------------------ ---------
async-generator    1.10
attrs              22.1.0
certifi            2022.9.24
cffi               1.15.1
charset-normalizer 2.1.1
exceptiongroup     1.0.4
h11                0.14.0
idna               3.4
outcome            1.2.0
pip                21.2.3
pycparser          2.21
PySocks            1.7.1
PyYAML             6.0
requests           2.28.1
selenium           4.7.2
setuptools         57.4.0
sniffio            1.3.0
sortedcontainers   2.4.0
soupsieve          2.3.2.post1
trio               0.22.0
trio-websocket     0.9.2
urllib3            1.26.13
watchdog           2.1.9
wsproto            1.2.0
※施行錯誤していたため余計なモジュールも入っていると思います。ご容赦ください。
```

# 実装

コードは[github](https://github.com/lilucso01git/t-dash_end.watcher2)からダウンロード出来ます。

```python:end.watcher2.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
from watchdog.observers.polling import PollingObserver
from watchdog.events import RegexMatchingEventHandler
import time
import requests
import yaml


def report_scraping():  # メソッドを追加
    url = 'http://localhost:8686/#/dice1/dashboard'
    driver = webdriver.Chrome(r'C:\Apps\ChromeDriver\chromedriver.exe')

    # ダッシュボード画面を開く
    driver.get(url)
    sleep(7)
    
    # テスト結果サマリーをスクレイピング
    td = driver.find_element(By.CSS_SELECTOR, "table > tbody > tr:nth-child(1)")
    td = td.text
    
    # 終了する
    driver.quit()
    
    return td

    
# LINE にメッセージを送信する
def line_notify(msg, msg2, TOKEN): # 第二引数「msg2」を追加
    try:
        access_token = TOKEN
        msg = msg + "\nテストが完了しました。" # msg, msg2 を結合
        msg = msg + "\n" + msg2
        
        headers = {'Authorization': 'Bearer ' + access_token}
        payload = {'message': msg}
        
        print("LINE メッセージを POST します。")
        r = requests.post(LINE_NOTIFY_URL, headers=headers, params=payload,)
        if r.status_code == 200:
            print("LINE メッセージを送信しました。")
        else:
            print("ステータスコード: " + r.status_code)
            raise Exception
    except Exception as e:
        print(e)

class MyFileWatchHandler(RegexMatchingEventHandler):
    def __init__(self, regexes):
        super().__init__(regexes=regexes)

    # ファイル作成時の動作
    def on_created(self, event):
        filepath = event.src_path
        print(filepath)
        tests_summary = report_scraping() # report_scraping() を呼び出す
        line_notify(filepath, tests_summary, TOKEN) #第2引数「tests_summary」を追加


if __name__ == "__main__":

    # config を読み込む
    try:
        with open("./config.yaml", "r", encoding="utf-8") as f:
            config = yaml.safe_load(f)

        # LINE_NOTIFY_URL
        LINE_NOTIFY_URL = config["chat_url"]
        # LINE access_token
        TOKEN = config["access_token"]
        # 監視対象パス
        TARGET_PATH = config["target_path"]
    except Exception as e:
        print(e)

    # 対象ファイルパスのパターン
    PATTERNS = [r'.+report-.+.html$']

    # 監視を開始する
    event_handler = MyFileWatchHandler(PATTERNS)
    observer = PollingObserver()
    observer.schedule(event_handler, TARGET_PATH, recursive=True)
    observer.start()
    try:
        while True:
            time.sleep(5)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()
```

前回のコードと違い箇所のみ解説します。
`def report_scraping():` メソッドを追加しました。
テスト実行完了後に、このメソッドを呼び出して **「T-DASH」のダッシュボードから最新のテスト結果サマリーをスクレイピング** しています。
スクレイピングした文字列を呼び出し元に返しています。

スクレイピングした文字列を受けったら、 `line_notify` の引数に渡して、LINEにメッセージを自動送信します。


# いざ、実行！！

**◆ 実行結果**

1. 「T-DASH」を起動しておきます。
1. 「end.watcher2.py」を実行します。
1. テスト実行が完了すると、ダッシュボードからテスト結果サマリーをスクレイピングする。
1. LINEにメッセージを送信する。

![chrome_y8TcV93zOE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302008/70def6fc-03ae-3388-d513-c93113c39deb.png)


# まとめ

いかがでしたでしょうか。
今回は **「T-DASH」のテスト実行結果（成功/失敗サマリー）をチャットへ自動送信出来るか** やってみました！
テストの成功/失敗については、「テスト成功率」で取り敢えず判断できるので、ここで作業を完了します。
（2～3時間でさくっと実装できると思ったのですが、丸一日かかってしまい、へとへとなのでご勘弁ください）

予想よりかなり手こずりましたがなんとか目的は達成できました。
途中挫折しそうになりましたが、最後に泥臭い方法で頑張ってみたらどうにか成功できました（ほんとに良かった）。
標準機能で搭載していただければこういった労力が軽減されますので、今後のバージョンアップに期待していきたいと思います。
よろしくお願いいたします！！
最後までお読みくださいまして、ありがとうございました！