Repository: robotframework/SeleniumLibrary
Pull request #1850 (2023-08-12 19:29:18)

New attribute property keywords 1822
Added `Get DOM Attribute` and `Get Property` keywords and several acceptance tests. I know these test can be cleaned up a bit. One bit of review I am looking for is whether all the tests are there? Or if there might be something missing? I also added some comments within the tests. One issue I have been coming across is that when I go into an internal test (either acceptance or unit) some of them I have a tough time trying to see exactly what is is testing, and what should I expect it to do and verify. So I started to play around a bit with comments. I don't think there that great and need improvement. But this starts to highlight one idea for explaining tests better.


Patch:
diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py
index 94a901de..140f527d 100644
--- a/src/SeleniumLibrary/keywords/element.py
+++ b/src/SeleniumLibrary/keywords/element.py
@@ -409,6 +409,38 @@ class ElementKeywords(LibraryComponent):
         """
         return self.find_element(locator).get_attribute(attribute)
 
+    @keyword
+    def get_dom_attribute(
+        self, locator: Union[WebElement, str], attribute: str
+    ) -> str:
+        """Returns the value of ``attribute`` from the element ``locator``. `Get DOM Attribute` keyword
+        only returns attributes declared within the element's HTML markup.  If the requested attribute
+        is not there, the keyword returns ${None}.
+
+        See the `Locating elements` section for details about the locator
+        syntax.
+
+        Example:
+        | ${id}= | `Get DOM Attribute` | css:h1 | id |
+
+        """
+        return self.find_element(locator).get_dom_attribute(attribute)
+
+    @keyword
+    def get_property(
+        self, locator: Union[WebElement, str], property: str
+    ) -> str:
+        """Returns the value of ``property`` from the element ``locator``.
+
+        See the `Locating elements` section for details about the locator
+        syntax.
+
+        Example:
+        | ${text_length}= | `Get Property` | css:h1 | text_length |
+
+        """
+        return self.find_element(locator).get_property(property)
+
     @keyword
     def element_attribute_value_should_be(
         self,


Test patch:
diff --git a/atest/acceptance/keywords/elements.robot b/atest/acceptance/keywords/elements.robot
index 95bf9073..5419baa1 100644
--- a/atest/acceptance/keywords/elements.robot
+++ b/atest/acceptance/keywords/elements.robot
@@ -3,6 +3,7 @@ Documentation     Tests elements
 Test Setup        Go To Page "links.html"
 Resource          ../resource.robot
 Library           String
+Library           DebugLibrary
 
 *** Test Cases ***
 Get Many Elements
@@ -60,6 +61,92 @@ Get Element Attribute
     ${class}=    Get Element Attribute    ${second_div}    class
     Should Be Equal    ${class}    Second Class
 
+# About DOM Attributes and Properties
+# -----------------------------------
+# When implementing the new `Get DOM Attirbute` and `Get Property` keywords (#1822), several
+# questions were raised. Fundamentally what is the difference between a DOM attribute and
+# a Property. As [1] explains "Attributes are defined by HTML. Properties are defined by the
+# DOM (Document Object Model)."
+#
+# Below are some references which talk to some descriptions and oddities of DOM attributes
+# and properties.
+#
+# References:
+#    [1] HTML attributes and DOM properties:
+#      https://angular.io/guide/binding-syntax#html-attribute-vs-dom-property
+#    [2] W3C HTML Specification - Section 13.1.2.3 Attributes:
+#      https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
+#    [3] JavaScript.Info - Attributes and properties:
+#      https://javascript.info/dom-attributes-and-properties
+#    [4] "Which CSS properties are inherited?" - StackOverflow
+#      https://stackoverflow.com/questions/5612302/which-css-properties-are-inherited
+#    [5] MDN Web Docs: Attribute
+#      https://developer.mozilla.org/en-US/docs/Glossary/Attribute
+#    [6] MDN Web Docs: HTML attribute reference
+#      https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
+
+Get DOM Attribute
+    # Test get DOM attribute
+    ${id}=    Get DOM Attribute    link:Link with id    id
+    Should Be Equal    ${id}    some_id
+    # Test custom attribute
+    ${existing_custom_attr}=   Get DOM Attribute    id:emptyDiv  data-id
+    Should Be Equal    ${existing_custom_attr}    my_id    
+    ${doesnotexist_custom_attr}=   Get DOM Attribute    id:emptyDiv  data-doesnotexist
+    Should Be Equal    ${doesnotexist_custom_attr}    ${None}
+    # Get non existing DOM Attribute
+    ${class}=    Get DOM Attribute    link:Link with id    class
+    Should Be Equal    ${class}    ${NONE}
+
+More DOM Attributes
+    [Setup]    Go To Page "forms/enabled_disabled_fields_form.html"
+    # Test get empty boolean attribute
+    ${disabled}=    Get DOM Attribute    css:input[name="disabled_input"]    disabled
+    Should Be Equal    ${disabled}    true
+    # Test boolean attribute whose value is a string
+    ${disabled}=    Get DOM Attribute    css:input[name="disabled_password"]    disabled
+    Should Be Equal    ${disabled}    true
+    # Test empty string as the value for the attribute
+    ${empty_value}=    Get DOM Attribute    css:input[name="disabled_password"]    value
+    Should Be Equal    ${empty_value}    ${EMPTY}
+    # Test non-existing attribute
+    ${disabled}=    Get DOM Attribute    css:input[name="enabled_password"]    disabled
+    Should Be Equal    ${disabled}    ${NONE}
+
+Get Property
+    [Setup]    Go To Page "forms/enabled_disabled_fields_form.html"
+    ${tagName_prop}=   Get Property  css:input[name="readonly_empty"]    tagName
+    Should Be Equal    ${tagName_prop}    INPUT
+    # Get a boolean property
+    ${isConnected}=   Get Property  css:input[name="readonly_empty"]     isConnected
+    Should Be Equal    ${isConnected}    ${True}
+    # Test property which returns webelement
+    ${children_prop}=    Get Property    id:table1    children
+    Length Should Be    ${children_prop}    ${1}
+    ${isWebElement}=  Evaluate  isinstance($children_prop[0], selenium.webdriver.remote.webelement.WebElement)  modules=selenium
+    Should Be Equal    ${isWebElement}    ${True}
+    # ToDo: need to test own versus inherited property
+    # ToDo: Test enumerated property
+
+Get "Attribute" That Is Both An DOM Attribute and Property
+    [Setup]    Go To Page "forms/enabled_disabled_fields_form.html"
+    ${value_property}=    Get Property  css:input[name="readonly_empty"]   value
+    ${value_attribute}=    Get DOM Attribute  css:input[name="readonly_empty"]   value
+    Should Be Equal    ${value_property}    ${value_attribute}
+
+Modify "Attribute" That Is Both An DOM Attribute and Property
+    [Setup]    Go To Page "forms/prefilled_email_form.html"
+    ${initial_value_property}=    Get Property  css:input[name="email"]   value
+    ${initial_value_attribute}=    Get DOM Attribute  css:input[name="email"]   value
+    Should Be Equal    ${initial_value_property}    ${initial_value_attribute}
+    Should Be Equal    ${initial_value_attribute}    Prefilled Email
+    Input Text    css:input[name="email"]    robot@robotframework.org
+    ${changed_value_property}=    Get Property  css:input[name="email"]   value
+    ${changed_value_attribute}=    Get DOM Attribute  css:input[name="email"]   value
+    Should Not Be Equal    ${changed_value_property}    ${changed_value_attribute}
+    Should Be Equal    ${changed_value_attribute}    Prefilled Email
+    Should Be Equal    ${changed_value_property}    robot@robotframework.org
+
 Get Element Attribute Value Should Be Should Be Succesfull
     Element Attribute Value Should Be  link=Absolute external link  href  http://www.google.com/
     Element Attribute Value Should Be  link=Absolute external link  nothere  ${None}
diff --git a/utest/test/api/test_plugins.py b/utest/test/api/test_plugins.py
index 9b5a34f4..ea92654f 100644
--- a/utest/test/api/test_plugins.py
+++ b/utest/test/api/test_plugins.py
@@ -22,7 +22,7 @@ class ExtendingSeleniumLibrary(unittest.TestCase):
     def test_no_libraries(self):
         for item in [None, "None", ""]:
             sl = SeleniumLibrary(plugins=item)
-            self.assertEqual(len(sl.get_keyword_names()), 178)
+            self.assertEqual(len(sl.get_keyword_names()), 180)
 
     def test_parse_library(self):
         plugin = "path.to.MyLibrary"