end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

unittest by selenium for python のオレオレ sample

参考url https://www.seleniumqref.com/

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import copy
import getopt
import os
import random
import re

# chromedriver の入手先-> http://chromedriver.chromium.org/getting-started
from selenium import webdriver

from selenium.webdriver.common.by   import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support    import expected_conditions
from selenium.webdriver.support    import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

import sys
import time
import unittest

conf = {"chrome_driver": os.getcwd() + '\\chromedriver.exe',
        "chrome_options" :[
            #"--headless",
            "--enable-logging=False",
            "--ignore-certificate-errors", #以下、3行はSSLエラー対策らしい
            "--disable-extensions",
            "--disable-print-preview"]}

class FormReConstruct(unittest.TestCase):
    # test前 処理
    def setUp(self):
        chopt = webdriver.ChromeOptions()
        for option_tmp in conf["chrome_options"]:
            chopt.add_argument( option_tmp )
        self.driver = webdriver.Chrome(options = chopt,
                                       executable_path=conf["chrome_driver"])
        self.driver.implicitly_wait(10)

    # test後 処理
    def tearDown(self):
        self.driver.close()

    def test_form_urls_1(self):
    test_url = "http://cent80.a5.jp:8080/contact2/catalog/main/form.shtml"

        with self.subTest(test_url=test_url):
            self.subtest_input_all_valid(test_url)      # 全項目に正しい値を入力
            self.subtest_input_text(test_url)           # 各textへの入力test
            self.subtest_input_text_zipcode(test_url)   # 郵便番号→住所変換test
            self.subtest_input_radio(test_url)          # radioのnull test
            self.subtest_select_build_plan(test_url)    # 選択による他項目の表示切替え test
      
      
    def subtest_input_all_valid(self,test_url):
        self.init_input_page(test_url)

        #入力内容に全く問題なければ、.submitBtn のelement に .js-disabled がない
        submit_btn = self.driver.find_element_by_class_name("submitBtn")
        self.assertEqual("submitBtn", submit_btn.get_attribute("class"))
        #上記の処理までにvalidatioとしてはOKだが、おまけでsubmitをclick
        submit_btn.click()


    def init_input_page(self,test_url):
        retry_limit = 5
        while retry_limit:
            if self.init_input_page_sub(test_url):
                return True
            retry_limit -= 1
            print("retry init_input_page_sub()", retry_limit)
        return False

    def init_input_page_sub(self,test_url):
        self.driver.get(test_url) # 対象urlへaccess
        
        # onbeforeunload イベントにより
        # 「ページを離れようとしています。よろしいですか?」のalertが
        # 表示される場合の処理
        try:
            alert = self.driver.switch_to.alert
            alert.accept()
        except:
            pass

        # pageの全要素が読み込まれるまでwait
        # refer to https://qiita.com/uguisuheiankyo/items/cec03891a86dfda12c9a
        try:
            WebDriverWait(self.driver, 10).until(EC.presence_of_all_elements_located)
        except:
            print("timeout",presence_of_all_elements_located)
            return False

        # 氏名等の入力前にcatalog選択し、その後、初期値として、全項目を入力
        if self.pre_input_all_valid(test_url) == False:
            return False
        if self.input_all_valid(test_url) == False:
            return False
        return True

    def subtest_input_text(self,test_url):
        # 現行のvalidationは、/request/js/common.js に定義されてます
        elm_names = {}
        elm_names["name_f"] = elm_names["name_s"] = \
            [{"value":"",                             "valid":False },
             {"value": random_str(10,["katakana_h"]), "valid":False },
             {"value": random_str(10,["hiragana","katakana_z","kanji","kishuizon"]),
              "valid":True },
             {"value": random_str(11,["hiragana","katakana_z","kanji","kishuizon"]),
              "valid":False}]
        elm_names["name_fk"] = elm_names["name_sk"] = \
            [{"value":"",                             "valid":False },
             {"value": random_str(10,["katakana_z"]), "valid":True  },
             {"value": random_str(11,["katakana_z"]), "valid":False },
             # TODO フリガナは、全角数字やアルファベットを許さず
             # valudationが厳しい
             {"value": random_str(10,["katakana_z","number_z","alphabet_z"]),
              "valid":False },
             {"value": random_str(10,["katakana_z","number_z"]), "valid":False },
             {"value": random_str(10,["katakana_z"]),            "valid":True} ]
        elm_names["zip_f"] = [{"value":"",               "valid":False},
                              {"value":random_str(7,[]), "valid":False},
                              {"value":random_str(8,[]), "valid":False}]
        elm_names["add_1"] = [{"value":"",                  "valid":False},
                              {"value": random_str(100,["number_z","alphabet_z",
                                                        "hiragana","kanji","kishuizon"]),
                               "valid":True },
                              {"value": random_str(101,["number_z","alphabet_z",
                                                        "hiragana","kanji","kishuizon"]),
                               "valid":False },
                              {"value": random_str(50,[]), "valid":False }]
        elm_names["add_2"] = [{"value":"",                  "valid":False},
                              {"value": random_str(100,["number_z","alphabet_z",
                                                        "hiragana","kanji","kishuizon"]),
                               "valid":True },
                              {"value": random_str(101,["number_z","alphabet_z",
                                                        "hiragana","kanji","kishuizon"]),
                               "valid":False },
                              {"value": random_str(50,[]), "valid":True }]
        elm_names["tel1"] =  [{"value":"",                 "valid":False},
                              {"value": random_str(11,[]), "valid":False},
                              {"value":"09012345678",      "valid":True }]
        elm_names["email1"] = [{"value":"",                 "valid":False},
                               {"value": random_str(11,[]), "valid":False}]
        elm_names["hp_inquire"] = [{"value":"",                   "valid":True },
                                   {"value": random_str(1500,[]), "valid":False}]
        
        for elm_name in elm_names.keys():
            for test_case in elm_names[elm_name]:
                print(elm_name, test_case["value"], test_case["valid"] )

                # 画面の初期状態として、全項目に値を入力
                self.init_input_page(test_url)

                # 次にtest_caseにある値を入力
                form_elms = self.driver.find_elements_by_name(elm_name)

                form_elms[0].clear()
                form_elms[0].send_keys(test_case["value"] + "\t")
                time.sleep(0.5)

                submit_btn = self.driver.find_element_by_class_name("submitBtn")
                time.sleep(1)

                if test_case["valid"]==False:
                    self.assertEqual("submitBtn js-disabled",
                                     submit_btn.get_attribute("class"),
                                     elm_name +" "+ test_case["value"])
                else:
                    self.assertEqual("submitBtn",
                                     submit_btn.get_attribute("class"),
                                     elm_name +" "+ test_case["value"])

    # "build_plan" により、他項目の表示/非表示が切り替わるtest
    def subtest_select_build_plan(self,test_url):
        self.init_input_page(test_url)
        
        plan_elms      = self.driver.find_elements_by_name("build_plan")
        plan_size_elms = self.driver.find_elements_by_name("build_plan_size")
        plan_city_elms = self.driver.find_elements_by_name("build_plan_city")
        plan_area_elms = self.driver.find_elements_by_name("build_plan_area")

        test_cases = \
            [{"plan":"お選びください",      "size":False,"city":False,"area":False},
             {"plan":"あり(現住所と同じ)","size":True, "city":False,"area":False},
             {"plan":"あり(現住所と違う)","size":True, "city":True, "area":False},
             {"plan":"なし(購入希望)",    "size":True, "city":False,"area":True}]

        for test_case in test_cases:
            plan_elms[0].send_keys(test_case["plan"])
            time.sleep(2)
            self.assertEqual(test_case["size"], plan_size_elms[0].is_displayed())
            self.assertEqual(test_case["city"], plan_city_elms[0].is_displayed())
            self.assertEqual(test_case["area"], plan_area_elms[0].is_displayed())

        
    def subtest_input_radio(self,test_url):
        elm_defs = [{"elm_name":"sex",  "valid":True }, 
                    {"elm_name":"bunjo","valid":True }] #任意項目
        for elm_def in elm_defs:
            self.init_input_page(test_url)

            form_elms = self.driver.find_elements_by_name(elm_def["elm_name"])
            for form_elm in form_elms:
                elm_name = elm_def["elm_name"]
                
                # radioはseleniumでnull化できない為、javascriptにより値をclear
                if form_elm.is_selected():
                    js_str = "".join(["for( elm of document.getElementsByName('",
                                      elm_name,
                                      "')) elm.checked = false;"])
                    self.driver.execute_script(js_str)
                    
                submit_btn = self.driver.find_element_by_class_name("submitBtn")

            if elm_def["valid"]==False:
                self.assertEqual("submitBtn js-disabled",
                                 submit_btn.get_attribute("class"),
                                 elm_name)
            else:
                self.assertEqual("submitBtn",
                                 submit_btn.get_attribute("class"),
                                 elm_name)


    # 郵便番号→住所 自動変換のtest
    def subtest_input_text_zipcode(self,test_url):
        # google日本語apiにより住所へ変換していますが、
        # 新しい過ぎる?郵便番号は変換できないようです
        zipcodes = [{"zip_f":"100-0005", "todouhuken":"東京都","add_1":"千代田区丸の内"},
                    {"zip_f":"1000005",  "todouhuken":"東京都","add_1":"千代田区丸の内"},
                    {"zip_f":"150-0002", "todouhuken":"東京都","add_1":"渋谷区渋谷"},
                    {"zip_f":"105-8566", "todouhuken":"",      "add_1":""}, # ※1
                    {"zip_f":"999-9999", "todouhuken":"",      "add_1":""} ]

        # まず、対象urlへaccessし、全項目に正しく入力
        self.init_input_page(test_url)
        
        for zipcode in zipcodes:
            zip_elms  = self.driver.find_elements_by_name("zip_f")
            pref_elms = self.driver.find_elements_by_name("todouhuken")

            add_elms  = self.driver.find_elements_by_name("add_1")
            zip_elms[0].clear()
            pref_elms[0].send_keys("お選びください")
            add_elms[0].clear()

            zip_elms[0].send_keys(zipcode["zip_f"])
            btn_elms  = self.driver.find_elements_by_css_selector("a.addressBtn")
            btn_elms[0].click()
            time.sleep(2)

            self.assertEqual(zipcode["todouhuken"],pref_elms[0].get_attribute("value"))
            self.assertEqual(zipcode["add_1"],     add_elms[0].get_attribute("value") )


    def pre_input_all_valid(self,test_url):
        # catalog一覧が表示されるまでwait
        selector_str = '.hmSetList__list>li'
        WebDriverWait(self.driver,10).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR,selector_str)
            ))

        # catalog一覧内からrandomに選択
        li_elms = self.driver.find_elements_by_css_selector(selector_str)
        li_pos = random.randint(0, len(li_elms)-1)
        try:
            li_elms[li_pos].click()
        except:
            print("fail click ",selector_str)
            return False
        
        # 「お受け取り先の入力へ」がclick可能になるまでwait
        x_path_str = "//span[text()='お受け取り先の入力へ']"
        WebDriverWait(self.driver,10).until(
            EC.element_to_be_clickable((By.XPATH,x_path_str)
            ))

        span_elms = self.driver.find_elements_by_xpath(x_path_str)
        try:
            span_elms[0].click()
        except:
            print("fail click ",x_path_str)
            return False
        return True


    def input_all_valid(self,test_url):

        for elm_name in form_type_a_all_valid.keys():
            try:
                WebDriverWait(self.driver,10).until(
                    EC.element_to_be_clickable((By.NAME,elm_name)
                    ))
            except:
                print("timeout element_to_be_clickable",elm_name)
                return False

            elm_def = form_type_a_all_valid[elm_name]
            form_elms = self.driver.find_elements_by_name(elm_name)
            
            if form_elms[0].get_attribute("type")=="select-one":
                form_elms[0].send_keys(elm_def["value"])
                
            elif form_elms[0].get_attribute("type")=="radio":
                for form_elm in form_elms:
                    if form_elm.get_attribute("value")==elm_def["value"]:
                        form_elm.click()
                        form_elm.click()
                        # 親要素の選択
                        # form_elm.find_element_by_xpath("..").click()
                        break
            elif form_elms[0].get_attribute("type")=="checkbox" :
                for form_elm in form_elms:
                    if form_elm.get_attribute("value")==elm_def["value"]:
                        try:
                            form_elm.click()
                        except:
                            print("fail click ",
                                  form_elms[0].get_attribute("type"),
                                  elm_name )
                            return False
                        break
            else:
                # focos out時にvalidationされる場合もある為、タブも追加で押下げ
                form_elms[0].clear()
                form_elms[0].send_keys(elm_def["value"]+"\t")

        return True

                
form_type_a_all_valid = \
    {"name_f"           :{"value":"テスト髙橋"                      },
     "name_s"           :{"value":"テスト①太郎"                    },
     "name_fk"          :{"value":"タカハシ"                        },
     "name_sk"          :{"value":"イチタロウ"                      },
     "sex"              :{"value":"女"                              },
     "age"              :{"value":"26~30歳"                        },
     "job"              :{"value":"会社員(事務系)"                },
     "zip_f"            :{"value":"100-0005"                        },
     "todouhuken"       :{"value":"東京都"                          },
     "add_1"            :{"value":"千代田区丸の内"                  },
     "add_2"            :{"value":"1丁目 東京駅の住所です"         },
     "tel1"             :{"value":"03-1234-5678"                    },
     "email1"           :{"value":"test-user-01@example.com"        },
     "build_clas2"      :{"value":"新築"                            },
     "build_plan"       :{"value":"あり(現住所と同じ)"            },
     "build_plan_size"  :{"value":"25坪以下(約82平方メートル以下)"},
     #build_plan_size, build_plan_city, build_plan_area は
     #build_planの値により表示/非表示が切り替わります
     #"build_plan_city":{"type":"text"},
     #"build_plan_area":{"type":"select-one"}
     "build_time"       :{"value":"半年以内"                                        },
     "interest1"        :{"value":"土地情報の紹介・斡旋、土地購入に関するアドバイス"},
     "interest2"        :{"value":"プランニングのご相談"                            },
     "interest3"        :{"value":"ファイナンシャルプランナーによる家計アドバイス"  },
     "interest4"        :{"value":"法律家、税理士、会計士等へのご相談"              },
     "interest5"        :{"value":"工場見学会や建築現場・ご入居者様宅の見学会"      },
     "build_budget"     :{"value":"1500万円以下"                                    },
     "soil_budget"      :{"value":"1500万円以下"                                    },
     # bunjo の項目は、選択したcatalogによっては表示されない
     "bunjo"            :{"value":"希望する"                                        },
     "hp_inquire"       :{"value":"これはテストのご意見です。\n改行を入れています。"}}

def random_str(char_len,char_groups):

    chars = \
        {"number_h"   : "0123456789",
         "number_z"   : "0123456789",
         "alphabet_h" : "abcdefghij",
         "alphabet_z" : "abcdefghij",
         "symbol_h"   : "~!@#$%^&*(",
         "space"      : "       ",
         "hiragana"   : "あいうえおぎゃぎょー",
         "katakana_z" : "アイウエオギャギョー",
         "katakana_h" : "アイウエオャギョー",
         "kanji"      : "日本東京都港区虎ノ門",
         "kishuizon"  : "㈱髙﨑①Ⅰα㍻㋿─○"}
    
    str_source = "";
    if len(char_groups) > 0:
        for char_group in char_groups:
            str_source += chars[char_group]
    else:
        for char_group in chars.keys():
            str_source += chars[char_group]
        
    # 元となる文字列が短い場合、random.sample()が失敗する為
    while len(str_source) <= char_len:
        str_source += str_source
            
    random_str = "".join(random.sample(str_source, char_len))
    return random_str


if __name__ == "__main__":
    unittest.main()