diff --git a/build/conda-functional-requirements.txt b/build/conda-functional-requirements.txt index 041efffbbd94..756692183841 100644 --- a/build/conda-functional-requirements.txt +++ b/build/conda-functional-requirements.txt @@ -21,3 +21,4 @@ pythreejs ipyvolume beakerx bqplot +K3D \ No newline at end of file diff --git a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts index 87072602b99c..8c7a0e2bf632 100644 --- a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts +++ b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts @@ -108,6 +108,9 @@ use(chaiAsPromised); async function openBeakerXIpynb() { return openNotebookFile('beakerx_widgets.ipynb'); } + async function openK3DIpynb() { + return openNotebookFile('k3d_widgets.ipynb'); + } async function openBqplotIpynb() { return openNotebookFile('bqplot_widgets.ipynb'); } @@ -130,9 +133,7 @@ use(chaiAsPromised); }); test('Output displayed after executing a cell', async () => { const { notebookUI } = await openABCIpynb(); - if (!ioc.mockJupyter) { - await assert.eventually.isFalse(notebookUI.cellHasOutput(0)); - } + await assert.eventually.isFalse(notebookUI.cellHasOutput(0)); await notebookUI.executeCell(0); @@ -146,9 +147,7 @@ use(chaiAsPromised); async function openNotebookAndTestSliderWidget() { const result = await openStandardWidgetsIpynb(); const notebookUI = result.notebookUI; - if (!ioc.mockJupyter) { - await assert.eventually.isFalse(notebookUI.cellHasOutput(0)); - } + await assert.eventually.isFalse(notebookUI.cellHasOutput(0)); await verifySliderWidgetIsAvailableAfterExecution(notebookUI); @@ -174,9 +173,7 @@ use(chaiAsPromised); test('Slider Widget', openNotebookAndTestSliderWidget); test('Text Widget', async () => { const { notebookUI } = await openStandardWidgetsIpynb(); - if (!ioc.mockJupyter) { - await assert.eventually.isFalse(notebookUI.cellHasOutput(1)); - } + await assert.eventually.isFalse(notebookUI.cellHasOutput(1)); await notebookUI.executeCell(1); @@ -192,9 +189,7 @@ use(chaiAsPromised); }); test('Checkox Widget', async () => { const { notebookUI } = await openStandardWidgetsIpynb(); - if (!ioc.mockJupyter) { - await assert.eventually.isFalse(notebookUI.cellHasOutput(2)); - } + await assert.eventually.isFalse(notebookUI.cellHasOutput(2)); await notebookUI.executeCell(2); @@ -210,9 +205,7 @@ use(chaiAsPromised); }); test('Render ipysheets', async () => { const { notebookUI } = await openIPySheetsIpynb(); - if (!ioc.mockJupyter) { - await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); - } + await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); await notebookUI.executeCell(1); await notebookUI.executeCell(3); @@ -225,378 +218,390 @@ use(chaiAsPromised); assert.include(cellOutput, 'World'); }); }); - suite('With real Jupyter', () => { - setup(function () { - if (ioc.mockJupyter) { - return this.skip(); - } + test('Widget renderes after closing and re-opening notebook', async () => { + const result = await openNotebookAndTestSliderWidget(); + + await result.notebookUI.page?.close(); + await result.webViewPanel.dispose(); + + // Open the same notebook again and test. + await openNotebookAndTestSliderWidget(); + }); + test('Widget renderes after restarting kernel', async () => { + const { notebookUI, notebookEditor } = await openNotebookAndTestSliderWidget(); + + // Clear the output + await notebookUI.clearOutput(); + await retryIfFail(async () => notebookUI.cellHasOutput(0)); + + // Restart the kernel. + await notebookEditor.restartKernel(); + + // Execute cell again and verify output is displayed. + await verifySliderWidgetIsAvailableAfterExecution(notebookUI); + }); + test('Widget renderes after interrupting kernel', async () => { + const { notebookUI, notebookEditor } = await openNotebookAndTestSliderWidget(); + + // Clear the output + await notebookUI.clearOutput(); + await retryIfFail(async () => notebookUI.cellHasOutput(0)); + + // Restart the kernel. + await notebookEditor.interruptKernel(); + + // Execute cell again and verify output is displayed. + await verifySliderWidgetIsAvailableAfterExecution(notebookUI); + }); + test('Button Interaction across Cells', async () => { + const { notebookUI } = await openStandardWidgetsIpynb(); + await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); + await assert.eventually.isFalse(notebookUI.cellHasOutput(4)); + + await notebookUI.executeCell(3); + await notebookUI.executeCell(4); + + const button = await retryIfFail(async () => { + // Find the button & the lable in cell output for 3 & 4 respectively. + const buttons = await (await notebookUI.getCellOutput(3)).$$('button.widget-button'); + const cell4Output = await notebookUI.getCellOutputHTML(4); + + assert.equal(buttons.length, 1, 'No button'); + assert.include(cell4Output, 'Not Clicked'); + + return buttons[0]; }); - test('Widget renderes after closing and re-opening notebook', async () => { - const result = await openNotebookAndTestSliderWidget(); - await result.notebookUI.page?.close(); - await result.webViewPanel.dispose(); + // When we click the button, the text in the label will get updated (i.e. output in Cell 4 will be udpated). + await button.click(); + + await retryIfFail(async () => { + const cell4Output = await notebookUI.getCellOutputHTML(4); + assert.include(cell4Output, 'Button Clicked'); + }); + }); + test('Search ipysheets with textbox in another cell', async () => { + const { notebookUI } = await openIPySheetsIpynb(); + await assert.eventually.isFalse(notebookUI.cellHasOutput(6)); + await assert.eventually.isFalse(notebookUI.cellHasOutput(7)); + + await notebookUI.executeCell(5); + await notebookUI.executeCell(6); + await notebookUI.executeCell(7); + + // Wait for sheets to get rendered. + await retryIfFail(async () => { + const cellOutputHtml = await notebookUI.getCellOutputHTML(7); + + assert.include(cellOutputHtml, 'test'); + assert.include(cellOutputHtml, 'train'); - // Open the same notebook again and test. - await openNotebookAndTestSliderWidget(); + const cellOutput = await notebookUI.getCellOutput(6); + const highlighted = await cellOutput.$$('td.htSearchResult'); + assert.equal(highlighted.length, 0); }); - test('Widget renderes after restarting kernel', async () => { - const { notebookUI, notebookEditor } = await openNotebookAndTestSliderWidget(); - // Clear the output - await notebookUI.clearOutput(); - await retryIfFail(async () => notebookUI.cellHasOutput(0)); + // Type `test` into textbox. + await retryIfFail(async () => { + const cellOutput = await notebookUI.getCellOutput(6); + const textboxes = await cellOutput.$$('input[type=text]'); + assert.equal(textboxes.length, 1, 'No Texbox'); + await textboxes[0].focus(); - // Restart the kernel. - await notebookEditor.restartKernel(); + await notebookUI.type('test'); + }); - // Execute cell again and verify output is displayed. - await verifySliderWidgetIsAvailableAfterExecution(notebookUI); + // Confirm cell is filtered and highlighted. + await retryIfFail(async () => { + const cellOutput = await notebookUI.getCellOutput(7); + const highlighted = await cellOutput.$$('td.htSearchResult'); + assert.equal(highlighted.length, 2); }); - test('Widget renderes after interrupting kernel', async () => { - const { notebookUI, notebookEditor } = await openNotebookAndTestSliderWidget(); + }); + test('Update ipysheets cells with textbox & slider in another cell', async () => { + const { notebookUI } = await openIPySheetsIpynb(); + await assert.eventually.isFalse(notebookUI.cellHasOutput(10)); + await assert.eventually.isFalse(notebookUI.cellHasOutput(12)); + await assert.eventually.isFalse(notebookUI.cellHasOutput(13)); + + await notebookUI.executeCell(9); + await notebookUI.executeCell(10); + await notebookUI.executeCell(12); + await notebookUI.executeCell(13); - // Clear the output - await notebookUI.clearOutput(); - await retryIfFail(async () => notebookUI.cellHasOutput(0)); + // Wait for slider to get rendered with value `0`. + const sliderLabel = await retryIfFail(async () => { + const cellOutputHtml = await notebookUI.getCellOutputHTML(10); - // Restart the kernel. - await notebookEditor.interruptKernel(); + assert.include(cellOutputHtml, 'ui-slider-handle'); + assert.include(cellOutputHtml, 'left: 0%'); - // Execute cell again and verify output is displayed. - await verifySliderWidgetIsAvailableAfterExecution(notebookUI); + const cellOutput = await notebookUI.getCellOutput(10); + const sliderLables = await cellOutput.$$('div.widget-readout'); + + return sliderLables[0]; }); - test('Button Interaction across Cells', async () => { - const { notebookUI } = await openStandardWidgetsIpynb(); - await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); - await assert.eventually.isFalse(notebookUI.cellHasOutput(4)); - await notebookUI.executeCell(3); - await notebookUI.executeCell(4); + // Confirm slider lable reads `0`. + await retryIfFail(async () => { + const sliderValue = await notebookUI.page?.evaluate((ele) => ele.innerHTML.trim(), sliderLabel); + assert.equal(sliderValue || '', '0'); + }); - const button = await retryIfFail(async () => { - // Find the button & the lable in cell output for 3 & 4 respectively. - const buttons = await (await notebookUI.getCellOutput(3)).$$('button.widget-button'); - const cell4Output = await notebookUI.getCellOutputHTML(4); + // Wait for textbox to get rendered. + const textbox = await retryIfFail(async () => { + const cellOutput = await notebookUI.getCellOutput(12); + const textboxes = await cellOutput.$$('input[type=number]'); + assert.equal(textboxes.length, 1); - assert.equal(buttons.length, 1, 'No button'); - assert.include(cell4Output, 'Not Clicked'); + const value = await notebookUI.page?.evaluate((el) => (el as HTMLInputElement).value, textboxes[0]); + assert.equal(value || '', '0'); - return buttons[0]; - }); + return textboxes[0]; + }); - // When we click the button, the text in the label will get updated (i.e. output in Cell 4 will be udpated). - await button.click(); + // Wait for sheets to get rendered. + await retryIfFail(async () => { + const cellOutputHtml = await notebookUI.getCellOutputHTML(13); + assert.include(cellOutputHtml, '>50.000'); + assert.notInclude(cellOutputHtml, '>100.000'); + }); - await retryIfFail(async () => { - const cell4Output = await notebookUI.getCellOutputHTML(4); - assert.include(cell4Output, 'Button Clicked'); - }); + // Type `50` into textbox. + await retryIfFail(async () => { + await textbox.focus(); + await notebookUI.type('50'); }); - test('Search ipysheets with textbox in another cell', async () => { - const { notebookUI } = await openIPySheetsIpynb(); - await assert.eventually.isFalse(notebookUI.cellHasOutput(6)); - await assert.eventually.isFalse(notebookUI.cellHasOutput(7)); - - await notebookUI.executeCell(5); - await notebookUI.executeCell(6); - await notebookUI.executeCell(7); - - // Wait for sheets to get rendered. - await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(7); - - assert.include(cellOutputHtml, 'test'); - assert.include(cellOutputHtml, 'train'); - const cellOutput = await notebookUI.getCellOutput(6); - const highlighted = await cellOutput.$$('td.htSearchResult'); - assert.equal(highlighted.length, 0); - }); - - // Type `test` into textbox. - await retryIfFail(async () => { - const cellOutput = await notebookUI.getCellOutput(6); - const textboxes = await cellOutput.$$('input[type=text]'); - assert.equal(textboxes.length, 1, 'No Texbox'); - await textboxes[0].focus(); - - await notebookUI.type('test'); - }); - - // Confirm cell is filtered and highlighted. - await retryIfFail(async () => { - const cellOutput = await notebookUI.getCellOutput(7); - const highlighted = await cellOutput.$$('td.htSearchResult'); - assert.equal(highlighted.length, 2); - }); - }); - test('Update ipysheets cells with textbox & slider in another cell', async () => { - const { notebookUI } = await openIPySheetsIpynb(); - await assert.eventually.isFalse(notebookUI.cellHasOutput(10)); - await assert.eventually.isFalse(notebookUI.cellHasOutput(12)); - await assert.eventually.isFalse(notebookUI.cellHasOutput(13)); - - await notebookUI.executeCell(9); - await notebookUI.executeCell(10); - await notebookUI.executeCell(12); - await notebookUI.executeCell(13); - - // Wait for slider to get rendered with value `0`. - const sliderLabel = await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(10); - - assert.include(cellOutputHtml, 'ui-slider-handle'); - assert.include(cellOutputHtml, 'left: 0%'); - - const cellOutput = await notebookUI.getCellOutput(10); - const sliderLables = await cellOutput.$$('div.widget-readout'); - - return sliderLables[0]; - }); - - // Confirm slider lable reads `0`. - await retryIfFail(async () => { - const sliderValue = await notebookUI.page?.evaluate((ele) => ele.innerHTML.trim(), sliderLabel); - assert.equal(sliderValue || '', '0'); - }); - - // Wait for textbox to get rendered. - const textbox = await retryIfFail(async () => { - const cellOutput = await notebookUI.getCellOutput(12); - const textboxes = await cellOutput.$$('input[type=number]'); - assert.equal(textboxes.length, 1); - - const value = await notebookUI.page?.evaluate((el) => (el as HTMLInputElement).value, textboxes[0]); - assert.equal(value || '', '0'); - - return textboxes[0]; - }); - - // Wait for sheets to get rendered. - await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(13); - assert.include(cellOutputHtml, '>50.000'); - assert.notInclude(cellOutputHtml, '>100.000'); - }); - - // Type `50` into textbox. - await retryIfFail(async () => { - await textbox.focus(); - await notebookUI.type('50'); - }); - - // Confirm slider label reads `50`. - await retryIfFail(async () => { - const sliderValue = await notebookUI.page?.evaluate((ele) => ele.innerHTML.trim(), sliderLabel); - assert.equal(sliderValue || '', '50'); - }); - - // Wait for sheets to get updated with calculation. - await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(13); - - assert.include(cellOutputHtml, '>50.000'); - assert.include(cellOutputHtml, '>100.000'); - }); - }); - test('Render ipyvolume', async () => { - const { notebookUI } = await openIPyVolumeIpynb(); - await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); - - await notebookUI.executeCell(1); - await notebookUI.executeCell(2); - await notebookUI.executeCell(3); - await notebookUI.executeCell(4); - - // Confirm sliders and canvas are rendered. - await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(1); - assert.include(cellOutputHtml, ' { - const cellOutputHtml = await notebookUI.getCellOutputHTML(4); - assert.include(cellOutputHtml, ' { - const { notebookUI } = await openPyThreejsIpynb(); - await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); - await assert.eventually.isFalse(notebookUI.cellHasOutput(8)); - - await notebookUI.executeCell(1); - await notebookUI.executeCell(2); - await notebookUI.executeCell(3); - await notebookUI.executeCell(4); - await notebookUI.executeCell(5); - await notebookUI.executeCell(6); - await notebookUI.executeCell(7); - await notebookUI.executeCell(8); - - // Confirm canvas is rendered. - await retryIfFail(async () => { - let cellOutputHtml = await notebookUI.getCellOutputHTML(3); - assert.include(cellOutputHtml, ' { - const { notebookUI } = await openBeakerXIpynb(); - if (!ioc.mockJupyter) { - await assert.eventually.isFalse(notebookUI.cellHasOutput(1)); - await assert.eventually.isFalse(notebookUI.cellHasOutput(2)); - await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); - } + // Confirm slider label reads `50`. + await retryIfFail(async () => { + const sliderValue = await notebookUI.page?.evaluate((ele) => ele.innerHTML.trim(), sliderLabel); + assert.equal(sliderValue || '', '50'); + }); - await notebookUI.executeCell(1); - await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(1); - // Confirm svg graph has been rendered. - assert.include(cellOutputHtml, ' { - // Confirm graph modal dialog has been rendered. - const cellOutput = await notebookUI.getCellOutput(2); - const modals = await cellOutput.$$('div.modal-content'); - assert.isAtLeast(modals.length, 1); - }); - - // This last part if flakey. BeakerX can fail itself loading settings - await notebookUI.executeCell(3); - await retryIfFail(async () => { - // Confirm form with fields have been rendered. - const cellOutput = await notebookUI.getCellOutput(3); - const textAreas = await cellOutput.$$('div.widget-textarea'); - assert.isAtLeast(textAreas.length, 1); - }); - }); - test('Render bqplot', async () => { - const { notebookUI } = await openBqplotIpynb(); - if (!ioc.mockJupyter) { - await assert.eventually.isFalse(notebookUI.cellHasOutput(2)); - await assert.eventually.isFalse(notebookUI.cellHasOutput(4)); - } + // Wait for sheets to get updated with calculation. + await retryIfFail(async () => { + const cellOutputHtml = await notebookUI.getCellOutputHTML(13); - await notebookUI.executeCell(1); - await notebookUI.executeCell(2); - - await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(2); - // Confirm svg graph has been rendered. - assert.include(cellOutputHtml, ' { - const cellOutput = await notebookUI.getCellOutput(4); - // Confirm no points have been rendered. - const dots = await cellOutput.$$('path.dot'); - assert.equal(dots.length, 0); - }); - - // Draw points on previous plot. - await notebookUI.executeCell(5); - await retryIfFail(async () => { - const cellOutput = await notebookUI.getCellOutput(4); - // Confirm points have been rendered. - const dots = await cellOutput.$$('path.dot'); - assert.isAtLeast(dots.length, 1); - }); - - // Chage color of plot points to red. - await notebookUI.executeCell(7); - await retryIfFail(async () => { - const cellOutput = await notebookUI.getCellOutput(4); - const dots = await cellOutput.$$('path.dot'); - assert.isAtLeast(dots.length, 1); - const dotHtml = await notebookUI.page?.evaluate((ele) => ele.outerHTML, dots[0]); - // Confirm color of dot is red. - assert.include(dotHtml || '', 'red'); - }); - - // Chage color of plot points to red. - await notebookUI.executeCell(8); - await retryIfFail(async () => { - const cellOutput = await notebookUI.getCellOutput(4); - const dots = await cellOutput.$$('path.dot'); - assert.isAtLeast(dots.length, 1); - const dotHtml = await notebookUI.page?.evaluate((ele) => ele.outerHTML, dots[0]); - // Confirm color of dot is red. - assert.include(dotHtml || '', 'yellow'); - }); - }); - test('Render output and interact', async () => { - const { notebookUI } = await openOutputAndInteractIpynb(); - await notebookUI.executeCell(0); - await notebookUI.executeCell(1); - - await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(1); - // Confirm border is visible - assert.include(cellOutputHtml, 'border'); - }); - - // Run the cell that will stick output into the out border - await notebookUI.executeCell(2); - - // Make sure output is shown - await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(1); - // Confirm output went inside of previous cell - assert.include(cellOutputHtml, 'Hello world'); - }); - - // Make sure output on print cell is empty - await retryIfFail(async () => { - const cell = await notebookUI.getCell(2); - const output = await cell.$$('.cell-output-wrapper'); - assert.equal(output.length, 0, 'Cell should not have any output'); - }); - - // interact portion - await notebookUI.executeCell(3); - await notebookUI.executeCell(4); - await notebookUI.executeCell(5); - // See if we have a slider in our output - const slider = await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(5); - assert.include(cellOutputHtml, 'slider', 'Cell output should have rendered a slider'); - const sliderInner = await (await notebookUI.getCellOutput(5)).$$('.slider-container'); - assert.ok(sliderInner.length, 'Slider not found'); - return sliderInner[0]; - }); - - // Click on the slider to change the value. - const rect = await slider.boundingBox(); - if (rect) { - await notebookUI.page?.mouse.move(rect?.x + 5, rect.y + rect.height / 2); - await notebookUI.page?.mouse.down(); - await notebookUI.page?.mouse.up(); - } + assert.include(cellOutputHtml, '>50.000'); + assert.include(cellOutputHtml, '>100.000'); + }); + }); + test('Render ipyvolume', async () => { + const { notebookUI } = await openIPyVolumeIpynb(); + await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); + + await notebookUI.executeCell(1); + await notebookUI.executeCell(2); + await notebookUI.executeCell(3); + await notebookUI.executeCell(4); + + // Confirm sliders and canvas are rendered. + await retryIfFail(async () => { + const cellOutputHtml = await notebookUI.getCellOutputHTML(1); + assert.include(cellOutputHtml, ' { + const cellOutputHtml = await notebookUI.getCellOutputHTML(4); + assert.include(cellOutputHtml, ' { + const { notebookUI } = await openPyThreejsIpynb(); + await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); + await assert.eventually.isFalse(notebookUI.cellHasOutput(8)); - // Make sure the output value has changed to something other than 10 - await retryIfFail(async () => { - const cellOutputHtml = await notebookUI.getCellOutputHTML(5); - assert.notInclude(cellOutputHtml, '
10', 'Slider click did not update the span');
-                });
+            await notebookUI.executeCell(1);
+            await notebookUI.executeCell(2);
+            await notebookUI.executeCell(3);
+            await notebookUI.executeCell(4);
+            await notebookUI.executeCell(5);
+            await notebookUI.executeCell(6);
+            await notebookUI.executeCell(7);
+            await notebookUI.executeCell(8);
+
+            // Confirm canvas is rendered.
+            await retryIfFail(async () => {
+                let cellOutputHtml = await notebookUI.getCellOutputHTML(3);
+                assert.include(cellOutputHtml, ' {
+            const { notebookUI } = await openBeakerXIpynb();
+            await assert.eventually.isFalse(notebookUI.cellHasOutput(1));
+            await assert.eventually.isFalse(notebookUI.cellHasOutput(2));
+            await assert.eventually.isFalse(notebookUI.cellHasOutput(3));
+
+            await notebookUI.executeCell(1);
+            await retryIfFail(async () => {
+                const cellOutputHtml = await notebookUI.getCellOutputHTML(1);
+                // Confirm svg graph has been rendered.
+                assert.include(cellOutputHtml, ' {
+                // Confirm graph modal dialog has been rendered.
+                const cellOutput = await notebookUI.getCellOutput(2);
+                const modals = await cellOutput.$$('div.modal-content');
+                assert.isAtLeast(modals.length, 1);
+            });
+
+            // This last part if flakey. BeakerX can fail itself loading settings
+            await notebookUI.executeCell(3);
+            await retryIfFail(async () => {
+                // Confirm form with fields have been rendered.
+                const cellOutput = await notebookUI.getCellOutput(3);
+                const textAreas = await cellOutput.$$('div.widget-textarea');
+                assert.isAtLeast(textAreas.length, 1);
+            });
+        });
+        test('Render bqplot', async () => {
+            const { notebookUI } = await openBqplotIpynb();
+            await assert.eventually.isFalse(notebookUI.cellHasOutput(2));
+            await assert.eventually.isFalse(notebookUI.cellHasOutput(4));
+
+            await notebookUI.executeCell(1);
+            await notebookUI.executeCell(2);
+
+            await retryIfFail(async () => {
+                const cellOutputHtml = await notebookUI.getCellOutputHTML(2);
+                // Confirm svg graph has been rendered.
+                assert.include(cellOutputHtml, ' {
+                const cellOutput = await notebookUI.getCellOutput(4);
+                // Confirm no points have been rendered.
+                const dots = await cellOutput.$$('path.dot');
+                assert.equal(dots.length, 0);
+            });
+
+            // Draw points on previous plot.
+            await notebookUI.executeCell(5);
+            await retryIfFail(async () => {
+                const cellOutput = await notebookUI.getCellOutput(4);
+                // Confirm points have been rendered.
+                const dots = await cellOutput.$$('path.dot');
+                assert.isAtLeast(dots.length, 1);
+            });
+
+            // Chage color of plot points to red.
+            await notebookUI.executeCell(7);
+            await retryIfFail(async () => {
+                const cellOutput = await notebookUI.getCellOutput(4);
+                const dots = await cellOutput.$$('path.dot');
+                assert.isAtLeast(dots.length, 1);
+                const dotHtml = await notebookUI.page?.evaluate((ele) => ele.outerHTML, dots[0]);
+                // Confirm color of dot is red.
+                assert.include(dotHtml || '', 'red');
+            });
+
+            // Chage color of plot points to red.
+            await notebookUI.executeCell(8);
+            await retryIfFail(async () => {
+                const cellOutput = await notebookUI.getCellOutput(4);
+                const dots = await cellOutput.$$('path.dot');
+                assert.isAtLeast(dots.length, 1);
+                const dotHtml = await notebookUI.page?.evaluate((ele) => ele.outerHTML, dots[0]);
+                // Confirm color of dot is red.
+                assert.include(dotHtml || '', 'yellow');
+            });
+        });
+        test('Render output and interact', async () => {
+            const { notebookUI } = await openOutputAndInteractIpynb();
+            await notebookUI.executeCell(0);
+            await notebookUI.executeCell(1);
+
+            await retryIfFail(async () => {
+                const cellOutputHtml = await notebookUI.getCellOutputHTML(1);
+                // Confirm border is visible
+                assert.include(cellOutputHtml, 'border');
+            });
+
+            // Run the cell that will stick output into the out border
+            await notebookUI.executeCell(2);
+
+            // Make sure output is shown
+            await retryIfFail(async () => {
+                const cellOutputHtml = await notebookUI.getCellOutputHTML(1);
+                // Confirm output went inside of previous cell
+                assert.include(cellOutputHtml, 'Hello world');
+            });
+
+            // Make sure output on print cell is empty
+            await retryIfFail(async () => {
+                const cell = await notebookUI.getCell(2);
+                const output = await cell.$$('.cell-output-wrapper');
+                assert.equal(output.length, 0, 'Cell should not have any output');
+            });
+
+            // interact portion
+            await notebookUI.executeCell(3);
+            await notebookUI.executeCell(4);
+            await notebookUI.executeCell(5);
+            // See if we have a slider in our output
+            const slider = await retryIfFail(async () => {
+                const cellOutputHtml = await notebookUI.getCellOutputHTML(5);
+                assert.include(cellOutputHtml, 'slider', 'Cell output should have rendered a slider');
+                const sliderInner = await (await notebookUI.getCellOutput(5)).$$('.slider-container');
+                assert.ok(sliderInner.length, 'Slider not found');
+                return sliderInner[0];
+            });
+
+            // Click on the slider to change the value.
+            const rect = await slider.boundingBox();
+            if (rect) {
+                await notebookUI.page?.mouse.move(rect?.x + 5, rect.y + rect.height / 2);
+                await notebookUI.page?.mouse.down();
+                await notebookUI.page?.mouse.up();
+            }
+
+            // Make sure the output value has changed to something other than 10
+            await retryIfFail(async () => {
+                const cellOutputHtml = await notebookUI.getCellOutputHTML(5);
+                assert.notInclude(cellOutputHtml, '
10', 'Slider click did not update the span');
+            });
+        });
+        test('Render k3d', async () => {
+            const { notebookUI } = await openK3DIpynb();
+            await assert.eventually.isFalse(notebookUI.cellHasOutput(3));
+            await assert.eventually.isFalse(notebookUI.cellHasOutput(5));
+
+            await notebookUI.executeCell(3);
+            await retryIfFail(async () => {
+                const cellOutputHtml = await notebookUI.getCellOutputHTML(3);
+                // Confirm svg graph has been rendered.
+                assert.include(cellOutputHtml, ' {
+                const cellOutputHtml = await notebookUI.getCellOutputHTML(5);
+                // Slider should be rendered.
+                assert.include(cellOutputHtml, 'ui-slider');
             });
         });
     });
diff --git a/src/test/datascience/uiTests/notebooks/k3d_widgets.ipynb b/src/test/datascience/uiTests/notebooks/k3d_widgets.ipynb
new file mode 100644
index 000000000000..383af7e1347d
--- /dev/null
+++ b/src/test/datascience/uiTests/notebooks/k3d_widgets.ipynb
@@ -0,0 +1,141 @@
+{
+    "cells": [
+        {
+            "source": [
+                "# Prerequisites\n",
+                "\n",
+                "### pip install K3D"
+            ],
+            "cell_type": "markdown",
+            "metadata": {},
+            "execution_count": null,
+            "outputs": []
+        },
+        {
+            "source": [
+                "This ipynb file used to fail even with ipywidgets enabled.\n",
+                "There was a problem with serialization of the array buffer.\n",
+                "https://github.com/K3D-tools/K3D-jupyter/blob/821a59ed88579afaafababd6291e8692d70eb088/examples/camera_manipulation.ipynb\n",
+                "\n",
+                "# Note:\n",
+                "Other K3D notebooks worked, except this, hence this test is mandatory.\n",
+                "Who knows what other widgets (or same widgets under different conditions, like K3d) could fail similarly."
+            ],
+            "cell_type": "markdown",
+            "metadata": {},
+            "execution_count": null,
+            "outputs": []
+        },
+        {
+            "cell_type": "markdown",
+            "metadata": {},
+            "source": [
+                "# This will render a 3d image, see here for sample output.\n",
+                "![k3d](https://user-images.githubusercontent.com/1948812/79030314-49dadd80-7b4d-11ea-9a39-03ddad09d119.gif)\n"
+            ]
+        },
+        {
+            "cell_type": "code",
+            "execution_count": 5,
+            "metadata": {},
+            "source": [
+                "import k3d\n",
+                "import numpy as np\n",
+                "from numpy import sin,cos,pi\n",
+                "from ipywidgets import interact, interactive, fixed\n",
+                "import ipywidgets as widgets\n",
+                "import time\n",
+                "import math\n",
+                "\n",
+                "plot = k3d.plot()\n",
+                "\n",
+                "plot.camera_auto_fit = False\n",
+                "\n",
+                "T = 1.618033988749895\n",
+                "r = 4.77\n",
+                "zmin,zmax = -r,r\n",
+                "xmin,xmax = -r,r\n",
+                "ymin,ymax = -r,r\n",
+                "Nx,Ny,Nz = 77,77,77\n",
+                "\n",
+                "x = np.linspace(xmin,xmax,Nx)\n",
+                "y = np.linspace(ymin,ymax,Ny)\n",
+                "z = np.linspace(zmin,zmax,Nz)\n",
+                "x,y,z = np.meshgrid(x,y,z,indexing='ij')\n",
+                "p = 2 - (cos(x + T*y) + cos(x - T*y) + cos(y + T*z) + cos(y - T*z) + cos(z - T*x) + cos(z + T*x))\n",
+                "iso = k3d.marching_cubes(p.astype(np.float32),xmin=xmin,xmax=xmax,ymin=ymin,ymax=ymax, zmin=zmin, zmax=zmax, level=0.0)\n",
+                "plot += iso\n",
+                "\n",
+                "plot.display()"
+            ]
+        },
+        {
+            "source": [
+                "# Here we expect 2 sliders to get rendered. Moving those will update the 3d image in previous cell."
+            ],
+            "cell_type": "markdown",
+            "metadata": {},
+            "execution_count": null,
+            "outputs": []
+        },
+        {
+            "cell_type": "code",
+            "execution_count": 4,
+            "metadata": {},
+            "source": [
+                "@interact(x=widgets.FloatSlider(value=0,min=-3,max=4,step=0.01))\n",
+                "def g(x):\n",
+                "    iso.level=x\n",
+                "    \n",
+                "@interact(rad=widgets.FloatSlider(value=0,min=0,max=2*math.pi,step=0.01))\n",
+                "def g(rad):\n",
+                "    plot.camera = [3*r*sin(rad),3*r*cos(rad),0,\n",
+                "                   0,0,0,\n",
+                "                   0,0,1]"
+            ]
+        },
+        {
+            "cell_type": "code",
+            "execution_count": null,
+            "metadata": {},
+            "outputs": [],
+            "source": []
+        }
+    ],
+    "metadata": {
+        "celltoolbar": "Attachments",
+        "file_extension": ".py",
+        "language_info": {
+            "codemirror_mode": {
+                "name": "ipython",
+                "version": 3
+            },
+            "file_extension": ".py",
+            "mimetype": "text/x-python",
+            "name": "python",
+            "nbconvert_exporter": "python",
+            "pygments_lexer": "ipython3",
+            "version": "3.7.3-final"
+        },
+        "mimetype": "text/x-python",
+        "name": "python",
+        "npconvert_exporter": "python",
+        "pygments_lexer": "ipython3",
+        "toc": {
+            "base_numbering": 1,
+            "nav_menu": {},
+            "number_sections": false,
+            "sideBar": false,
+            "skip_h1_title": false,
+            "title_cell": "Table of Contents",
+            "title_sidebar": "Contents",
+            "toc_cell": false,
+            "toc_position": {},
+            "toc_section_display": false,
+            "toc_window_display": false
+        },
+        "version": 3
+    },
+    "nbformat": 4,
+    "nbformat_minor": 2
+}