Skip to content

Commit d66b427

Browse files
test: this test if stacktraces are cleared in output widgets
Depends on jupyter-server/jupyter_server#326
1 parent 29646f3 commit d66b427

File tree

5 files changed

+85
-1
lines changed

5 files changed

+85
-1
lines changed

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,8 @@ def get_data_files():
241241
'pytest',
242242
'pytest-tornasync',
243243
'matplotlib',
244-
'ipywidgets'
244+
'ipywidgets',
245+
'pyppeteer',
245246
]
246247
},
247248
'url': 'https://github.com/voila-dashboards/voila',

share/jupyter/voila/templates/base/static/main.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,28 @@
66
* The full license is in the file LICENSE, distributed with this software. *
77
****************************************************************************/
88

9+
window.VoilaDebug = VoilaDebug = {
10+
_widgetManagerPromiseResolve: null,
11+
_widgetManagerPromise: null,
12+
async waitForWidgetManager() {
13+
await _widgetManagerPromise;
14+
},
15+
async waitForViews(modelId) {
16+
const widgetManager = await window.VoilaDebug._widgetManagerPromise;
17+
const model = await widgetManager._models[modelId];
18+
await Promise.all(Object.values(model.views))
19+
},
20+
async waitForAllViews() {
21+
console.log(window.VoilaDebug._widgetManagerPromise)
22+
const widgetManager = await window.VoilaDebug._widgetManagerPromise;
23+
for(const modelId in widgetManager._models) {
24+
await VoilaDebug.waitForViews(modelId);
25+
}
26+
}
27+
}
28+
29+
window.VoilaDebug._widgetManagerPromise = new Promise((resolve) => window.VoilaDebug._widgetManagerPromiseResolve = resolve),
30+
931
// NOTE: this file is not transpiled, async/await is the only modern feature we use here
1032
require([window.voila_js_url || 'static/voila'], function(voila) {
1133
// 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) {
4567
});
4668
await widgetManager.build_widgets();
4769
voila.renderMathJax();
70+
window.VoilaDebug._widgetManagerPromiseResolve(widgetManager);
4871
}
4972

5073
if (document.readyState === 'complete') {

tests/app/show_traceback_test.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import asyncio
2+
import os
3+
14
import pytest
5+
import pyppeteer
26

37

48
@pytest.fixture(params=[True, False])
@@ -21,3 +25,45 @@ async def test_syntax_error(http_server_client, syntax_error_notebook_url, show_
2125
else:
2226
assert 'There was an error when executing cell' in output
2327
assert 'This should not be executed' not in output
28+
29+
30+
async def test_output_widget_traceback(http_server_client, exception_runtime_notebook_url, show_tracebacks):
31+
options = dict(headless=False, devtools=True) if os.environ.get('VOILA_TEST_DEBUG_VISUAL', False) else {}
32+
# with headless and gpu enabled, we get slightly different results on the same OS
33+
# we can enable it if we need to, since we allow for a tolerance
34+
launcher = pyppeteer.launcher.Launcher(options=options, autoClose=False, args=['--font-render-hinting=none', '--disable-gpu'])
35+
browser = await launcher.launch()
36+
page = await browser.newPage()
37+
try:
38+
await page.goto(http_server_client.get_url(exception_runtime_notebook_url), waitUntil='networkidle2')
39+
await page.evaluate('async () => await VoilaDebug.widgetManagerPromise')
40+
await page.evaluate('async () => await VoilaDebug.waitForAllViews()')
41+
await page.evaluate("document.querySelector('.button2').click()")
42+
for i in range(20):
43+
await asyncio.sleep(0.05)
44+
output_text = await page.evaluate("document.querySelector('.output_exception').innerText")
45+
if output_text:
46+
break
47+
else:
48+
assert False, f"Never received output"
49+
if show_tracebacks:
50+
assert 'this happens after the notebook is executed' in output_text
51+
else:
52+
assert 'this happens after the notebook is executed' not in output_text
53+
except Exception as e: # noqa
54+
if os.environ.get('VOILA_TEST_DEBUG_VISUAL', False):
55+
import traceback
56+
traceback.print_exc()
57+
# may want to add --async-test-timeout=60 to pytest arguments
58+
print("Waiting for 60 second for visual inspection (hit ctrl-c to break)")
59+
import sys
60+
sys.stdout.flush()
61+
sys.stderr.flush()
62+
try:
63+
await asyncio.sleep(60)
64+
except: # noqa ignore ctrl-c
65+
pass
66+
raise e
67+
finally:
68+
await browser.close()
69+
await launcher.killChrome()

tests/conftest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ def syntax_error_notebook_url(base_url):
2222
return base_url + "voila/render/syntax_error.ipynb"
2323

2424

25+
@pytest.fixture
26+
def exception_runtime_notebook_url(base_url):
27+
return base_url + "voila/render/exception_runtime.ipynb"
28+
29+
2530
@pytest.fixture
2631
def voila_notebook(notebook_directory):
2732
return os.path.join(notebook_directory, 'print.ipynb')
33+
34+
35+
# this forces the event_loop fixture in pytest_asyncio to use the same ioloop as pytest_tornasync
36+
@pytest.fixture()
37+
def event_loop(io_loop):
38+
import asyncio
39+
return asyncio.get_event_loop()

voila/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ def start(self):
413413
parent=self,
414414
connection_dir=self.connection_dir,
415415
kernel_spec_manager=self.kernel_spec_manager,
416+
allow_tracebacks=self.voila_configuration.show_tracebacks,
417+
traceback_replacement_message='An error occurred, run Voilà with --show_traceback=True or --debug to show the traceback.',
416418
allowed_message_types=[
417419
'comm_open',
418420
'comm_close',

0 commit comments

Comments
 (0)