diff --git a/ui-tests/test/editor.spec.ts b/ui-tests/test/editor.spec.ts index f17271f60e3..bf3d216d278 100644 --- a/ui-tests/test/editor.spec.ts +++ b/ui-tests/test/editor.spec.ts @@ -76,6 +76,40 @@ test.describe('Editor', () => { await expect(page.locator('.jp-Notebook')).toHaveCount(0); }); + test('Should display a partial view with white space at the bottom', async ({ + page, + tmpPath, + }) => { + const file = `${tmpPath}/${FILE}`; + await page.goto(`edit/${file}`); + + await expect(page.locator('.cm-editor')).toBeVisible(); + // wait for the file content to be rendered in the editor + await expect(page.locator('.cm-content')).toContainText('name: notebook'); + + // the edit page should display a partial view, with white space below + // the main panel + const spacer = page.locator('#spacer-widget-bottom'); + await expect(spacer).toBeVisible(); + const spacerBox = await spacer.boundingBox(); + expect(spacerBox?.height).toBeGreaterThanOrEqual(16); + }); + + test('Should not render the micro toolbar for files', async ({ + page, + tmpPath, + }) => { + const file = `${tmpPath}/${FILE}`; + await page.goto(`edit/${file}`); + + await expect(page.locator('.cm-editor')).toBeVisible(); + + // the micro toolbar is added to the DOM but should be hidden via CSS + const microToolbar = page.locator('.jp-MainAreaWidget > .jp-Toolbar-micro'); + await expect(microToolbar).toHaveCount(1); + await expect(microToolbar).toBeHidden(); + }); + test('Renaming the file via the menu entry', async ({ page, tmpPath }) => { const file = `${tmpPath}/${FILE}`; await page.goto(`edit/${file}`); diff --git a/ui-tests/test/filebrowser.spec.ts b/ui-tests/test/filebrowser.spec.ts index 41cb88ea69e..fdfb7b5b2c8 100644 --- a/ui-tests/test/filebrowser.spec.ts +++ b/ui-tests/test/filebrowser.spec.ts @@ -103,6 +103,43 @@ test.describe('File Browser', () => { await notebook.close(); }); + test('Show the Upload button in the toolbar by default', async ({ page }) => { + const toolbar = page.locator('.jp-FileBrowser-toolbar'); + + await expect(toolbar.getByText('Upload')).toBeVisible(); + }); + + test('Filter the file browser listing with the file filter', async ({ + page, + }) => { + await page.filebrowser.refresh(); + + const toggleButton = page.locator( + 'jp-button[data-command="filebrowser:toggle-file-filter"]' + ); + const filterInput = page.locator('.jp-FileBrowser-filterBox input'); + const listing = page.locator('.jp-DirListing-item'); + + // the file filter input is hidden by default + await expect(filterInput).toBeHidden(); + await expect(listing).toHaveCount(3); + + // clicking the toggle button shows the filter input + await toggleButton.click(); + await expect(filterInput).toBeVisible(); + + // typing a query narrows down the listing + await filterInput.fill('folder1'); + await expect(listing).toHaveCount(1); + await expect(listing).toHaveText(/folder1/); + + // clicking the toggle button again hides the filter input and restores + // the full listing + await toggleButton.click(); + await expect(filterInput).toBeHidden(); + await expect(listing).toHaveCount(3); + }); + test('Toggle the Date Created column from the header context menu', async ({ page, }) => { @@ -138,3 +175,36 @@ test.describe('File Browser settings', () => { await expect(header.locator('.jp-id-created')).toBeVisible(); }); }); + +test.describe('File Browser toolbar settings', () => { + test.use({ + mockSettings: { + ...galata.DEFAULT_SETTINGS, + '@jupyter-notebook/tree-extension:widget': { + toolbar: [ + { + name: 'uploader', + disabled: true, + }, + ], + }, + }, + }); + + test('Should hide the Upload button when disabled in the settings', async ({ + page, + }) => { + const toolbar = page.locator('.jp-FileBrowser-toolbar'); + + // other toolbar items should still be visible + await expect(toolbar.getByText('New', { exact: true })).toBeVisible(); + await expect( + toolbar.locator('[data-jp-item-name="refresh"]') + ).toBeVisible(); + + // the Upload button should not be added to the toolbar + await expect(toolbar.locator('[data-jp-item-name="uploader"]')).toHaveCount( + 0 + ); + }); +}); diff --git a/ui-tests/test/help.spec.ts b/ui-tests/test/help.spec.ts new file mode 100644 index 00000000000..40b6bdf8ff3 --- /dev/null +++ b/ui-tests/test/help.spec.ts @@ -0,0 +1,73 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { expect } from '@jupyterlab/galata'; + +import { test } from './fixtures'; + +test.describe('Help menu', () => { + test('Should open the About dialog', async ({ page }) => { + await page.menu.clickMenuItem('Help>About Jupyter Notebook'); + + const dialog = page.locator('.jp-Dialog.jp-AboutNotebook'); + await expect(dialog).toBeVisible(); + + // The version reported in the dialog should be a valid version number + await expect(dialog.locator('.jp-AboutNotebook-version')).toHaveText( + /^Version: \d+\.\d+\.\d+/ + ); + + // The external links should open in a new tab + const githubLink = dialog.getByRole('link', { + name: 'JUPYTER NOTEBOOK ON GITHUB', + }); + await expect(githubLink).toHaveAttribute( + 'href', + 'https://github.com/jupyter/notebook' + ); + await expect(githubLink).toHaveAttribute('target', '_blank'); + + const contributorsLink = dialog.getByRole('link', { + name: 'CONTRIBUTOR LIST', + }); + await expect(contributorsLink).toHaveAttribute( + 'href', + 'https://github.com/jupyter/notebook/pulse' + ); + await expect(contributorsLink).toHaveAttribute('target', '_blank'); + + await expect( + dialog.locator('.jp-AboutNotebook-about-copyright') + ).toBeVisible(); + + // Dismiss the dialog + await dialog.getByRole('button', { name: 'Dismiss' }).click(); + await expect(dialog).toHaveCount(0); + }); + + test('Should open the Documentation in a new browser tab', async ({ + page, + }) => { + const documentationUrl = 'https://jupyter-notebook.readthedocs.io/'; + + // Stub the external website so the test does not depend on network access + await page.context().route(`${documentationUrl}**`, (route) => + route.fulfill({ + contentType: 'text/html', + body: '
Documentation', + }) + ); + + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.menu.clickMenuItem('Help>Documentation'), + ]); + + // Only check the URL of the new tab, without waiting for the page to load + await popup.waitForURL(`${documentationUrl}en/stable/`, { + waitUntil: 'commit', + }); + expect(popup.url()).toEqual(`${documentationUrl}en/stable/`); + await popup.close(); + }); +}); diff --git a/ui-tests/test/notebook.spec.ts b/ui-tests/test/notebook.spec.ts index 803f1a06f93..4ce410f6ac4 100644 --- a/ui-tests/test/notebook.spec.ts +++ b/ui-tests/test/notebook.spec.ts @@ -156,6 +156,94 @@ test.describe('Notebook', () => { expect(await panel.screenshot()).toMatchSnapshot(imageName); }); + test('Edit Notebook Metadata should open the right panel with Advanced Tools expanded', async ({ + page, + tmpPath, + }) => { + const notebook = 'simple.ipynb'; + await page.contents.uploadFile( + path.resolve(__dirname, `./notebooks/${notebook}`), + `${tmpPath}/${notebook}` + ); + await page.goto(`notebooks/${tmpPath}/${notebook}`); + + await waitForKernelReady(page); + + await page.menu.clickMenuItem('Edit>Edit Notebook Metadata'); + + const panel = page.locator('#jp-right-stack'); + await expect(panel).toBeVisible(); + + const notebookTools = page.locator('#notebook-tools.jp-NotebookTools'); + await expect(notebookTools).toBeVisible(); + + // The Advanced Tools section should be expanded + const advancedTools = notebookTools.locator('.jp-Collapse', { + hasText: 'Advanced Tools', + }); + await expect(advancedTools.locator('.jp-Collapse-header')).not.toHaveClass( + /jp-Collapse-header-collapsed/ + ); + await expect(advancedTools.locator('.jp-Collapse-contents')).toBeVisible(); + }); + + test('Tab title should reflect the current document', async ({ + page, + tmpPath, + }) => { + const notebook = 'simple.ipynb'; + await page.contents.uploadFile( + path.resolve(__dirname, `./notebooks/${notebook}`), + `${tmpPath}/${notebook}` + ); + await page.goto(`notebooks/${tmpPath}/${notebook}`); + + // The tab title should be the notebook name with the ".ipynb" suffix stripped + await expect(page).toHaveTitle('simple'); + + // The tree page should have the default title + await page.goto(`tree/${tmpPath}`); + await expect(page).toHaveTitle('Home'); + }); + + test('Favicon should switch to busy while a cell is running', async ({ + page, + tmpPath, + }) => { + const notebook = 'empty.ipynb'; + await page.contents.uploadFile( + path.resolve(__dirname, `./notebooks/${notebook}`), + `${tmpPath}/${notebook}` + ); + await page.goto(`notebooks/${tmpPath}/${notebook}`); + + await waitForKernelReady(page); + + const favicon = page.locator('link[rel*="icon"]'); + + await page.click('.jp-Cell-inputArea'); + + // Enter code in the first cell + await page + .locator( + '.jp-Cell-inputArea >> .cm-editor >> .cm-content[contenteditable="true"]' + ) + .type('import time; time.sleep(3)'); + + // Run the cell + await runAndAdvance(page); + + // The favicon should switch to the busy icon while the kernel is busy + await expect(favicon).toHaveAttribute('href', /favicon-busy-1\.ico/, { + timeout: 15000, + }); + + // And back to the idle notebook icon when the execution is done + await expect(favicon).toHaveAttribute('href', /favicon-notebook\.ico/, { + timeout: 30000, + }); + }); + test('Clicking on "Close and Shut Down Notebook" should close the browser tab', async ({ page, tmpPath, diff --git a/ui-tests/test/terminal.spec.ts b/ui-tests/test/terminal.spec.ts new file mode 100644 index 00000000000..ecadd54d2b4 --- /dev/null +++ b/ui-tests/test/terminal.spec.ts @@ -0,0 +1,133 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { expect, IJupyterLabPageFixture } from '@jupyterlab/galata'; + +import { Page } from '@playwright/test'; + +import { test } from './fixtures'; + +/** + * Create a new terminal from the New dropdown of the file browser toolbar. + * + * The terminal session is created via the page so it is tracked by Galata + * and automatically disposed at the end of the test. + */ +const openTerminalFromNewDropdown = async ( + page: IJupyterLabPageFixture +): Promise<{ terminal: Page; name: string }> => { + const terminalPromise = page.waitForEvent('popup'); + await page.click('.jp-DropdownMenu >> text="New"'); + await page.click('.lm-Menu [data-command="terminal:create-new"]'); + const terminal = await terminalPromise; + await terminal.waitForLoadState(); + + // the terminal should open in a new tab on the /terminals/