From bbcc8370e9acfd27edfa266e361c1899572527ec Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Wed, 9 Aug 2023 12:47:51 -0400 Subject: [PATCH 1/8] Initial commit adding keywords and tests for #1822 --- atest/acceptance/keywords/elements.robot | 41 ++++++++++++++++++++++++ src/SeleniumLibrary/keywords/element.py | 31 ++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/atest/acceptance/keywords/elements.robot b/atest/acceptance/keywords/elements.robot index 95bf90734..8e503d21e 100644 --- a/atest/acceptance/keywords/elements.robot +++ b/atest/acceptance/keywords/elements.robot @@ -60,6 +60,47 @@ Get Element Attribute ${class}= Get Element Attribute ${second_div} class Should Be Equal ${class} Second Class +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} + # ToDo: + # Ref: https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + +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 attribute + ${disabled}= Get DOM Attribute css:input[name="disabled_input"] disabled + Should Be Equal ${disabled} true # ${True} + ${disabled}= Get DOM Attribute css:input[name="disabled_password"] disabled + Should Be Equal ${disabled} true # disabled + # 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} + ${disabled}= Get DOM Attribute css:input[name="enabled_password"] disabled + Should Be Equal ${disabled} ${NONE} # false + +Get Property + [Setup] Go To Page "forms/enabled_disabled_fields_form.html" + # ${attributes}= Get Property css:input[name="readonly_empty"] attributes + ${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} + + # ToDo: nned to test own versus inherited property + # ToDo: Test enumerated property + ${children}= Get Property id:table1 children + 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/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index 94a901dec..7d16f8f36 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -409,6 +409,37 @@ def get_element_attribute( """ 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. + + 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, From ce741a7f0049bbc1b6abd28034ea52661a06f939 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Wed, 9 Aug 2023 13:56:04 -0400 Subject: [PATCH 2/8] Added another atest to check for "attibute" that is both an attribute and property --- atest/acceptance/keywords/elements.robot | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/atest/acceptance/keywords/elements.robot b/atest/acceptance/keywords/elements.robot index 8e503d21e..c548a2b7b 100644 --- a/atest/acceptance/keywords/elements.robot +++ b/atest/acceptance/keywords/elements.robot @@ -99,8 +99,16 @@ Get Property # ToDo: nned to test own versus inherited property # ToDo: Test enumerated property + # Test proprty which returns webelement ${children}= Get Property id:table1 children + +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} + 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} From 5da9e12b07c0f075859494659136d61241663437 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Wed, 1 Nov 2023 19:42:15 -0400 Subject: [PATCH 3/8] Updated documentation for expected value if attribute is not there --- src/SeleniumLibrary/keywords/element.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index 7d16f8f36..140f527df 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -414,7 +414,8 @@ 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. + 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. From b3007618d9d18dd3634ef5608fb79bcc8306e7ae Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 10 Nov 2023 16:15:15 -0500 Subject: [PATCH 4/8] Cleaned up Get DOM Attribute, Get Property atests --- atest/acceptance/keywords/elements.robot | 55 +++++++++++++++++------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/atest/acceptance/keywords/elements.robot b/atest/acceptance/keywords/elements.robot index c548a2b7b..eb0bd76ac 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,48 +61,72 @@ 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 + 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} - # ToDo: - # Ref: https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - -Get non existing DOM Attribute + # 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 attribute + # Test get empty boolean attribute ${disabled}= Get DOM Attribute css:input[name="disabled_input"] disabled - Should Be Equal ${disabled} true # ${True} + 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 # 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} # false + Should Be Equal ${disabled} ${NONE} Get Property [Setup] Go To Page "forms/enabled_disabled_fields_form.html" - # ${attributes}= Get Property css:input[name="readonly_empty"] attributes ${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} - - # ToDo: nned to test own versus inherited property + # 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 - # Test proprty which returns webelement - ${children}= Get Property id:table1 children - Get "Attribute" That Is Both An DOM Attribute and Property [Setup] Go To Page "forms/enabled_disabled_fields_form.html" From 4a1f413c4a4b29d196e76b8da5371019a23d3c32 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 10 Nov 2023 16:17:02 -0500 Subject: [PATCH 5/8] Updated number of library keywords to 179 --- utest/test/api/test_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/test/api/test_plugins.py b/utest/test/api/test_plugins.py index 89e33c247..b80581af1 100644 --- a/utest/test/api/test_plugins.py +++ b/utest/test/api/test_plugins.py @@ -22,7 +22,7 @@ def setUpClass(cls): def test_no_libraries(self): for item in [None, "None", ""]: sl = SeleniumLibrary(plugins=item) - self.assertEqual(len(sl.get_keyword_names()), 177) + self.assertEqual(len(sl.get_keyword_names()), 179) def test_parse_library(self): plugin = "path.to.MyLibrary" From 6af2c8226a6d663df6073ff27b82014ca6259ba3 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 10 Nov 2023 16:37:25 -0500 Subject: [PATCH 6/8] Revert "Updated number of library keywords to 179" This reverts commit 4a1f413c4a4b29d196e76b8da5371019a23d3c32. --- utest/test/api/test_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/test/api/test_plugins.py b/utest/test/api/test_plugins.py index b80581af1..89e33c247 100644 --- a/utest/test/api/test_plugins.py +++ b/utest/test/api/test_plugins.py @@ -22,7 +22,7 @@ def setUpClass(cls): def test_no_libraries(self): for item in [None, "None", ""]: sl = SeleniumLibrary(plugins=item) - self.assertEqual(len(sl.get_keyword_names()), 179) + self.assertEqual(len(sl.get_keyword_names()), 177) def test_parse_library(self): plugin = "path.to.MyLibrary" From bbb999001449f608cf269887b0d8792c73a2d92c Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 10 Nov 2023 17:09:19 -0500 Subject: [PATCH 7/8] Updated number of library keywords to 179 --- utest/test/api/test_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/test/api/test_plugins.py b/utest/test/api/test_plugins.py index 89e33c247..b80581af1 100644 --- a/utest/test/api/test_plugins.py +++ b/utest/test/api/test_plugins.py @@ -22,7 +22,7 @@ def setUpClass(cls): def test_no_libraries(self): for item in [None, "None", ""]: sl = SeleniumLibrary(plugins=item) - self.assertEqual(len(sl.get_keyword_names()), 177) + self.assertEqual(len(sl.get_keyword_names()), 179) def test_parse_library(self): plugin = "path.to.MyLibrary" From 7ab0a2c7ed5844abf8e3d424cbd73930c0606db8 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 10 Nov 2023 17:37:01 -0500 Subject: [PATCH 8/8] Added test where the property is different than the attribute Using prefilled value on an input element, first verify that the value attribute and property are the same. Then modifying the value by inputting text verify the value property has changed but the attribute value remains the same. --- atest/acceptance/keywords/elements.robot | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/atest/acceptance/keywords/elements.robot b/atest/acceptance/keywords/elements.robot index eb0bd76ac..5419baa15 100644 --- a/atest/acceptance/keywords/elements.robot +++ b/atest/acceptance/keywords/elements.robot @@ -134,6 +134,19 @@ Get "Attribute" That Is Both An DOM Attribute and Property ${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}