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

[RFR] Add regex capability to match_messages #112

Merged
merged 1 commit into from
Dec 3, 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
78 changes: 58 additions & 20 deletions src/widgetastic_patternfly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,25 +274,55 @@ def dismiss(self):
message.dismiss()

def match_messages(self, text=None, t=None, partial=False, inverse=False):
"""
Return a list of flash messages matching the specified alert text and type(s)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 👍 👍


Args:
text: text to compare against each flash message's text attribute. This parameter
can be either a string, in which case normal string comparison will be performed,
or a compiled regular expression, in which case it will use re.match()
(default: None).

t: an alert type string or tuple/list/set of types, to compare against each flash
message's type attribute (default: None).

partial: If partial is False, and if `text` is a string, then a message will be
considered a match if the message's text attribute and `text` are equal. If partial
is True, then `text` needs only be contained in the message's text to be considered
a match. This argument has no effect if `text` is a compiled regular expression
(default: False).

inverse: If inverse is False, then the match is performed as described above. If
inverse is True, then the matching logic is inverted, and only messages that fail
to match will be returned.

Returns:
List of matching FlashMessage objects.
"""
msg_type = t if isinstance(t, (tuple, list, set, type(None))) else (t, )
# below is a little bit tricky statement
# if inverse is True, further comparison statements will be treated as is
# and inverted by not_ otherwise
# If inverse is True, further comparison statements will be treated as is.
# Otherwise, they will be inverted by not_().
op = bool if inverse else not_
log_part = 'partial' if partial else 'exact'
if t is not None:
self.logger.info('%s match of the flash message %r of type %r', log_part, text, t)
log_inverse = "inverse " if inverse else ""
log_type = f", type: {t!r}" if msg_type else ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mshriver, are we going to stick to py 3.6+ ? I'm about usage of f-strings in public modules

if isinstance(text, Pattern):
log_part = "pattern"
log_text = f", pattern: {text.pattern!r}"
else:
self.logger.info('%s match of the flash message %r', log_part, text)
log_part = "partial" if partial else "exact"
log_text = f", text: {text!r}" if text else ""
log_msg = f"Performing {log_inverse}{log_part} match of flash messages{log_text}{log_type}"
self.logger.info(log_msg)

matched_messages = []
for message in self.messages:
if msg_type and op(message.type in msg_type):
continue

if text and op((partial and text in message.text) or
(not partial and text == message.text)):
if isinstance(text, Pattern) and op(text.match(message.text)):
continue
if isinstance(text, str) and op((partial and text in message.text) or
(not partial and text == message.text)):
continue

matched_messages.append(message)
return matched_messages

Expand All @@ -309,7 +339,13 @@ def assert_no_error(self, wait=0):
return True

def assert_message(self, text, t=None, partial=False, wait=0):
log_part = 'partial' if partial else 'exact'
# If text is a compiled regex instead of a string, log it as a pattern match.
if isinstance(text, Pattern):
log_part = 'pattern'
log_text = text.pattern
else:
log_part = 'partial' if partial else 'exact'
log_text = text
try:
msgs = wait_for(self.match_messages, func_kwargs=dict(text=text, t=t, partial=partial),
timeout=wait)[0]
Expand All @@ -319,11 +355,13 @@ def assert_message(self, text, t=None, partial=False, wait=0):
raise TimedOutError
except TimedOutError:
if t is not None:
e_text = '{}: {}'.format(t, text)
e_text = f"{t}: {log_text}"
else:
e_text = text
raise AssertionError('assert {} match of message: {}. \n Available messages: {}'
.format(log_part, e_text, [msg.text for msg in self.messages]))
e_text = log_text
msg_text = [msg.text for msg in self.messages]
log_msg = (f'assert {log_part} match of message: {e_text}.\n '
f'Available messages: {msg_text}')
raise AssertionError(log_msg)

def assert_success_message(self, text, t=None, partial=False, wait=0):
self.assert_no_error(wait=wait)
Expand Down Expand Up @@ -1284,7 +1322,7 @@ def get_item_by_nodeid(self, nodeid):
except NoSuchElementException:
raise CandidateNotFound({
'message':
'Could not find the item with nodeid {} in Boostrap tree {}'.format(
'Could not find the item with nodeid {} in Bootstrap tree {}'.format(
nodeid,
self.tree_id),
'path': '',
Expand Down Expand Up @@ -1430,7 +1468,7 @@ def expand_path(self, *path, **kwargs):
if not self.validate_node(node, step, image):
raise CandidateNotFound({
'message':
'Could not find the item {} in Boostrap tree {}'.format(
'Could not find the item {} in Bootstrap tree {}'.format(
self.pretty_path(steps_tried),
self.tree_id),
'path': path,
Expand All @@ -1444,7 +1482,7 @@ def expand_path(self, *path, **kwargs):
if node is not None and not self.expand_node(self.get_nodeid(node)):
raise CandidateNotFound({
'message':
'Could not find the item {} in Boostrap tree {}'.format(
'Could not find the item {} in Bootstrap tree {}'.format(
self.pretty_path(steps_tried),
self.tree_id),
'path': path,
Expand All @@ -1462,7 +1500,7 @@ def expand_path(self, *path, **kwargs):
else:
raise CandidateNotFound({
'message':
'Could not find the item {} in Boostrap tree {}'.format(
'Could not find the item {} in Bootstrap tree {}'.format(
self.pretty_path(steps_tried),
self.tree_id),
'path': path,
Expand Down
44 changes: 44 additions & 0 deletions testing/test_flashmessages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import re
from collections import namedtuple

from widgetastic.widget import View
from widgetastic_patternfly import FlashMessages

Message = namedtuple('Message', 'text type')

MSGS = [Message('Retirement date set to 12/31/19 15:55 UTC', 'success'),
Message('Retirement date removed', 'success'),
Message('All changes have been reset', 'warning'),
Message('Set/remove retirement date was cancelled by the user', 'success'),
Message('Retirement initiated for 1 VM and Instance from the CFME Database', 'success')]


def test_flashmessage(browser):
class TestView(View):
flash = FlashMessages('.//div[@id="flash_msg_div"]')

view = TestView(browser)
msgs = view.flash.read()
assert len(msgs) == len(MSGS)
for i in range(len(msgs)):
assert msgs[i] == MSGS[i].text

view.flash.assert_no_error()

# regex match
t = re.compile('^Retirement')
view.flash.assert_message(t)
view.flash.assert_success_message(t)

# partial match
t = 'etirement'
view.flash.assert_message(t, partial=True)
view.flash.assert_success_message(t, partial=True)

# inverse match
t = 'This message does not exist'
assert not view.flash.match_messages(t)
assert view.flash.match_messages(t, inverse=True)

view.flash.dismiss()
assert not view.flash.read()
59 changes: 54 additions & 5 deletions testing/testing_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ <h2>Widgetastic About Modal</h2>
</div>


<!--------------------------------- Bootstrap-Datepicker------------------------------------------>
<!--------------------------------- Bootstrap-Datepicker------------------------------------------>
<div class="datepicker_container" align="center" style="width: 300px; text-align:center">
<div class="datepicker_readonly">
<label class="control-label " for="date_readonly">
Expand Down Expand Up @@ -126,11 +126,11 @@ <h2>Widgetastic About Modal</h2>
});
})
</script>
<!--------------------------------- Bootstrap-Datepicker End ------------------------------------>
<!--------------------------------- Bootstrap-Datepicker End ------------------------------------>

<!--------------------------------- AggregateStatusCard ------------------------------------------>
<!-- from reference
markup: https://www.patternfly.org/pattern-library/cards/aggregate-status-card/#example-code-1 -->
<!--------------------------------- AggregateStatusCard ------------------------------------------>
<!-- from reference
markup: https://www.patternfly.org/pattern-library/cards/aggregate-status-card/#example-code-1 -->
<div class="cards-pf">
<div class="container-fluid container-cards-pf">
<div class="row row-cards-pf">
Expand Down Expand Up @@ -785,5 +785,54 @@ <h4 class="modal-title" id="myModalLabel">Modal Title</h4>
</ul>
</div>
<!--End BootstrapNav------------------------------------->
<!--Begin FlashMessages------------------------------------->
<div id="flash_msg_div" style="">
<div class="flash_text_div">
<div class="alert alert-success alert-dismissable">
<button class="close" data-dismiss="alert">
<span class="pficon pficon-close"></span>
</button>
<span class="pficon pficon-ok"></span>
<strong>Retirement date set to 12/31/19 15:55 UTC</strong>
</div>
</div>
<div class="flash_text_div">
<div class="alert alert-success alert-dismissable">
<button class="close" data-dismiss="alert">
<span class="pficon pficon-close"></span>
</button>
<span class="pficon pficon-ok"></span>
<strong>Retirement date removed</strong>
</div>
</div>
<div class="flash_text_div">
<div class="alert alert-warning">
<button class="close" data-dismiss="alert">
<span class="pficon pficon-close"></span>
</button>
<span class="pficon pficon-warning-triangle-o"></span>
<strong>All changes have been reset</strong>
</div>
</div>
<div class="flash_text_div">
<div class="alert alert-success alert-dismissable">
<button class="close" data-dismiss="alert">
<span class="pficon pficon-close"></span>
</button>
<span class="pficon pficon-ok"></span>
<strong>Set/remove retirement date was cancelled by the user</strong>
</div>
</div>
<div class="flash_text_div">
<div class="alert alert-success alert-dismissable">
<button class="close" data-dismiss="alert">
<span class="pficon pficon-close"></span>
</button>
<span class="pficon pficon-ok"></span>
<strong>Retirement initiated for 1 VM and Instance from the CFME Database</strong>
</div>
</div>
</div>
<!--End FlashMessages------------------------------------->
</body>
</html>