基於 python--selenium 與 requests 的 web ui/ 介面混合測試框架

雲中一隻貓發表於2020-11-30

一、序

接觸自動化測試已經好多年,也好多年停留在哪裡。

最開始跟大部分同學一樣,是從@蟲師那裡瞭解到併入門的,那時候python還沒這麼流行

最開始的時候還是用java跟ruby寫的,想想種種過往也無奈感嘆,為啥當時就不能好好學習一下java;

說到底還是當初太年輕,好了廢話不多說,我們開始吧!

二、開始

對於UI型別的自動化我一直不太感冒,可以說不支援;
去年年初兩個月寫了大幾百條UI自動化,半年後只有一半湊合使用
(業務發展太快就不要用了UI,當時痛定思痛編寫 WEB遍歷工具 接下來也會跟大家分享!)
今年下任務編寫UI自動化時,通過不斷思考,將UI與介面相結合能更好的在業務發生較大變化時,及時響應,及時調整;

# 三、程式碼原理圖

共計分為5層:
1、底層驅動------可以隨意切換底層驅動【UI底層框架-selenium,介面底層驅動--requests】
2、元素/介面胚層【儲存元素及介面yaml檔案;介面yaml資料進行初始化】
3、Case層
4、Scene層
5、TestCase層
其中Scene層和Case層可按照業務複雜度及個人編寫愛好進行整合。

四、TestCase層程式碼

  • 每個TestCase都可以獨立執行;
  • 每個Scene方法會返回True或False;
  • 通過self.assertTrue()判斷是否執行正確
class TestCaseRoleAdd(unittest.TestCase):

def setUp(self):
self.sm_first = SceneRoleAdd()

def test_1_role_add_delete(self):
self.assertTrue(self.sm_first.login_erp())
self.assertTrue(self.sm_first.add_role())
self.assertTrue(self.sm_first.allocate_function_button())
self.assertTrue(self.sm_first.delete_role())
print("Test finished-noReport:Pass")

def tearDown(self):
self.sm_first.close()

if __name__ == '__main__':
testSuite1 = unittest.TestLoader().loadTestsFromTestCase(TestCaseRoleAdd)
suite = unittest.TestSuite(testSuite1)
unittest.TextTestRunner(verbosity=2).run(suite)

五、Scene層程式碼

  • 主要對Case層程式碼進行拼接組合;
  • 此層的出現主要是讓Case層能夠實現PO,這樣出現問題時,能夠快速查詢。
class SceneRoleAdd():
step_role = StepRole()
step_ERP_login = StepERPLogin()

# 登入ERP系統
@catch_exception
def login_erp(self):
self.step_ERP_login.login()

# 新增角色
@catch_exception
def add_role(self):
self.step_role.into_role()
self.step_role.add_role()

# 角色新增功能及按鈕
@catch_exception
def allocate_function_button(self):
role_name = self.step_role.role_name
self.step_role.search_role(role_name)
self.step_role.allocate_function()
self.step_role.search_role(role_name)
self.step_role.allocate_button()

# 刪除角色
@catch_exception
def delete_role(self):
role_name = self.step_role.role_name
self.step_role.search_role(role_name)
self.step_role.delete_role()
self.step_role.delete_role_verify(role_name)

# 退出瀏覽器
@catch_exception
def close(self):
self.step_role.close()


if __name__ == '__main__':
pass

裝飾器--捕獲Scene層異常

mLog = log.Log()
mTag = 'base_scene'

# 採集操作日誌,捕獲異常
+ 捕獲異常的同時進行截圖操作

def catch_exception(origin_func):
def wrapper(self, *args, **kwargs):
try:
print(f"Test---{origin_func.__name__} start")
origin_func(self, *args, **kwargs)
print(f"Test---{origin_func.__name__} end")
return True
except Exception as err:
traceback.print_exc()
self.step_ERP_login.sc_shot(origin_func.__name__)
mLog.log(mTag, f"Test---{origin_func.__name__} err:" + str(err))
return False
return wrapper

六、Case層程式碼

  • 每個Case層方法會繼承一個Base_step,以便通過Base_step型別進行方法擴充套件
  • Case是此框架中的核心所有的錯誤均需定位到此層
class StepRole(Base_step):
role_name = "test" + datetime.today().strftime("%Y%m%d%H%M%S")
loginName = None

def __init__(self):
super(StepRole,self).__init__()

@property
def userId(self):
return self.yaml_config_data.get_key_by_str_list(self.env_name + '.TEST_ERP.userId')

def into_role(self):
self.into_menu(menu_role)
self.isElementExistXpathByName(menu_depot_assert)
self.switch_to_frame(1)

def add_role(self):
# 增加按鈕
self.id_click(role_add_btn)
#
self.xpath_input(role_add_role_name, self.role_name)
# 儲存
self.id_click(role_add_save_btn)

def allocate_function(self):
self.id_click(role_allocate_func_btn)
# self.switch_to_default()
self.xpath_await_visibility(role_allocate_frame)
self.xpath_switch_to_frame(role_allocate_frame)
self.xpath_click(role_allocate_all)
self.id_click(role_allocate_save_btn)
self.xpath_click(role_allocate_assure_btn)
self.switch_to_parent_frame()

七、元素及介面層

1. 元素層

1.1 Selenum 驅動

  • 所有的selenium方法在此層進行封裝以便將來選擇其他框架是能夠快速更換。
class BaseSelenium(object):
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)

@base_log_aop
def xpath_input(self, value, input=''):
self.input_data('xpath', value, input)

@base_log_aop
def xpath_click(self, value):
self.click('xpath', value=value)

@base_log_aop
def xpath_text_click(self, value):
value = f'//*[text()="{value}"]'
self.click('xpath', value=value)

@base_log_aop
def xpath_double_click(self, value):
self.double_click('xpath', value=value)

@base_log_aop
def elements_index_click(self, locate_type, value, index):
self.get_element_from_elements(locate_type, value, index).click()

@base_log_aop
def xpath_elements_click(self, value):
locate_elements = self.locate_elements('xpath', value)
for i in locate_elements:
i.click()

# 裝飾器儲存相關底層操作
def base_log_aop(origin_func):
def wrapper(self, *args, **kwargs):
mLog.log("BaseSelenium", f"{origin_func.__name__}:" + ','.join([str(i) for i in args]))
return origin_func(self, *args, **kwargs)
return wrapper

1.2 定位元素

  • 定位元素建議使用一種型別就可以,當前系統中使用id進行定位越來越少,CSS或xpath選一種吧。
menu_role = ['系統', '角色管理']
menu_role_assert = '角色列表'

role_add_btn = "addRole"
# 角色名稱
role_add_role_name = '//form[@id="role"]//tr//span/input[1]'
role_add_save_btn = "saveRole"
# 分配功能
role_allocate_frame = '//iframe[@class="cboxIframe"]'
role_allocate_func_btn = "btnSetFunctions"
role_allocate_all = '//ul[@id="tt"]/li/div/span[3]'
role_allocate_save_btn = 'btnOK'
role_allocate_assure_btn = '//span[text()="確定"]'
# 分配按鈕
role_allocate_button_btn = "btnSetPushBtn"
role_allocate_button_pos = '//input[@type="checkbox"]'

2. 介面層

2.1 介面底層驅動

  • 這個介面方法已經使用很長時間了,沒毛病
def request_fun(http, api_path, json_p, change_dict, special=1):
api_data = ERPYaml(api_path)
mode = api_data.get_key_by_str_list('request.method')
url = api_data.get_key_by_str_list('request.url')
postData = api_data.get_key_by_str_list('request.json')
headers = api_data.get_key_by_str_list('request.headers')
if change_dict != {} and change_dict is not None:
# url = str_change_data(url, change_dict)
#
if special == 1:
postData = dict_change_data(postData, change_dict)
url = str_change_data(url, change_dict)
#
elif special == 2:
postData = special_1_postdata(postData, change_dict)
url = str_change_data(url, change_dict)
#
elif special == 3:
postData = dict_change_data(postData, change_dict)
url = str2_change_data(url, change_dict)
elif special == 4:
postData = special_2_postdata(postData, change_dict)
url = str_change_data(url, change_dict)
else:
pass
headers = dict_change_data(headers, change_dict)
url = http + url
# 先區分資料傳送方式(json or para
# 再區分是GET or POST
mLog.log("request_fun", "mode:%s" % mode)
mLog.log("request_fun","url:%s" % url)
mLog.log("request_fun", "postData:%s" % postData)
mLog.log("request_fun", "headers:%s" % headers)
if json_p == 1 or json_p == '1':
postData = json.dumps(postData)
if mode == 'POST':
reponse_data = requests.post(url=url, data=postData, headers=headers)
elif mode == "GET":
reponse_data = requests.get(url=url, data=postData, headers=headers)
else:
raise AttributeError(u'mode輸入錯誤,mode=%s' % mode)
elif json_p == 0 or json_p == '0':
if mode == 'POST':
reponse_data = requests.post(url=url, params=postData, headers=headers)
elif mode == "GET":
reponse_data = requests.get(url=url, params=postData, headers=headers)
else:
raise AttributeError(u'mode輸入錯誤,mode=%s' % mode)
else:
raise AttributeError(u'json_p輸入錯誤,json_p=%s' % json_p)
# 獲取介面返回資料中的資料,進行全域性化(
# 新增介面時需要新增引數名稱(global_name)及正在表示式(regular_input
mLog.log("request_fun", f"reponse_data:{reponse_data.text}")
return reponse_data

2.2 介面底層封裝

  • 這裡相當於對yaml中儲存的http介面進行初始化
class APIRealization(BaseAPI):
api_path = mAPIPathERP
yaml_path = mYaml
role_add = api_path + os.sep + 'a5_role_add.yml'
role_add_functions = api_path + os.sep + 'a6_role_add_functions.yml'

# 新增角色
def response_role_add(self, change_dict):
change_dict.update(self.local_change_dict)
return request_fun(self.url_header, self.role_add, 0, change_dict, 2)

# 角色新增功能
def response_role_add_functions(self, change_dict):
change_dict.update(self.local_change_dict)
return request_fun(self.url_header, self.role_add_functions, 0, change_dict, 2)

2.3 yaml檔案

  • 儲存http介面的檔案,yaml檔案作為配置檔案使用的頻率越來高,主要還是好用。
request:
url: /role/add
method: POST
headers:
Content-Type: "application/x-www-form-urlencoded"
Cookie: $Cookie
json:
info: "{\"name\":\"$role_name\"}"

相關文章