Search code examples
pythonseleniumselenium-webdriverbokeh

Dynamically deduce and implement appropriate selenium web driver for user


Firstly, I apologise for any vagueness and the lack of code. My encounter with Selenium wasn't expected, and has arisen from the plotting library "Bokeh"'s 2.0.0+'s requirement for a headless browser within which to render images suitable for export.

Issue: I want to produce an executable application on both Mac and Windows. Within this application lies the functionality of Bokeh to produce PNGs using a headless browser i.e. Selenium. When running the application as an executable, and running the function that spawns the PNG, I'm greeted with this error in the running executable's accompanying console window:

RuntimeError: Neither firefox and geckodriver nor a variant of chromium browser 
and chromedriver are available on system PATH. You can install the former with 
'conda install -c conda-forge firefox geckodriver'.

I've attempted building the project from both the default Poetry, and secondary Conda environments following the output of that error, but both result in the same issue. The issue only arises when running the compiled scripts via the executable, and not from a shell (i.e. python run.py).

So, I know I can programmatically determine which OS the user runs the program on with system.platform(), but what I want to know is if I can do the same determination of browser in use so that I can implement the appropriate web driver to be passed in to the function in order to pass in that web driver to the function that spawns the PNG using it. There are two difficulties here, the first is that the web driver needs to be decided upon - though this can be done through checking to see if an instance of the browser can be created using selenium.webdriver.Firefox.get('/') or the same for .Chrome to deduce whether either of them are present. The second difficulty is making sure the web driver pulled is the same version as the browser installed, though I think that this might be built into the .get('/').

tl;dr: How to dynamically deduce and implement appropriate selenium.webdriver instance for users across both Mac and Windows, and across supported selenium browsers and subsequent selenium web drivers?

Thanks for reading.


Solution

  • The best solution I could come up with on the short notice that was required for this answer was to make both of Bokeh's natively supported web browsers' drivers available. First, I made sure users were aware of the requirement to have a Firefox or Chromium-based browser installed, and that the program would take care of the driver. Then, in the program, I imported the Python packages of both chromedriver_autoinstaller and geckodriver_autoinstaller. Which while not terribly well documented, do work albeit rather uncleanly.

    In the initialisation of the code, I simply added the lines

    chromedriver_autoinstaller.install() 
    geckodriver_autoinstaller.install() 
    

    which attempts to find the local installation of both browser variations, and subsequently find the version of web driver to which they correspond (must match exact version). If someone wanted to install only one of these drivers based on the browser being available, they could implement a variant of this answer:

    try:
        browser = webdriver.Firefox('/path/to/geckodriver')  # Optional argument, if not specified will search path.
        browser.get('/')
    except (IOException, Exception):
        try:
            browser = webdriver.Chrome('path/to/chromedriver') # Optional argument, if not specified will search path.
            browser.get('/')
        except (IOException, Exception):
            print('No Browser Available')
    

    Finally, when utilising Bokeh's export functionality, you could specify the parameters as

    from bokeh.io import export_png
    
    export_png(plot, filename="plot.png", driver=browser)
    

    From that doc - "Bokeh will search for available browsers (and drivers) and use what’s available, unless configured otherwise".