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 3bb851e65019..1d7746e690e8 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,15 @@ use(chaiAsPromised); async function openStandardWidgetsIpynb() { return openNotebookFile('standard_widgets.ipynb'); } + 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(); @@ -192,7 +207,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 +227,188 @@ 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'); + }); + }); + 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, '