Repository: bokeh/bokeh

Issue:
WebDriverException when using export_png
Bokeh v0.12.10

When I try to use `export_png` in my application, I receive (pretty-printed):
```
{
  "errorMessage": "'undefined' is not an object (evaluating 'window.Bokeh.documents')",
  "request": {
    "headers": {
      "Accept": "application\/json",
      "Accept-Encoding": "identity",
      "Connection": "close",
      "Content-Length": "349",
      "Content-Type": "application\/json;charset=UTF-8",
      "Host": "127.0.0.1:32956",
      "User-Agent": "Python-urllib\/3.5"
    },
    "httpVersion": "1.1",
    "method": "POST",
    "post": "{\"sessionId\": \"ae4dd6f0-c218-11e7-8813-fba0ae2a2a1f\", \"script\": \"\\n\/\/ add private window prop to check that render is complete\\nwindow._bokeh_render_complete = false;\\nfunction done() {\\n  window._bokeh_render_complete = true;\\n}\\n\\nvar doc = window.Bokeh.documents[0];\\n\\nif (doc.is_idle)\\n  done();\\nelse\\n  doc.idle.connect(done);\\n\", \"args\": []}",
    "url": "\/execute",
    "urlParsed": {
      "anchor": "",
      "query": "",
      "file": "execute",
      "directory": "\/",
      "path": "\/execute",
      "relative": "\/execute",
      "port": "",
      "host": "",
      "password": "",
      "user": "",
      "userInfo": "",
      "authority": "",
      "protocol": "",
      "source": "\/execute",
      "queryKey": {
        
      },
      "chunks": [
        "execute"
      ]
    },
    "urlOriginal": "\/session\/ae4dd6f0-c218-11e7-8813-fba0ae2a2a1f\/execute"
  }
}
```

Apparently, `Bokeh.documents` can still be uninitialized when the page is loaded by Selenium.
Maybe `wait_until_render_complete` should use `WebDriverWait` to also check that `Bokeh.documets` exists?


Discussion:
Adding
```python
def is_bokeh_loaded(driver):
    return driver.execute_script('return !!(window.Bokeh && window.Bokeh.documents);')

WebDriverWait(driver, 5, poll_frequency=0.1).until(is_bokeh_loaded)
```
before `driver.execute_script(_WAIT_SCRIPT)` seem to fix the problem. Is this an acceptable solution?
@p-himik that seems like a pretty reasonable thing to do. I guess it would be nice to understand the conditions that necessitate that since it has not been run into by other folks that I have heard at least. 

cc @canavandl any thoughts?
I am getting the same bug with export_png:


  "errorMessage": "undefined is not an object (evaluating 'doc.is_idle')",
  "request": {
    "headers": {
      "Accept": "application\/json",
      "Accept-Encoding": "identity",
      "Connection": "close",
      "Content-Length": "349",
      "Content-Type": "application\/json;charset=UTF-8",
      "Host": "127.0.0.1:51886",
      "User-Agent": "Python http auth"
    },
    "httpVersion": "1.1",
    "method": "POST",
    "post": "{\"script\": \"\\n\/\/ add private window prop to check that render is complete\\nwindow._bokeh_render_complete = false;\\nfunction done() {\\n  window._bokeh_render_complete = true;\\n}\\n\\nvar doc = window.Bokeh.documents[0];\\n\\nif (doc.is_idle)\\n  done();\\nelse\\n  doc.idle.connect(done);\\n\", \"args\": [], \"sessionId\": \"8346b220-ce0b-11e7-8561-e908b7afeb5c\"}",
    "url": "\/execute",
    "urlParsed": {
      "anchor": "",
      "query": "",
      "file": "execute",
      "directory": "\/",
      "path": "\/execute",
      "relative": "\/execute",
      "port": "",
      "host": "",
      "password": "",
      "user": "",
      "userInfo": "",
      "authority": "",
      "protocol": "",
      "source": "\/execute",
      "queryKey": {
        
      },
      "chunks": [
        "execute"
      ]
    },
    "urlOriginal": "\/session\/8346b220-ce0b-11e7-8561-e908b7afeb5c\/execute"
  }
}
Mine seems to be the same problem. (Which is based on the bokeh group topic: https://groups.google.com/a/continuum.io/forum/#!topic/bokeh/BmO8sk2IB5Y)

@bryevdv To me it seems like the condition is the following:
_After the layout has been added to the root, exporting the png of a fig is not possible anymore._
Please see this gist, which shows the export both working and not working:
https://gist.github.com/AzraelDD/c4573f531fbc52cf5d0811d81d30a3fa

@p-himik Your fix does not work for me, but maybe that is because my error is slightly different:
`"errorMessage": "undefined is not an object (evaluating 'doc.is_idle')"`
instead of your
`"errorMessage": "'undefined' is not an object (evaluating 'window.Bokeh.documents')"`
I'm having the same error when trying save plots on button click when values are put in textinput and plots have been updated:
```
def exppng():
	export_png(plot1, filename="p1.png")
button.on_click(exppng)
```
Just "export_png(plot1, filename="p1.png")" shows no error and works
I'm having the same error when trying save plots on button click when values are put in textinput and plots have been updated:

def exppng():
	export_png(plot1, filename="p1.png")
button.on_click(exppng)
Just "export_png(plot1, filename="p1.png")" shows no error and works

Same error as this !
@p-himik would you be able to submit the small PR with the fix you described above?

Resolving patch:
diff --git a/bokeh/io/export.py b/bokeh/io/export.py
--- a/bokeh/io/export.py
+++ b/bokeh/io/export.py
@@ -13,6 +13,7 @@
 # Boilerplate
 #-----------------------------------------------------------------------------
 from __future__ import absolute_import, division, print_function, unicode_literals
+from six import raise_from
 
 import logging
 log = logging.getLogger(__name__)
@@ -241,6 +242,17 @@ def wait_until_render_complete(driver):
     from selenium.webdriver.support.ui import WebDriverWait
     from selenium.common.exceptions import TimeoutException
 
+    def is_bokeh_loaded(driver):
+        return driver.execute_script('''
+            const b = window.Bokeh;
+            return b && b.documents && b.documents.length > 0;
+        ''')
+
+    try:
+        WebDriverWait(driver, 5, poll_frequency=0.1).until(is_bokeh_loaded)
+    except TimeoutException as e:
+        raise_from(RuntimeError('Bokeh was not loaded in time. Something may have gone wrong.'), e)
+
     driver.execute_script(_WAIT_SCRIPT)
 
     def is_bokeh_render_complete(driver):


Test patch: