Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[qacode] new ControlTable class, #248 #264

Merged
merged 7 commits into from
Apr 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
netzulo marked this conversation as resolved.
Show resolved Hide resolved
"""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):
netzulo marked this conversation as resolved.
Show resolved Hide resolved
netzulo marked this conversation as resolved.
Show resolved Hide resolved
"""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