Repository: ManageIQ/integration_tests

Issue:
In chargeback rate assignment - 'Add' button
In chargeback rate assignment form, when the form is filled and we are about to press the 'Add' (assignment) button, the Add button of the row is pressed instead:
Reproduction branch: https://github.com/gshefer/integration_tests/tree/chargeback_reports
Reproduction file: cfme/tests/containers/test_chargeback.py

![cfme chargeback](https://cloud.githubusercontent.com/assets/21076742/24146804/fc7f99f4-0e3f-11e7-9ca6-a505b1532157.png)



Discussion:
Yes, I will add additional matching conditions for the buttons, probably the classes on it. Not sure how we will do it with the gray add buttons, they will have to live in a sub-view fenced by a locator.
So the referenced PR will enable you to fence the query only to the blue buttons by class. Should we add support for the gray Add buttons, their lookup will have to be fenced by a nested view.

Resolving patch:
diff --git a/cfme/web_ui/form_buttons.py b/cfme/web_ui/form_buttons.py
--- a/cfme/web_ui/form_buttons.py
+++ b/cfme/web_ui/form_buttons.py
@@ -6,6 +6,8 @@
 from selenium.common.exceptions import NoSuchElementException
 from xml.sax.saxutils import quoteattr
 
+from widgetastic.xpath import quote
+
 from cfme.fixtures import pytest_selenium as sel
 from cfme.web_ui import fill
 from utils import version
@@ -26,6 +28,8 @@ class FormButton(Pretty):
     """
     pretty_attrs = ['_alt', '_dimmed_alt', '_force', '_partial', '_ng_click']
 
+    PRIMARY = 'btn-primary'
+
     class Button:
         """Holds pieces of the XPath to be assembled."""
         TAG_TYPES = "//a | //button | //img | //input"
@@ -42,12 +46,15 @@ class Button:
         ON_CURRENT_TAB = (
             "not(ancestor::div[contains(@class, 'tab-pane') and not(contains(@class, 'active'))])")
 
-    def __init__(self, alt, dimmed_alt=None, force_click=False, partial_alt=False, ng_click=None):
+    def __init__(
+            self, alt, dimmed_alt=None, force_click=False, partial_alt=False, ng_click=None,
+            classes=None):
         self._alt = alt
         self._dimmed_alt = dimmed_alt
         self._force = force_click
         self._partial = partial_alt
         self._ng_click = ng_click
+        self._classes = classes or []
 
     def alt_expr(self, dimmed=False):
         if self._partial:
@@ -86,19 +93,24 @@ def _format_generator(self, dimmed=False, include_dimmed_alt=False):
         d["ALT_EXPR"] = self.alt_expr(dimmed=dimmed)
         if include_dimmed_alt:
             d["DIMMED_ALT"] = quoteattr(self._dimmed_alt or self._alt)
+        if self._classes:
+            d['CLASSES'] = 'and ({})'.format(
+                ' and '.join('contains(@class, {})'.format(quote(kls)) for kls in self._classes))
+        else:
+            d['CLASSES'] = ''
         return d
 
     def locate(self):
         return (
             "({TAG_TYPES})[{ALT_EXPR} and {NOT_DIMMED} and {TYPE_CONDITION} and {IS_DISPLAYED} "
-            "and {ON_CURRENT_TAB}]"
+            "and {ON_CURRENT_TAB} {CLASSES}]"
             .format(**self._format_generator(dimmed=False)))
 
     @property
     def is_dimmed(self):
         locator = (
             "({TAG_TYPES})[{ALT_EXPR} and {DIMMED} and {TYPE_CONDITION} and {IS_DISPLAYED} "
-            "and {ON_CURRENT_TAB}]"
+            "and {ON_CURRENT_TAB} {CLASSES}]"
             "|"  # A bit different type of a button
             "({TAG_TYPES})[normalize-space(.)={DIMMED_ALT} and {IS_DISPLAYED} and "
             "(@disabled='true' or contains(@class, 'btn-disabled')) and {ON_CURRENT_TAB}]"


Test patch: