From d66b42724cdc9ace17a2547557820ef992b103e5 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Mon, 2 Nov 2020 16:35:42 +0100 Subject: [PATCH 1/4] test: this test if stacktraces are cleared in output widgets Depends on https://github.com/jupyter/jupyter_server/pull/326 --- setup.py | 3 +- .../voila/templates/base/static/main.js | 23 ++++++++++ tests/app/show_traceback_test.py | 46 +++++++++++++++++++ tests/conftest.py | 12 +++++ voila/app.py | 2 + 5 files changed, 85 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 327fe1e4b..4c6d23dda 100644 --- a/setup.py +++ b/setup.py @@ -241,7 +241,8 @@ def get_data_files(): 'pytest', 'pytest-tornasync', 'matplotlib', - 'ipywidgets' + 'ipywidgets', + 'pyppeteer', ] }, 'url': 'https://github.com/voila-dashboards/voila', diff --git a/share/jupyter/voila/templates/base/static/main.js b/share/jupyter/voila/templates/base/static/main.js index c703a05b5..be037cd8c 100644 --- a/share/jupyter/voila/templates/base/static/main.js +++ b/share/jupyter/voila/templates/base/static/main.js @@ -6,6 +6,28 @@ * The full license is in the file LICENSE, distributed with this software. * ****************************************************************************/ +window.VoilaDebug = VoilaDebug = { + _widgetManagerPromiseResolve: null, + _widgetManagerPromise: null, + async waitForWidgetManager() { + await _widgetManagerPromise; + }, + async waitForViews(modelId) { + const widgetManager = await window.VoilaDebug._widgetManagerPromise; + const model = await widgetManager._models[modelId]; + await Promise.all(Object.values(model.views)) + }, + async waitForAllViews() { + console.log(window.VoilaDebug._widgetManagerPromise) + const widgetManager = await window.VoilaDebug._widgetManagerPromise; + for(const modelId in widgetManager._models) { + await VoilaDebug.waitForViews(modelId); + } + } +} + +window.VoilaDebug._widgetManagerPromise = new Promise((resolve) => window.VoilaDebug._widgetManagerPromiseResolve = resolve), + // NOTE: this file is not transpiled, async/await is the only modern feature we use here require([window.voila_js_url || 'static/voila'], function(voila) { // requirejs doesn't like to be passed an async function, so create one inside @@ -45,6 +67,7 @@ require([window.voila_js_url || 'static/voila'], function(voila) { }); await widgetManager.build_widgets(); voila.renderMathJax(); + window.VoilaDebug._widgetManagerPromiseResolve(widgetManager); } if (document.readyState === 'complete') { diff --git a/tests/app/show_traceback_test.py b/tests/app/show_traceback_test.py index 23d6290b0..4adee5612 100644 --- a/tests/app/show_traceback_test.py +++ b/tests/app/show_traceback_test.py @@ -1,4 +1,8 @@ +import asyncio +import os + import pytest +import pyppeteer @pytest.fixture(params=[True, False]) @@ -21,3 +25,45 @@ async def test_syntax_error(http_server_client, syntax_error_notebook_url, show_ else: assert 'There was an error when executing cell' in output assert 'This should not be executed' not in output + + +async def test_output_widget_traceback(http_server_client, exception_runtime_notebook_url, show_tracebacks): + options = dict(headless=False, devtools=True) if os.environ.get('VOILA_TEST_DEBUG_VISUAL', False) else {} + # with headless and gpu enabled, we get slightly different results on the same OS + # we can enable it if we need to, since we allow for a tolerance + launcher = pyppeteer.launcher.Launcher(options=options, autoClose=False, args=['--font-render-hinting=none', '--disable-gpu']) + browser = await launcher.launch() + page = await browser.newPage() + try: + await page.goto(http_server_client.get_url(exception_runtime_notebook_url), waitUntil='networkidle2') + await page.evaluate('async () => await VoilaDebug.widgetManagerPromise') + await page.evaluate('async () => await VoilaDebug.waitForAllViews()') + await page.evaluate("document.querySelector('.button2').click()") + for i in range(20): + await asyncio.sleep(0.05) + output_text = await page.evaluate("document.querySelector('.output_exception').innerText") + if output_text: + break + else: + assert False, f"Never received output" + if show_tracebacks: + assert 'this happens after the notebook is executed' in output_text + else: + assert 'this happens after the notebook is executed' not in output_text + except Exception as e: # noqa + if os.environ.get('VOILA_TEST_DEBUG_VISUAL', False): + import traceback + traceback.print_exc() + # may want to add --async-test-timeout=60 to pytest arguments + print("Waiting for 60 second for visual inspection (hit ctrl-c to break)") + import sys + sys.stdout.flush() + sys.stderr.flush() + try: + await asyncio.sleep(60) + except: # noqa ignore ctrl-c + pass + raise e + finally: + await browser.close() + await launcher.killChrome() diff --git a/tests/conftest.py b/tests/conftest.py index 5b343e941..7a4ea00fa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,6 +22,18 @@ def syntax_error_notebook_url(base_url): return base_url + "voila/render/syntax_error.ipynb" +@pytest.fixture +def exception_runtime_notebook_url(base_url): + return base_url + "voila/render/exception_runtime.ipynb" + + @pytest.fixture def voila_notebook(notebook_directory): return os.path.join(notebook_directory, 'print.ipynb') + + +# this forces the event_loop fixture in pytest_asyncio to use the same ioloop as pytest_tornasync +@pytest.fixture() +def event_loop(io_loop): + import asyncio + return asyncio.get_event_loop() diff --git a/voila/app.py b/voila/app.py index a32983471..ff10fe6a9 100644 --- a/voila/app.py +++ b/voila/app.py @@ -413,6 +413,8 @@ def start(self): parent=self, connection_dir=self.connection_dir, kernel_spec_manager=self.kernel_spec_manager, + allow_tracebacks=self.voila_configuration.show_tracebacks, + traceback_replacement_message='An error occurred, run VoilĂ  with --show_traceback=True or --debug to show the traceback.', allowed_message_types=[ 'comm_open', 'comm_close', From 50b3a848afd83f99d208834985d53aad0b0e6f95 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Mon, 2 Nov 2020 17:07:01 +0100 Subject: [PATCH 2/4] fix? --- share/jupyter/voila/templates/base/static/main.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/share/jupyter/voila/templates/base/static/main.js b/share/jupyter/voila/templates/base/static/main.js index be037cd8c..1ece04cca 100644 --- a/share/jupyter/voila/templates/base/static/main.js +++ b/share/jupyter/voila/templates/base/static/main.js @@ -18,10 +18,9 @@ window.VoilaDebug = VoilaDebug = { await Promise.all(Object.values(model.views)) }, async waitForAllViews() { - console.log(window.VoilaDebug._widgetManagerPromise) const widgetManager = await window.VoilaDebug._widgetManagerPromise; for(const modelId in widgetManager._models) { - await VoilaDebug.waitForViews(modelId); + await window.VoilaDebug.waitForViews(modelId); } } } From 491435b538aec8c3d6c610c2b973d8565d1da354 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Mon, 2 Nov 2020 17:15:00 +0100 Subject: [PATCH 3/4] fix? --- tests/app/show_traceback_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/app/show_traceback_test.py b/tests/app/show_traceback_test.py index 4adee5612..2aefb0e33 100644 --- a/tests/app/show_traceback_test.py +++ b/tests/app/show_traceback_test.py @@ -36,8 +36,8 @@ async def test_output_widget_traceback(http_server_client, exception_runtime_not page = await browser.newPage() try: await page.goto(http_server_client.get_url(exception_runtime_notebook_url), waitUntil='networkidle2') - await page.evaluate('async () => await VoilaDebug.widgetManagerPromise') - await page.evaluate('async () => await VoilaDebug.waitForAllViews()') + await page.evaluate('async () => await window.VoilaDebug.widgetManagerPromise') + await page.evaluate('async () => await window.VoilaDebug.waitForAllViews()') await page.evaluate("document.querySelector('.button2').click()") for i in range(20): await asyncio.sleep(0.05) From 4658adee2e03eebd20d6e6f7199d544fc65a41ea Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Tue, 3 Nov 2020 10:17:48 +0100 Subject: [PATCH 4/4] wait for main script to load --- share/jupyter/voila/templates/base/voila_setup.macro.html.j2 | 3 ++- tests/app/show_traceback_test.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/share/jupyter/voila/templates/base/voila_setup.macro.html.j2 b/share/jupyter/voila/templates/base/voila_setup.macro.html.j2 index 34693afb5..9f7e49d86 100644 --- a/share/jupyter/voila/templates/base/voila_setup.macro.html.j2 +++ b/share/jupyter/voila/templates/base/voila_setup.macro.html.j2 @@ -7,9 +7,10 @@