はじめまして。 おすしです。
先日開催されたJaSST’22 Tokyoにて、TestRailの紹介セッションを拝見しました。
便利そうだなぁ。きれいなレポートいいなぁ。オリジナリティ溢れるエクセル項目書はもう触りたくないなぁ。
と、つぶやいていた私にTestRailの調査依頼が来ました。
自分の意見を言っておくのは大事ですね。
上司「うちの部で使っている何かと、TestRailを連携して何かいい感じのことやって」
なんという自由度の高い依頼でしょう。すばらしい。
今回はTestRailとSelenium/Pythonの自動テストを連携したお話です。
連携方法やテストコードは下の方で公開しています。
(後で見たら「このコードを書いたのは誰だぁ!!(自分)」となるのですが、あえて公開します。)
■#2 mabl編はこちら
連携図
自動化した内容
今回の連携で自動化したのは以下の3点です。
-
テストが実行されるたびにTestRailにテスト結果を記入する。
-
テストが失敗した場合はGithubにissueを作成する。
※「29 days ago」になっていますが、テスト実施時に自動起票されています。 -
失敗したテストにテスト失敗時のスクリーンショットを添付し、「欠陥」にissueへのリンクを設定する。
連携手順①GithubとTestRailの設定
Githubの設定
※ユーザの登録やリポジトリの作成方法までは、ほかのサイトなどを参考にしてください。
今回はBTSとしてGithubを利用しましたが、TestRailには公式の連携先がたくさんあります。連携方法も記載されているので、ぜひこちらで探してみてください。
https://docs.testrail.techmatrix.jp/testrail/docs/integrate/
Githubのアクセストークンを発行する
- Githubのヘッダー右端のユーザーアイコンから「Settings」を押します
- 左メニューの一番下にある「Developer settings」を押します
- 左メニューの「Personal access tokens」を押します
- 「Generate new token」を押します
- 「Note」には何のためのトークンかわかるように名前を付けます
例:TestRail接続用 - 「Expiration」はトークンの有効期限です。使用目的に合わせた適切な日数を入れます
- 「Select scopes」は権限の範囲です。今回はTestRailでissueを見るだけなので、どこにもチェックは入れません
- 「Generate token」を押します
- トークンをコピーします。このキーは二度と表示されないので、確実にコピーして保存しておきます
TestRailの設定
※プロジェクトを作成するところまでは、ほかのサイトなどを参考にしてください。
■TestRailにユーザー変数を追加する(Github連携)
- ダッシュボードの右端にある「管理」を押します
- 「管理」カテゴリにある「プロジェクト」を押します
- 連携するプロジェクトを押します
- タブの「ユーザー変数」を押します
- 「ユーザー変数の追加」を押します
-
まずはGithubのIDを入れる変数を作成します。
① 「ラベル」:GithubのログインIDなどわかるような名前にします
②「システム名」:github_userなどわかるような名前にします
③「タイプ」:「文字列」にします
④「フォールバック」:空欄にします
⑤ 「OK」を押します -
次にGithubのパスワードを入れる変数を作成します。手順は上と同じですが、「タイプ」をパスワードにします。内容が●でマスクされるようになります。
TestRailでユーザー変数を設定する(Github連携)
- 右上にあるユーザー名から「個人設定」を押します
- 「設定」タブを開きます
- 前の手順で作成した変数に、GithubにログインするためのIDとパスワードを入力します
- 「✔設定の保存」を押します
TestRailのプロジェクトに参照を設定する(Github連携)
- TestRailで、連携するプロジェクトを開きます
- プロジェクト画面の右上にある「✐編集」を押します
- 「欠陥」タブを開きます
- 「欠陥表示 URL」に、リポジトリのissueページのURLを入れます
https://github.com/ユーザ名/リポジトリ名/issues/ケースIDのプレースホルダ
具体例: https://github.com/hoge/TestSelenium/issues/%id%※%id%のところがケースIDのプレースホルダです。ここに数字が入ってリンクになります
5. 「欠陥追加 URL」に新規起票用のURLを入れます
https://github.com/ユーザ名/リポジトリ名/issues/new
具体例: https://github.com/hoge/TestSelenium/issues/new
6. 「欠陥プラグイン」からGithubを選択します
7. 自動入力されたプラグインで、以下の赤文字の部分を入力します
[connection]
address=https://api.github.com/
user=**%github_user% //設定したユーザIDの変数名**
password=**%github_password% //設定したパスワードの変数名**
[repository]
owner=**hoge //リポジトリのオーナー名**
name=**TestRailSelenium //リポジトリ名**
[push.fields]
summary=on
milestone=on
assignee=on
label=on
description=on
[hover.fields]
summary=on
milestone=on
assignee=on
label=on
description=on
8. 「✔プロジェクトの保存」を押します
■TestRailでAPIを許可する
- ダッシュボードの右端にある「管理」を押します
- 「管理」カテゴリにある「サイト設定」を押します
- 「API」タブを開きます
- 「API の有効化」にチェックを入れます
連携手順②コードの実装
「ソフトウェア・テストの技法」よりこちらのページを使用させていただきました。
–マイヤーズの三角形問題:http://milk0824.sakura.ne.jp/services/myers/
–動作確認環境
・Python 3.10.2
・pytest 7.1.1
・requests 2.27.1
・selenium 4.1.3
・webdriver-manager 3.5.4
プロジェクトフォルダの構成
こちらがプロジェクトフォルダの構成です。上から順番に説明していきます。
Config/config.py
TestRailとGithubの接続に必要なトークンやログイン情報を記載します。
## Githubの設定
token_github = "Githubのトークンを入れます"
base_url_github = "GithubのレポジトリのURLを入れます"
## TestRailの設定
user_testrail = "TestRailのユーザーIDを入れます"
password_testrail = "TestRailのパスワードを入れます"
base_url_testrail = "TestRailのURLを入れます(index.php?の前の部分まで)"
Data/test_data.csv
テスト用のデータです。以下のようなCSVファイルを使用しています。
※Githubとの連携を確認するため、いくつか失敗するデータを入れておきます。
No | A | B | C | enterd_A | enterd_B | enterd_C | result | objectives | testID |
---|---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 1 | 1 | 正三角形 | 正三角形のテスト | |
2 | 10 | 10 | 5 | 10 | 10 | 5 | 二等辺三角形 | 二等辺三角形のテスト(ABが等しい) | |
3 | 10 | 5 | 10 | 10 | 5 | 10 | 失敗するテスト | 二等辺三角形のテスト | |
(中略) | |||||||||
42 | 5 | 5 | Null | 5 | 5 | 入力は1~99999の整数のみです | CがNullのテスト |
・各カラムの内容は
・No:テスト番号
・A,B,C:各辺に入れる値
・enterd_A,enterd_B,enterd_C:テキストボックスに値を入力した時の期待結果
・result:「表示」ボタンを押した後の期待結果
・objectives:テストの目的
・testID:TestRailのテストID。インポート後に記入します
このCSVファイルをTestRailにインポートしてテストを作成します。
読み込ませた後は、TestRailのここの数字をCSVファイルの「testID」に入れておきます。
このIDを基準に自動処理を行います。
Module/github.py
GithubのAPIを利用するためのファイルです。
今回は以下の二つのAPIを使用します
・issueを作成するAPI
・作成したissueのIDを取得するAPI
import json
import requests
import Config.config as config
class GithubAPIClient:
def __init__(self):
self.GITHUB_TOKEN = config.token_github
self.base_url = config.base_url_github
def create_issue(self, title):
"""Githubにissueを作成する"""
uri = f"/issues"
url = self.base_url + uri
headers = {}
headers['Authorization'] = 'Bearer ' + self.GITHUB_TOKEN
headers['Content-Type'] = 'application/json'
headers['Accept'] = 'application/vnd.github.v3+json'
data = {}
data["title"] = title
# issueの本文
data["body"] = "これは自動作成されたissueです。"
payload = bytes(json.dumps(data), 'utf-8')
response = requests.post(url, headers=headers, data=payload)
return response
def get_issues(self):
"""issueのデータを取得する"""
uri = f"/issues"
url = self.base_url + uri
headers = {}
headers['Authorization'] = 'Bearer ' + self.GITHUB_TOKEN
headers['Accept'] = 'application/vnd.github.v3+json'
response = requests.get(url, headers=headers)
return response.json()
Module/testrail.py
TestRailのAPIを利用するためのファイルです。
今回は以下のAPIを使用します。
・テスト結果を入力するAPI
・テスト結果に画像データを添付するAPI
TestRailはAPI組み込み用のパーツを公開しています。
https://docs.testrail.techmatrix.jp/testrail/docs/api/getting-started/
こちらを利用すると連携が容易です。
import base64
import json
import requests
import Config.config as config
class APIClient:
def __init__(self):
self.user = config.user_testrail
self.password = config.password_testrail
self.base_url = config.base_url_testrail
if not self.base_url.endswith('/'):
self.base_url += '/'
self.__url = self.base_url + 'index.php?/api/v2/'
def send_get(self, uri, filepath=None):
# 中略。TestRailのパーツを利用
def post_test_result_pass(self, testID):
"""TestRailにテスト結果PassedをPOSTする"""
uri = f"add_result/{testID}"
# TestRailでPassの結果は「1」
data = {"status_id": 1,
"comment": "テスト成功。Pythonでadd_resultAPIにより入力",
"version": "バージョン0.1",
"elapsed": "",
"defects": "",
"assignedto_id": ""
}
self.send_post(uri, data)
def attach_data_to_test_result(self, testID, path):
"""TestRailのテスト結果にスクショをアップロードする"""
# オプション「&limit=1」をつけて最新のテスト結果を取得します
uri = f"get_results/{testID}&limit=1"
result = self.send_get(uri)
# テスト結果IDを取り出します
test_id = result["results"][0]["id"]
# 取得した結果IDに画像ファイルをアップロードします
uri = f"add_attachment_to_result/{test_id}"
self.send_post(uri, path)
def post_test_result_fail(self, testID, errorlog, defectID):
"""TestRailにテスト結果FailedをPOSTする"""
uri = f"add_result/{testID}"
# TestRailでFailの結果は「5」
data = {"status_id": 5,
"version": "バージョン0.1",
"elapsed": "",
"assignedto_id": ""
}
data["comment"] = errorlog
data["defects"] = defectID
self.send_post(uri, data)
class APIError(Exception):
pass
Pages/BasePage.py
操作の基本となるパーツを記載したファイルです。
今回はAPIがメインなので、詳しい説明は省略します。
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
class BasePage:
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(self.driver, 10)
def click_element(self, locator):
self.wait.until(EC.visibility_of_element_located(locator)).click()
def send_keys(self, locator, text):
self.wait.until(EC.visibility_of_element_located(
locator)).send_keys(text)
def get_element_text(self, locator):
element = self.wait.until(EC.visibility_of_element_located(locator))
return element.text
def get_element_value(self, locator):
element = self.wait.until(EC.visibility_of_element_located(locator))
return element.get_attribute("value")
def take_screenshot(self, name):
self.driver.save_screenshot(name)
Pages/Sankaku.py
テスト対象ページの操作パーツを記載したファイルです。
こちらも詳しい説明は省略します。
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from Pages.BasePage import BasePage
class MainPage(BasePage):
# ページ情報定義
URL = "http://milk0824.sakura.ne.jp/services/myers/"
# ロケータ定義
CONTENT_TOP = (By.XPATH, '//*[@id="MAIN_WND"]/h1')
HEN_A = (By.XPATH, '//*[@id="SIDE_A"]')
HEN_B = (By.XPATH, '//*[@id="SIDE_B"]')
HEN_C = (By.XPATH, '//*[@id="SIDE_C"]')
HYOUJI_BUTTON = (By.XPATH, '//*[@id="MAIN_WND"]/div[2]/center/button')
KEKKA_TEXT = (By.XPATH, '//*[@id="DISPLAY"]')
def __init__(self, driver):
super().__init__(driver)
def _wait_until_page_displayed(self):
self.wait.until(EC.visibility_of_element_located(self.HEN_A))
def input_text(self, place, text):
if place == "A":
locator = self.HEN_A
elif place == "B":
locator = self.HEN_B
elif place == "C":
locator = self.HEN_C
self.send_keys(locator, text)
def click_hyouji_button(self):
self.click_element(self.HYOUJI_BUTTON)
Screenshots
スクリーンショットを格納するフォルダです。
Tests/conftest.py
ドライバの生成や、テストパラメータの読み込みを行うファイルです。
こちらも詳しい説明は省略します。そのうち別の記事にします。
import csv
import pytest
from selenium import webdriver
from selenium.webdriver.chrome import service
from webdriver_manager.chrome import ChromeDriverManager
@pytest.fixture(scope='class')
def init_driver(request):
chrome_service = service.Service(executable_path=ChromeDriverManager().install())
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--incognito")
web_driver = webdriver.Chrome(service=chrome_service, options=chrome_options)
web_driver.set_window_size("1200", "900")
request.cls.driver = web_driver
yield
# 事後処理
web_driver.quit()
def load_csv():
with open(r"../Data/test_data.csv", encoding='utf-8') as test_data_file:
data = csv.DictReader(test_data_file)
read_data = [row for row in data]
test_data = []
for data in read_data:
num = data["No"]
value_A = data["A"]
value_B = data["B"]
value_C = data["C"]
result = data["result"]
objectives = data["objectives"]
testID = data["testID"]
t = (num, value_A, value_B, value_C, result, objectives, testID)
test_data.append(t)
return test_data
Tests/test_Sankaku.py
テスト内容を記載したファイルです。
import pytest
import sys
from Pages.mainpage import MainPage
from Module.testrail import APIClient
from Module.github import GithubAPIClient
from Tests.conftest import load_csv
@pytest.mark.usefixtures("init_driver", "make_screenshot_directry", "send_mail")
class Test_Sankaku():
"""マイヤーズの三角形問題テストクラス"""
test_data = load_csv()
screenshot_directry = "../Screenshots"
@pytest.fixture(autouse=True)
def classObjects(self):
self.MainPage = MainPage(self.driver)
self.APIClient = APIClient()
self.GithubAPIClient = GithubAPIClient()
@pytest.mark.parametrize("num, value_A, value_B, value_C, result, objectives, testID", test_data)
def test_method_csv(self, num, value_A, value_B, value_C, result, objectives, testID):
"""テストケース実行"""
print(f"マイヤーズの三角形問題 - テスト{num}開始 目的 : {objectives}")
self.MainPage.driver.get(self.MainPage.URL)
self.MainPage.input_text("A", value_A)
self.MainPage.input_text("B", value_B)
self.MainPage.input_text("C", value_C)
self.MainPage.click_hyouji_button()
screenshot_path = str(self.screenshot_directry + "\\" + sys._getframe().f_code.co_name + str(num) + '.png')
self.MainPage.take_screenshot(screenshot_path)
# TestRail、Githubと連携する処理
if self.MainPage.get_element_text(self.MainPage.KEKKA_TEXT) == result:
self.APIClient.post_test_result_Pass(testID)
assert True
else:
errorlog = "三角形の判定と期待結果が一致しません"
# Githubにissueを作成する処理
self.GithubAPIClient.create_issue(errorlog)
# 作成したissueの番号を取得する
issue_data = self.GithubAPIClient.get_issues()
# 作成したすぐ後に取得するので、[0]が目的のデータ
issue_num_int = issue_data[0]["number"]
issue_num = str(issue_num_int)
# TestRailにテスト結果と、Githubで作成したissueの番号を記載する
self.APIClient.post_test_result_Fail(testID, errorlog, issue_num)
# 失敗したテストのスクショをTestRailにアップする
self.APIClient.attach_data_to_test_result(testID, screenshot_path)
assert False
print(f"マイヤーズの三角形問題 - テスト{num}終了")
実行手順
test_Sankaku.pyの階層に移動したあと、’pytest’コマンドで実行します。
TestRailやGithubのページを更新すると、自動的に結果の入力やissueの作成がされていることを確認できます。
まとめ
今回は基本部分のみの紹介でしたが、他のAPIと組み合わせるとより便利になります。
テストが終わったらレポートを添付したメールを送信したり、テストログや画像をissueに載せたりすることも可能です。
面倒な操作は自動化して、どんどんテストを楽にしましょう。