Skip to content

Commit 6f2cf2c

Browse files
authored
Add more UI tests (#8042)
* Add more UI tests for Notebook-specific features * Check the file editor partial view instead of taking a snapshot * Use the filebrowser:create-new-terminal command id in the terminal UI tests * Trim redundant comments from the new UI tests * Bound the UI test snapshot regeneration step and only rerun failures * Assert terminal sessions via the Running tab instead of the REST API
1 parent 8a9d348 commit 6f2cf2c

8 files changed

Lines changed: 570 additions & 4 deletions

File tree

‎.github/workflows/ui-tests.yml‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,11 @@ jobs:
8484

8585
- name: Update snapshots
8686
if: failure()
87+
timeout-minutes: 6
8788
run: |
8889
cd ui-tests
89-
# remove previous snapshots from other browser
90-
jlpm rimraf "test/**/*-snapshots/*.png"
91-
# generate new snapshots
92-
jlpm run test:update --browser ${{ matrix.browser }}
90+
# only regenerate snapshots for the tests that just failed
91+
jlpm run test:update --browser ${{ matrix.browser }} --last-failed
9392
9493
- name: Upload updated snapshots
9594
if: failure()

‎ui-tests/test/editor.spec.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,39 @@ test.describe('Editor', () => {
7676
await expect(page.locator('.jp-Notebook')).toHaveCount(0);
7777
});
7878

79+
test('Should display a partial view with white space at the bottom', async ({
80+
page,
81+
tmpPath,
82+
}) => {
83+
const file = `${tmpPath}/${FILE}`;
84+
await page.goto(`edit/${file}`);
85+
86+
await expect(page.locator('.cm-editor')).toBeVisible();
87+
await expect(page.locator('.cm-content')).toContainText('name: notebook');
88+
89+
// the edit page should display a partial view, with white space below
90+
// the main panel
91+
const spacer = page.locator('#spacer-widget-bottom');
92+
await expect(spacer).toBeVisible();
93+
const spacerBox = await spacer.boundingBox();
94+
expect(spacerBox?.height).toBeGreaterThanOrEqual(16);
95+
});
96+
97+
test('Should not render the micro toolbar for files', async ({
98+
page,
99+
tmpPath,
100+
}) => {
101+
const file = `${tmpPath}/${FILE}`;
102+
await page.goto(`edit/${file}`);
103+
104+
await expect(page.locator('.cm-editor')).toBeVisible();
105+
106+
// the micro toolbar is added to the DOM but should be hidden via CSS
107+
const microToolbar = page.locator('.jp-MainAreaWidget > .jp-Toolbar-micro');
108+
await expect(microToolbar).toHaveCount(1);
109+
await expect(microToolbar).toBeHidden();
110+
});
111+
79112
test('Renaming the file via the menu entry', async ({ page, tmpPath }) => {
80113
const file = `${tmpPath}/${FILE}`;
81114
await page.goto(`edit/${file}`);

‎ui-tests/test/filebrowser.spec.ts‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,38 @@ test.describe('File Browser', () => {
297297
}
298298
});
299299

300+
test('Show the Upload button in the toolbar by default', async ({ page }) => {
301+
const toolbar = page.locator('.jp-FileBrowser-toolbar');
302+
303+
await expect(toolbar.getByText('Upload')).toBeVisible();
304+
});
305+
306+
test('Filter the file browser listing with the file filter', async ({
307+
page,
308+
}) => {
309+
await page.filebrowser.refresh();
310+
311+
const toggleButton = page.locator(
312+
'jp-button[data-command="filebrowser:toggle-file-filter"]'
313+
);
314+
const filterInput = page.locator('.jp-FileBrowser-filterBox input');
315+
const listing = page.locator('.jp-DirListing-item');
316+
317+
await expect(filterInput).toBeHidden();
318+
await expect(listing).toHaveCount(3);
319+
320+
await toggleButton.click();
321+
await expect(filterInput).toBeVisible();
322+
323+
await filterInput.fill('folder1');
324+
await expect(listing).toHaveCount(1);
325+
await expect(listing).toHaveText(/folder1/);
326+
327+
await toggleButton.click();
328+
await expect(filterInput).toBeHidden();
329+
await expect(listing).toHaveCount(3);
330+
});
331+
300332
test('Toggle the Date Created column from the header context menu', async ({
301333
page,
302334
}) => {
@@ -332,3 +364,35 @@ test.describe('File Browser settings', () => {
332364
await expect(header.locator('.jp-id-created')).toBeVisible();
333365
});
334366
});
367+
368+
test.describe('File Browser toolbar settings', () => {
369+
test.use({
370+
mockSettings: {
371+
...galata.DEFAULT_SETTINGS,
372+
'@jupyter-notebook/tree-extension:widget': {
373+
toolbar: [
374+
{
375+
name: 'uploader',
376+
disabled: true,
377+
},
378+
],
379+
},
380+
},
381+
});
382+
383+
test('Should hide the Upload button when disabled in the settings', async ({
384+
page,
385+
}) => {
386+
const toolbar = page.locator('.jp-FileBrowser-toolbar');
387+
388+
// other toolbar items should still be visible
389+
await expect(toolbar.getByText('New', { exact: true })).toBeVisible();
390+
await expect(
391+
toolbar.locator('[data-jp-item-name="refresh"]')
392+
).toBeVisible();
393+
394+
await expect(toolbar.locator('[data-jp-item-name="uploader"]')).toHaveCount(
395+
0
396+
);
397+
});
398+
});

‎ui-tests/test/help.spec.ts‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Jupyter Development Team.
2+
// Distributed under the terms of the Modified BSD License.
3+
4+
import { expect } from '@jupyterlab/galata';
5+
6+
import { test } from './fixtures';
7+
8+
test.describe('Help menu', () => {
9+
test('Should open the About dialog', async ({ page }) => {
10+
await page.menu.clickMenuItem('Help>About Jupyter Notebook');
11+
12+
const dialog = page.locator('.jp-Dialog.jp-AboutNotebook');
13+
await expect(dialog).toBeVisible();
14+
15+
await expect(dialog.locator('.jp-AboutNotebook-version')).toHaveText(
16+
/^Version: \d+\.\d+\.\d+/
17+
);
18+
19+
// The external links should open in a new tab
20+
const githubLink = dialog.getByRole('link', {
21+
name: 'JUPYTER NOTEBOOK ON GITHUB',
22+
});
23+
await expect(githubLink).toHaveAttribute(
24+
'href',
25+
'https://github.com/jupyter/notebook'
26+
);
27+
await expect(githubLink).toHaveAttribute('target', '_blank');
28+
29+
const contributorsLink = dialog.getByRole('link', {
30+
name: 'CONTRIBUTOR LIST',
31+
});
32+
await expect(contributorsLink).toHaveAttribute(
33+
'href',
34+
'https://github.com/jupyter/notebook/pulse'
35+
);
36+
await expect(contributorsLink).toHaveAttribute('target', '_blank');
37+
38+
await expect(
39+
dialog.locator('.jp-AboutNotebook-about-copyright')
40+
).toBeVisible();
41+
42+
await dialog.getByRole('button', { name: 'Dismiss' }).click();
43+
await expect(dialog).toHaveCount(0);
44+
});
45+
46+
test('Should open the Documentation in a new browser tab', async ({
47+
page,
48+
}) => {
49+
const documentationUrl = 'https://jupyter-notebook.readthedocs.io/';
50+
51+
// Stub the external website so the test does not depend on network access
52+
await page.context().route(`${documentationUrl}**`, (route) =>
53+
route.fulfill({
54+
contentType: 'text/html',
55+
body: '<html><body>Documentation</body></html>',
56+
})
57+
);
58+
59+
const [popup] = await Promise.all([
60+
page.waitForEvent('popup'),
61+
page.menu.clickMenuItem('Help>Documentation'),
62+
]);
63+
64+
// Only check the URL of the new tab, without waiting for the page to load
65+
await popup.waitForURL(`${documentationUrl}en/stable/`, {
66+
waitUntil: 'commit',
67+
});
68+
expect(popup.url()).toEqual(`${documentationUrl}en/stable/`);
69+
await popup.close();
70+
});
71+
});

‎ui-tests/test/notebook.spec.ts‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,91 @@ test.describe('Notebook', () => {
156156
expect(await panel.screenshot()).toMatchSnapshot(imageName);
157157
});
158158

159+
test('Edit Notebook Metadata should open the right panel with Advanced Tools expanded', async ({
160+
page,
161+
tmpPath,
162+
}) => {
163+
const notebook = 'simple.ipynb';
164+
await page.contents.uploadFile(
165+
path.resolve(__dirname, `./notebooks/${notebook}`),
166+
`${tmpPath}/${notebook}`
167+
);
168+
await page.goto(`notebooks/${tmpPath}/${notebook}`);
169+
170+
await waitForKernelReady(page);
171+
172+
await page.menu.clickMenuItem('Edit>Edit Notebook Metadata');
173+
174+
const panel = page.locator('#jp-right-stack');
175+
await expect(panel).toBeVisible();
176+
177+
const notebookTools = page.locator('#notebook-tools.jp-NotebookTools');
178+
await expect(notebookTools).toBeVisible();
179+
180+
const advancedTools = notebookTools.locator('.jp-Collapse', {
181+
hasText: 'Advanced Tools',
182+
});
183+
await expect(advancedTools.locator('.jp-Collapse-header')).not.toHaveClass(
184+
/jp-Collapse-header-collapsed/
185+
);
186+
await expect(advancedTools.locator('.jp-Collapse-contents')).toBeVisible();
187+
});
188+
189+
test('Tab title should reflect the current document', async ({
190+
page,
191+
tmpPath,
192+
}) => {
193+
const notebook = 'simple.ipynb';
194+
await page.contents.uploadFile(
195+
path.resolve(__dirname, `./notebooks/${notebook}`),
196+
`${tmpPath}/${notebook}`
197+
);
198+
await page.goto(`notebooks/${tmpPath}/${notebook}`);
199+
200+
// The tab title should be the notebook name with the ".ipynb" suffix stripped
201+
await expect(page).toHaveTitle('simple');
202+
203+
await page.goto(`tree/${tmpPath}`);
204+
await expect(page).toHaveTitle('Home');
205+
});
206+
207+
test('Favicon should switch to busy while a cell is running', async ({
208+
page,
209+
tmpPath,
210+
}) => {
211+
const notebook = 'empty.ipynb';
212+
await page.contents.uploadFile(
213+
path.resolve(__dirname, `./notebooks/${notebook}`),
214+
`${tmpPath}/${notebook}`
215+
);
216+
await page.goto(`notebooks/${tmpPath}/${notebook}`);
217+
218+
await waitForKernelReady(page);
219+
220+
const favicon = page.locator('link[rel*="icon"]');
221+
222+
await page.click('.jp-Cell-inputArea');
223+
224+
// Enter code in the first cell
225+
await page
226+
.locator(
227+
'.jp-Cell-inputArea >> .cm-editor >> .cm-content[contenteditable="true"]'
228+
)
229+
.type('import time; time.sleep(3)');
230+
231+
// Run the cell
232+
await runAndAdvance(page);
233+
234+
await expect(favicon).toHaveAttribute('href', /favicon-busy-1\.ico/, {
235+
timeout: 15000,
236+
});
237+
238+
// the favicon goes back to the idle icon once execution is done
239+
await expect(favicon).toHaveAttribute('href', /favicon-notebook\.ico/, {
240+
timeout: 30000,
241+
});
242+
});
243+
159244
test('Clicking on "Close and Shut Down Notebook" should close the browser tab', async ({
160245
page,
161246
tmpPath,

0 commit comments

Comments
 (0)