Skip to content

Commit

Permalink
Merge pull request #11 from RedHatQE/pv-read-fill
Browse files Browse the repository at this point in the history
Handling of read/fill for parametrized views; BaseInput supports locators.
  • Loading branch information
Milan Falešník committed Jan 6, 2017
2 parents 58e4813 + 20a36b7 commit c622829
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 20 deletions.
5 changes: 4 additions & 1 deletion src/widgetastic/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,10 @@ def __get__(self, o, t=None):
class ParametrizedLocator(ParametrizedString):
def __get__(self, o, t=None):
result = super(ParametrizedLocator, self).__get__(o, t)
return Locator(result)
if isinstance(result, ParametrizedString):
return result
else:
return Locator(result)


class Parameter(ParametrizedString):
Expand Down
65 changes: 49 additions & 16 deletions src/widgetastic/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,16 @@ def fill(self, values):

widget = getattr(self, name)
try:
if widget.fill(values[name]):
value = values[name]
if isinstance(widget, ParametrizedViewRequest):
if not isinstance(value, dict):
raise ValueError('When filling parametrized view a dict is required')
for param_tuple, fill_value in value.items():
if not isinstance(param_tuple, tuple):
param_tuple = (param_tuple, )
if widget(*param_tuple).fill(fill_value):
was_change = True
elif widget.fill(value):
was_change = True
except NotImplementedError:
continue
Expand All @@ -506,7 +515,19 @@ def read(self):
for widget_name in self.widget_names():
widget = getattr(self, widget_name)
try:
value = widget.read()
if isinstance(widget, ParametrizedViewRequest):
# Special handling of the parametrized views
all_presences = widget.view_class.all(widget.parent_object.browser)
value = {}
for param_tuple in all_presences:
# For each presence store it in a dictionary
args = param_tuple
if len(param_tuple) < 2:
# Single value - no tuple
param_tuple = param_tuple[0]
value[param_tuple] = widget(*args).read()
else:
value = widget.read()
except (NotImplementedError, NoSuchElementException, DoNotReadThisWidget):
continue

Expand Down Expand Up @@ -540,6 +561,19 @@ class ParametrizedView(View):
"""View that needs parameters to be run."""
PARAMETERS = ()

@classmethod
def all(cls, browser):
"""Method that returns tuples of parameters that correspond to PARAMETRS attribute.
It is required for proper functionality of :py:meth:`read` so it knows the exact instances
of the view.
Returns:
An iterable that contains tuples. Values in the tuples must map exactly to the keys in
the PARAMETERS class attribute.
"""
raise NotImplementedError('You need to implement the all() classmethod')


class ParametrizedViewRequest(object):
def __init__(self, parent_object, view_class, *args, **kwargs):
Expand Down Expand Up @@ -619,25 +653,24 @@ class BaseInput(Widget):
name: If you want to look the input up by name, use this parameter, pass the name.
id: If you want to look the input up by id, use this parameter, pass the id.
"""
def __init__(self, parent, name=None, id=None, logger=None):
if (name is None and id is None) or (name is not None and id is not None):
raise TypeError('TextInput must have either name= or id= specified but also not both.')
def __init__(self, parent, name=None, id=None, locator=None, logger=None):
if (locator and (name or id)) or (name and (id or locator)) or (id and (name or locator)):
raise TypeError('You can only pass one of name, id or locator!')
Widget.__init__(self, parent, logger=logger)
self.name = name
self.id = id
if name or id:
if name is not None:
id_attr = '@name={}'.format(quote(name))
elif id is not None:
id_attr = '@id={}'.format(quote(id))
self.locator = './/*[(self::input or self::textarea) and {}]'.format(id_attr)
else:
self.locator = locator

def __repr__(self):
if self.name is not None:
return '{}(name={!r})'.format(type(self).__name__, self.name)
else:
return '{}(id={!r})'.format(type(self).__name__, self.id)
return '{}(locator={!r})'.format(type(self).__name__, self.locator)

def __locator__(self):
if self.name is not None:
id_attr = '@name={}'.format(quote(self.name))
else:
id_attr = '@id={}'.format(quote(self.id))
return './/*[(self::input or self::textarea) and {}]'.format(id_attr)
return self.locator


class TextInput(BaseInput):
Expand Down
5 changes: 3 additions & 2 deletions testing/test_basic_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class TestForm(View):
table = Table('#with-thead')

view = TestForm(browser)
assert view.table.headers == (None, 'Column 1', 'Column 2', 'Column 3')
assert view.table.headers == (None, 'Column 1', 'Column 2', 'Column 3', 'Column 4')
assert len(list(view.table.rows())) == 3
assert len(list(view.table.rows(column_1='qwer'))) == 1
assert len(list(view.table.rows(column_1__startswith='bar_'))) == 2
Expand Down Expand Up @@ -137,7 +137,8 @@ class TestForm(View):
(None, 'foo_x'),
('Column 1', 'bar_x'),
('Column 2', 'baz_x'),
('Column 3', 'bat_x')]
('Column 3', 'bat_x'),
('Column 4', '')]

assert view.table[0].column_2.text == 'yxcv'

Expand Down
53 changes: 52 additions & 1 deletion testing/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import pytest
from widgetastic.utils import ParametrizedLocator, ParametrizedString, Parameter
from widgetastic.widget import (
ParametrizedView, ParametrizedViewRequest, Text, View, Widget, do_not_read_this_widget)
ParametrizedView, ParametrizedViewRequest, Text, View, Widget, do_not_read_this_widget,
Checkbox)


def test_can_create_view(browser):
Expand Down Expand Up @@ -139,6 +140,14 @@ class table_row(ParametrizedView):
ROOT = ParametrizedLocator('.//tr[@data-test={rowid|quote}]')

col1 = Text('./td[2]')
checkbox = Checkbox(locator=ParametrizedString('.//td/input[@id={rowid|quote}]'))

@classmethod
def all(cls, browser):
result = []
for e in browser.elements('.//table[@id="with-thead"]//tr[td]'):
result.append((browser.get_attribute('data-test', e), ))
return result

view = MyView(browser)
assert isinstance(view.table_row, ParametrizedViewRequest)
Expand All @@ -153,3 +162,45 @@ class table_row(ParametrizedView):

with pytest.raises(TypeError):
view.table_row(foo='bar')

view.fill({'table_row': {
'abc-123': {'checkbox': True},
('abc-345', ): {'checkbox': False},
('def-345', ): {'checkbox': True},
}})

assert view.read() == {
'table_row': {
'abc-123': {'col1': 'qwer', 'checkbox': True},
'abc-345': {'col1': 'bar_x', 'checkbox': False},
'def-345': {'col1': 'bar_y', 'checkbox': True}}}

assert view.fill({'table_row': {
'abc-123': {'checkbox': False},
('abc-345', ): {'checkbox': False},
('def-345', ): {'checkbox': False},
}})

assert view.read() == {
'table_row': {
'abc-123': {'col1': 'qwer', 'checkbox': False},
'abc-345': {'col1': 'bar_x', 'checkbox': False},
'def-345': {'col1': 'bar_y', 'checkbox': False}}}

assert not view.fill({'table_row': {
'abc-123': {'checkbox': False},
('abc-345', ): {'checkbox': False},
('def-345', ): {'checkbox': False},
}})


def test_parametrized_view_read_without_all(browser):
class MyView(View):
class table_row(ParametrizedView):
PARAMETERS = ('rowid', )
ROOT = ParametrizedLocator('.//tr[@data-test={rowid|quote}]')

col1 = Text('./td[2]')

view = MyView(browser)
assert list(view.read().keys()) == []
4 changes: 4 additions & 0 deletions testing/testing_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ <h3>test test</h3>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
<th>Column 4</th>
</tr>
</thead>
<tbody>
Expand All @@ -42,18 +43,21 @@ <h3>test test</h3>
<td>qwer</td>
<td>yxcv</td>
<td>uiop</td>
<td><input type="checkbox" id='abc-123'></td>
</tr>
<tr data-test='abc-345'>
<td>foo_x</td>
<td>bar_x</td>
<td>baz_x</td>
<td>bat_x</td>
<td><input type="checkbox" id='abc-345'></td>
</tr>
<tr data-test='def-345'>
<td>foo_y</td>
<td>bar_y</td>
<td>baz_y</td>
<td>bat_y</td>
<td><input type="checkbox" id='def-345'></td>
</tr>

</tbody>
Expand Down

0 comments on commit c622829

Please sign in to comment.