import os from datetime import datetime import time from selenium.webdriver.common.by import By from selenium.common.exceptions import TimeoutException from selenium.common.exceptions import NoSuchElementException import pytest from comm.comm import * from comm.read_data import * from comm.write_data import * from selenium import webdriver from po.login_page import LoginPage from po.home_page import HomePage from po.test_package_list_page import TestPackageListPage from po.member_detail_page import MemberDetailPage from po.report_page import ReportPage from po.share_add_page import ShareAddPage from po.maq_answer_page import MAQAnswerPage from po.caq_answer_page import CAQAnswerPage from comm.my_random import * class TestPackageList: driver = None test_package_name = "MAQV2自动测试包-20230727155906" member_email = None @pytest.mark.parametrize('data_read', ["testPackageList"], indirect=True) def test_create_package(self, data_read: dict, driver): """ 创建测试包 :param data_read: 读取数据 :return: """ # 创建home页面的对象 home_page = HomePage(driver) # 点击左侧菜单进入页面 home_page.menu_select(data_read["menu"]) # 创建测试包列表页面的对象 test_package_list = TestPackageListPage(driver) # 创建测试包并接收创建的测试包名称 create_package_name = test_package_list.create_package(data_read["add"]) # 赋值到类变量 TestPackageList.test_package_name = create_package_name time.sleep(5) # 判断是否成功创建 assert test_package_list.is_create_success(create_package_name) @pytest.mark.parametrize('data_read', ["testPackageList"], indirect=True) def test_edit_package(self, data_read: dict, driver): """ 修改测试包 :return: """ # 创建测试包列表页面的对象 test_package_list = TestPackageListPage(driver) # 勾选新创建的测试包 test_package_list.select_package_checkbox(TestPackageList.test_package_name) # 点击修改按钮 test_package_list.click(test_package_list.es.edit_btn) # 获取修改数据 edit_data = data_read["edit"] # 修改测试包 test_package_name = test_package_list.edit_package(edit_data) TestPackageList.test_package_name = test_package_name time.sleep(5) # 校验是否修改成功 flag = test_package_list.verify_edit_success(test_package_name, edit_data) # 切换到上一层iframe test_package_list.switch_parent_iframe() # 点击关闭按钮 test_package_list.click(test_package_list.es.edit_cancel_btn) assert flag @pytest.mark.parametrize('data_read', ["memberDetail"], indirect=True) def test_add_member(self, data_read: dict, driver): """ 新增评测人员 :param data_read: :param driver: :return: """ # 创建测试包列表页面的对象 test_package_list = TestPackageListPage(driver) # 点击测试包名称 test_package_list.click_package_name(TestPackageList.test_package_name) time.sleep(5) # 创建测评人员名单页面对象 member_detail = MemberDetailPage(driver) # 新增测评人员 member_email = member_detail.add_member(data_read["add"]) TestPackageList.member_email = member_email time.sleep(5) # 校验是否新增成功 assert member_detail.is_create_success(member_email) @pytest.mark.parametrize('data_read', ["memberDetail"], indirect=True) def test_edit_member(self, data_read: dict, driver): """ 修改测评人员 :param data_read: :param driver: :return: """ # 创建测评人员名单页面对象 member_detail = MemberDetailPage(driver) # 修改测评人员 member_email = member_detail.edit_member(TestPackageList.member_email, data_read["edit"]) TestPackageList.member_email = member_email time.sleep(5) # 校验是否修改成功 assert member_detail.is_edit_success(TestPackageList.member_email, data_read["edit"]) @pytest.mark.parametrize('data_read', ["memberDetail"], indirect=True) def test_export_member(self, data_read: dict, driver): """导出""" # 创建测评人员名单页面对象 member_detail = MemberDetailPage(driver) # 获取当前项目根路径 root_path = os.getcwd() # 拼接下载文件目录 dir_path = root_path + r"\download" # 获取目录下所有文件 old_file_names = [] for filename in os.listdir(dir_path): if os.path.isfile(os.path.join(dir_path, filename)): old_file_names.append(filename) # 点击导出按钮 member_detail.click(member_detail.es.export_btn) # 点击确认框确认 member_detail.click(member_detail.es.layer_confirm_btn, timeout=10) i = 0 while i < data_read["downloadWaitTime"]: # 获取目录下所有文件 new_file_names = [] for filename in os.listdir(dir_path): if os.path.isfile(os.path.join(dir_path, filename)): new_file_names.append(filename) # 判断旧文件的数量与新文件的数量是否相同,相同则睡眠一秒 if len(old_file_names) == len(new_file_names): time.sleep(1) i += 1 continue else: # 否则直接退出循环 break else: assert False assert True @pytest.mark.parametrize('data_read', ["memberDetail"], indirect=True) def test_export_url(self, data_read: dict, driver): """导出测试链接""" # 创建测评人员名单页面对象 member_detail = MemberDetailPage(driver) # 获取当前项目根路径 root_path = os.getcwd() # 拼接下载文件目录 dir_path = root_path + r"\download" # 获取目录下所有文件 old_file_names = [] for filename in os.listdir(dir_path): if os.path.isfile(os.path.join(dir_path, filename)): old_file_names.append(filename) # 点击导出测试链接按钮 member_detail.click(member_detail.es.export_url_btn) # 点击确认框确认 member_detail.click(member_detail.es.layer_confirm_btn, timeout=10) i = 0 while i < data_read["downloadWaitTime"]: # 获取目录下所有文件 new_file_names = [] for filename in os.listdir(dir_path): if os.path.isfile(os.path.join(dir_path, filename)): new_file_names.append(filename) # 判断旧文件的数量与新文件的数量是否相同,相同则睡眠一秒 if len(old_file_names) == len(new_file_names): time.sleep(1) i += 1 continue else: # 否则直接退出循环 break else: assert False assert True @pytest.mark.parametrize('data_read', ["memberDetail"], indirect=True) def test_import(self, data_read: dict, driver): """批量导入""" # 创建测评人员名单页面对象 member_detail = MemberDetailPage(driver) # 获取当前项目根路径 root_path = os.getcwd() # 拼接导入文件路径 file_path = root_path + r"\import\member_import.xlsx" # 点击批量导入按钮 member_detail.click(member_detail.es.import_btn) # 发送文件路径到文件上传输入框 member_detail.fill(member_detail.es.import_input, file_path) time.sleep(2) # 点击导入按钮 member_detail.click(member_detail.es.import_confirm_btn) time.sleep(2) # 判断是否导入成功 try: member_detail.get_ele(member_detail.es.import_success_text, timeout=10) # 点击确认按钮 member_detail.click(member_detail.es.layer_confirm_btn) assert True except TimeoutException: try: # 点击确认按钮,如果不存在则不点击 member_detail.click(member_detail.es.layer_confirm_btn, timeout=10) except TimeoutException: pass assert False @pytest.mark.parametrize('data_read', ["memberDetail"], indirect=True) def test_send_email(self, data_read: dict, driver): """发送邮件""" # 创建测评人员名单页面对象 member_detail = MemberDetailPage(driver) # 勾选table头复选框 member_detail.click(member_detail.es.table_thead_checkbox) # 勾选不发送的测评人员 member_detail.unselect_email_checkbox(TestPackageList.member_email) # 点击发送邮件按钮 member_detail.click(member_detail.es.send_email_btn) # 点击确认按钮 member_detail.click(member_detail.es.layer_confirm_btn, timeout=10) time.sleep(2) # 再次点击确认按钮 member_detail.click(member_detail.es.layer_confirm_btn, timeout=10) # 点击查看发送状态按钮 member_detail.click(member_detail.es.send_status_btn) time.sleep(5) # 切换到上一层iframe member_detail.switch_parent_iframe() # 切换到发送状态iframe member_detail.switch_iframe(member_detail.es.send_status_iframe) # 获取到所有tr列表 trs = member_detail.get_eles(member_detail.es.send_status_table_tr) # 判断数量是否大于0 if len(trs) > 0: assert True else: assert False # 删除发送任务 # 勾选所有记录 member_detail.click(member_detail.es.table_thead_checkbox) # 点击删除按钮 member_detail.click(member_detail.es.delete_btn) # 点击确认按钮 member_detail.click(member_detail.es.layer_confirm_btn, timeout=10) # 关闭标签 # 创建home页面对象 home = HomePage(driver) # 关闭发送状态页面 home.close_tab(member_detail.es.tab_name_send_status) @pytest.mark.parametrize('data_read', ["memberDetail"], indirect=True) def test_send_sms(self, data_read: dict, driver): """发送短信""" # 创建测评人员名单页面对象 member_detail = MemberDetailPage(driver) # 勾选table头复选框 member_detail.click(member_detail.es.table_thead_checkbox) # 勾选不发送的测评人员 member_detail.unselect_email_checkbox(TestPackageList.member_email) # 点击发送邮件按钮 member_detail.click(member_detail.es.send_sms_btn) # 点击确认按钮 member_detail.click(member_detail.es.layer_confirm_btn, timeout=10) time.sleep(2) # 再次点击确认按钮 member_detail.click(member_detail.es.layer_confirm_btn, timeout=10) # 点击查看发送状态按钮 member_detail.click(member_detail.es.send_status_btn) time.sleep(5) # 切换到上一层iframe member_detail.switch_parent_iframe() # 切换到发送状态iframe member_detail.switch_iframe(member_detail.es.send_status_iframe) # 获取到所有tr列表 trs = member_detail.get_eles(member_detail.es.send_status_table_tr) # 判断数量是否大于0 if len(trs) > 0: assert True else: assert False # 删除发送任务 # 勾选所有记录 member_detail.click(member_detail.es.table_thead_checkbox) # 点击删除按钮 member_detail.click(member_detail.es.delete_btn) # 点击确认按钮 member_detail.click(member_detail.es.layer_confirm_btn, timeout=10) # 关闭标签 # 创建home页面对象 home = HomePage(driver) # 关闭发送状态页面 home.close_tab(member_detail.es.tab_name_send_status) @pytest.mark.parametrize('data_read', ["memberDetail"], indirect=True) def test_skip_report(self, data_read: dict, driver): """跳转查看报告页面""" # 创建测评人员名单页面对象 member_detail = MemberDetailPage(driver) # 点击查看报告 member_detail.click(member_detail.es.table_report_btn) time.sleep(5) # 返回上一层iframe member_detail.switch_parent_iframe() # 判断是否存在查看报告的iframe try: member_detail.get_ele(member_detail.es.report_iframe, timeout=10) # 关闭标签 # 创建home页面对象 home = HomePage(driver) # 关闭发送状态页面 home.close_tab(member_detail.es.tab_name_send_status) assert True except TimeoutException: assert False @pytest.mark.parametrize('data_read', ["memberDetail"], indirect=True) def test_remove_member(self, data_read: dict, driver): """ 删除测评人员 :param data_read: :param driver: :return: """ # 创建测评人员名单页面对象 member_detail = MemberDetailPage(driver) # 勾选table头复选框 member_detail.click(member_detail.es.table_thead_checkbox) # 勾选不删除的测评人员 member_detail.unselect_email_checkbox(TestPackageList.member_email) # 点击删除按钮 member_detail.click(member_detail.es.delete_btn) # 点击删除确认按钮 member_detail.click(member_detail.es.layer_confirm_btn, timeout=10) # 判断删除是否成功 try: member_detail.wait_visible(member_detail.es.operate_success_layer) assert True except TimeoutException: assert False # 创建home页面对象 home = HomePage(driver) # 关闭当前页面 home.close_tab(member_detail.es.tab_name_member_detail) @pytest.mark.parametrize('data_read', ["testPackageList"], indirect=True) def test_edit_prompt(self, data_read: dict, driver): """事中提示语修改""" # 创建测试包列表页面的对象 test_package_list = TestPackageListPage(driver) # 勾选新创建的测试包 test_package_list.select_package_checkbox(TestPackageList.test_package_name) # 点击事中提示语按钮 test_package_list.click(test_package_list.es.hint_btn) # 切换到iframe test_package_list.switch_iframe(test_package_list.es.hint_iframe) # 点击第四个输入框 test_package_list.click(test_package_list.es.hint_textarea) # 修改内容 test_package_list.fill(test_package_list.es.hint_modal_span, data_read["hint"]) # 点击保存 test_package_list.click(test_package_list.es.hint_modal_save_btn) # 返回上一层 test_package_list.switch_parent_iframe() # 点击确认按钮 test_package_list.click(test_package_list.es.layer_confirm_btn) # 判断操作成功弹窗是否存在 flag = True try: test_package_list.get_ele(test_package_list.es.operate_success_layer, timeout=20) except TimeoutException: flag = False if flag: time.sleep(2) # 获取到分享链接 url = test_package_list.get_share_url(TestPackageList.test_package_name) # 打开新标签页加载url test_package_list.goto_new_table(url) # 创建分享页面对象 share_add = ShareAddPage(driver) # 填写信息提交并获取测试链接 test_url = share_add.get_test_url(data_read["name"], data_read["email"]) # 当前页面打开链接 share_add.goto(test_url) # 点击提交按钮 share_add.click(share_add.es.submit_btn, timeout=10) # 获取数据 info = data_read["info"] # 填写基本信息并提交 share_add.fill_info(data_read["email"], info["position"], info["dept"]) # 创建answer对象 answer_page = MAQAnswerPage(driver) # 开始答题 flag = answer_page.answer(hint=data_read["hint"], question=data_read["question"]["MAQ"]) # 答完题关闭当前标签页 driver.close() # 切换到第一个标签页 answer_page.switch_window(0) # 网络异常重新答题 if flag == "网络异常": # 创建测试包列表页面的对象 test_package_list = TestPackageListPage(driver) # 勾选新创建的测试包 test_package_list.select_package_checkbox(TestPackageList.test_package_name) # 获取到分享链接 url = test_package_list.get_share_url(TestPackageList.test_package_name) # 打开新标签页加载url test_package_list.goto_new_table(url) # 创建分享页面对象 share_add = ShareAddPage(driver) # 填写信息提交并获取测试链接 test_url = share_add.get_test_url(data_read["name"], data_read["email"]) # 当前页面打开链接 share_add.goto(test_url) # 点击提交按钮 share_add.click(share_add.es.submit_btn, timeout=10) # 获取数据 info = data_read["info"] # 填写基本信息并提交 share_add.fill_info(data_read["email"], info["position"], info["dept"]) # 创建answer对象 answer_page = MAQAnswerPage(driver) # 开始答题 flag = answer_page.answer(hint=data_read["hint"], question=data_read["question"]["MAQ"]) # 答完题关闭当前标签页 driver.close() # 切换到第一个标签页 answer_page.switch_window(0) if flag is not None or not flag: assert flag @pytest.mark.parametrize('data_read', ["testPackageList"], indirect=True) def test_verify_member_status(self, data_read: dict, driver): """验证测评人员状态""" # 创建测试包列表页面的对象 test_package_list = TestPackageListPage(driver) # 点击测试包名称 test_package_list.click_package_name(TestPackageList.test_package_name) time.sleep(5) # 创建测评人员名单页面对象 member_detail = MemberDetailPage(driver) # 获取列表 tr = member_detail.get_eles(member_detail.es.table_data_tr)[0] # 获取当前日期 now = datetime.now() # 提取年、月、日 year = now.year month = now.month if month < 10: month = "0" + str(month) day = now.day if day < 10: day = "0" + str(day) # 拼接日期 date = f"{year}-{month}-{day}" # 获取答题开始时间 start_time = tr.find_element(By.XPATH, "td[6]").text # 获取答题结束时间 end_time = tr.find_element(By.XPATH, "td[7]").text # 获取状态 status = tr.find_element(By.XPATH, "td[12]/span").text # 验证 if date in start_time and date in end_time and status == "完成": assert True else: assert False # 创建home页面对象 home = HomePage(driver) # 关闭发送状态页面 home.close_tab(member_detail.es.tab_name_member_detail) @pytest.mark.parametrize('data_read', ["testPackageList"], indirect=True) def test_generate_export_report(self, data_read: dict, driver): """重新生成并导出报告""" # 创建测试包列表页面的对象 test_package_list = TestPackageListPage(driver) # 勾选测试包 test_package_list.select_package_checkbox(TestPackageList.test_package_name) # 点击评测报告按钮 test_package_list.click(test_package_list.es.report_btn) time.sleep(5) # 获取当前项目根路径 root_path = os.getcwd() # 拼接下载文件目录 dir_path = root_path + r"\download" # 获取目录下所有文件 old_file_names = [] for filename in os.listdir(dir_path): if os.path.isfile(os.path.join(dir_path, filename)): old_file_names.append(filename) # 创建report页面对象 report = ReportPage(driver) # 勾选所有记录 report.click(report.es.table_thead_checkbox) # 点击重新生成报告并导出 report.click(report.es.generate_export_btn) time.sleep(2) while True: try: # 加载框存在睡眠一秒 report.get_ele(report.es.layer_reload) time.sleep(1) except NoSuchElementException: # 不存在跳出循环 break i = 0 while i < data_read["downloadWaitTime"]: # 获取目录下所有文件 new_file_names = [] for filename in os.listdir(dir_path): if os.path.isfile(os.path.join(dir_path, filename)): new_file_names.append(filename) # 判断旧文件的数量与新文件的数量是否相同,相同则睡眠一秒 if len(old_file_names) == len(new_file_names): time.sleep(1) i += 1 continue else: # 否则直接退出循环 break else: assert False assert True @pytest.mark.parametrize('data_read', ["testPackageList"], indirect=True) def test_download_report(self, data_read: dict, driver): """下载已生成报告""" # 获取当前项目根路径 root_path = os.getcwd() # 拼接下载文件目录 dir_path = root_path + r"\download" # 获取目录下所有文件 old_file_names = [] for filename in os.listdir(dir_path): if os.path.isfile(os.path.join(dir_path, filename)): old_file_names.append(filename) # 创建report页面对象 report = ReportPage(driver) # 点击下载已生成报告 report.click(report.es.download_report_btn) time.sleep(2) while True: try: # 加载框存在睡眠一秒 report.get_ele(report.es.layer_reload) time.sleep(1) except NoSuchElementException: # 不存在跳出循环 break i = 0 while i < data_read["downloadWaitTime"]: # 获取目录下所有文件 new_file_names = [] for filename in os.listdir(dir_path): if os.path.isfile(os.path.join(dir_path, filename)): new_file_names.append(filename) # 判断旧文件的数量与新文件的数量是否相同,相同则睡眠一秒 if len(old_file_names) == len(new_file_names): time.sleep(1) i += 1 continue else: # 否则直接退出循环 break else: assert False assert True @pytest.mark.parametrize('data_read', ["testPackageList"], indirect=True) def test_export_report(self, data_read: dict, driver): """导出报告""" # 获取当前项目根路径 root_path = os.getcwd() # 拼接下载文件目录 dir_path = root_path + r"\download" # 获取目录下所有文件 old_file_names = listdir(dir_path) # 创建report页面对象 report = ReportPage(driver) # 点击下载已生成报告 report.click(report.es.export_btn) time.sleep(2) # 等待加载框消失 report.wait_layer_reload_hide() i = 0 while i < data_read["downloadWaitTime"]: # 获取目录下所有文件 new_file_names = listdir(dir_path) # 判断旧文件的数量与新文件的数量是否相同,相同则睡眠一秒 if len(old_file_names) == len(new_file_names): time.sleep(1) i += 1 continue else: # 否则直接退出循环 break else: assert False assert True @pytest.mark.parametrize('data_read', ["testPackageList"], indirect=True) def test_export_data(self, data_read: dict, driver): """导出测试数据""" # 获取当前项目根路径 root_path = os.getcwd() # 拼接下载文件目录 dir_path = root_path + r"\download" # 获取目录下所有文件 old_file_names = listdir(dir_path) # 创建report页面对象 report = ReportPage(driver) # 点击导出测试数据按钮 report.click(report.es.export_data_btn) time.sleep(2) # 等待加载框消失 report.wait_layer_reload_hide() new_file_names = [] i = 0 while i < data_read["downloadWaitTime"]: # 获取目录下所有文件 new_file_names = listdir(dir_path) # 判断旧文件的数量与新文件的数量是否相同,相同则睡眠一秒 if len(old_file_names) == len(new_file_names): time.sleep(1) i += 1 continue else: # 判断新的文件名是否包含未确认 for file_name in new_file_names: if "未确认" in file_name: new_file_names = listdir(dir_path) break else: # 否则直接退出循环 break else: assert False # 获取两个列表中的差集 result = set(old_file_names).symmetric_difference(set(new_file_names)) result_list = list(result) file_name = dir_path + "\\" + result_list[0] # 读取文件数据 data_list = read_excel(file_name, "Sheet1") # 导出的数据 excel_data = data_list[3] # 答题的数据 answer_data: dict = data_read["question"]["MAQ"] # 对比数据 for key in answer_data.keys(): if str(excel_data[key]) != str(excel_data[key]): print(key + "数据错误") # 创建report页面对象 report = ReportPage(driver) home = HomePage(driver) # 关闭标签页 home.close_tab(report.es.tab_name_report) assert False # 创建report页面对象 report = ReportPage(driver) home = HomePage(driver) # 关闭标签页 home.close_tab(report.es.tab_name_report) assert True @pytest.mark.parametrize('data_read', ["testPackageList"], indirect=True) def test_hr_resend(self, data_read: dict, driver): """HR邮件补发""" # 创建测试包列表页面的对象 test_package_list = TestPackageListPage(driver) # 点击测试包名称 test_package_list.click_package_name(TestPackageList.test_package_name) # 创建测评人员名单对象 member = MemberDetailPage(driver) # 点击查看报告邮件发送记录 member.click(member.es.report_send_log_btn) time.sleep(5) member.switch_parent_iframe() # 切换iframe member.switch_iframe(member.es.report_send_log_iframe) # 获取补发之前的重试次数 before_resend_number = int(member.get_ele(member.es.resend_number_text).text) # 点击全选按钮 member.click(member.es.table_thead_checkbox) # 点击HR邮件补发按钮 member.click(member.es.hr_resend_btn) # 点击确认按钮 member.click(member.es.layer_confirm_btn) time.sleep(2) # 等待加载完成 while True: try: # 加载框存在睡眠一秒 member.get_ele(member.es.layer_reload) time.sleep(1) except NoSuchElementException: # 不存在跳出循环 break time.sleep(2) # 获取补发之后的重试次数 after_resend_number = int(member.get_ele(member.es.resend_number_text).text) # 比较两次 if before_resend_number + 1 == after_resend_number: assert True else: assert False @pytest.mark.parametrize('data_read', ["testPackageList"], indirect=True) def test_member_resend(self, data_read: dict, driver): """测试者邮件补发""" # 创建测评人员名单对象 member = MemberDetailPage(driver) member.switch_parent_iframe() # 切换iframe member.switch_iframe(member.es.report_send_log_iframe) # 获取补发之前的重试次数 before_resend_number = int(member.get_ele(member.es.resend_number_text).text) # 点击全选按钮 member.click(member.es.table_thead_checkbox) # 点击测试者邮件补发按钮 member.click(member.es.member_resend_btn) # 点击确认按钮 member.click(member.es.layer_confirm_btn) time.sleep(2) # 等待加载完成 while True: try: # 加载框存在睡眠一秒 member.get_ele(member.es.layer_reload) time.sleep(1) except NoSuchElementException: # 不存在跳出循环 break time.sleep(2) # 获取补发之后的重试次数 after_resend_number = int(member.get_ele(member.es.resend_number_text).text) # 比较两次 if before_resend_number + 1 == after_resend_number: assert True else: assert False home = HomePage(driver) # 关闭标签页 home.close_tab(member.es.tab_name_report_send_log) home.close_tab(member.es.tab_name_member_detail) # @pytest.mark.parametrize('data_read', ["memberDetail"], indirect=True) # def test_recover_package(self, data_read: dict, driver): # # 创建测试包列表页面的对象 # test_package_list = TestPackageListPage(driver) # # 点击回收测试包按钮 # test_package_list.click(test_package_list.es.recover_btn) # # 点击确认按钮 # test_package_list.click(test_package_list.es.layer_confirm_btn) # time.sleep(2) # # 再次点击确认按钮 # test_package_list.click(test_package_list.es.layer_confirm_btn) # # 获取到分享链接 # url = test_package_list.get_share_url(TestPackageList.test_package_name) # # 打开新标签页加载url # test_package_list.goto_new_table(url) # # 创建分享页面对象 # share_add = ShareAddPage(driver) # # 填写信息提交并获取测试链接 # test_url = share_add.get_test_url(data_read["edit"]["name"], data_read["edit"]["email"]) # # 当前页面打开链接 # share_add.goto(test_url) # # 判断是否能够获取到提交按钮 # try: # share_add.get_ele(share_add.es.submit_btn, timeout=10) # assert False # except TimeoutException: # assert True # # # 关闭当前标签页 # driver.close() # # 切换到第一个标签页 # share_add.switch_window(0) if __name__ == '__main__': pytest.main(["-s", __file__])