diff --git a/AUTHORS.rst b/AUTHORS.rst index 5f3a5f3253..91bdec75af 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -40,3 +40,4 @@ For example: * Ben French (BenjaminFrench) * Rupert Barrow (rupertbarrow) * Rupesh J (rupeshjSFDC) +* Bhimeswara Vamsi Punnam (b-vamsipunnam) diff --git a/cumulusci/robotframework/Salesforce.py b/cumulusci/robotframework/Salesforce.py index 99eebdb0d0..0f0ae2c3b5 100644 --- a/cumulusci/robotframework/Salesforce.py +++ b/cumulusci/robotframework/Salesforce.py @@ -934,3 +934,61 @@ def select_window(self, locator="MAIN", timeout=None): "'Select Window' is deprecated; use 'Switch Window' instead", "WARN" ) self.selenium.switch_window(locator=locator, timeout=timeout) + + @capture_screenshot_on_error + def get_all_picklist_values(self, name, timeout="10s"): + """Return available non-empty values from a Salesforce Lightning picklist. + + Opens the picklist identified by its visible field label and returns + the non-empty option values currently rendered in the Lightning picklist. + + Examples: + | ${values}= | Get All Picklist Values | Stage | + | ${values}= | Get All Picklist Values | Lead Status | timeout=15s | + """ + picklist_locator = f"label:{name}" + option_xpath = ( + "xpath://lightning-base-combobox-item" + "//span[contains(@class,'slds-media__body') or @slot='label']" + ) + + try: + self.selenium.set_focus_to_element(picklist_locator) + self.scroll_element_into_view(picklist_locator) + self.selenium.click_element(picklist_locator) + self.selenium.wait_until_element_is_visible( + option_xpath, + timeout=timeout, + ) + + values = [] + for element in self.selenium.get_webelements(option_xpath): + try: + text = element.text.strip() + if text: + values.append(text) + except ( + StaleElementReferenceException, + WebDriverException, + ): + continue + + self.builtin.log( + f"Retrieved {len(values)} values from picklist '{name}'.", + "INFO", + ) + return values + + except ElementNotFound: + raise AssertionError( + f"Picklist '{name}' was not found on the page." + ) from None + + finally: + try: + self.selenium.press_keys(None, "ESC") + except Exception as err: + self.builtin.log( + f"Failed to close picklist dropdown: {err}", + "DEBUG", + ) diff --git a/cumulusci/robotframework/tests/salesforce/picklist.robot b/cumulusci/robotframework/tests/salesforce/picklist.robot new file mode 100644 index 0000000000..84a681d29c --- /dev/null +++ b/cumulusci/robotframework/tests/salesforce/picklist.robot @@ -0,0 +1,63 @@ +*** Settings *** +Documentation +... This suite tests the `Get All Picklist Values` keyword. +... Specifically, it verifies that the keyword can open a Salesforce +... Lightning picklist and return the available option values. + +Library String +Library DateTime +Library Collections +Resource cumulusci/robotframework/Salesforce.robot + +Suite Setup run keywords +... Open test browser +... AND Create opportunity +Suite Teardown Delete Records and Close Browser + + +*** Keywords *** +Create opportunity + [Documentation] + ... Creates a test opportunity with a random name and a close + ... date 30 days in the future. A reference to the created + ... opportunity will be stored in the suite variable ${opportunity}. + + ${30 days from now}= Get Current Date increment=30 days result_format=%Y-%m-%d + ${random}= Generate random string 8 [NUMBERS] + ${opportunity_id}= Salesforce Insert Opportunity + ... Name=Picklist Test ${random} + ... CloseDate=${30 days from now} + ... Amount=100000 + ... Probability=42 + ... StageName=Prospecting + ... Description=Picklist keyword test + &{opportunity}= Salesforce Get Opportunity ${opportunity_id} + set suite variable ${opportunity} + [Return] ${opportunity} + + +*** Test Cases *** +Test Get All Picklist Values + [Documentation] + ... Verify that Get All Picklist Values returns available values + ... from a Lightning picklist field. + + [Setup] run keywords + ... go to object home Opportunity + ... AND click link ${opportunity['Name']} + ... AND click object button Edit + ... AND wait until modal is open + + ${values}= Get All Picklist Values Stage + Log ${values} + + Run keyword and continue on failure + ... Should Not Be Empty + ... ${values} + ... Expected Stage picklist values to be returned but received an empty list + + Run keyword and continue on failure + ... List Should Contain Value + ... ${values} + ... Prospecting + ... Expected Stage picklist to contain 'Prospecting' but values were '${values}' \ No newline at end of file diff --git a/cumulusci/robotframework/tests/test_salesforce.py b/cumulusci/robotframework/tests/test_salesforce.py index d18514c85e..fb9b53897e 100644 --- a/cumulusci/robotframework/tests/test_salesforce.py +++ b/cumulusci/robotframework/tests/test_salesforce.py @@ -4,6 +4,7 @@ from SeleniumLibrary.errors import ElementNotFound from cumulusci.robotframework.Salesforce import Salesforce +from selenium.common.exceptions import StaleElementReferenceException # _init_locators has a special code block @@ -77,3 +78,74 @@ def setup_class(cls): def test_breakpoint(self, mock_robot_context): """Verify that the keyword doesn't raise an exception""" assert self.sflib.breakpoint() is None + + +@mock.patch("robot.libraries.BuiltIn.BuiltIn._get_context") +class TestKeywordGetAllPicklistValues: + @classmethod + def setup_class(cls): + cls.sflib = Salesforce(locators={"body": "//whatever"}) + + def test_returns_rendered_non_empty_picklist_values(self, mock_robot_context): + option_1 = mock.Mock(text="Warm") + option_2 = mock.Mock(text="Cold") + option_3 = mock.Mock(text="Warm") + option_4 = mock.Mock(text=" ") + + with mock.patch.object(self.sflib, "scroll_element_into_view"): + self.sflib.selenium.get_webelements.return_value = [ + option_1, + option_2, + option_3, + option_4, + ] + + values = self.sflib.get_all_picklist_values("Status") + + assert values == ["Warm", "Cold", "Warm"] + + self.sflib.selenium.set_focus_to_element.assert_called_once_with("label:Status") + self.sflib.selenium.click_element.assert_called_once_with("label:Status") + self.sflib.selenium.wait_until_element_is_visible.assert_called_once() + self.sflib.selenium.press_keys.assert_any_call(None, "ESC") + + def test_ignores_stale_options(self, mock_robot_context): + good_option = mock.Mock(text="Active") + + stale_option = mock.Mock() + type(stale_option).text = mock.PropertyMock( + side_effect=StaleElementReferenceException() + ) + + with mock.patch.object(self.sflib, "scroll_element_into_view"): + self.sflib.selenium.get_webelements.return_value = [ + stale_option, + good_option, + ] + + values = self.sflib.get_all_picklist_values("Status") + + assert values == ["Active"] + + def test_raises_assertion_when_picklist_not_found(self, mock_robot_context): + with mock.patch.object( + self.sflib.selenium, + "set_focus_to_element", + side_effect=ElementNotFound(), + ): + with pytest.raises( + AssertionError, + match="Picklist 'Status' was not found on the page.", + ): + self.sflib.get_all_picklist_values("Status") + + def test_uses_custom_timeout(self, mock_robot_context): + option = mock.Mock(text="Active") + + with mock.patch.object(self.sflib, "scroll_element_into_view"): + self.sflib.selenium.get_webelements.return_value = [option] + + self.sflib.get_all_picklist_values("Status", timeout="15s") + + _, kwargs = self.sflib.selenium.wait_until_element_is_visible.call_args + assert kwargs["timeout"] == "15s"