I am using Robot Framework to do test automation and would like to know if there is a way for me to extend the Selenium Webdriver to handle a loading element universally. For reference when I say loading element I am referring to a custom Angular element.
I understand that I can do a Wait Until Element Not Visible, but what I really want to do is create a constant listener on the Selenium Webdriver to halt whenever this loading element is visible. I would like to not constantly use a robot keyword to check if the element is visible, and be forced to implement arbitrary-ish sleeps.
If my question is still not clear, below is some pseudocode / psuedoflow I was thinking of. I'm assuming Selenium is single threaded, otherwise this idea would be fruitless.
Selenium Webdriver:
AddListener(new LoadingListener)
LoadingListener
While ( LoadingElement is Visible)
Sleep 1s
Please respond with any feedback. If this isn't possible let me know, If you have any ideas also let me know.
I haven't ever done this, but you might be able to use the listener interface to call a function before or after every keyword. For example, the listener might look something like this (untested):
from robot.libraries.BuiltIn import BuiltIn
xpath = //*[#id='loading_element']
class CustomListener(object):
ROBOT_LISTENER_API_VERSION = 2
def start_keyword(self, name, attrs):
selib = BuiltIn().get_library_instance("SeleniumLibrary")
selib.wait_until_element_is_not_visible(xpath)
This may throw an exception if the element doesn't exist on the current page, and you also need to add code to handle the case where a keyword runs before the browser is open, but hopefully it gives the general idea.
For more information about the listener interface see the section titled Listener Interface in the Robot Framework User Guide.
Another direction you could take is to use the SeleniumLibrary keyword Execute Async Javascript.
The document states:
Executes asynchronous JavaScript code.
Similar to Execute Javascript except that scripts executed with this
keyword must explicitly signal they are finished by invoking the
provided callback. This callback is always injected into the executed
function as the last argument.
Scripts must complete within the script timeout or this keyword will
fail
With the following code example from the docs:
${result} = Execute Async JavaScript
... var callback = arguments[arguments.length - 1];
... function answer(){callback("text");};
... window.setTimeout(answer, 2000);
Should Be Equal ${result} text
Below is the solution I ended up using :)
Worked well, thank you for all comments and help!
from robot.libraries.BuiltIn import BuiltIn
from datetime import datetime
class LoaderListener(object):
ROBOT_LISTENER_API_VERSION = 2
ROBOT_LIBRARY_SCOPE = "GLOBAL"
def __init__(self):
self.ROBOT_LIBRARY_LISTENER = self
self.selib = BuiltIn().get_library_instance("SeleniumLibrary")
def _start_suite(self, name, attrs):
BuiltIn().log_to_console("Loader Listener Has Been Attached.")
def start_keyword(self, name, attrs):
try:
# If this doesn't fail we have the loader
self.selib.find_element("id=loader")
# Take time stamp for when we begin waiting
BuiltIn().log_to_console("The Page Is Loading.")
start_time = datetime.now()
# Wait Until Element Not Visible
# Sometimes this will fail, because by the time we get here the loader is gone.
# In that case, just continue as normal
try:
self.selib.wait_until_element_is_not_visible("id=loader")
except:
pass
# Take time stamp for when we have stopped waiting
end_time = datetime.now()
# Calculate time elapsed
elapsed_time = (end_time-start_time)
# Create output message and log to conosle
output_message = "The Page Has Finished Loading in {0}".format(elapsed_time)
BuiltIn().log_to_console(output_message)
except:
# We don't have the element...
pass
Related
I've created a GUI that ask the user an User/Password. The class that creates the GUI calls another class that creates a web browser and try to login in a website using a method of the same class. If the login is successful, a variable of the GUI's object becomes 'True'
My main file is the next one :
from AskUserPassword import AskGUI
from MainInterfaceGUI import MainGUI
Ask = AskGUI()
Ask = Ask.show()
MainInterface = MainGUI()
if Ask.LoginSuccesful == True:
Ask.close()
MainInterface.show()
If the login is successful, I would like to hide the User/Password GUI and show the main GUI. The code above clearly doesn't work.
How can I make Python to wait until some condition of this type is met ?
Instead of constantly checking for the condition to be met, why not supply what you want upon login to do as a callback to your AskGUI object, and then have the AskGUI object call the callback when login is attempted? Something like:
def on_login(ask_gui):
if ask_gui.LoginSuccesful:
ask_gui.close()
MainInterface.show()
Ask = AskGUI()
Ask.login_callback = on_login
Then, in AskGUI, when the login button is clicked and you check the credentials, you do:
def on_login_click(self):
...
# Check login credentials.
self.LoginSuccessful = True
# Try to get self.login_callback, return None if it doesn't exist.
callback_function = getattr(self, 'login_callback', None)
if callback_function is not None:
callback_function(self)
Re.
I prefer to have all the structure in the main file. This is a reduced example but If I start to trigger from a method inside a class that is also inside another class... it's going to be hard to understand
I recommend this way because all the code to handle something that happens upon login is included in the class that needs to do the logging in. The code to handle which UI elements to display (on_login()) is included in the class that handles that.
You don't need anything in the background that keeps checking to see if Ask.LoginSuccessful has changed.
When you use a decent IDE, it's pretty easy to keep track of where each function is defined.
I have this code in a test helper class
#staticmethod
def setup_with_new_displayname():
""" Sets the application up for tests. 'execute_script' functions had
to be called as a workaround because issues with the webdriver ones
"""
time.sleep(5)
# the next line is where I have the problem:
TestHelper.driver.execute_script(
"document.querySelector('#displayname-input').value = 'gecó '")
TestHelper.driver.find_element_by_id("displayname-input").send_keys(
Keys.BACKSPACE) #Workaround for the field to notice input
# clicks the "Use this name" button
time.sleep(1) # otherwise the button is not always found
TestHelper.driver.execute_script(
"document.querySelector('#display-name-ok-btn').click()")
And I have this code that uses the one above:
#classmethod
def setUpClass(cls):
""" Get the display name prompt out of the way
(it is tested in test_display_name_dialog.py)
"""
TestHelper.setup_with_new_displayname()
time.sleep(3)
I use this VERY SAME seccond snippet at two places, and it works in the first case, but doesn't in the second (WTF?). The second time it opens up the page and the dialog, the focus is even on the text input, the cursor in it, but the text is not written there. (As you can see in my comments, I had problems before with the "no-execute_script" approach too).
The bash I run this from does not show any test results as it should, but is ready to take the next command, which tells me the test script is finished (well, at least it thinks so.) I even tried to print from the code as a way of debugging - no result.
Do you have any idea why this happens? And generally speaking, do you have any idea for a stabler approach for testing in Chrome? I feel I am ready to dump Webdriver, since it caused (almost) only frustration, and it's hard to see what I gained with it now.
The files are to be found here, if for some reason you would like to see more of them:
https://github.com/thenakedthunder/flack/blob/reactify/flack/src/test/selenium/test_helper.py
https://github.com/thenakedthunder/flack/blob/reactify/flack/src/test/selenium/test_channel_view.py
i am scraping one website, there is audio playing from the webpage, i want to know when will the audio finish playing, so i add event listener to the audio webElement to monitor 'onEnded' event to fire, and i want to get noticed from the callback() function within driver.execute_async_script.
but i always get error
TypeError: <function MissionContentItemHelper.audio_play_end at 0x10f3b7b70> is not JSON serializable.
my questions are:
1.does arguments[arguments.length - 1] refer to self.audio_play_end ?
2.why the error output is about 'MissionContentItemHelper.audio_play_end is not JSON serializable'? i can't see any clue between MissionContentItemHelper.audio_play_end and JSON.
3.is the way calling audio_play_end correct?
class MissionContentItemHelper:
def sleep(self):
audio = self.browser.find_element_by_css_selector('#audio')
self.browser.execute_async_script("""
var audio = arguments[0];
callback = arguments[arguments.length - 1];
audio.addEventListener("ended", function(_event) {
callback();
}
""", audio, self.audio_play_end)
#staticmethod
def audio_play_end():
print('the audio finished playing...')
execute_async_script's name confuses many people. It doesn't executes the script asynchronously from your test code, it only allows you to execute a script which runs asynchronously from the browser's main thread, like AJAX calls and JavaScript setTimeout.
These JavaScript functions and alike themselves return almost immediately, but they take a (JavaScript) function argument that is executed when the asynchronous operation completes. What execute_async_script does is exactly like execute_script, but instead of returning when the function itself returns (which is almost immediately) it provides its own callback JavaScript function (which it passes as the last argument, that's why the arguments[arguments.length - 1] is needed), that you can use in the JavaScript code you're passing into execute_async_script, to pass it on to the asynchronous function as the callback. execute_async_script returns only when this callback is called, instead of returning immediately.
For example, in the following code, the call to execute_async_script acts like a delay of 2 seconds:
browser.execute_async_script("setTimeout(arguments[0], 2000)")
Note that because I didn't pass any additional arguments to the script, then arguments[0] is actually the last argument.
In your case, you don't have to pass self.audio_play_end, but you have to call audio.play() inside your JavaScript snippet to start the audio. execute_async_script should return only when the audio is finished playing.
Usage works as following:
# driver allready i initialised
js = """
var done = arguments[0];
// my_async_acript here ..
.then(value=>done(value)
"""
value = driver.execute_async_script(js)
Call javascript function done(value) with the value you want to return.
Note: If you're getting a timeout-exception, check the developer-console for errors
This actually is a dublicate I think reference
It's important to know that the last argument passed is the promise resolver. That means:
self.browser.execute_async_script("""
let [element, resolve] = arguments
element.addEventListener("ended", resolve)
""", audio)
I am trying to gather the files: output.xml, report.html and log.html when the whole execution ends.
I can use a listener for this purpose but I want to avoid write a line like:
robot --listener "SomeListener.py" YourTestSuite.robot
Then, I looked at the documentation and I found that I can use a Test Library as a Listener and import it in my Test Suite, for example:
class SomeLibrary(object):
ROBOT_LISTENER_API_VERSION = 2 # or 3
ROBOT_LIBRARY_SCOPE = "GLOBAL" # or TEST SUITE
def __init__(self):
self.ROBOT_LIBRARY_LISTENER = self
def start_suite(self, data, result):
pass
def close(self):
pass
My problem is that the close method is called when the library goes out of the scope. So, I cannot gather the report files because they don't exist in that moment and I receive an Error. I also tried with the method:
def report_file(self, path):
pass
But, nothing happens, I guess that it could be because a Library as a Listener cannot use this methods from the Listener API or because the file is still not created.
Any idea about How can I gather those files using a library as a Listener?
I am open to ideas or suggestions.
Thanks.
The Listener specification actually specifies three methods that are triggered when the three files are generated:
startSuite
endSuite
startTest
endTest
startKeyword
endKeyword
logMessage
message
outputFile
logFile
reportFile
debugFile
Have a look at the last 4 ones, which should be helpful. For more details look at the listener API specification. 4.3.3 Listener interface methods
I am writing some functional tests and it takes 5 minutes to do some simple single-page testing because the find_element function takes 30 seconds to complete when the element is not found. I need to test for the absence of an element without having to wait for a timeout. I've been searching but so far have not found any alternative to find_element(). Here's my code:
def is_extjs_checkbox_selected_by_id(self, id):
start_time = time.time()
find_result = self.is_element_present(By.XPATH, "//*[#id='" + id + "'][contains(#class,'x-form-cb-checked')]") # This line is S-L-O-W
self.step(">>>>>> This took " + str( (time.time() - start_time) ) + " seconds")
return find_result
def is_element_present(self, how, what):
try: self.driver.find_element(by=how, value=what)
except NoSuchElementException, e: return False
return True
Thanks.
Well, I followed most of the recommendations here and in the other links and in the end, and have failed to achieve the goal. It has the exact same behavior of taking 30 seconds when the element is not found:
# Fail
def is_element_present_timeout(self, id_type, id_locator, secs_wait_before_testing):
start_time = time.time()
driver = self.driver
time.sleep(secs_wait_before_testing)
element_found = True
try:
element = WebDriverWait(driver, 0).until(
EC.presence_of_element_located((id_type, id_locator))
)
except:
element_found = False
elapsed_time = time.time() - start_time
self.step("elapsed time : " + str(elapsed_time))
return element_found
Here's a second approach using the idea of getting all elements
# Fail
def is_element_present_now(self, id_type, id_locator):
driver = self.driver
# This line blocks for 30 seconds if the id_locator is not found, i.e. fail
els = driver.find_elements(By.ID, id_locator)
the_length = els.__len__()
if the_length == 0:
result = False
else:
result = True
self.step('length='+str(the_length))
return result
Note, I've unaccepted previous answer since following poster's advice did not produce a successful result.
Given the code you show in the question, if you have a 30 second wait before Selenium decides that the element is missing, this means you are using implicit waits. You should stop using implicit waits and use explicit waits exclusively. The reason is that sooner or later you'll want to use an explicit wait to control precisely how long Selenium waits. Unfortunately, implicit and explicit waits just don't mix. The details are explained in this answer.
There are two general methods I use to test for the absence of an element, depending on conditions. The one problem that we face when using Selenium to test dynamic applications is the following: at which point can you be certain that the element whose absence you want to check is not going to show up a fraction of a second later than the moment at which you check? For instance, if you perform a test which clicks a button, which launches an Ajax request, which may cause an element indicating an error to be added to the DOM when the request fails, and you check for the presence of the error element immediately after asking Selenium to click the button, chances are you're going to miss the error message, if it so happens that the request fails. You'd have to wait at least a little bit to give the Ajax request a chance to complete. How long depends on your application.
This being said, here are the two methods that I use.
Pair an Absence With a Presence
I pair the absence test with a presence test, use find_elements rather than find_element, check for a a length of zero on the array returned by find_elements.
By "I pair the absence test with a presence test" I mean that I identify a condition like the presence of another element on the page. This condition must be such that if the condition is true, then I do not need to wait to test for the absence: I know that if the condition is true, the element whose absence I want to test must be either finally absent or present on the page. It won't show up a fraction of a second later.
For instance, I have a button that when clicked performs a check through an Ajax call, and once the Ajax call is done puts on the page <p>Check complete</p> and under it adds paragraphs with error messages if there are errors. To test for the case where there is no error, I'd wait for <p>Check complete</p> to come up and only then check for the absence of the error messages. I'd use find_elements to check for these error messages and if the length of the returned list is 0, I know there are none. Checking for these error messages themselves does not have to use any wait.
This also works for cases where there is no Ajax involved. For instance, if a page is generated by the server with or without a certain element, then there is no need to wait and the method described here can be used. The "presence" of the page (that is, the page has finished loading) is the only condition needed to check for the absence of the element you want to check.
Wait for a Timeout
In cases where I cannot benefit from some positive condition to pair with my absence check, then I use an explicit wait:
import selenium.webdriver.support.expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from nose.tools import assert_raises
def test():
WebDriverWait(driver, timeout).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".foo")))
assert_raises(TimeoutException, test)
In the code above driver a Selenium's WebDriver previously created, timeout is the desired timeout, and assert_raises is just an example of an assertion that could be used. The assertion passes if TimeoutException is raised.
After researching this and getting nowhere I finally came up with an acceptable solution. It's kind of embarrassingly simple how easy this was to solve. All I had to do was to manipulate the timeout with the implicitly_wait() function, which for some reason I was ignoring/not noticing. I'm using the syntax provided by Louis but it would be even simpler and would work just the same by taking my original is_element_present() function above and adding the driver.implicitly_wait() functions before and after the find_element() function.
# Success
def is_element_present(self, id_type, id_locator):
driver = self.driver
element_found = True
driver.implicitly_wait(1)
try:
element = WebDriverWait(driver, 0).until(
EC.presence_of_element_located((id_type, id_locator))
)
except:
element_found = False
driver.implicitly_wait(30)
return element_found
I wrote a simple method in Java:
public boolean isElementPresent(long waitTime, String elementLocation) {
WebDriverWait wait = new WebDriverWait(driver, waitTime); // you can set the wait time in second
try {
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath(elementLocation)));
} catch (Exception e) {
return false;
}
return true;
}
(you can see examples in python: http://selenium-python.readthedocs.org/en/latest/waits.html)