From 5a6735c27302bd89ad943728dafda3e72c8769e4 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 9 Jun 2023 15:15:41 -0400 Subject: [PATCH 01/21] Sketching out initial ideas for test suite on expected conditions --- atest/acceptance/expected_conditions.robot | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 atest/acceptance/expected_conditions.robot diff --git a/atest/acceptance/expected_conditions.robot b/atest/acceptance/expected_conditions.robot new file mode 100644 index 000000000..06c471857 --- /dev/null +++ b/atest/acceptance/expected_conditions.robot @@ -0,0 +1,34 @@ +*** Test Cases *** +# Wait Until Element State Is (Not) +# Get Element State +# Element States Should (Not) Be + +Check waiting for condition that takes a element + Fail + +Check waiting for condition that takes a title + Fail + +Check waiting for condition that takes a url + Fail + Wait Until url contains google + # verify took 2 seconds + +Check waiting for condition that takes locator and string + Wait Until Element State Is ${condition} ${locator} ${string} + Wait Until Element State Is ${condition} ${element} + Wait Until Condition Is ${condition} ${target} + Wait Until Condition Is ${condition} ${whatelse you need for this condition} + + + Wait Until State Is number_of_windows_to_be + Wait Until Expected Condition Is number_of_windows_to_be + Wait Until Condition Is number of windows to be 5 + Wait Until Condition Is text to be present in element attribute //some/xpath/to/an/element href http://hello + + Wait Until Condition Is number of windows to be 5 text to be present in element attribute //some/xpath/to/an/element href http://hello + + Wait Until number of windows to be 5 + Wait Until text to be present in element attribute //some/xpath/to/an/element href http://hello + Get Condition + Is number of windows to be 5 \ No newline at end of file From 583c9e600378c716a64361ac783b52f74286c3c2 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 10 Nov 2023 20:29:33 -0500 Subject: [PATCH 02/21] Added dynamic and delayed title change as initial test framework for expected conditions --- atest/resources/html/javascript/dynamic_content.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/atest/resources/html/javascript/dynamic_content.html b/atest/resources/html/javascript/dynamic_content.html index 284d5d4ee..8c602cd64 100644 --- a/atest/resources/html/javascript/dynamic_content.html +++ b/atest/resources/html/javascript/dynamic_content.html @@ -10,10 +10,17 @@ container = document.getElementById(target_container); container.appendChild(p); } + + function delayed_title_change() { + setTimeout(function(){ + document.title='Delayed'; + },600); + } change title
+ delayed change title
add content
title to ääää

From 2b10abf0c9cdd7596ebaf17bfff0937b75e729c3 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 10 Nov 2023 22:12:10 -0500 Subject: [PATCH 03/21] Initial (working) prototype of wait for.. keyword Wanted to just throw out a sample test case, test application (page), and a working keyword. Interesting this works at a very basic level. See next paths for a few functionality like - instead of snake_case create method for using "space case" - add check for presence of method - add polling delay, timeout - ...? --- .../keywords/expected_conditions.robot | 11 ++++++++ src/SeleniumLibrary/__init__.py | 2 ++ src/SeleniumLibrary/keywords/__init__.py | 1 + .../keywords/expectedconditions.py | 25 +++++++++++++++++++ 4 files changed, 39 insertions(+) create mode 100644 atest/acceptance/keywords/expected_conditions.robot create mode 100644 src/SeleniumLibrary/keywords/expectedconditions.py diff --git a/atest/acceptance/keywords/expected_conditions.robot b/atest/acceptance/keywords/expected_conditions.robot new file mode 100644 index 000000000..05a6496ab --- /dev/null +++ b/atest/acceptance/keywords/expected_conditions.robot @@ -0,0 +1,11 @@ +*** Settings *** +Test Setup Go To Page "javascript/dynamic_content.html" +Resource ../resource.robot + +*** Test Cases *** +Wait For Expected Conditions One Argument + Title Should Be Original + Click Element link=delayed change title + Wait For Expected Condition title_is Delayed + Title Should Be Delayed + diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index 5754cb0f0..3b781725d 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -35,6 +35,7 @@ BrowserManagementKeywords, CookieKeywords, ElementKeywords, + ExpectedConditionKeywords, FormElementKeywords, FrameKeywords, JavaScriptKeywords, @@ -490,6 +491,7 @@ def __init__( BrowserManagementKeywords(self), CookieKeywords(self), ElementKeywords(self), + ExpectedConditionKeywords(self), FormElementKeywords(self), FrameKeywords(self), JavaScriptKeywords(self), diff --git a/src/SeleniumLibrary/keywords/__init__.py b/src/SeleniumLibrary/keywords/__init__.py index 184efff14..fc9f357cf 100644 --- a/src/SeleniumLibrary/keywords/__init__.py +++ b/src/SeleniumLibrary/keywords/__init__.py @@ -18,6 +18,7 @@ from .browsermanagement import BrowserManagementKeywords # noqa from .cookie import CookieKeywords # noqa from .element import ElementKeywords # noqa +from .expectedconditions import ExpectedConditionKeywords # noqa from .formelement import FormElementKeywords # noqa from .frames import FrameKeywords # noqa from .javascript import JavaScriptKeywords # noqa diff --git a/src/SeleniumLibrary/keywords/expectedconditions.py b/src/SeleniumLibrary/keywords/expectedconditions.py new file mode 100644 index 000000000..fed8cfe21 --- /dev/null +++ b/src/SeleniumLibrary/keywords/expectedconditions.py @@ -0,0 +1,25 @@ +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from SeleniumLibrary.base import LibraryComponent, keyword +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +class ExpectedConditionKeywords(LibraryComponent): + @keyword + def wait_for_expected_condition(self, condition, *args): + wait = WebDriverWait(self.driver, 10) + # import sys,pdb;pdb.Pdb(stdout=sys.__stdout__).set_trace() + c = getattr(EC, condition) + wait.until(c(*args)) \ No newline at end of file From eb49c98fc7d13b813752c56a47b0b526702bf5c6 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 10 Nov 2023 22:38:26 -0500 Subject: [PATCH 04/21] Bumped number of library keywords by one --- 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..9b5a34f44 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()), 178) def test_parse_library(self): plugin = "path.to.MyLibrary" From 129e9b03e96556dc827a57e2e7ffe36f0c2f6842 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 10 Nov 2023 22:41:52 -0500 Subject: [PATCH 05/21] Moved previous expected condition tests out of current test suite These were some initial sketches for the keywords and their usage. As they are not working I have moved them out of the test suite. --- ...ected_conditions.robot => expected_conditions.robot.PROTOTYPE} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename atest/acceptance/{expected_conditions.robot => expected_conditions.robot.PROTOTYPE} (100%) diff --git a/atest/acceptance/expected_conditions.robot b/atest/acceptance/expected_conditions.robot.PROTOTYPE similarity index 100% rename from atest/acceptance/expected_conditions.robot rename to atest/acceptance/expected_conditions.robot.PROTOTYPE From 36e7da3832eb059cfbcac12cae45f2a2302bba5f Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sat, 11 Nov 2023 08:13:46 -0500 Subject: [PATCH 06/21] Adding another sample tests and modifying keyword behavior - Added 100 millisecond polling to wait. Seems like a good default. Will need to decide on how to let users set this. - Copied dynamic_content.html making into seperate expected_conditions.html test page. - Added another sample test case --- .../keywords/expected_conditions.robot | 8 ++- .../html/javascript/expected_conditions.html | 72 +++++++++++++++++++ .../keywords/expectedconditions.py | 2 +- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 atest/resources/html/javascript/expected_conditions.html diff --git a/atest/acceptance/keywords/expected_conditions.robot b/atest/acceptance/keywords/expected_conditions.robot index 05a6496ab..b032aa7cd 100644 --- a/atest/acceptance/keywords/expected_conditions.robot +++ b/atest/acceptance/keywords/expected_conditions.robot @@ -1,5 +1,5 @@ *** Settings *** -Test Setup Go To Page "javascript/dynamic_content.html" +Test Setup Go To Page "javascript/expected_conditions.html" Resource ../resource.robot *** Test Cases *** @@ -8,4 +8,8 @@ Wait For Expected Conditions One Argument Click Element link=delayed change title Wait For Expected Condition title_is Delayed Title Should Be Delayed - + +Wait For Expected Conditions using WebElement as locator + Click Button Change the button state + ${dynamic_btn}= Get WebElement id:enabledDisabledBtn + Wait For Expected Condition element_to_be_clickable ${dynamic_btn} diff --git a/atest/resources/html/javascript/expected_conditions.html b/atest/resources/html/javascript/expected_conditions.html new file mode 100644 index 000000000..9e2a38f39 --- /dev/null +++ b/atest/resources/html/javascript/expected_conditions.html @@ -0,0 +1,72 @@ + + + + + Original + + + + change title
+ delayed change title
+ add content
+ title to ääää
+

+ Change Title
+ Add Content
+

+
+
+
+
+
+ +
+

+ + +

+

+

+ + + + + + +
+

+ + + diff --git a/src/SeleniumLibrary/keywords/expectedconditions.py b/src/SeleniumLibrary/keywords/expectedconditions.py index fed8cfe21..94ce5da50 100644 --- a/src/SeleniumLibrary/keywords/expectedconditions.py +++ b/src/SeleniumLibrary/keywords/expectedconditions.py @@ -19,7 +19,7 @@ class ExpectedConditionKeywords(LibraryComponent): @keyword def wait_for_expected_condition(self, condition, *args): - wait = WebDriverWait(self.driver, 10) + wait = WebDriverWait(self.driver, 10, 0.1) # import sys,pdb;pdb.Pdb(stdout=sys.__stdout__).set_trace() c = getattr(EC, condition) wait.until(c(*args)) \ No newline at end of file From 48376f707985a9be74b821e1440fa8c62acec4d9 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sat, 11 Nov 2023 08:28:55 -0500 Subject: [PATCH 07/21] Added sample test case where an expected timeout would occur --- atest/acceptance/keywords/expected_conditions.robot | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/atest/acceptance/keywords/expected_conditions.robot b/atest/acceptance/keywords/expected_conditions.robot index b032aa7cd..a09283d2b 100644 --- a/atest/acceptance/keywords/expected_conditions.robot +++ b/atest/acceptance/keywords/expected_conditions.robot @@ -9,6 +9,12 @@ Wait For Expected Conditions One Argument Wait For Expected Condition title_is Delayed Title Should Be Delayed +Wait For Expected Condition Times out + Title Should Be Original + Click Element link=delayed change title + Wait For Expected Condition title_is Foo + # Verify failure + Wait For Expected Conditions using WebElement as locator Click Button Change the button state ${dynamic_btn}= Get WebElement id:enabledDisabledBtn From 30834595a95658f967f0f20ffdfca7f8902e102d Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Wed, 15 Nov 2023 12:04:15 +0100 Subject: [PATCH 08/21] added timeout argument to keyword wait_for_expected_condition Added timeout atest --- atest/acceptance/keywords/expected_conditions.robot | 6 +++--- src/SeleniumLibrary/keywords/expectedconditions.py | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/atest/acceptance/keywords/expected_conditions.robot b/atest/acceptance/keywords/expected_conditions.robot index a09283d2b..f66f08e8c 100644 --- a/atest/acceptance/keywords/expected_conditions.robot +++ b/atest/acceptance/keywords/expected_conditions.robot @@ -9,11 +9,11 @@ Wait For Expected Conditions One Argument Wait For Expected Condition title_is Delayed Title Should Be Delayed -Wait For Expected Condition Times out +Wait For Expected Condition Times out within set timeout + [Documentation] FAIL REGEXP: TimeoutException: Message: Expected Condition not met within set timeout of 0.5* Title Should Be Original Click Element link=delayed change title - Wait For Expected Condition title_is Foo - # Verify failure + Wait For Expected Condition title_is Delayed timeout=0.5 Wait For Expected Conditions using WebElement as locator Click Button Change the button state diff --git a/src/SeleniumLibrary/keywords/expectedconditions.py b/src/SeleniumLibrary/keywords/expectedconditions.py index 94ce5da50..19b143b92 100644 --- a/src/SeleniumLibrary/keywords/expectedconditions.py +++ b/src/SeleniumLibrary/keywords/expectedconditions.py @@ -11,6 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import string +from typing import Optional from SeleniumLibrary.base import LibraryComponent, keyword from selenium.webdriver.support.wait import WebDriverWait @@ -18,8 +20,9 @@ class ExpectedConditionKeywords(LibraryComponent): @keyword - def wait_for_expected_condition(self, condition, *args): - wait = WebDriverWait(self.driver, 10, 0.1) + def wait_for_expected_condition(self, condition: string, *args, timeout: Optional[float]=10): + wait = WebDriverWait(self.driver, timeout, 0.1) # import sys,pdb;pdb.Pdb(stdout=sys.__stdout__).set_trace() c = getattr(EC, condition) - wait.until(c(*args)) \ No newline at end of file + result = wait.until(c(*args), message="Expected Condition not met within set timeout of " + str(timeout)) + return result From 98d8f89878bb03432ca1daf08ea0a02c4173d973 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Wed, 22 Nov 2023 09:52:46 -0500 Subject: [PATCH 09/21] Some intial parsing and validation for parsing condition argument --- .../keywords/expectedconditions.py | 4 +++ .../test/keywords/test_expectedconditions.py | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 utest/test/keywords/test_expectedconditions.py diff --git a/src/SeleniumLibrary/keywords/expectedconditions.py b/src/SeleniumLibrary/keywords/expectedconditions.py index 19b143b92..01a426ff8 100644 --- a/src/SeleniumLibrary/keywords/expectedconditions.py +++ b/src/SeleniumLibrary/keywords/expectedconditions.py @@ -26,3 +26,7 @@ def wait_for_expected_condition(self, condition: string, *args, timeout: Optiona c = getattr(EC, condition) result = wait.until(c(*args), message="Expected Condition not met within set timeout of " + str(timeout)) return result + + def _parse_condition(self, condition: string): + parsed = condition.replace(' ','_').lower() + return parsed \ No newline at end of file diff --git a/utest/test/keywords/test_expectedconditions.py b/utest/test/keywords/test_expectedconditions.py new file mode 100644 index 000000000..3ade2e5fa --- /dev/null +++ b/utest/test/keywords/test_expectedconditions.py @@ -0,0 +1,31 @@ +import unittest + +from SeleniumLibrary.keywords import ExpectedConditionKeywords + +# Test cases + +# Parsing expected condition +# expect to match .. +# element_to_be_clickable +# Element To Be Clickable +# eLEment TO be ClIcKable +# expect to not match .. +# element__to_be_clickable +# elementtobeclickable +# element_to_be_clickble +# Ice Cream Cone Has Three Scopes + +# what about ..? +# ${ec_var} +# Element\ To\ Be\ Clickable +# Element${SPACE}To${SPACE}Be${SPACE}Clickable + +class ExpectedConditionKeywords(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ec_keywords = ExpectedConditionKeywords(None) + + def WorkInProgresstest_parse_condition(self): + results = [] + results.append(self.ec_keywords._parse_condition("Element To Be Clickable")) + results.append(self.ec_keywords._parse_condition("eLEment TO be ClIcKable")) From 7ad5296f47380ecb5a314d4579c381e6bc7e144c Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Thu, 18 Jan 2024 18:51:03 -0500 Subject: [PATCH 10/21] Basic working keyword for wait Made a few changes .. - Catch unknown expoected conditions and reopport an error. This can still be improved by adding a here are the known expected conditions as well as a "did you mean ..." type message - Brought in parser so one can use `Element To Be Clickable` instead of `element_to_be_clicable`. Admittedly it seems to simple and realized an issue, of case, as I type this. - Added some test cases --- .../keywords/expected_conditions.robot | 21 +++++++++++++++++++ src/SeleniumLibrary/errors.py | 4 ++++ .../keywords/expectedconditions.py | 9 ++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/atest/acceptance/keywords/expected_conditions.robot b/atest/acceptance/keywords/expected_conditions.robot index f66f08e8c..f524b4f8a 100644 --- a/atest/acceptance/keywords/expected_conditions.robot +++ b/atest/acceptance/keywords/expected_conditions.robot @@ -19,3 +19,24 @@ Wait For Expected Conditions using WebElement as locator Click Button Change the button state ${dynamic_btn}= Get WebElement id:enabledDisabledBtn Wait For Expected Condition element_to_be_clickable ${dynamic_btn} + +Wait For Expected Conditions Where Condition Written With Spaces + Title Should Be Original + Click Element link=delayed change title + Wait For Expected Condition title is Delayed + Title Should Be Delayed + +Wait For Expected Conditions Where Condition Is Variable + ${condition}= Set Variable title is + Title Should Be Original + Click Element link=delayed change title + Wait For Expected Condition ${condition} Delayed + Title Should Be Delayed + +Wait For Non Existing Expected Conditions + Click Button Change the button state + ${dynamic_btn}= Get WebElement id:enabledDisabledBtn + Run Keyword And Expect Error this_is_not_an_expected_con_dition is an unknown expected condition + ... Wait For Expected Condition this_is not an expected con dition ${dynamic_btn} + + diff --git a/src/SeleniumLibrary/errors.py b/src/SeleniumLibrary/errors.py index 68ed5ab3f..5dd4310d1 100644 --- a/src/SeleniumLibrary/errors.py +++ b/src/SeleniumLibrary/errors.py @@ -37,3 +37,7 @@ class NoOpenBrowser(SeleniumLibraryException): class PluginError(SeleniumLibraryException): pass + + +class UnkownExpectedCondition(SeleniumLibraryException): + pass \ No newline at end of file diff --git a/src/SeleniumLibrary/keywords/expectedconditions.py b/src/SeleniumLibrary/keywords/expectedconditions.py index 01a426ff8..17e23bfa4 100644 --- a/src/SeleniumLibrary/keywords/expectedconditions.py +++ b/src/SeleniumLibrary/keywords/expectedconditions.py @@ -15,15 +15,20 @@ from typing import Optional from SeleniumLibrary.base import LibraryComponent, keyword +from SeleniumLibrary.errors import UnkownExpectedCondition from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class ExpectedConditionKeywords(LibraryComponent): @keyword def wait_for_expected_condition(self, condition: string, *args, timeout: Optional[float]=10): + condition = self._parse_condition(condition) wait = WebDriverWait(self.driver, timeout, 0.1) - # import sys,pdb;pdb.Pdb(stdout=sys.__stdout__).set_trace() - c = getattr(EC, condition) + try: + c = getattr(EC, condition) + except: + # ToDo: provide hints as to what is avaialbel or find closet match + raise UnkownExpectedCondition(f"{condition} is an unknown expected condition") result = wait.until(c(*args), message="Expected Condition not met within set timeout of " + str(timeout)) return result From b02203184d0557b2f41dcddd10cc6085f81e3af0 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Thu, 18 Jan 2024 19:07:27 -0500 Subject: [PATCH 11/21] Added test case for capitals within expected condition name Hadn't realized I do force the expected condition ame to all lower case letters. So just needed to add a test case! --- atest/acceptance/keywords/expected_conditions.robot | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/atest/acceptance/keywords/expected_conditions.robot b/atest/acceptance/keywords/expected_conditions.robot index f524b4f8a..c3ec60cb6 100644 --- a/atest/acceptance/keywords/expected_conditions.robot +++ b/atest/acceptance/keywords/expected_conditions.robot @@ -33,6 +33,11 @@ Wait For Expected Conditions Where Condition Is Variable Wait For Expected Condition ${condition} Delayed Title Should Be Delayed +Wait For Expected Conditions Where Condition Is Strange Case + Click Button Change the button state + ${dynamic_btn}= Get WebElement id:enabledDisabledBtn + Wait For Expected Condition EleMENT tO BE cLiCkAbLe ${dynamic_btn} + Wait For Non Existing Expected Conditions Click Button Change the button state ${dynamic_btn}= Get WebElement id:enabledDisabledBtn From d23700e2ea91325f4fcce3dcf93a0c0d19bdd5ca Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sat, 20 Jan 2024 20:40:59 -0500 Subject: [PATCH 12/21] Added example test where expected condition expected "locator" tuple Some of the expected conditions expect a locator which is a tuple consisting of the By string [1] and the string containing the locator. I wanted to see what this was like. I am thinking that it would be good to somehow use the libraries locator strategy. Needed to also add to the test page so we had a new element after a delay. [1] https://www.selenium.dev/selenium/docs/api/py/_modules/selenium/webdriver/common/by.html#By --- .../keywords/expected_conditions.robot | 7 +++++- .../html/javascript/expected_conditions.html | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/atest/acceptance/keywords/expected_conditions.robot b/atest/acceptance/keywords/expected_conditions.robot index c3ec60cb6..afdc1d1e3 100644 --- a/atest/acceptance/keywords/expected_conditions.robot +++ b/atest/acceptance/keywords/expected_conditions.robot @@ -44,4 +44,9 @@ Wait For Non Existing Expected Conditions Run Keyword And Expect Error this_is_not_an_expected_con_dition is an unknown expected condition ... Wait For Expected Condition this_is not an expected con dition ${dynamic_btn} - +Wait For Expected Conditions When Condition Includes Locator + Title Should Be Original + ${byElem}= Evaluate ("id","added_btn") + Click Element link:delayed add element + Wait For Expected Condition Presence Of Element Located ${byElem} + Click Element id:added_btn \ No newline at end of file diff --git a/atest/resources/html/javascript/expected_conditions.html b/atest/resources/html/javascript/expected_conditions.html index 9e2a38f39..621421e83 100644 --- a/atest/resources/html/javascript/expected_conditions.html +++ b/atest/resources/html/javascript/expected_conditions.html @@ -3,6 +3,14 @@ Original + + + change title
delayed change title
+ delayed add element
add content
title to ääää

@@ -56,6 +76,9 @@

+

+

+

From 7bdca3c76d2265c3eb2438605b98f95e3865893d Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Mon, 25 Mar 2024 20:39:18 -0400 Subject: [PATCH 13/21] Added keyword documentation for new `Wait For Expected Condition` keyword --- .../keywords/expectedconditions.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/SeleniumLibrary/keywords/expectedconditions.py b/src/SeleniumLibrary/keywords/expectedconditions.py index 17e23bfa4..7b896fd6c 100644 --- a/src/SeleniumLibrary/keywords/expectedconditions.py +++ b/src/SeleniumLibrary/keywords/expectedconditions.py @@ -22,6 +22,32 @@ class ExpectedConditionKeywords(LibraryComponent): @keyword def wait_for_expected_condition(self, condition: string, *args, timeout: Optional[float]=10): + """Waits until ``condition`` is true or ``timeout`` expires. + + The condition must be one of selenium's expected condition which + can be found within the selenium + [https://www.selenium.dev/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.expected_conditions.html#module-selenium.webdriver.support.expected_conditions Python API] + documentation. The expected condition can written as snake_case + (ex title_is) or it can be space delimited (ex Title Is). Some + conditions require additional arguments or ``args`` which should + be passed along after the expected condition. + + Fails if the timeout expires before the condition becomes true. + The default value is 10 seconds. + + Examples: + | `Wait For Expected Condition` | alert_is_present | + | `Wait For Expected Condition` | Title Is | New Title | + + If the expected condition expects a locator then one can pass + as arguments a tuple containing the selenium locator strategies + and the locator. + + Example of expected condition expecting locator: + | ${byElem}= | Evaluate ("id","added_btn") + | `Wait For Expected Condition` | Presence Of Element Located | ${byElem} + """ + condition = self._parse_condition(condition) wait = WebDriverWait(self.driver, timeout, 0.1) try: From fd751bb9153b9c3164fe33f733f6262a9ee97928 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Tue, 2 Apr 2024 19:04:10 -0400 Subject: [PATCH 14/21] Fix Firefox unit tests Made unit test compatible to both Selenium v4.17.2 and above as well as v4.16.0 and prior --- .../keywords/test_firefox_profile_parsing.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/utest/test/keywords/test_firefox_profile_parsing.py b/utest/test/keywords/test_firefox_profile_parsing.py index 23edcfbf8..3a7e895e2 100644 --- a/utest/test/keywords/test_firefox_profile_parsing.py +++ b/utest/test/keywords/test_firefox_profile_parsing.py @@ -56,8 +56,21 @@ def test_single_method(self): def _parse_result(self, result): to_str = "" - if "key1" in result.default_preferences: - to_str = f"{to_str} key1 {result.default_preferences['key1']}" - if "key2" in result.default_preferences: - to_str = f"{to_str} key2 {result.default_preferences['key2']}" + result_attr = self._get_preferences_attribute(result) + if "key1" in result_attr: + to_str = f"{to_str} key1 {result_attr['key1']}" + if "key2" in result_attr: + to_str = f"{to_str} key2 {result_attr['key2']}" self.results.append(to_str) + + def _get_preferences_attribute(self, result): + # -- temporary fix to transition selenium to v4.17.2 from v4.16.0 and prior + # from inspect import signature + # sig = signature(result) + if hasattr(result,'default_preferences'): + return result.default_preferences + elif hasattr(result,'_desired_preferences'): + return result._desired_preferences + else: + return None + # -- From 204ceda7ad1b766a4b36432b2418f3ca0123c1dc Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Tue, 2 Apr 2024 19:43:17 -0400 Subject: [PATCH 15/21] Reduced the timeout on the expected timeout within expected conditions The delay for the title change was 600 miliseconds while the timeout was 0.5 seconds. I suspect this gives little time for error. The 600 milisecond timeout was specifically choosen along with the other timeouts in the expected conditions tests such that any combination of delays would be unique. This gives us a framework to test various combinations. So I didn't want to change that. Noting I could simply swap the 600 millisecond test for the 900 millisecond tes .. which might be the best bet. But for now trying to reduce the timeout to 0.3 seconds. --- atest/acceptance/keywords/expected_conditions.robot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/acceptance/keywords/expected_conditions.robot b/atest/acceptance/keywords/expected_conditions.robot index afdc1d1e3..4fa40dbfe 100644 --- a/atest/acceptance/keywords/expected_conditions.robot +++ b/atest/acceptance/keywords/expected_conditions.robot @@ -10,10 +10,10 @@ Wait For Expected Conditions One Argument Title Should Be Delayed Wait For Expected Condition Times out within set timeout - [Documentation] FAIL REGEXP: TimeoutException: Message: Expected Condition not met within set timeout of 0.5* + [Documentation] FAIL REGEXP: TimeoutException: Message: Expected Condition not met within set timeout of 0.3* Title Should Be Original Click Element link=delayed change title - Wait For Expected Condition title_is Delayed timeout=0.5 + Wait For Expected Condition title_is Delayed timeout=0.3 Wait For Expected Conditions using WebElement as locator Click Button Change the button state From e1a4f4cd81f9dd881aa19ac2dfa2fdabada80119 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Tue, 2 Apr 2024 21:09:03 -0400 Subject: [PATCH 16/21] Expanded selenium versions upto 4.19.0 --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index defc9fd99..500d831af 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,7 +11,7 @@ jobs: matrix: python-version: [3.8, 3.11] # 3.12, pypy-3.9 rf-version: [5.0.1, 6.1.1, 7.0] - selenium-version: [4.14.0, 4.15.2, 4.16.0] #4.17.0, 4.18.0 + selenium-version: [4.14.0, 4.15.2, 4.16.0, 4.17.2, 4.18.1, 4.19.0] browser: [firefox, chrome, headlesschrome] #edge steps: From f17f8f001e46bf87520bc9cc65cf7adbd9de78b2 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 5 Apr 2024 09:39:10 -0400 Subject: [PATCH 17/21] Fix acceptance tests --- atest/acceptance/keywords/choose_file.robot | 10 +++++----- atest/acceptance/keywords/click_element.robot | 2 +- atest/acceptance/keywords/page_load_timeout.robot | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/atest/acceptance/keywords/choose_file.robot b/atest/acceptance/keywords/choose_file.robot index 89dc975fd..c03b12751 100644 --- a/atest/acceptance/keywords/choose_file.robot +++ b/atest/acceptance/keywords/choose_file.robot @@ -45,11 +45,11 @@ Choose File With Grid From Library Using SL choose_file method Input Text Should Work Same Way When Not Using Grid [Documentation] - ... LOG 1:6 DEBUG GLOB: POST*/session/*/clear {* - ... LOG 1:9 DEBUG Finished Request - ... LOG 1:10 DEBUG GLOB: POST*/session/*/value*"text": "* - ... LOG 1:13 DEBUG Finished Request - ... LOG 1:14 DEBUG NONE + ... LOG 1:6 DEBUG GLOB: POST*/session/*/clear {* + ... LOG 1:9 DEBUG Finished Request + ... LOG 1:10 DEBUG REGEXP: POST.*/session/.*/value.*['\\\"]text['\\\"]: ['\\\"].* + ... LOG 1:13 DEBUG Finished Request + ... LOG 1:14 DEBUG NONE [Tags] NoGrid [Setup] Touch ${CURDIR}${/}temp.txt Input Text file_to_upload ${CURDIR}${/}temp.txt diff --git a/atest/acceptance/keywords/click_element.robot b/atest/acceptance/keywords/click_element.robot index 4464ad874..d1fd4d4b3 100644 --- a/atest/acceptance/keywords/click_element.robot +++ b/atest/acceptance/keywords/click_element.robot @@ -40,7 +40,7 @@ Click Element Action Chain [Tags] NoGrid [Documentation] ... LOB 1:1 INFO Clicking 'singleClickButton' using an action chain. - ... LOG 1:6 DEBUG GLOB: *actions {"actions": [{* + ... LOG 1:6 DEBUG REGEXP: .*actions {['\\\"]actions['\\\"]: \\\[\\\{.* Click Element singleClickButton action_chain=True Element Text Should Be output single clicked diff --git a/atest/acceptance/keywords/page_load_timeout.robot b/atest/acceptance/keywords/page_load_timeout.robot index 6555267c1..a39df7d50 100644 --- a/atest/acceptance/keywords/page_load_timeout.robot +++ b/atest/acceptance/keywords/page_load_timeout.robot @@ -7,7 +7,7 @@ Test Teardown Close Browser And Reset Page Load Timeout *** Test Cases *** Should Open Browser With Default Page Load Timeout [Documentation] Verify that 'Open Browser' changes the page load timeout. - ... LOG 1.1.1:27 DEBUG REGEXP: POST http://localhost:\\d{2,5}/session/[a-f0-9-]+/timeouts {"pageLoad": 300000} + ... LOG 1.1.1:27 DEBUG REGEXP: POST http://localhost:\\d{2,5}/session/[a-f0-9-]+/timeouts {['\\\"]pageLoad['\\\"]: 300000} ... LOG 1.1.1:29 DEBUG STARTS: Remote response: status=200 # Note: previous log check was 33 and 37. Recording to see if something is swtiching back and forth Open Browser To Start Page @@ -21,8 +21,8 @@ Should Run Into Timeout Exception Should Set Page Load Timeout For All Opened Browsers [Documentation] One browser is already opened as global suite setup. - ... LOG 2:1 DEBUG REGEXP: POST http://localhost:\\d{2,5}/session/[a-f0-9-]+/timeouts {"pageLoad": 5000} - ... LOG 2:5 DEBUG REGEXP: POST http://localhost:\\d{2,5}/session/[a-f0-9-]+/timeouts {"pageLoad": 5000} + ... LOG 2:1 DEBUG REGEXP: POST http://localhost:\\d{2,5}/session/[a-f0-9-]+/timeouts {['\\\"]pageLoad['\\\"]: 5000} + ... LOG 2:5 DEBUG REGEXP: POST http://localhost:\\d{2,5}/session/[a-f0-9-]+/timeouts {['\\\"]pageLoad['\\\"]: 5000} Open Browser To Start Page Set Selenium Page Load Timeout 5 s From de1a4fbe2b43b454e5b6e2b1578af28825c73518 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Fri, 5 Apr 2024 11:20:32 -0400 Subject: [PATCH 18/21] Append results to zip file This may greatly increase the archive file size which we need to simplify our matrix. But at least we should get some more insights into failing tests. --- atest/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/run.py b/atest/run.py index e4059716f..8b5bac4f6 100755 --- a/atest/run.py +++ b/atest/run.py @@ -259,7 +259,7 @@ def create_zip(): zip_name = f"rf-{robot_version}-python-{python_version}.zip" zip_path = os.path.join(ZIP_DIR, zip_name) print("Zip created in: %s" % zip_path) - zip_file = zipfile.ZipFile(zip_path, "w") + zip_file = zipfile.ZipFile(zip_path, "a") for root, dirs, files in os.walk(RESULTS_DIR): for file in files: file_path = os.path.join(root, file) From 8db00db599896d5013316424ebd7c5442407ae92 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sun, 14 Apr 2024 16:39:30 -0500 Subject: [PATCH 19/21] Added selenium version and browser name to archive --- atest/run.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/atest/run.py b/atest/run.py index 8b5bac4f6..2c86cf994 100755 --- a/atest/run.py +++ b/atest/run.py @@ -52,6 +52,7 @@ from robot import rebot_cli from robot import __version__ as robot_version +from selenium import __version__ as selenium_version from robot.utils import is_truthy try: @@ -251,12 +252,12 @@ def process_output(browser): return exit.code -def create_zip(): +def create_zip(browser = None): if os.path.exists(ZIP_DIR): shutil.rmtree(ZIP_DIR) os.mkdir(ZIP_DIR) python_version = platform.python_version() - zip_name = f"rf-{robot_version}-python-{python_version}.zip" + zip_name = f"rf-{robot_version}-python-{python_version}-selenium-{selenium_version}-{browser}.zip" zip_path = os.path.join(ZIP_DIR, zip_name) print("Zip created in: %s" % zip_path) zip_file = zipfile.ZipFile(zip_path, "a") @@ -326,5 +327,5 @@ def create_zip(): interpreter, browser, rf_options, selenium_grid, event_firing_webdriver ) if args.zip: - create_zip() + create_zip(browser) sys.exit(failures) From a0e241d72275ff1e211986b440e14c521fb3d7bb Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sun, 14 Apr 2024 17:05:09 -0500 Subject: [PATCH 20/21] Upload only failed artifacts --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 500d831af..23e3a3e2d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -88,7 +88,7 @@ jobs: # xvfb-run --auto-servernum python atest/run.py --zip headlesschrome --grid True - uses: actions/upload-artifact@v1 - if: success() || failure() + if: failure() with: name: SeleniumLibrary Test results path: atest/zip_results From cc41bc1b58646d02bd220ad7c33f951bee82a2a0 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sun, 14 Apr 2024 17:43:50 -0500 Subject: [PATCH 21/21] Updated some expected log messages in chrome tests --- .../multiple_browsers_options.robot | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/atest/acceptance/multiple_browsers_options.robot b/atest/acceptance/multiple_browsers_options.robot index 14dfde440..40ce798d7 100644 --- a/atest/acceptance/multiple_browsers_options.robot +++ b/atest/acceptance/multiple_browsers_options.robot @@ -9,32 +9,32 @@ Documentation Creating test which would work on all browser is not possible. *** Test Cases *** Chrome Browser With Selenium Options As String [Documentation] - ... LOG 1:14 DEBUG GLOB: *"goog:chromeOptions"* - ... LOG 1:14 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* + ... LOG 1:14 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].* + ... LOG 1:14 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].* Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("--disable-dev-shm-usage") Chrome Browser With Selenium Options As String With Attribute As True [Documentation] - ... LOG 1:14 DEBUG GLOB: *"goog:chromeOptions"* - ... LOG 1:14 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* - ... LOG 1:14 DEBUG GLOB: *"--headless=new"* + ... LOG 1:14 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].* + ... LOG 1:14 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].* + ... LOG 1:14 DEBUG REGEXP: .*['\\\"]--headless=new['\\\"].* Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument ( "--disable-dev-shm-usage" ) ; add_argument ( "--headless=new" ) Chrome Browser With Selenium Options With Complex Object [Tags] NoGrid [Documentation] - ... LOG 1:14 DEBUG GLOB: *"goog:chromeOptions"* - ... LOG 1:14 DEBUG GLOB: *"mobileEmulation": {"deviceName": "Galaxy S5"* - ... LOG 1:14 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* + ... LOG 1:14 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].* + ... LOG 1:14 DEBUG REGEXP: .*['\\\"]mobileEmulation['\\\"]: {['\\\"]deviceName['\\\"]: ['\\\"]Galaxy S5['\\\"].* + ... LOG 1:14 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].* Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument ( "--disable-dev-shm-usage" ) ; add_experimental_option( "mobileEmulation" , { 'deviceName' : 'Galaxy S5'}) Chrome Browser With Selenium Options Object [Documentation] - ... LOG 2:14 DEBUG GLOB: *"goog:chromeOptions"* - ... LOG 2:14 DEBUG GLOB: *args": ["--disable-dev-shm-usage"?* + ... LOG 2:14 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].* + ... LOG 2:14 DEBUG REGEXP: .*args['\\\"]: \\\[['\\\"]--disable-dev-shm-usage['\\\"].* ${options} = Get Chrome Options Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} ... desired_capabilities=${DESIRED_CAPABILITIES} options=${options} @@ -47,8 +47,8 @@ Chrome Browser With Selenium Options Invalid Method Chrome Browser With Selenium Options Argument With Semicolon [Documentation] - ... LOG 1:14 DEBUG GLOB: *"goog:chromeOptions"* - ... LOG 1:14 DEBUG GLOB: *["has;semicolon"* + ... LOG 1:14 DEBUG REGEXP: .*['\\\"]goog:chromeOptions['\\\"].* + ... LOG 1:14 DEBUG REGEXP: .*\\\[['\\\"]has;semicolon['\\\"].* Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} ... desired_capabilities=${DESIRED_CAPABILITIES} options=add_argument("has;semicolon")