Skip to content

Commit

Permalink
[qacode] new ControlTable class, #248 (#264)
Browse files Browse the repository at this point in the history
* [qacode] starting issue #248 , WIP control_table

+ new class ControlTable
+ inherit of ControlForm
+ validate tag table tag at instantiation

* [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] 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] some ControlTable fixes + complexity

+ #248 ControlTable
+ #265 Complexity

* [qacode] renamed test class for ControlTable

* [qacode] some suite vars fixes ControlTable tests

* [qacode] CHANGELOG for PR #264

+ issues: #248 , #265
  • Loading branch information
netzulo committed Apr 19, 2019
1 parent 500e9fe commit 7da1cec
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 30 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,23 @@ 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
- Moved log messages to new class to centralize them #untracked
- 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
Expand Down
3 changes: 2 additions & 1 deletion qacode/configs/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 12 additions & 4 deletions qacode/core/loggers/logger_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,20 @@
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
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 <select>" # noqa:E501
CDD_BADTAG = "Can't use this for not <select> 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 <select>" # noqa:E501
CDD_BADTAG = "ctl_dd | Can't use this for not <select> 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 <table> tag element" # noqa:E501
CT_LOADED = "ctl_tb | ctl.table property for <table>" # 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
5 changes: 4 additions & 1 deletion qacode/core/webs/controls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
38 changes: 19 additions & 19 deletions qacode/core/webs/controls/control_dropdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -32,8 +33,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):
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
12 changes: 8 additions & 4 deletions qacode/core/webs/controls/control_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -54,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
Expand Down Expand Up @@ -97,14 +99,16 @@ 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")
raise ControlException(msg=MSG.CF_BADTAG)
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

Expand Down
144 changes: 144 additions & 0 deletions qacode/core/webs/controls/control_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
"""Package module qacode.core.webs.control_form"""


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
from selenium.webdriver.remote.webelement import WebElement


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.
Some elements need to search False to be search at future
"""
kwargs.update({"instance": "ControlTable"})
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)
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 <caption> 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})
# Preload
self.tbodies = self.__try__("find_children", "tbody")
if bool(self.tbodies):
self._rows = self.__load_table_html5__()
else:
self._rows = self.__load_table_html4__()

def __load_table_html4__(self):
"""Allow to load table with this structure
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__"""
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
"""
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, 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
Loading

0 comments on commit 7da1cec

Please sign in to comment.