From 0a08d1d995f85f78dd3052e96f7dd256c86b8783 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 23 Mar 2020 16:39:11 -0700 Subject: [PATCH 1/2] Add more tests --- .../nativeEditor.ui.functional.test.ts | 146 +++++++++++- .../uiTests/notebooks/ipySheet_widgets.ipynb | 208 ++++++++++++++++++ 2 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 src/test/datascience/uiTests/notebooks/ipySheet_widgets.ipynb diff --git a/src/test/datascience/uiTests/nativeEditor.ui.functional.test.ts b/src/test/datascience/uiTests/nativeEditor.ui.functional.test.ts index 3bb851e65019..d1fb3a291c1e 100644 --- a/src/test/datascience/uiTests/nativeEditor.ui.functional.test.ts +++ b/src/test/datascience/uiTests/nativeEditor.ui.functional.test.ts @@ -85,6 +85,12 @@ use(chaiAsPromised); if (nb.metadata && nb.metadata.kernelspec) { delete nb.metadata.kernelspec; } + // Clear all output (from previous executions). + nb.cells.forEach(cell => { + if (Array.isArray(cell.outputs)) { + cell.outputs = []; + } + }); const result = await openNotebook(ioc, disposables, JSON.stringify(nb)); notebookUi = result.notebookUI; return result; @@ -98,6 +104,9 @@ use(chaiAsPromised); async function openStandardWidgetsIpynb() { return openNotebookFile('standard_widgets.ipynb'); } + async function openIPySheetsIpynb() { + return openNotebookFile('ipySheet_widgets.ipynb'); + } test('Notebook has 3 cells', async () => { const { notebookUI } = await openABCIpynb(); @@ -192,7 +201,7 @@ use(chaiAsPromised); await notebookUI.executeCell(3); await notebookUI.executeCell(4); - // await sleep(500_000); + 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'); @@ -212,6 +221,141 @@ use(chaiAsPromised); assert.include(cell4Output, 'Button Clicked'); }); }); + test('Render ipysheets', async () => { + const { notebookUI } = await openIPySheetsIpynb(); + await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); + + await notebookUI.executeCell(1); + await notebookUI.executeCell(3); + + await retryIfFail(async () => { + const cellOutput = await notebookUI.getCellOutputHTML(3); + + assert.include(cellOutput, 'Hello'); + assert.include(cellOutput, 'World'); + }); + }); + test('Render ipysheets', async () => { + const { notebookUI } = await openIPySheetsIpynb(); + await assert.eventually.isFalse(notebookUI.cellHasOutput(3)); + + await notebookUI.executeCell(1); + await notebookUI.executeCell(3); + + await retryIfFail(async () => { + const cellOutput = await notebookUI.getCellOutputHTML(3); + + assert.include(cellOutput, 'Hello'); + assert.include(cellOutput, 'World'); + }); + }); + 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'); + }); + }); }); }); }); diff --git a/src/test/datascience/uiTests/notebooks/ipySheet_widgets.ipynb b/src/test/datascience/uiTests/notebooks/ipySheet_widgets.ipynb new file mode 100644 index 000000000000..2cf40ef38208 --- /dev/null +++ b/src/test/datascience/uiTests/notebooks/ipySheet_widgets.ipynb @@ -0,0 +1,208 @@ +{ + "cells": [ + { + "source": [ + "# Prerequisites\n", + "\n", + "### pip install ipysheet" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "from ipywidgets import FloatSlider, IntSlider, Image\n", + "import ipysheet" + ] + }, + { + "source": [ + "# 1. Test Rendering a Sheet" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "sheet = ipysheet.sheet(rows=3, columns=4)\n", + "cell1 = ipysheet.cell(0, 0, 'Hello')\n", + "cell2 = ipysheet.cell(2, 0, 'World')\n", + "cell_value = ipysheet.cell(2,2, 42.)\n", + "sheet" + ] + }, + { + "source": [ + "# 2. Test Searching a Sheet (interact with textbox in a different cell)" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from ipysheet import from_dataframe\n", + "from ipywidgets import Text, VBox, link\n", + "\n", + "df = pd.DataFrame({'A': 1.,\n", + " 'B': pd.Timestamp('20130102'),\n", + " 'C': pd.Series(1, index=list(range(4)), dtype='float32'),\n", + " 'D': np.array([False, True, False, False], dtype='bool'),\n", + " 'E': pd.Categorical([\"test\", \"train\", \"test\", \"train\"]),\n", + " 'F': 'foo'})\n", + "\n", + "df.loc[[0, 2], ['B']] = np.nan\n", + "\n", + "\n", + "sheet2 = from_dataframe(df)\n", + "\n", + "search_box = Text(description='Search:')\n", + "link((search_box, 'value'), (sheet2, 'search_token'))" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "search_box" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "sheet2" + ] + }, + { + "source": [ + "# 3. Test calculations (slider update cell value via python code)" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import FloatSlider, IntSlider, Image, IntText, link\n", + "import ipysheet\n", + "\n", + "slider = IntSlider(description=\"Continuous\", continuous_update=True)\n", + "textbox = IntText(description=\"Continuous\", continuous_update=True)\n", + "\n", + "link((slider, 'value'), (textbox, 'value'))" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [], + "source": [ + "slider" + ] + }, + { + "source": [ + "* Typing value into textbox will move slider\n", + "* The value in cell will also get updated" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [], + "source": [ + "textbox" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [], + "source": [ + "sheet = ipysheet.sheet()\n", + "\n", + "cell1 = ipysheet.cell(0, 0, slider, style={'min-width': '150px'})\n", + "cell3 = ipysheet.cell(2, 2, 50.)\n", + "cell_sum = ipysheet.cell(3, 2, 50.)\n", + "\n", + "@ipysheet.calculation(inputs=[(cell1, 'value'), cell3], output=cell_sum)\n", + "def calculate(a, b):\n", + " return a + b\n", + "\n", + "sheet" + ] + }, + { + "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.8.1-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 +} From 4403515f7592412b49c6dd77c68febb0120d93a5 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 23 Mar 2020 17:01:12 -0700 Subject: [PATCH 2/2] ipyvolume and pythreejs tests --- build/conda-functional-requirements.txt | 2 + src/test/datascience/uiTests/helpers.ts | 5 +- .../nativeEditor.ui.functional.test.ts | 53 ++++++ .../uiTests/notebooks/ipyvolume_widgets.ipynb | 107 ++++++++++++ .../uiTests/notebooks/pythreejs_widgets.ipynb | 156 ++++++++++++++++++ 5 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 src/test/datascience/uiTests/notebooks/ipyvolume_widgets.ipynb create mode 100644 src/test/datascience/uiTests/notebooks/pythreejs_widgets.ipynb diff --git a/build/conda-functional-requirements.txt b/build/conda-functional-requirements.txt index 7f11f53981a7..0b4f9cb2d2db 100644 --- a/build/conda-functional-requirements.txt +++ b/build/conda-functional-requirements.txt @@ -17,3 +17,5 @@ flask django isort pathlib2>=2.2.0 ; python_version<'3.6' # Python 2.7 compatibility (pytest) +pythreejs +ipyvolume diff --git a/src/test/datascience/uiTests/helpers.ts b/src/test/datascience/uiTests/helpers.ts index 2f6143af303b..55725dcb1f2b 100644 --- a/src/test/datascience/uiTests/helpers.ts +++ b/src/test/datascience/uiTests/helpers.ts @@ -38,7 +38,7 @@ const maxWaitTimeForMessage = 15_000; export const waitTimeForUIToUpdate = 3_000; export class BaseWebUI implements IAsyncDisposable { - protected page?: playwright.Page; + public page?: playwright.Page; private readonly disposables: IDisposable[] = []; private readonly webServerPromise = createDeferred(); private webServer?: WebServer; @@ -50,6 +50,9 @@ export class BaseWebUI implements IAsyncDisposable { await this.browser?.close(); await this.page?.close(); } + public async type(text: string): Promise { + await this.page?.keyboard.type(text); + } public _setWebServer(webServer: WebServer) { this.webServer = webServer; this.webServerPromise.resolve(webServer); diff --git a/src/test/datascience/uiTests/nativeEditor.ui.functional.test.ts b/src/test/datascience/uiTests/nativeEditor.ui.functional.test.ts index d1fb3a291c1e..1d7746e690e8 100644 --- a/src/test/datascience/uiTests/nativeEditor.ui.functional.test.ts +++ b/src/test/datascience/uiTests/nativeEditor.ui.functional.test.ts @@ -107,6 +107,12 @@ use(chaiAsPromised); async function openIPySheetsIpynb() { return openNotebookFile('ipySheet_widgets.ipynb'); } + async function openIPyVolumeIpynb() { + return openNotebookFile('ipyvolume_widgets.ipynb'); + } + async function openPyThreejsIpynb() { + return openNotebookFile('pythreejs_widgets.ipynb'); + } test('Notebook has 3 cells', async () => { const { notebookUI } = await openABCIpynb(); @@ -356,6 +362,53 @@ use(chaiAsPromised); 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, '