From bc2a2ea0de6207126016f90dcad6e24c8114c67c Mon Sep 17 00:00:00 2001 From: netzulo Date: Sun, 14 Apr 2019 08:30:45 +0200 Subject: [PATCH 1/7] [qacode] starting issue #248 , WIP control_table + new class ControlTable + inherit of ControlForm + validate tag table tag at instantiation --- qacode/configs/settings.json | 3 +- qacode/core/loggers/logger_messages.py | 2 + qacode/core/webs/controls/__init__.py | 5 +- qacode/core/webs/controls/control_dropdown.py | 4 +- qacode/core/webs/controls/control_form.py | 7 +- qacode/core/webs/controls/control_table.py | 77 +++++++++++ .../001_functionals/suite_008_controltable.py | 125 ++++++++++++++++++ ..._008_pagebase.py => suite_009_pagebase.py} | 0 ...tlink.py => suite_010_reportertestlink.py} | 0 9 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 qacode/core/webs/controls/control_table.py create mode 100644 tests/001_functionals/suite_008_controltable.py rename tests/001_functionals/{suite_008_pagebase.py => suite_009_pagebase.py} (100%) rename tests/001_functionals/{suite_009_reportertestlink.py => suite_010_reportertestlink.py} (100%) diff --git a/qacode/configs/settings.json b/qacode/configs/settings.json index 502b1925..ab75a18d 100644 --- a/qacode/configs/settings.json +++ b/qacode/configs/settings.json @@ -51,7 +51,8 @@ "web_controls": { "control_base": false, "control_form": false, - "control_dropdown": false + "control_dropdown": false, + "control_table": false }, "web_pages": false, "benchmarks": true diff --git a/qacode/core/loggers/logger_messages.py b/qacode/core/loggers/logger_messages.py index fc4f2d31..34625408 100644 --- a/qacode/core/loggers/logger_messages.py +++ b/qacode/core/loggers/logger_messages.py @@ -72,3 +72,5 @@ CDD_DESELECTALL_LOADED = "ctl_form | dropdown_select: deselected all" # noqa:E501 CDD_LOADED = "ctl_form | ctl.dropdown property for tag element" # noqa:E501 +# ControlTable +CT_BADTAG = "Can't use this for not tag element" # noqa:E501 diff --git a/qacode/core/webs/controls/__init__.py b/qacode/core/webs/controls/__init__.py index 561b4ab3..4f854677 100755 --- a/qacode/core/webs/controls/__init__.py +++ b/qacode/core/webs/controls/__init__.py @@ -3,7 +3,10 @@ from qacode.core.webs.controls import control_base +from qacode.core.webs.controls import control_dropdown from qacode.core.webs.controls import control_form +from qacode.core.webs.controls import control_table -__all__ = ['control_base', 'control_form'] +__all__ = [ + 'control_base', 'control_dropdown', 'control_form', 'control_table'] diff --git a/qacode/core/webs/controls/control_dropdown.py b/qacode/core/webs/controls/control_dropdown.py index 9dabf322..4f9f512b 100644 --- a/qacode/core/webs/controls/control_dropdown.py +++ b/qacode/core/webs/controls/control_dropdown.py @@ -32,8 +32,8 @@ def __check_reload__form__(self): if it's neccessary reload element properties """ super(ControlDropdown, self).__check_reload__form__() - reload_dropdown_needed = not self.element or not self.dropdown - if reload_dropdown_needed: + reload_needed = not self.element or not self.dropdown + if reload_needed: self.reload(**self.settings) def reload(self, **kwargs): diff --git a/qacode/core/webs/controls/control_form.py b/qacode/core/webs/controls/control_form.py index df56537b..7ae8b670 100644 --- a/qacode/core/webs/controls/control_form.py +++ b/qacode/core/webs/controls/control_form.py @@ -18,6 +18,8 @@ class ControlForm(ControlBase): strict_tag = None # tag=select IS_DROPDOWN = None + # tag=select + IS_TABLE = None def __init__(self, bot, **kwargs): """Instance of ControlForm. Load properties from settings dict. @@ -97,14 +99,17 @@ def __load_strict_tag__(self, strict_tag): instance ControlForm specific properties """ self.IS_DROPDOWN = False + self.IS_TABLE = False self.strict_tag = strict_tag - valid_tags = ['select'] + valid_tags = ['select', 'table'] self.bot.log.debug(MSG.CF_STRICTTAG_LOADING) if self.strict_tag.value not in valid_tags: raise ControlException( msg="This tag can be loaded as strict_rule") if self.tag == HtmlTag.TAG_SELECT.value: self.IS_DROPDOWN = True + if self.tag == HtmlTag.TAG_TABLE.value: + self.IS_TABLE = True self.bot.log.debug(MSG.CF_STRICTTAG_LOADED) return True diff --git a/qacode/core/webs/controls/control_table.py b/qacode/core/webs/controls/control_table.py new file mode 100644 index 00000000..ac7f417e --- /dev/null +++ b/qacode/core/webs/controls/control_table.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +"""Package module qacode.core.webs.control_form""" + + +from qacode.core.exceptions.control_exception import ControlException +from qacode.core.loggers import logger_messages as MSG +from qacode.core.webs.controls.control_base import ControlBase +from qacode.core.webs.controls.control_form import ControlForm + + +class ControlTable(ControlForm): + """TODO: doc class""" + + _table = None + + def __init__(self, bot, **kwargs): + """Instance of ControlForm. Load properties from settings dict. + Some elements need to search False to be search at future + """ + kwargs.update({"instance": "ControlTable"}) + strict_rules = kwargs.get("strict_rules") + if not bool(strict_rules): + strict_rules.append( + {"tag": "table", "type": "tag", "severity": "hight"}) + super(ControlTable, self).__init__(bot, **kwargs) + if not self.IS_TABLE and self.tag is not None: + raise ControlException(msg=MSG.CT_BADTAG) + self.bot.log.debug(MSG.CDD_LOADED) + + def __load_table__(self, element=None): + """Allow to load all TR > TD items from a TABLE element + + Examples: + Use case 1. TABLE > (TR > TH)+(TR > TD) + Use case 2. TABLE > (THEAD > (TR > TH))+(TBODY > (TR > TH)) + """ + if element is None: + element = self.element + self._table = ControlBase(self.bot, **{ + "selector": self.selector, + "element": element}) + # Load rows + # thead = self._table.find_child("thead") + # tbody = self._table.find_child("tbody") + # Load columns + # Load cells + # raise NotImplementedError("TODO: WIP zone") + + def __check_reload__form__(self): + """Allow to check before methods calls to ensure + if it's neccessary reload element properties + """ + super(ControlTable, self).__check_reload__form__() + reload_needed = not self.element or not self.table + if reload_needed: + self.reload(**self.settings) + if not self.IS_TABLE and self.tag is not None: + raise ControlException(msg=MSG.CT_BADTAG) + + def reload(self, **kwargs): + """Reload 'self.settings' property:dict and call to instance + logic with new configuration + """ + super(ControlTable, self).reload(**kwargs) + self.__load_table__(element=self.element) + + @property + def table(self): + """GETTER for 'table' property""" + return self._table + + @table.setter + def table(self, value): + """SETTER for 'table' property""" + if value is None or not isinstance(value, ControlBase): + raise ControlException("Can't set not 'Control' instance") + self.__load_table__(element=value) diff --git a/tests/001_functionals/suite_008_controltable.py b/tests/001_functionals/suite_008_controltable.py new file mode 100644 index 00000000..d2a792b2 --- /dev/null +++ b/tests/001_functionals/suite_008_controltable.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +"""Testsuite for package qacode.core.webs.controls""" + + +import pytest +from qacode.core.exceptions.control_exception import ControlException +from qacode.core.testing.test_info import TestInfoBotUnique +from qacode.core.webs.controls.control_base import ControlBase +from qacode.core.webs.controls.control_table import ControlTable +from qautils.files import settings +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support.ui import Select + + +SETTINGS = settings(file_path="qacode/configs/") +SKIP_CONTROLS = SETTINGS['tests']['skip']['web_controls']['control_table'] +SKIP_CONTROLS_MSG = 'web_controls DISABLED by config file' + + +class TestControlDropdown(TestInfoBotUnique): + """Test Suite for ControlBase class""" + + # app from config + app = None + # page from config: app + page = None + url = None + # page from config: app + page_inputs = None + url_inputs = None + # elements from config: page + form_login = None + txt_username = None + txt_password = None + btn_submit = None + # elements from config: page_data + dd_base = None + dd_menu_data = None + dd_menu_data_lists = None + tbl_ok = None + + @classmethod + def setup_class(cls, **kwargs): + """TODO: doc method""" + super(TestControlDropdown, cls).setup_class( + config=settings(file_path="qacode/configs/"), + skip_force=SKIP_CONTROLS) + cls.add_property('app', cls.settings_app('qadmin')) + # page + cls.add_property('page', cls.settings_page('qacode_login')) + cls.add_property('url', cls.page.get('url')) + cls.add_property('form_login', cls.settings_control('form_login')) + cls.add_property('txt_username', cls.settings_control('txt_username')) + cls.add_property('txt_password', cls.settings_control('txt_password')) + cls.add_property('btn_submit', cls.settings_control('btn_submit')) + # page_inputs + cls.add_property('page_inputs', cls.settings_page('qacode_inputs')) + cls.add_property('url_inputs', cls.page_inputs.get('url')) + cls.add_property('dd_base', cls.settings_control('dd_base')) + cls.add_property('dd_menu_data', cls.settings_control('dd_menu_data')) + cls.add_property( + 'dd_menu_data_lists', cls.settings_control('dd_menu_data_lists')) + cls.add_property('tbl_ok', cls.settings_control('tbl_ok')) + + def setup_method(self, test_method): + """Configure self.attribute""" + super(TestControlDropdown, self).setup_method( + test_method, config=settings(file_path="qacode/configs/")) + + def setup_login_to_data(self): + """Do login before to exec some testcases""" + # setup_login + self.bot.navigation.get_url(self.page.get('url'), wait_for_load=10) + txt_username = self.bot.navigation.find_element( + self.txt_username.get("selector")) + txt_password = self.bot.navigation.find_element( + self.txt_password.get("selector")) + btn_submit = self.bot.navigation.find_element( + self.btn_submit.get("selector")) + self.bot.navigation.ele_write(txt_username, "admin") + self.bot.navigation.ele_write(txt_password, "admin") + self.bot.navigation.ele_click(btn_submit) + self.bot.navigation.ele_click( + self.bot.navigation.find_element_wait( + self.dd_menu_data.get("selector"))) + self.bot.navigation.ele_click( + self.bot.navigation.find_element_wait( + self.dd_menu_data_lists.get("selector"))) + # end setup_login + + @pytest.mark.skipIf(SKIP_CONTROLS, SKIP_CONTROLS_MSG) + @pytest.mark.parametrize("on_instance_search", [True, False]) + @pytest.mark.parametrize("auto_reload", [True, False]) + @pytest.mark.parametrize("strict_rules", [ + [{"tag": "table", "type": "tag", "severity": "hight"}] + ]) + def test_controltable_instance(self, on_instance_search, + strict_rules, auto_reload): + """Testcase: test_controltable_instance""" + self.setup_login_to_data() + cfg = self.tbl_ok + cfg.update({ + "instance": "ControlTable", + "on_instance_search": on_instance_search, + "auto_reload": auto_reload, + "strict_rules": strict_rules + }) + # functional testcases + ctl = ControlTable(self.bot, **cfg) + self.assert_is_instance(ctl, ControlTable) + self.assert_equals(ctl.selector, cfg.get('selector')) + self.assert_equals(ctl.instance, cfg.get('instance')) + self.assert_equals(ctl.name, cfg.get('name')) + self.assert_equals(ctl.locator, 'css selector') + self.assert_equals( + ctl.on_instance_search, cfg.get('on_instance_search')) + self.assert_equals(ctl.auto_reload, cfg.get('auto_reload')) + self.assert_equals( + len(ctl.strict_rules), len(cfg.get('strict_rules'))) + if on_instance_search: + self.assert_is_instance(ctl.element, WebElement) + if auto_reload is not None: + self.assert_none(ctl.table) + ctl.reload(**ctl.settings) + self.assert_is_instance(ctl.table, ControlBase) diff --git a/tests/001_functionals/suite_008_pagebase.py b/tests/001_functionals/suite_009_pagebase.py similarity index 100% rename from tests/001_functionals/suite_008_pagebase.py rename to tests/001_functionals/suite_009_pagebase.py diff --git a/tests/001_functionals/suite_009_reportertestlink.py b/tests/001_functionals/suite_010_reportertestlink.py similarity index 100% rename from tests/001_functionals/suite_009_reportertestlink.py rename to tests/001_functionals/suite_010_reportertestlink.py From 55398a476855ddf81357aabbc9e33b8443a1e558 Mon Sep 17 00:00:00 2001 From: netzulo Date: Sun, 14 Apr 2019 10:04:12 +0200 Subject: [PATCH 2/7] [qacode] more WIP changes for control_table #248 + added new property rows allow to acces to tr tags as rows at list of ControlBase array + tests --- qacode/core/loggers/logger_messages.py | 12 ++-- qacode/core/webs/controls/control_table.py | 68 +++++++++++++++++-- .../001_functionals/suite_008_controltable.py | 10 ++- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/qacode/core/loggers/logger_messages.py b/qacode/core/loggers/logger_messages.py index 34625408..8f086861 100644 --- a/qacode/core/loggers/logger_messages.py +++ b/qacode/core/loggers/logger_messages.py @@ -68,9 +68,11 @@ CDD_SELECT_LOADED = "ctl_dd | select: selected" # noqa:E501 CDD_SELECT_LOADING = "ctl_dd | deselect: deselecting..." # noqa:E501 CDD_DESESELECT_LOADED = "ctl_dd | select: deselected" # noqa:E501 -CDD_DESELECTALL_LOADING = "ctl_form | dropdown_select: deselecting all..." # noqa:E501 -CDD_DESELECTALL_LOADED = "ctl_form | dropdown_select: deselected all" # noqa:E501 -CDD_LOADED = "ctl_form | ctl.dropdown property for tag element" # noqa:E501 +CDD_DESELECTALL_LOADING = "ctl_dd | dropdown_select: deselecting all..." # noqa:E501 +CDD_DESELECTALL_LOADED = "ctl_dd | dropdown_select: deselected all" # noqa:E501 +CDD_LOADED = "ctl_dd | ctl.dropdown property for tag element" # noqa:E501 # ControlTable -CT_BADTAG = "Can't use this for not
tag element" # noqa:E501 +CT_BADTAG = "ctl_tb | Can't use this for not
tag element" # noqa:E501 +CT_LOADED = "ctl_tb | ctl.table property for
" # noqa:E501 +CT_TBLNOTCHILD = "ctl_tb | this table haven't got '{}' selector" # noqa:E501 diff --git a/qacode/core/webs/controls/control_table.py b/qacode/core/webs/controls/control_table.py index ac7f417e..6ac0c592 100644 --- a/qacode/core/webs/controls/control_table.py +++ b/qacode/core/webs/controls/control_table.py @@ -3,15 +3,25 @@ from qacode.core.exceptions.control_exception import ControlException +from qacode.core.exceptions.core_exception import CoreException from qacode.core.loggers import logger_messages as MSG from qacode.core.webs.controls.control_base import ControlBase from qacode.core.webs.controls.control_form import ControlForm +from selenium.common.exceptions import WebDriverException class ControlTable(ControlForm): """TODO: doc class""" _table = None + _rows = None + + # public properties + + caption = None + thead = None + tfoot = None + tbodies = None def __init__(self, bot, **kwargs): """Instance of ControlForm. Load properties from settings dict. @@ -25,11 +35,14 @@ def __init__(self, bot, **kwargs): super(ControlTable, self).__init__(bot, **kwargs) if not self.IS_TABLE and self.tag is not None: raise ControlException(msg=MSG.CT_BADTAG) - self.bot.log.debug(MSG.CDD_LOADED) + self.bot.log.debug(MSG.CT_LOADED) def __load_table__(self, element=None): """Allow to load all TR > TD items from a TABLE element + Before structure some checks are necessary for some children elements: + caption {ControlBase}-- optional
element + Examples: Use case 1. TABLE > (TR > TH)+(TR > TD) Use case 2. TABLE > (THEAD > (TR > TH))+(TBODY > (TR > TH)) @@ -39,13 +52,51 @@ def __load_table__(self, element=None): self._table = ControlBase(self.bot, **{ "selector": self.selector, "element": element}) - # Load rows - # thead = self._table.find_child("thead") - # tbody = self._table.find_child("tbody") - # Load columns - # Load cells + # Preload + self.tbodies = self.__try__("find_children", "tbodies") + is_html5 = False + if bool(self.tbodies): + is_html5 = True + self.caption = self.__try__("find_child", "caption") + self.thead = self.__try__("find_child", "thead") + self.tfoot = self.__try__("find_child", "tfoot") + # Load column headers + if not is_html5: + columns = self._table.find_children("tr :not(td)") # noqa + for column in columns: + column.name = column.text + rows = [] + ctls_rows = self._table.find_children("tr") + for index, ctl_row in enumerate(ctls_rows): + if index == 0: + rows.append(self.__get_row__(ctl_row, "th")) + else: + rows.append(self.__get_row__(ctl_row, "td")) + self._rows = rows + else: + # is_hmtl5==True + # raise NotImplementedError("TODO: WIP zone") + pass # raise NotImplementedError("TODO: WIP zone") + def __get_row__(self, ctl_row, selector): + """WARNING: this method just can be used from __load_table__""" + row = [] + for cell in ctl_row.find_children(selector): + text = cell.get_text() + cell.settings.update({"name": text}) + cell.name = text + row.append(cell) + return row + + def __try__(self, method, selector): + """Allow to exec some method to handle exception""" + try: + return getattr(self._table, method)(selector) + except (ControlException, CoreException, WebDriverException): + self.bot.log.debug(MSG.CT_TBLNOTCHILD.format(selector)) + return None + def __check_reload__form__(self): """Allow to check before methods calls to ensure if it's neccessary reload element properties @@ -75,3 +126,8 @@ def table(self, value): if value is None or not isinstance(value, ControlBase): raise ControlException("Can't set not 'Control' instance") self.__load_table__(element=value) + + @property + def rows(self): + """GETTER for 'rows' property""" + return self._rows diff --git a/tests/001_functionals/suite_008_controltable.py b/tests/001_functionals/suite_008_controltable.py index d2a792b2..3529306b 100644 --- a/tests/001_functionals/suite_008_controltable.py +++ b/tests/001_functionals/suite_008_controltable.py @@ -3,13 +3,11 @@ import pytest -from qacode.core.exceptions.control_exception import ControlException from qacode.core.testing.test_info import TestInfoBotUnique from qacode.core.webs.controls.control_base import ControlBase from qacode.core.webs.controls.control_table import ControlTable from qautils.files import settings from selenium.webdriver.remote.webelement import WebElement -from selenium.webdriver.support.ui import Select SETTINGS = settings(file_path="qacode/configs/") @@ -123,3 +121,11 @@ def test_controltable_instance(self, on_instance_search, self.assert_none(ctl.table) ctl.reload(**ctl.settings) self.assert_is_instance(ctl.table, ControlBase) + self.assert_is_instance(ctl.rows, list) + # Use case 1. not html5:: TABLE > (TR > TH)+(TR > TD) + for row in ctl.rows: + self.assert_is_instance(row, list) + for cell in row: + self.assert_is_instance(cell, ControlBase) + # Use case 2. html5:: TABLE > (THEAD > (TR > TH))+(TBODY > (TR > TH)) + # raise NotImplementedError("TODO: WIP zone") From 1bea511a236a2ef0b6c62c1d303929dbf8251a1e Mon Sep 17 00:00:00 2001 From: netzulo Date: Thu, 18 Apr 2019 22:55:57 +0200 Subject: [PATCH 3/7] [qacode] more fixes for issue #248 + some exception messages moved to logger_messages + controls can handle None strict_tags values + ctl_dd refactor , added internal check for some methods + fix last commit refactor for ctl_form, bad call + ctl_tb added html5 structure data tags load + added more tests for ctl_tb --- qacode/core/loggers/logger_messages.py | 4 ++ qacode/core/webs/controls/control_dropdown.py | 34 ++++++++--------- qacode/core/webs/controls/control_form.py | 5 +-- qacode/core/webs/controls/control_table.py | 25 ++++++------- .../001_functionals/suite_008_controltable.py | 37 +++++++++++++++++-- 5 files changed, 68 insertions(+), 37 deletions(-) diff --git a/qacode/core/loggers/logger_messages.py b/qacode/core/loggers/logger_messages.py index 8f086861..427d6a4f 100644 --- a/qacode/core/loggers/logger_messages.py +++ b/qacode/core/loggers/logger_messages.py @@ -63,6 +63,7 @@ CF_PARSERULES_LOADED = "ctl_form | parse_rules: parsed" # noqa:E501 CF_RELOAD_LOADED = "ctl_form | reload: reloaded ctl" # noqa:E501 CF_STRICT_ATTRS_RAISES = "Validation raises for strict_attrs for this element: control={}, strict_attrs=[{}]" # noqa:E501 +CF_BADTAG = "ctl_form | This tag can't be loaded as strict_rule" # noqa:E501 # ControlDropdown CDD_SELECT_LOADING = "ctl_dd | select: selecting..." # noqa:E501 CDD_SELECT_LOADED = "ctl_dd | select: selected" # noqa:E501 @@ -72,7 +73,10 @@ CDD_DESELECTALL_LOADED = "ctl_dd | dropdown_select: deselected all" # noqa:E501 CDD_LOADED = "ctl_dd | ctl.dropdown property for tag element" # noqa:E501 +CDD_BADPARAMS = "ctl_dd | Can't use this function with all flags with True values" # noqa:E501 +CDD_BADINDEXTYPE = "ctl_dd | index must be an int value" # noqa:E501 # ControlTable CT_BADTAG = "ctl_tb | Can't use this for not tag element" # noqa:E501 CT_LOADED = "ctl_tb | ctl.table property for
" # noqa:E501 CT_TBLNOTCHILD = "ctl_tb | this table haven't got '{}' selector" # noqa:E501 +CT_TBL2ORMORETBODIES = "2 or more tbodys not supported, Open an issue on Github" # noqa:E501 diff --git a/qacode/core/webs/controls/control_dropdown.py b/qacode/core/webs/controls/control_dropdown.py index 4f9f512b..b9178e90 100644 --- a/qacode/core/webs/controls/control_dropdown.py +++ b/qacode/core/webs/controls/control_dropdown.py @@ -18,10 +18,11 @@ def __init__(self, bot, **kwargs): Some elements need to search False to be search at future """ kwargs.update({"instance": "ControlDropdown"}) - strict_rules = kwargs.get("strict_rules") + strict_rules = kwargs.get("strict_rules") or [] if not bool(strict_rules): strict_rules.append( {"tag": "select", "type": "tag", "severity": "hight"}) + kwargs.update({"strict_rules": strict_rules}) super(ControlDropdown, self).__init__(bot, **kwargs) if not self.IS_DROPDOWN and self.tag is not None: raise ControlException(msg=MSG.CDD_BADTAG) @@ -43,6 +44,16 @@ def reload(self, **kwargs): super(ControlDropdown, self).reload(**kwargs) self.dropdown = Select(self.element) + def __check_dropdown__(self, text, by_value=False, by_index=False): + """Internal funcionality for select/deselect methods""" + self.__check_reload__form__() + if self.dropdown is None: + raise ControlException(msg=MSG.CDD_BADTAG) + if by_value and by_index: + raise ControlException(msg=MSG.CDD_BADPARAMS) + if by_index and not isinstance(text, int): + raise ControlException(msg=MSG.CDD_BADINDEXTYPE) + def select(self, text, by_value=False, by_index=False): """The Select class only works with tags which have select tags. Using the Index of Dropdown (int) @@ -63,19 +74,12 @@ def select(self, text, by_value=False, by_index=False): ControlException -- if tag is not 'select' ControlException -- if all flags are 'True' """ + self.__check_dropdown__( + text, by_value=by_value, by_index=by_index) self.bot.log.debug(MSG.CDD_SELECT_LOADING) - self.__check_reload__form__() - if self.dropdown is None: - raise ControlException( - msg="Element must be dropdown, tag={})".format(self.tag)) - if by_value and by_index: - raise ControlException( - msg="Can't use this function with all flags with True values") if by_value: self.dropdown.select_by_value(text) elif by_index: - if not isinstance(text, int): - raise ControlException(msg="index must be an int value") self.dropdown.select_by_index(int(text)) else: self.dropdown.select_by_visible_text(text) @@ -102,15 +106,11 @@ def deselect(self, text, by_value=False, by_index=False): ControlException -- if all flags are 'True' """ self.bot.log.debug(MSG.CDD_SELECT_LOADING) - self.__check_reload__form__() - if by_value and by_index: - raise ControlException( - msg="Can't use this function with all flags with True values") + self.__check_dropdown__( + text, by_value=by_value, by_index=by_index) if by_value: self.dropdown.deselect_by_value(text) elif by_index: - if not isinstance(text, int): - raise ControlException(msg="index must be an int value") self.dropdown.deselect_by_index(int(text)) else: self.dropdown.deselect_by_visible_text(text) @@ -124,6 +124,6 @@ def deselect_all(self): ControlException -- if tag is not 'select' """ self.bot.log.debug(MSG.CDD_DESELECTALL_LOADING) - self.__check_reload__form__() + self.__check_dropdown__('') self.dropdown.deselect_all() self.bot.log.debug(MSG.CDD_DESELECTALL_LOADED) diff --git a/qacode/core/webs/controls/control_form.py b/qacode/core/webs/controls/control_form.py index 7ae8b670..cef675a6 100644 --- a/qacode/core/webs/controls/control_form.py +++ b/qacode/core/webs/controls/control_form.py @@ -56,7 +56,7 @@ def __load__rules__(self, enabled=False): """Validate strict rules for each type of StricRule""" self.bot.log.debug(MSG.CF_PARSERULES_LOADING) if not enabled: - self.warning(MSG.CF_STRICT_DISABLED) + self.bot.log.warning(MSG.CF_STRICT_DISABLED) return False typed_rules = list() # parsing rules > to enums > to instance @@ -104,8 +104,7 @@ def __load_strict_tag__(self, strict_tag): valid_tags = ['select', 'table'] self.bot.log.debug(MSG.CF_STRICTTAG_LOADING) if self.strict_tag.value not in valid_tags: - raise ControlException( - msg="This tag can be loaded as strict_rule") + raise ControlException(msg=MSG.CF_BADTAG) if self.tag == HtmlTag.TAG_SELECT.value: self.IS_DROPDOWN = True if self.tag == HtmlTag.TAG_TABLE.value: diff --git a/qacode/core/webs/controls/control_table.py b/qacode/core/webs/controls/control_table.py index 6ac0c592..d7dbcf1b 100644 --- a/qacode/core/webs/controls/control_table.py +++ b/qacode/core/webs/controls/control_table.py @@ -28,10 +28,11 @@ def __init__(self, bot, **kwargs): Some elements need to search False to be search at future """ kwargs.update({"instance": "ControlTable"}) - strict_rules = kwargs.get("strict_rules") + strict_rules = kwargs.get("strict_rules") or [] if not bool(strict_rules): strict_rules.append( {"tag": "table", "type": "tag", "severity": "hight"}) + kwargs.update({"strict_rules": strict_rules}) super(ControlTable, self).__init__(bot, **kwargs) if not self.IS_TABLE and self.tag is not None: raise ControlException(msg=MSG.CT_BADTAG) @@ -54,17 +55,20 @@ def __load_table__(self, element=None): "element": element}) # Preload self.tbodies = self.__try__("find_children", "tbodies") - is_html5 = False if bool(self.tbodies): - is_html5 = True + # table html5 structure self.caption = self.__try__("find_child", "caption") self.thead = self.__try__("find_child", "thead") self.tfoot = self.__try__("find_child", "tfoot") - # Load column headers - if not is_html5: - columns = self._table.find_children("tr :not(td)") # noqa - for column in columns: - column.name = column.text + if len(self.tbodies) > 1: + raise ControlException(MSG.CT_TBL2ORMORETBODIES) + rows = [] + rows.append(self.__get_row__(self.thead.find_child("tr"), "th")) + for ctl_row in self.tbodies[0].find_children("tr"): + rows.append(self.__get_row__(ctl_row, "td")) + self._rows = rows + else: + # table (TR > TH)+(TR > TD) + self.assert_lower(len(ctl.rows), 3) for row in ctl.rows: self.assert_is_instance(row, list) + self.assert_lower(len(row), 2) for cell in row: self.assert_is_instance(cell, ControlBase) # Use case 2. html5:: TABLE > (THEAD > (TR > TH))+(TBODY > (TR > TH)) - # raise NotImplementedError("TODO: WIP zone") + + @pytest.mark.skipIf(SKIP_CONTROLS, SKIP_CONTROLS_MSG) + @pytest.mark.parametrize("strict_rules", [None]) + def test_controltable_instance_raises(self, strict_rules): + """Testcase: test_controltable_instance_raises""" + self.setup_login_to_data() + cfg = self.tbl_ok + cfg.update({ + "instance": "ControlTable", + "strict_rules": strict_rules, + "selector": "span" + }) + # functional testcases + ctl = ControlTable(self.bot, **cfg) + self.assert_is_instance(ctl, ControlTable) + self.assert_equals(ctl.selector, cfg.get('selector')) + self.assert_equals(ctl.instance, cfg.get('instance')) + self.assert_equals(ctl.name, cfg.get('name')) + self.assert_equals(ctl.locator, 'css selector') + + @pytest.mark.skipIf(SKIP_CONTROLS, SKIP_CONTROLS_MSG) + def test_controltable_loadtable_ok(self): + """Testcase: test_controltable_loadtable""" + self.setup_login_to_data() + ctl = ControlTable(self.bot, **self.tbl_ok) + ctl.__load_table__() From fff45240cd4ebb1928d0abb9463ee945ca7e6aa6 Mon Sep 17 00:00:00 2001 From: netzulo Date: Fri, 19 Apr 2019 10:07:04 +0200 Subject: [PATCH 4/7] [qacode] some ControlTable fixes + complexity + #248 ControlTable + #265 Complexity --- qacode/core/webs/controls/control_table.py | 58 +++++++++++-------- .../001_functionals/suite_008_controltable.py | 50 +++++++++++----- 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/qacode/core/webs/controls/control_table.py b/qacode/core/webs/controls/control_table.py index d7dbcf1b..5ab32a01 100644 --- a/qacode/core/webs/controls/control_table.py +++ b/qacode/core/webs/controls/control_table.py @@ -8,6 +8,7 @@ from qacode.core.webs.controls.control_base import ControlBase from qacode.core.webs.controls.control_form import ControlForm from selenium.common.exceptions import WebDriverException +from selenium.webdriver.remote.webelement import WebElement class ControlTable(ControlForm): @@ -54,29 +55,39 @@ def __load_table__(self, element=None): "selector": self.selector, "element": element}) # Preload - self.tbodies = self.__try__("find_children", "tbodies") + self.tbodies = self.__try__("find_children", "tbody") if bool(self.tbodies): - # table html5 structure - self.caption = self.__try__("find_child", "caption") - self.thead = self.__try__("find_child", "thead") - self.tfoot = self.__try__("find_child", "tfoot") - if len(self.tbodies) > 1: - raise ControlException(MSG.CT_TBL2ORMORETBODIES) - rows = [] - rows.append(self.__get_row__(self.thead.find_child("tr"), "th")) - for ctl_row in self.tbodies[0].find_children("tr"): - rows.append(self.__get_row__(ctl_row, "td")) - self._rows = rows + self._rows = self.__load_table_html5__() else: - # table (TR > TH)+(TR > TD) + """ + rows = [] + ctls_rows = self._table.find_children("tr") + for index, ctl_row in enumerate(ctls_rows): + if index == 0: + rows.append(self.__get_row__(ctl_row, "th")) + else: + rows.append(self.__get_row__(ctl_row, "td")) + return rows + + def __load_table_html5__(self): + """Allow to load table with this structure + TABLE > (THEAD > (TR > TH))+(TBODY > (TR > TH)) + """ + self.caption = self.__try__("find_child", "caption") + self.thead = self.__try__("find_child", "thead") + self.tfoot = self.__try__("find_child", "tfoot") + if len(self.tbodies) > 1: + raise ControlException(MSG.CT_TBL2ORMORETBODIES) + rows = [] + rows.append(self.__get_row__(self.thead.find_child("tr"), "th")) + for ctl_row in self.tbodies[0].find_children("tr"): + rows.append(self.__get_row__(ctl_row, "td")) + return rows def __get_row__(self, ctl_row, selector): """WARNING: this method just can be used from __load_table__""" @@ -122,11 +133,12 @@ def table(self): @table.setter def table(self, value): """SETTER for 'table' property""" - if value is None or not isinstance(value, ControlBase): - raise ControlException("Can't set not 'Control' instance") + if value is None or not isinstance(value, WebElement): + raise ControlException("Can't set not 'WebElement' instance") self.__load_table__(element=value) @property def rows(self): """GETTER for 'rows' property""" + self.__check_reload__form__() return self._rows diff --git a/tests/001_functionals/suite_008_controltable.py b/tests/001_functionals/suite_008_controltable.py index 1fcfa1eb..073aa461 100644 --- a/tests/001_functionals/suite_008_controltable.py +++ b/tests/001_functionals/suite_008_controltable.py @@ -59,11 +59,13 @@ def setup_class(cls, **kwargs): cls.add_property( 'dd_menu_data_lists', cls.settings_control('dd_menu_data_lists')) cls.add_property('tbl_ok', cls.settings_control('tbl_ok')) + cls.add_property('tbl_html5_ok', cls.settings_control('tbl_html5_ok')) def setup_method(self, test_method): """Configure self.attribute""" super(TestControlDropdown, self).setup_method( test_method, config=settings(file_path="qacode/configs/")) + self.setup_login_to_data() def setup_login_to_data(self): """Do login before to exec some testcases""" @@ -93,11 +95,11 @@ def setup_login_to_data(self): [{"tag": "table", "type": "tag", "severity": "hight"}], [] ]) + @pytest.mark.parametrize("ctl_name", ['tbl_ok', 'tbl_html5_ok']) def test_controltable_instance(self, on_instance_search, - strict_rules, auto_reload): + strict_rules, auto_reload, ctl_name): """Testcase: test_controltable_instance""" - self.setup_login_to_data() - cfg = self.tbl_ok + cfg = getattr(self, ctl_name) cfg.update({ "instance": "ControlTable", "on_instance_search": on_instance_search, @@ -125,20 +127,28 @@ def test_controltable_instance(self, on_instance_search, self.assert_is_instance(ctl.table, ControlBase) self.assert_is_instance(ctl.rows, list) # Use case 1. not html5:: TABLE > (TR > TH)+(TR > TD) - self.assert_lower(len(ctl.rows), 3) - for row in ctl.rows: - self.assert_is_instance(row, list) - self.assert_lower(len(row), 2) - for cell in row: - self.assert_is_instance(cell, ControlBase) + if ctl_name == 'tbl_ok': + self.assert_lower(len(ctl.rows), 3) + for row in ctl.rows: + self.assert_is_instance(row, list) + self.assert_lower(len(row), 2) + for cell in row: + self.assert_is_instance(cell, ControlBase) # Use case 2. html5:: TABLE > (THEAD > (TR > TH))+(TBODY > (TR > TH)) + if ctl_name == 'tbl_html5_ok': + self.assert_lower(len(ctl.rows), 4) + for row in ctl.rows: + self.assert_is_instance(row, list) + self.assert_lower(len(row), 3) + for cell in row: + self.assert_is_instance(cell, ControlBase) @pytest.mark.skipIf(SKIP_CONTROLS, SKIP_CONTROLS_MSG) @pytest.mark.parametrize("strict_rules", [None]) - def test_controltable_instance_raises(self, strict_rules): + @pytest.mark.parametrize("ctl_name", ['tbl_ok', 'tbl_html5_ok']) + def test_controltable_instance_raises(self, strict_rules, ctl_name): """Testcase: test_controltable_instance_raises""" - self.setup_login_to_data() - cfg = self.tbl_ok + cfg = getattr(self, ctl_name) cfg.update({ "instance": "ControlTable", "strict_rules": strict_rules, @@ -153,8 +163,18 @@ def test_controltable_instance_raises(self, strict_rules): self.assert_equals(ctl.locator, 'css selector') @pytest.mark.skipIf(SKIP_CONTROLS, SKIP_CONTROLS_MSG) - def test_controltable_loadtable_ok(self): - """Testcase: test_controltable_loadtable""" - self.setup_login_to_data() + def test_controltable_internals_ok(self): + """Testcase: test_controltable_internals_ok""" ctl = ControlTable(self.bot, **self.tbl_ok) ctl.__load_table__() + ctl.__check_reload__form__() + + @pytest.mark.skipIf(SKIP_CONTROLS, SKIP_CONTROLS_MSG) + def test_controltable_properties_ok(self): + """Testcase: test_controltable_properties_ok""" + ctl = ControlTable(self.bot, **self.tbl_ok) + rows_before = len(ctl.rows) + ctl.table = ctl.element + rows_after = len(ctl.rows) + self.assert_is_instance(ctl.table, ControlBase) + self.assert_equals(rows_before, rows_after) From 4c9e68977666a740d77563aafdc5dc4337fcb1d7 Mon Sep 17 00:00:00 2001 From: netzulo Date: Fri, 19 Apr 2019 10:24:41 +0200 Subject: [PATCH 5/7] [qacode] renamed test class for ControlTable --- tests/001_functionals/suite_008_controltable.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/001_functionals/suite_008_controltable.py b/tests/001_functionals/suite_008_controltable.py index 073aa461..81fce125 100644 --- a/tests/001_functionals/suite_008_controltable.py +++ b/tests/001_functionals/suite_008_controltable.py @@ -15,7 +15,7 @@ SKIP_CONTROLS_MSG = 'web_controls DISABLED by config file' -class TestControlDropdown(TestInfoBotUnique): +class TestControlTable(TestInfoBotUnique): """Test Suite for ControlBase class""" # app from config @@ -40,7 +40,7 @@ class TestControlDropdown(TestInfoBotUnique): @classmethod def setup_class(cls, **kwargs): """TODO: doc method""" - super(TestControlDropdown, cls).setup_class( + super(TestControlTable, cls).setup_class( config=settings(file_path="qacode/configs/"), skip_force=SKIP_CONTROLS) cls.add_property('app', cls.settings_app('qadmin')) @@ -63,7 +63,7 @@ def setup_class(cls, **kwargs): def setup_method(self, test_method): """Configure self.attribute""" - super(TestControlDropdown, self).setup_method( + super(TestControlTable, self).setup_method( test_method, config=settings(file_path="qacode/configs/")) self.setup_login_to_data() From 4b5d6bac7d3e14360044a6ea1686e60176acd482 Mon Sep 17 00:00:00 2001 From: netzulo Date: Fri, 19 Apr 2019 10:28:56 +0200 Subject: [PATCH 6/7] [qacode] some suite vars fixes ControlTable tests --- tests/001_functionals/suite_008_controltable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/001_functionals/suite_008_controltable.py b/tests/001_functionals/suite_008_controltable.py index 81fce125..34c7bc15 100644 --- a/tests/001_functionals/suite_008_controltable.py +++ b/tests/001_functionals/suite_008_controltable.py @@ -99,7 +99,7 @@ def setup_login_to_data(self): def test_controltable_instance(self, on_instance_search, strict_rules, auto_reload, ctl_name): """Testcase: test_controltable_instance""" - cfg = getattr(self, ctl_name) + cfg = getattr(self, ctl_name).copy() cfg.update({ "instance": "ControlTable", "on_instance_search": on_instance_search, @@ -148,7 +148,7 @@ def test_controltable_instance(self, on_instance_search, @pytest.mark.parametrize("ctl_name", ['tbl_ok', 'tbl_html5_ok']) def test_controltable_instance_raises(self, strict_rules, ctl_name): """Testcase: test_controltable_instance_raises""" - cfg = getattr(self, ctl_name) + cfg = getattr(self, ctl_name).copy() cfg.update({ "instance": "ControlTable", "strict_rules": strict_rules, From ca71017f47f9af0fb6cac0d10b8e684ca93e69f4 Mon Sep 17 00:00:00 2001 From: netzulo Date: Fri, 19 Apr 2019 11:13:20 +0200 Subject: [PATCH 7/7] [qacode] CHANGELOG for PR #264 + issues: #248 , #265 --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d95e4cb7..c727cceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - New module at 'qacode.core.loggers' named 'logger_messages' #untracked - New nav_base method named ele_wait_value #untracked - Move dropdown methods to new control dropdown class #258 +- Added new class named 'ControlTable' #248 ### Changed - Separate benchmark test from all functional tests at tox -e coverage #251 @@ -18,12 +19,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Refactor for control suites after changes from #247 , #untracked - Updated USAGE.rst documentation #258 - Now get_text check for input tag #untracked +- Function with Cognitive Complexity of 13 (exceeds 5 allowed) #265 +- New internal method to reduce duplication at ControlDropdown #untracked ### Fixed - Can't use dropdown methods if ControlForm strict_tags is disabled #247 - Some PageExceptions was failing at instantiation #untracked - Now get_tag update self property -- fixed CI complexity issue for #261 +- Fixed CI complexity issue for #261 +- Some ControlForm+inherit could fail if stric_rules was None #248 ### Removed - Deleted ControlGroup + tests #256