diff --git a/Aops_Auto_Test/Aops_Web_Auto_Test/page_element/common.yaml b/Aops_Auto_Test/Aops_Web_Auto_Test/page_element/common.yaml index 5741ff5712098ac1042d7bc27f2dca663769a959..c381718c1aa04db4cfe5e3b539a672892f3cb55f 100644 --- a/Aops_Auto_Test/Aops_Web_Auto_Test/page_element/common.yaml +++ b/Aops_Auto_Test/Aops_Web_Auto_Test/page_element/common.yaml @@ -17,4 +17,10 @@ tr: 'xpath==//tbody//tr[not (@aria-hidden)]' next_page: 'xpath==//li[contains(@class,"ant-pagination-next") and @aria-disabled="false"]' table: 'class==ant-table-content' close_button: 'class==ant-modal-close' -error_info: 'xpath==//label[contains(@for, "****")]/parent::div/following-sibling::div[1]//div[@class="ant-form-item-explain-error"]' +error_info: 'xpath==//label[contains(@for, "****") or contains(@title, "****")]/parent::div/following-sibling::div[1]//div[@class="ant-form-item-explain-error"]' + +tbody_tds: 'xpath==//tbody[contains(@class,"ant-table-tbody")]/tr/td[****]' +first_page: 'xpath==//li[@title="1" and contains(@class,"ant-pagination-item")]' +last_page: 'xpath==//li[(@title="Next Page" or @title="下一页")and contains(@class,"ant-pagination-next")]/preceding-sibling::li[1]' +previous_page_common: 'xpath==//li[(@title="Previous Page" or @title="上一页")and contains(@class,"ant-pagination-prev")]' +next_page_common: 'xpath==//li[(@title="Next Page" or @title="下一页")and contains(@class,"ant-pagination-next")]' \ No newline at end of file diff --git a/Aops_Auto_Test/Aops_Web_Auto_Test/page_element/operation_magt.yaml b/Aops_Auto_Test/Aops_Web_Auto_Test/page_element/operation_magt.yaml index 12dbd82f42b6defbdcda0a5501f295824043877f..e8a6a96e5e9642f9528c621c764767451ad23e3f 100644 --- a/Aops_Auto_Test/Aops_Web_Auto_Test/page_element/operation_magt.yaml +++ b/Aops_Auto_Test/Aops_Web_Auto_Test/page_element/operation_magt.yaml @@ -6,4 +6,5 @@ new_operation: 'xpath==//span[text()="新建操作" or text()="New Operation"]/p operate_name_form: 'id==form_item_operate_name' operation_name_td: 'xpath==//div[text()="****" and parent::td[contains(@class, "ant-table-cell")]]' notification_content: 'class==ant-form-item-explain-error' +edit_operation: 'xpath==//div[contains(@class,"truncate") and text()="****"]/parent::td/following-sibling::td[2]//a[text()="编辑"]' delete_operation: 'xpath==//div[contains(@class,"truncate") and text()="****"]/parent::td/following-sibling::td[2]//a[text()="删除"]' \ No newline at end of file diff --git a/Aops_Auto_Test/Aops_Web_Auto_Test/page_object/base_page.py b/Aops_Auto_Test/Aops_Web_Auto_Test/page_object/base_page.py index b05688898253b3af86085d0f6c04e60ff0943cb7..ceae732c7b096c46bb11d6b294e572afa7b7c88c 100644 --- a/Aops_Auto_Test/Aops_Web_Auto_Test/page_object/base_page.py +++ b/Aops_Auto_Test/Aops_Web_Auto_Test/page_object/base_page.py @@ -1,5 +1,8 @@ # -*-coding:utf-8-*- +import time +from typing import List, Union, Tuple +from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver import ActionChains from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait @@ -11,7 +14,6 @@ from Aops_Web_Auto_Test.utils.LogUtil import my_log import pandas as pd - base_page = Element('common') driver = None @@ -21,7 +23,7 @@ class WebPage(object): def __init__(self, driver): self.driver = driver - self.log= my_log() + self.log = my_log() self.timeout = 10 self.wait = WebDriverWait(self.driver, self.timeout, 0.1) @@ -99,11 +101,19 @@ class WebPage(object): return number def input_text(self, locator, txt): + """ + 表单输入,不清理表单,如需要清理请调用 clear_before_input_text + """ + ele = self.find_element(locator) + ele.send_keys(txt) + self.log.info("在{}中输入文本:{}".format(locator, txt)) + + def clear_before_input_text(self, locator, txt): """输入(输入前先清空)""" ele = self.find_element(locator) ele.clear() ele.send_keys(txt) - self.log.info("在{}中输入文本:{}".format(locator, txt)) + self.log.info("输入文本:{}".format(txt)) def click_element(self, locator): """点击元素""" @@ -124,11 +134,11 @@ class WebPage(object): try: element = self.element_clickable(locator) - actions = ActionChains() + actions = ActionChains(self.driver) actions.move_to_element(element).click().perform() return True - except ( - NoSuchElementException, StaleElementReferenceException, ElementNotInteractableException, TimeoutException): + except (NoSuchElementException, StaleElementReferenceException, + ElementNotInteractableException, TimeoutException): pass raise ElementNotInteractableException(f"无法点击元素:{locator}") @@ -307,9 +317,112 @@ class WebPage(object): print(f"等待{locator} 元素不可点击") return None - def clear_before_input_text(self, locator, txt): - """输入(输入前先清空)""" - ele = self.find_element(locator) - ele.clear() - ele.send_keys(txt) - self.log.info("输入文本:{}".format(txt)) + +class CommonPagingWebPage(WebPage): + def get_table_column_data(self, column_index=1) -> List[WebElement]: + return self.find_elements(self.replace_locator_text(base_page['tbody_tds'], str(column_index))) + + @property + def first_page(self) -> Union[WebElement, None]: + first_page = self.find_element(base_page['first_page']) + if not isinstance(first_page, WebElement): + return None + return first_page + + @property + def last_page(self) -> Union[WebElement, None]: + last_page = self.find_element(base_page['last_page']) + if not isinstance(last_page, WebElement): + return None + return last_page + + @property + def previous_page_button(self) -> Union[WebElement, None]: + button = self.find_element(base_page['previous_page_common']) + if not isinstance(button, WebElement): + return None + return button + + @property + def next_page_button(self) -> Union[WebElement, None]: + button = self.find_element(base_page['next_page_common']) + if not isinstance(button, WebElement): + return None + return button + + def get_column_data_all_pages(self, column_index=1) -> Tuple[int, List]: + """ + + Args: + column_index: which column + + Returns: status_code, data_list + status code: 0 ok. others error. + """ + if not self.first_page: + print(f'not found first page button') + return 1, [] + + try: + self.first_page.click() + except Exception as e: + print(f'waring: first page click interrupted {e}') + self.first_page.click() + + column_data = [] + # 从第一页开始点下一页,直到点到下一页不能点 + while True: + table_data = self.get_table_column_data(column_index) + if table_data: + column_data.extend(list(map(lambda d: d.text, table_data))) + next_button = self.next_page_button + if not next_button: + print(f'not found next page button @get_table_column_data_all_pages') + break + if next_button.get_attribute('aria-disabled') != 'false': + break + next_button.click() + # 数据加载时间 + # TODO: 现在主流前端js框架, 对虚拟DOM的局部修改, 不会反应在document.readystate状态上。 + # 目前python没有很好的办法监控dom局部变更状态。 暂定用sleep等table更新完成。 + time.sleep(2) + + return 0, column_data + + def search_in_column(self, search_text, column_index=1) -> Union[WebElement, None]: + """ + + Args: + search_text: 查询的数据 + column_index: 查询的字段序号 + + Returns: WebElement | None + """ + if not self.first_page: + print(f'not found first page button') + return None + try: + self.first_page.click() + except Exception as e: + print(f'waring: first page click interrupted {e}') + self.first_page.click() + + while True: + table_data = self.get_table_column_data(column_index) + if table_data: + for td in table_data: + if td.text == search_text: + return td + + next_button = self.next_page_button + if not next_button: + print(f'not found next page button @get_table_column_data_all_pages') + break + if next_button.get_attribute('aria-disabled') != 'false': + break + next_button.click() + # 数据加载时间 + # TODO: 现在主流前端js框架, 对虚拟DOM的局部修改, 不会反应在document.readystate状态上。 + # 目前python没有很好的办法监控dom局部变更状态。 暂定用sleep等table更新完成。 + time.sleep(2) + return None diff --git a/Aops_Auto_Test/Aops_Web_Auto_Test/page_object/operation_magt.py b/Aops_Auto_Test/Aops_Web_Auto_Test/page_object/operation_magt.py index 1d2ab1dab652a444ecb43f79a026cd5a67a4a1a6..286bad4cf4cbe78d546bbffd001e4dc861264e02 100644 --- a/Aops_Auto_Test/Aops_Web_Auto_Test/page_object/operation_magt.py +++ b/Aops_Auto_Test/Aops_Web_Auto_Test/page_object/operation_magt.py @@ -1,8 +1,15 @@ +""" +operation management page objects +""" +from typing import List, Tuple, Union + from selenium.webdriver.remote.webelement import WebElement from selenium.common.exceptions import ElementClickInterceptedException +from Aops_Web_Auto_Test.common.createtestdata import correct_operation_name from Aops_Web_Auto_Test.common.readelement import Element -from Aops_Web_Auto_Test.page_object.base_page import WebPage +from Aops_Web_Auto_Test.page_object.base_page import CommonPagingWebPage +from Aops_Web_Auto_Test.page_object.script_magt import ScriptManagementPage from Aops_Web_Auto_Test.utils.LogUtil import my_log @@ -14,7 +21,7 @@ class ElementNotFoundError(Exception): pass -class OptMagtPage(WebPage): +class OptMagtPage(CommonPagingWebPage): @property def automated_execution_tag(self) -> WebElement: e = self.find_element(opt_page['automated_execution']) @@ -62,6 +69,10 @@ class OptMagtPage(WebPage): return onf def delete_operation(self, operation_name: str): + self.operation_management_tag.click() + operation = self.search_in_column(operation_name) + if not operation: + return del_button = self.find_element(self.replace_locator_text(opt_page['delete_operation'], operation_name)) del_button.click() try: @@ -72,12 +83,46 @@ class OptMagtPage(WebPage): def search_name_tabledata(self, name) -> WebElement: return self.find_element(self.replace_locator_text(opt_page['operation_name_td'], name)) - def get_notification_text(self): - text = self.element_text(opt_page['notification_content']) - log.info("获取notice的文本:{}".format(text)) - return text - - def add_new_operation(self, test_data): + def add_new_operation(self, test_data, return_operation=True) -> Union[WebElement, None]: + self.script_management_tag.click() + self.refresh() self.new_operation_tag.click() self.operate_name_form_input_tag.send_keys(test_data) self.click_confirm_button() + if not return_operation: + return None + return self.search_in_column(test_data) + + def get_used_operation_list(self, script_page: ScriptManagementPage) -> List[str]: + + script_page.enter_script_mgmt_page() + script_page.refresh() + used_ops = self.get_table_column_data(2) + if used_ops: + return list(map(lambda d: d.text, used_ops)) + return [] + + def get_unused_operation_name(self, script_page) -> str: + self.script_management_tag.click() + used_operation_names = self.get_used_operation_list(script_page) + + operation_name = correct_operation_name(5, 10) + for _ in range(15): + if operation_name not in used_operation_names: + break + operation_name = correct_operation_name(5, 10) + return operation_name + + def rename_operation(self, old_name: str, new_name: str) -> WebElement: + self.operation_management_tag.click() + edit_button = self.find_element(self.replace_locator_text(opt_page['edit_operation'], old_name)) + edit_button.click() + self.clear_before_input_text(opt_page['operate_name_form'], new_name) + self.click_confirm_button() + new_element = self.search_name_tabledata(new_name) + return new_element + + def add_unused_operation(self, script_page) -> Tuple[WebElement, str]: + operation_name = self.get_unused_operation_name(script_page) + new_operation = self.add_new_operation(operation_name) + return new_operation, operation_name diff --git a/Aops_Auto_Test/Aops_Web_Auto_Test/test_case/automated_execution/script_magt/test_new_operation.py b/Aops_Auto_Test/Aops_Web_Auto_Test/test_case/automated_execution/script_magt/test_new_operation.py index 140ddb8e8c154f0625d135d859d8f76eb7220fe9..5fa3a63c87461285b7fd2fbb238abb4831036f97 100644 --- a/Aops_Auto_Test/Aops_Web_Auto_Test/test_case/automated_execution/script_magt/test_new_operation.py +++ b/Aops_Auto_Test/Aops_Web_Auto_Test/test_case/automated_execution/script_magt/test_new_operation.py @@ -4,6 +4,7 @@ Subject: test Automated execution -> Script Management -> Operation Management - import pytest from selenium.common.exceptions import ElementClickInterceptedException +from Aops_Web_Auto_Test.page_object.script_magt import ScriptManagementPage from Aops_Web_Auto_Test.utils.LogUtil import my_log from Aops_Web_Auto_Test.page_object.operation_magt import OptMagtPage from Aops_Web_Auto_Test.common.createtestdata import correct_operation_name @@ -16,6 +17,11 @@ def page(drivers) -> OptMagtPage: return OptMagtPage(drivers) +@pytest.fixture(scope='class') +def script_page(drivers) -> ScriptManagementPage: + return ScriptManagementPage(drivers) + + class TestNewOperation: @pytest.fixture(autouse=True) def setup_and_clear(self, page): @@ -29,6 +35,8 @@ class TestNewOperation: log.warn(f"have a ElementClickInterceptedException @page.cancel_button.click") if self.operation_name: page.delete_operation(self.operation_name) + page.refresh() + page.page_resoure_load_complete() def test_new_operation_001_right_input(self, page): """ @@ -47,20 +55,22 @@ class TestNewOperation: """ 新建操作-空输入验证 """ + page.operation_management_tag.click() + page.refresh() page.new_operation_tag.click() page.click_confirm_button() self.need_cancel = 1 - assert page.get_notification_text() in ('请输入操作名称', 'Please enter the operate') + assert page.get_item_explain_error('form_item_operate_name') in ('请输入操作名称', 'Please enter the operate') def test_new_operation_004_too_long_input(self, page): """ 新建操作-超长输入验证 """ test_data = correct_operation_name(129, 500) - - page.add_new_operation(test_data) self.need_cancel = 1 - assert page.get_notification_text() in ('操作名称不能超过128字符', 'Operate Name cannot exceed 128 characters') + page.add_new_operation(test_data, return_operation=False) + notice_info = page.get_item_explain_error('form_item_operate_name') + assert notice_info in ('操作名称不能超过128字符', 'Operate Name cannot exceed 128 characters') def test_new_operation_005_duplicate_input(self, page): """ @@ -73,7 +83,7 @@ class TestNewOperation: td = page.search_name_tabledata(test_data) assert td - page.add_new_operation(test_data) + page.add_new_operation(test_data, return_operation=False) notice_text = page.get_top_right_notice_text().strip() @@ -94,6 +104,49 @@ class TestNewOperation: td = page.search_name_tabledata(test_data) assert td is None + def test_new_operation_007_proper_display(self, page, script_page): + """ + 新建操作 - 新增操作名在页面显示验证 + """ + test_data = correct_operation_name(5, 12) + # add operation name. + page.add_new_operation(test_data) + self.operation_name = test_data + + # check in script form + page.script_management_tag.click() + script_operation_names = script_page.get_script_operate_list() + + assert self.operation_name in script_operation_names + + def test_new_operation_008_edit_operation(self, page, script_page): + """ + 编辑操作-操作名未应用在脚本时编辑验证 + """ + new_operation, operation_name = page.add_unused_operation(script_page) + new_operation_name = operation_name + '_new' + + new_operation = page.rename_operation(operation_name, new_operation_name) + log.info(f'rename operation {operation_name} --> {new_operation_name}') + self.operation_name = new_operation_name + + assert new_operation + + def test_new_operation_011_delete_unused_operation(self, page, script_page): + """ + 删除操作-操作名未应用在脚本时删除验证 + """ + # add operation + new_operation, operation_name = page.add_unused_operation(script_page) + assert new_operation + # delete operation + page.delete_operation(operation_name) + page.refresh() + page.operation_management_tag.click() + + empty_operation = page.search_name_tabledata(operation_name) + assert empty_operation is None + if __name__ == '__main__': pytest.main(['Aops_Web_Auto_Test/test_case/automated_execution/script_magt/test_new_operation.py', '-s'])