開発したフォームのテストをしたいとして、Seleniumなどで自動化するにしても準備は結構大変!
例えば、
- フォームの入力項目はいくつあるのか
- フォームの入力項目のタイプはどんなものがあるのか
- 自動テストを実施する際はどのような手段で入力項目を参照すればいいのか
など、フォームごとにたいてい異なるので、いちいち準備していくのは大変と感じます。
そこでテストだけでなく、その準備も自動化したいと思い、ひとまずPythonで組んでみました。
目次
更新履歴
日時 | 更新内容 |
---|---|
2021-06-06 | 新規作成 |
2021-07-04 | 1.設定ファイルの名前を変更 url.csv → settings.ini 2.テストデータ用ファイルの形式を変更 data.csv → data.tsv ※やはりtsvこそ至高。。。 |
2021-07-18 | テスト用URLを変更 |
2021-08-21 | 画像判定のヒストグラム閾値をsetting.iniに書き出すよう変更 |
大まかなイメージ
テストに至るまでの全体イメージは下記の通り
- フォームを解析して入力項目の数や種類を明らかにする(※本記事)
- 解析しきれなかった情報は手入力する
- テストしたいデータを手入力する
- テストする
構造
初期に必要な構造
必要な構造は2つ、
- フォームを解析するFormScan.py
- webdriverディレクトリにchromewebdriver.exe
を用意する(main.pyはキャプチャミスった)、必要なファイルはFormScan.pyを実行すると作成される
ChromeWebDriverは公式から自分のChromeのバージョンに合致するものを用意する(https://chromedriver.chromium.org/downloads)
※main.pyは無視してよい
ソースコード
※コピペで使えるぞ!
import os
import csv
from selenium import webdriver
from selenium.webdriver.common.by import By
class FormScan:
def init(self):
# ディレクトリが存在しない場合、ディレクトリを作成する
if not os.path.exists(os.getcwd() + "\\config"):
os.makedirs(os.getcwd() + "\\config")
if not os.path.exists(os.getcwd() + "\\config\\param"):
os.makedirs(os.getcwd() + "\\config\\param")
def writePages(self,driver):
writer = open(os.getcwd() + "\\config\\url.csv", "w", encoding="utf-8")
writer.write("[settings]\n")
writer.write("input_url=" + driver.current_url + "\n")
#submitが見つかれば記録する
elem_submit = driver.find_elements(By.XPATH, "//input[@type='submit']")
if len(elem_submit) == 1:
#確認画面に遷移するための要素
if(elem_submit[0].get_attribute("name")!=""):
writer.write("confirm_move_name="+elem_submit[0].get_attribute("name")+"\n")
elif(elem_submit[0].get_attribute("id") != ""):
writer.write("confirm_move_id="+elem_submit[0].get_attribute("id")+"\n")
else:
#見つからなければor複数見つかれば空で初期化する
writer.write("confirm_move_name=\n")
#formタグが見つかれば確認画面のURLを記録する
elem_form = driver.find_elements_by_tag_name("form")
if len(elem_form) == 1:
writer.write("confirm_url=" + elem_form[0].get_attribute("action") + "\n")
else:
writer.write("confirm_url=\n")
# 画像比較時のヒストグラム閾値
writer.write("img_hist=90\n")
def getTypeText(self, driver):
elem_fields = {}
target_types = ["text", "tel", "url", "email", "search", "password", "date", "month", "time", "datetime",
"datetime-local", "number", "range", "color", "file"]
for type in target_types:
elements = driver.find_elements(By.XPATH, "//input[@type='" + type + "']")
for elem in elements:
elem_fields[elem.get_attribute("name")] = elem.get_attribute("type")
return elem_fields
def getTypeTextarea(self, driver):
elem_fields = {}
elements = driver.find_elements(By.XPATH, "//textarea")
for elem in elements:
elem_fields[elem.get_attribute("name")] = elem.get_attribute("type")
return elem_fields
def getTypeRadio(self, driver):
return FormScan.execRadioAndCheckbox(self, driver, "radio")
def getTypeCheckbox(self, driver):
return FormScan.execRadioAndCheckbox(self, driver, "checkbox")
def execTextAndTextarea(self, driver, type):
elem_fields = {}
elements = driver.find_elements(By.XPATH, "//input[@type='" + type + "']")
for elem in elements:
elem_fields[elem.get_attribute("name")] = elem.get_attribute("type")
return elem_fields
def execRadioAndCheckbox(self, driver, type):
elem_fields = {}
type_arr = {}
elements = driver.find_elements(By.XPATH, "//input[@type='" + type + "']")
for elem in elements:
if elem.get_attribute("name") not in type_arr:
type_arr[elem.get_attribute("name")] = {}
elem_fields[elem.get_attribute("name")] = elem.get_attribute(("type"))
#checkboxがlabelに囲まれているケース
if len(elem.find_elements_by_xpath("ancestor::label")) != 0:
type_arr[elem.get_attribute("name")][elem.get_attribute("value")] = elem.find_element_by_xpath(
"ancestor::label").text
#checkboxとlabelが別々の場所に存在するケース
else:
labels = driver.find_elements(By.XPATH, "//label")
for label in labels:
if (elem.get_attribute("id") == label.get_attribute("for")): # idとlabelkが一致したらディクショナリ化
type_arr[elem.get_attribute("name")][elem.get_attribute("value")] = label.text
# パラメータを記録
for radio in type_arr:
writer = open(os.getcwd() + "\\config\\param\\" + type + "_" + radio + ".csv", "w", encoding="utf-8")
for value in type_arr[radio]:
writer.write(value + "," + type_arr[radio][value] + "\n")
return elem_fields
def getTypeTextSelect(self, driver):
elem_fields = {}
elements = driver.find_elements(By.XPATH, "//select")
for elem in elements:
elem_fields[elem.get_attribute("name")] = elem.get_attribute("type")
option_arr = elem.find_elements(By.XPATH, "//option") # selectタグの内側のoptionタグを取得
writer = open(os.getcwd() + "\\config\\param\\select_" + elem.get_attribute("name") + ".csv", "w", encoding="utf-8")
for option in option_arr:
writer.write(option.get_attribute("value") + "," + option.text + "\n")
return elem_fields
def searchFormTags(self, driver):
elem_fields = {}
# inputタグ処理
elem_fields.update(FormScan.getTypeText(self, driver))
# textareaタグ処理
elem_fields.update(FormScan.getTypeTextarea(self, driver))
# radioタグ処理
elem_fields.update(FormScan.getTypeRadio(self, driver))
# checkboxタグ処理
elem_fields.update(FormScan.getTypeCheckbox(self, driver))
# selectタグ処理
elem_fields.update(FormScan.getTypeTextSelect(self, driver))
return elem_fields
def exec(self,url):
if not os.path.exists(os.getcwd() + "\\webdriver\\chromedriver.exe"):
print("Error:"+os.getcwd() + "\\webdriver\\chromedriver.exeにWebDriverを配備してください")
exit()
# ChromeWebdriverファイルのパス指定
driver = webdriver.Chrome(executable_path=os.getcwd() + "\\webdriver\\chromedriver.exe")
# ブラウザを起動し指定のURLを開く
driver.get(url)
elements = FormScan.searchFormTags(self, driver)
with open(os.getcwd() + "\\config\\data.tsv", "w", encoding="utf-8", newline="") as w:
writer = csv.writer(w,delimiter="\t")
writer.writerow(elements)
writer.writerow(elements.values())
FormScan.writePages(self,driver)
FormScan().init()
FormScan().exec("http://holiday-programmer.net/form_check/")
最後exec()の引数に解析したいフォームのURLを指定する。
実行後はどうなるか
解析した結果が赤枠内のディレクトリ・ファイルとして作成される
config/data.tsvの中身
1行目は入力項目のname属性、2行目はtype属性(textareaなどのtype属性がないものも含む)となり、テストで入力する値は3行目からとなるイメージ。
config/settings.iniの中身
input_url⇒入力画面のURL
confirm_move_id⇒次の画面に遷移するためのid要素
confirm_url⇒次の画面のURL
img_hist→画像判定時に同一画像と判定するヒストグラム閾値
※ confirm_move_id、confirm_urlは解析できなければ=の右が空になる
config/param/以下のファイル群
radio,checkbox,selectなどの選択系の項目は、選択肢の名前とvalue属性が記録される。