11

I have a very complex py.test python-selenium test setup where I create a Firefox webdriver inside a py.test fixture. Here is some idea of what I am doing:

'driver.py':

class Driver(object):
    """
    Driver class with basic wrappers around the selenium webdriver
    and other convenience methods.
    """
    def __init__(self, config, options):
        """Sets the driver and the config.
        """

        self.remote = options.getoption("--remote")
        self.headless = not options.getoption("--with-head")    
        if self.headless:
            self.display = Display(visible=0, size=(13660, 7680))
            self.display.start()

        # Start the selenium webdriver
        self.webdriver = fixefox_module.get_driver()

'conftest.py':

@pytest.fixture
def basedriver(config, options):

    driver = driver.Driver(config, options)

    yield driver

    print("Debug 1")
    driver.webdriver.quit()
    print("Debug 2")

And when running the test I can only see Debug 1 printed out. The whole process stops at this point and does not seem to proceed. The whole selenium test is stuck at the webdriver.quit).

The tests, however, completed successfully...

What reasons could be for that behavior?

Addendum:

The reason why the execution hangs seems to be a popup that asks the user if he wants to leave the page because of unsaved data. That means that the documentation for the quit method is incorrect. It states:

Quits the driver and close every associated window.
Alex
  • 41,580
  • 88
  • 260
  • 469
  • The most common selenium exceptions are cause by a selenium driver mismatch. Does your geckodriver version match the Firefox version you are testing on? – Chuk Ultima Feb 09 '18 at 10:35
  • The tests work fine. Its only that the `quit` method os the webdriver seems to get stuck... – Alex Feb 09 '18 at 10:40
  • Could you try to use the `driver.webdriver.close()` method first? Does that one work correctly? – Chuk Ultima Feb 09 '18 at 10:54
  • That creates an error `SessionNotCreatedException: Message: Tried to run command without establishing a connection`... – Alex Feb 09 '18 at 11:06
  • Strange, I see a comment in this issue (https://github.com/mozilla/geckodriver/issues/1071) that confirms what I thought : _If there is only one browser window open and you use driver.close(), it should quit the webdriver session._ Do you have any other debugging logs? – Chuk Ultima Feb 09 '18 at 11:40
  • @ChukUltima: Not logs I am aware of – Alex Feb 13 '18 at 12:25
  • @Alex : Was test failed or an error is reported. – anurag0510 Feb 13 '18 at 13:36
  • No there were no errors during the test. Maybe an update of firefox changed a boolean value and a popup appeared everytime I `quit` the browser... – Alex Feb 13 '18 at 13:42
  • @Alex : Okay then when you quit the browser, the driver.quit() must have returned the None object. In that case your second statement should have been executed which was written after driver.quit() method. – anurag0510 Feb 14 '18 at 15:57
  • The `quit`statement never returned anything. It was stuck in the `quit` method of the webdriver. – Alex Feb 14 '18 at 16:07
  • @Alex : A function with no return statement always returns a None object in python. Also I checked on the case you presented here myself. In every case it returned me a None object if I close the browser i.e. either by taking accept action on Alert or by taking dismiss action on alert and again trying to close the browser by some means manually and my script run was complete. Also I believe you should go through my answer which will explain you the case in details. If you need mt sample script for verification please let me know. – anurag0510 Feb 15 '18 at 08:48
  • No you don't understand! The `quit` method NEVER finishes. The whole process is hanging WITHIN the `quit`method. The `quit` method never finishes... The code is stuck in the `quit` method. I am not able to execute any code after the `quit` method, because the process was stuck in the `quit` method. Hope its clearer now... – Alex Feb 15 '18 at 09:01
  • I understood your problem and gave the best possible solution. – anurag0510 Feb 15 '18 at 10:21
  • FirefoxDriver(FD)andFirefoxBrowser(FB):When you called quit method it made use of JSONWireProtocol to send request to FD which sends the http requests to FB, and as soon as FB receives the response it will reply to the calling method which in this case is quit, – anurag0510 Feb 15 '18 at 10:22
  • based on the reply from FD it will decide to throw an error or return None Object(which means method executed successfully).As no response from FD we can say Browser never completed processing the quit method and responded to FD which leaves the quit method in waiting state for response from FD. Let me know if you still faces the problem in understanding the issues cause. – anurag0510 Feb 15 '18 at 10:22

6 Answers6

7

This is a non-trivial problem, to which selenium acts really a inconsistent. The quit method should, as documented, just close the browser window(s) but it does not. Instead you get a popup asking the user if he wants to leave the page:

enter image description here

The nasty thing is that this popup appears only after the user called

driver.quit()

One way to fix this is to set the following profile for the driver

from selenium import webdriver
profile = webdriver.FirefoxProfile()
# other settings here
profile.set_preference("dom.disable_beforeunload", True)
driver = webdriver.Firefox(firefox_profile = profile)
Alex
  • 41,580
  • 88
  • 260
  • 469
4

The warning to close is true by default in firefox as you can see in about:config and you can disable them for your profile:

Firefox config

And since,

The reason why the execution hangs seems to be a popup that asks the user if he wants to leave the page because of unsaved data.

You can set browser.tabs.warnOnClose in your Firefox configuration profile as follows:

from selenium import webdriver

profile = webdriver.FirefoxProfile()
profile.set_preference("browser.tabs.warnOnClose", False)
driver = webdriver.Firefox(firefox_profile = profile)

You can look at profile.DEFAULT_PREFERENCES which is the json at python/site-packages/selenium/webdriver/firefox/webdriver_prefs.json

Default Preferences

abybaddi009
  • 1,014
  • 9
  • 22
2

As far as I understood, there are basically two questions asked which I will try to answer :

  1. Why failure of driver.webdriver.quit() method call leaves the script in hang/unresponsive state instead of raising any exception ?
  2. Why the testcase was still a pass if the script never completed it's execution cycle ?

For answering the first question I will try to explain the Selenium Architecture which will clear most of our doubts.

So how Selenium Webdriver Functions ?

Every statement or command you write using Selenium Client Library will be converted to JSON Wire Protocol over http which in turn will be passed to our browser drivers(chromedriver, geckodriver) . So basically these generated http URLs (based on REST architecture) reaches to browser drivers. Inside the browser drivers there are http servers which will internally pass the received URLs to Real Browser (as HTTP over HTTP Server) for which the appropriate response will be generated by Web Browser and sent back to Browser Drivers (as HTTP over HTTP Server) which in turn will use JSON Wire Protocol to send the response back to Selenium Client Library which will finally decide on how to proceed further based on response achieved. Please refer to attached image for more clarification :

enter image description here

Now coming back to the question where script is in hold we can simply conclude that our browser is still working on request received that's why no response is sent back to Browser Driver which in turn left Selenium Library quit() function in hold i.e. waiting for the request processing completion.

So there are variety of workarounds available which we can use, among which one is already explained by Alex. But I believe there is a much better way to handle such conditions, as browser Selenium could leave us in hold/freeze state for other cases too as per my experience so I personally prefer Thread Kill Approach with Timeout as Selenium Object always runs in main() thread. We can allocate a specific time to the main thread and can kill main() thread if timeout session time is reached.

Now moving to the second question which is :

Why the testcase was still a pass if the script never completed it's execution cycle ?

Well I don't have much idea on how pytest works but I do have basic idea on how test engine operates based on which I will try to answer this one.

For starters it's not at all possible for any test case to pass until the full script run is completed. Again, if your test cases are passing there could be very few possible scenarios such as :

  • Your test methods never made use of method which leaves the whole execution in hang/freeze state.
  • You must have called the method inside test tear down environment (w.r.t [TestNG][4] test engine in Java : @AfterClass, @AfterTest, @AfterGroups, @AfterMethod, @AfterSuite) meaning your test execution is completed already. So this might be the reason for tests showing up as successful completion.

I am still not sure what proper cause is there for second reason. I will keep looking and update the post if came up with something.

@Alex : Can you update the question with better understanding i.e. your current test design which I can explore to find better explanation.

anurag0510
  • 763
  • 1
  • 8
  • 17
1

So I was able to reproduce your issue using below sample HTML file

<html>
<body>
    Please enter a value for me: <input name="name" >
<script>
    window.onbeforeunload = function(e) {
        return 'Dialog text here.';
    };

</script>
<h2>ask questions on exit</h2>
</body>
</html>

Then I ran a sample script which reproduces the hang

from selenium import webdriver

driver = webdriver.Firefox()

driver.get("http://localhost:8090/index.html")
driver.find_element_by_name("name").send_keys("Tarun")
driver.quit()

This will hang selenium python indefinitely. Which is not a good thing as such. The issue is the window.onload and window.onbeforeunload are tough for Selenium to handle because of the lifecycle of when it happens. onload happens even before selenium has injected its own code to suppress alert and confirm for handling. I am pretty sure onbeforeunload also is not in reach of selenium.

So there are multiple ways to get around.

Change in app

Ask the devs not to use onload or onbeforeunload events. Will they listen? Not sure

Disable beforeunload in profile

This is what you have already posted in your answer

from selenium import webdriver
profile = webdriver.FirefoxProfile()
# other settings here
profile.set_preference("dom.disable_beforeunload", True)
driver = webdriver.Firefox(firefox_profile = profile)

Disable the events through code

try:
    driver.execute_script("window.onunload = null; window.onbeforeunload=null")
finally:
    pass
driver.quit()

This would work only if you don't have multiple tabs opened or the tab suppose to generate the popup is on focus. But is a good generic way to handle this situation

Not letting Selenium hang

Well the reason selenium hangs is that it send a request to the geckodriver which then sends it to the firefox and one of these just doesn't respond as they wait for user to close the dialog. But the problem is Selenium python driver doesn't set any timeout on this connection part.

To solve the problem it is as simple as adding below two lines of code

import socket
socket.setdefaulttimeout(10)
try:
   driver.quit()
finally:
   # Set to something higher you want
   socket.setdefaulttimeout(60)

But the issue with this approach is that driver/browser will still not be closed. This is where you need even more robust approach to kill the browser as discussed in below answer

In Python, how to check if Selenium WebDriver has quit or not?

Code from above link for making answer complete

from selenium import webdriver
import psutil

driver = webdriver.Firefox()

driver.get("http://tarunlalwani.com")

driver_process = psutil.Process(driver.service.process.pid)

if driver_process.is_running():
    print ("driver is running")

    firefox_process = driver_process.children()
    if firefox_process:
        firefox_process = firefox_process[0]

        if firefox_process.is_running():
            print("Firefox is still running, we can quit")
            driver.quit()
        else:
            print("Firefox is dead, can't quit. Let's kill the driver")
            firefox_process.kill()
    else:
        print("driver has died")
Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
0

The best way to guarantee you run your teardown code in pytest is to define a finalizer function and add it as a finalizer to that fixture. This guarantees that even if something fails before the yield command, you still get your teardown.

To avoid a popup hanging up your teardown, invest in some WebdriverWait.until commands that timeout whenever you want them to. Popup appears, test cannot proceed, times out, teardown is called.

Aphid
  • 343
  • 1
  • 9
0

For ChromeDriver users:

options = Options()
options.add_argument('no-sandbox')
driver.close()
driver.quit()

credits to... https://bugs.chromium.org/p/chromedriver/issues/detail?id=1135

Paul Roub
  • 36,322
  • 27
  • 84
  • 93
hyukkyulee
  • 1,024
  • 1
  • 12
  • 17