diff --git a/galata/README.md b/galata/README.md index 25fb80f93460..85e62fd78d05 100644 --- a/galata/README.md +++ b/galata/README.md @@ -126,7 +126,14 @@ To debug tests, a good way is to use the inspector tool of playwright: ``` jupyter lab --config jupyter_server_test_config.py & -PWDEBUG=1 jlpm playwright test +jlpm playwright test --debug +``` + +Or the [UI mode](https://playwright.dev/docs/test-ui-mode): + +``` +jupyter lab --config jupyter_server_test_config.py & +jlpm playwright test --ui ``` ### Dealing with login diff --git a/galata/src/fixtures.ts b/galata/src/fixtures.ts index 6a153c346ebc..088ec0673797 100644 --- a/galata/src/fixtures.ts +++ b/galata/src/fixtures.ts @@ -321,9 +321,7 @@ export const test: TestType< page: Page, helpers: IJupyterLabPage ): Promise => { - await page.waitForSelector('#jupyterlab-splash', { - state: 'detached' - }); + await page.locator('#jupyterlab-splash').waitFor({ state: 'detached' }); await helpers.waitForCondition(() => { return helpers.activity.isTabActive('Launcher'); }); diff --git a/galata/src/galata.ts b/galata/src/galata.ts index cbf9ef565969..de3e25854d50 100644 --- a/galata/src/galata.ts +++ b/galata/src/galata.ts @@ -578,7 +578,10 @@ export namespace galata { * #### Notes * The goal is to freeze the file browser display */ - export async function freezeContentLastModified(page: Page): Promise { + export async function freezeContentLastModified( + page: Page, + filter?: (directoryList: T[]) => T[] + ): Promise { // Listen for closing connection (may happen when request are still being processed) let isClosed = false; const ctxt = page.context(); @@ -609,6 +612,9 @@ export namespace galata { data['type'] === 'directory' && Array.isArray(data['content']) ) { + if (filter) { + data['content'] = filter(data['content']); + } const now = Date.now(); const aDayAgo = new Date(now - 24 * 3600 * 1000).toISOString(); for (const entry of data['content'] as any[]) { diff --git a/galata/src/helpers/activity.ts b/galata/src/helpers/activity.ts index b36acd2da690..8ca8e6c490cb 100644 --- a/galata/src/helpers/activity.ts +++ b/galata/src/helpers/activity.ts @@ -50,11 +50,11 @@ export class ActivityHelper { .inputValue(); return activeTab === name; } else { - const tab = await this.getTab(name); - if (!tab) { + const tab = this.getTabLocator(name); + if (!(await tab.count())) { return false; } - const classes = ((await tab.getAttribute('class')) ?? '').split(' '); + const classes = await Utils.getLocatorClassList(tab); return classes.includes('jp-mod-current'); } } @@ -64,15 +64,19 @@ export class ActivityHelper { * * @param name Activity name * @returns Handle on the tab or null if the tab is not found + * + * @deprecated You should use locator instead {@link getTabLocator} */ async getTab(name?: string): Promise | null> { - let handle: ElementHandle | null = null; - try { - handle = await this.getTabLocator(name).elementHandle({ timeout: 500 }); - } catch { - handle = null; + const locator = this.getTabLocator(name); + const start = Date.now(); + while ((await locator.count()) == 0 && Date.now() - start < 500) { + await this.page.waitForTimeout(50); + } + if ((await locator.count()) > 0) { + return locator.elementHandle(); } - return handle; + return null; } /** @@ -91,6 +95,8 @@ export class ActivityHelper { * * @param name Activity name * @returns Handle on the tab or null if the tab is not found + * + * @deprecated You should use locator instead {@link getPanelLocator} */ async getPanel(name?: string): Promise | null> { const page = this.page; @@ -108,14 +114,14 @@ export class ActivityHelper { locator = page.getByRole('main').locator(`[role="tabpanel"][id="${id}"]`); } - let handle: ElementHandle | null = null; - try { - handle = await locator.elementHandle({ timeout: 500 }); - } catch { - handle = null; + const start = Date.now(); + while ((await locator.count()) == 0 && Date.now() - start < 500) { + await this.page.waitForTimeout(50); } - - return handle; + if ((await locator.count()) > 0) { + return locator.elementHandle(); + } + return null; } /** @@ -161,16 +167,12 @@ export class ActivityHelper { * @returns Whether the action is successful */ async activateTab(name: string): Promise { - const tab = await this.getTab(name); - if (tab) { + const tab = this.getTabLocator(name); + if ((await tab.count()) === 1) { await tab.click(); - await this.page.waitForFunction( - ({ tab }) => { - return tab.ariaSelected === 'true'; - }, - { tab } + await Utils.waitForCondition( + async () => (await tab.getAttribute('aria-selected')) === 'true' ); - return true; } diff --git a/galata/src/helpers/debuggerpanel.ts b/galata/src/helpers/debuggerpanel.ts index a12e27fca7d9..108133617873 100644 --- a/galata/src/helpers/debuggerpanel.ts +++ b/galata/src/helpers/debuggerpanel.ts @@ -3,13 +3,11 @@ * Distributed under the terms of the Modified BSD License. */ -import { ElementHandle, Page } from '@playwright/test'; +import { ElementHandle, Locator, Page } from '@playwright/test'; import { SidebarHelper } from './sidebar'; import { NotebookHelper } from './notebook'; import { waitForCondition } from '../utils'; -const DEBUGGER_ITEM = 'debugger-icon'; - /** * Debugger Helper */ @@ -22,47 +20,57 @@ export class DebuggerHelper { /** * Returns true if debugger toolbar item is enabled, false otherwise + * + * @param name Notebook name */ - async isOn(): Promise { - if (!(await this.notebook.isAnyActive())) { - return false; - } - const item = await this.notebook.getToolbarItem(DEBUGGER_ITEM); - if (item) { - const button = await item.$('button'); - if (button) { - return (await button.getAttribute('aria-pressed')) === 'true'; - } + async isOn(name?: string): Promise { + const toolbar = await this.notebook.getToolbarLocator(name); + const button = toolbar?.locator('.jp-DebuggerBugButton'); + if (((await button?.count()) ?? 0) > 0) { + return (await button!.getAttribute('aria-pressed')) === 'true'; } return false; } /** * Enables the debugger toolbar item + * + * @param name Notebook name */ - async switchOn(): Promise { - await waitForCondition(async () => { - const item = await this.notebook.getToolbarItem(DEBUGGER_ITEM); - if (item) { - const button = await item.$('button'); - if (button) { - return (await button.getAttribute('aria-disabled')) !== 'true'; - } - } - return false; - }, 2000); - if (!(await this.isOn())) { - await this.notebook.clickToolbarItem(DEBUGGER_ITEM); + async switchOn(name?: string): Promise { + const toolbar = await this.notebook.getToolbarLocator(name); + if (!toolbar) { + return; } + const button = toolbar.locator('.jp-DebuggerBugButton'); + await waitForCondition(async () => (await button.count()) === 1); + await waitForCondition( + async () => (await button!.isDisabled()) === false, + 2000 + ); + + if (!(await this.isOn(name))) { + await button!.click(); + } + await waitForCondition(async () => await this.isOn(name)); } /** * Disables the debugger toolbar item + * + * @param name Notebook name */ - async switchOff(): Promise { - if (await this.isOn()) { - await this.notebook.clickToolbarItem(DEBUGGER_ITEM); + async switchOff(name?: string): Promise { + const toolbar = await this.notebook.getToolbarLocator(name); + if (!toolbar) { + return; + } + const button = toolbar.locator('.jp-DebuggerBugButton'); + await waitForCondition(async () => (await button.count()) === 1); + if (await this.isOn(name)) { + await button!.click(); } + await waitForCondition(async () => !(await this.isOn(name))); } /** @@ -74,8 +82,17 @@ export class DebuggerHelper { /** * Returns handle to the variables panel content + * + * @deprecated You should use locator instead {@link getVariablesPanelLocator} */ async getVariablesPanel(): Promise | null> { + return (await this.getVariablesPanelLocator()).elementHandle(); + } + + /** + * Returns locator to the variables panel content + */ + async getVariablesPanelLocator(): Promise { return this._getPanel('.jp-DebuggerVariables'); } @@ -83,7 +100,7 @@ export class DebuggerHelper { * Waits for variables to be populated in the variables panel */ async waitForVariables(): Promise { - await this.page.waitForSelector('.jp-DebuggerVariables-body ul'); + await this.page.locator('.jp-DebuggerVariables-body ul').waitFor(); } /** @@ -96,13 +113,22 @@ export class DebuggerHelper { await this.page .locator('.lm-Menu-itemLabel:text("Render Variable")') .click(); - await this.page.waitForSelector('.jp-VariableRendererPanel-renderer'); + await this.page.locator('.jp-VariableRendererPanel-renderer').waitFor(); } /** * Returns handle to callstack panel content + * + * @deprecated You should use locator instead {@link getCallStackPanelLocator} */ async getCallStackPanel(): Promise | null> { + return (await this.getCallStackPanelLocator()).elementHandle(); + } + + /** + * Returns locator to callstack panel content + */ + async getCallStackPanelLocator(): Promise { return this._getPanel('.jp-DebuggerCallstack'); } @@ -110,15 +136,25 @@ export class DebuggerHelper { * Waits for the callstack body to populate in the callstack panel */ async waitForCallStack(): Promise { - await this.page.waitForSelector( - '.jp-DebuggerCallstack-body >> .jp-DebuggerCallstackFrame' - ); + await this.page + .locator('.jp-DebuggerCallstack-body >> .jp-DebuggerCallstackFrame') + .first() + .waitFor(); } /** * Returns handle to breakpoints panel content + * + * @deprecated You should use locator instead {@link getBreakPointsPanelLocator} */ async getBreakPointsPanel(): Promise | null> { + return (await this.getBreakPointsPanelLocator()).elementHandle(); + } + + /** + * Returns locator to breakpoints panel content + */ + async getBreakPointsPanelLocator(): Promise { return this._getPanel('.jp-DebuggerBreakpoints'); } @@ -126,15 +162,25 @@ export class DebuggerHelper { * Waits for the breakpoints to appear in the breakpoints panel */ async waitForBreakPoints(): Promise { - await this.page.waitForSelector( - '.jp-DebuggerBreakpoints >> .jp-DebuggerBreakpoint' - ); + await this.page + .locator('.jp-DebuggerBreakpoints >> .jp-DebuggerBreakpoint') + .first() + .waitFor(); } /** * Returns handle to sources panel content + * + * @deprecated You should use locator instead {@link getSourcePanelLocator} */ async getSourcePanel(): Promise | null> { + return (await this.getSourcePanelLocator()).elementHandle(); + } + + /** + * Returns locator to sources panel content + */ + async getSourcePanelLocator(): Promise { return this._getPanel('.jp-DebuggerSources'); } @@ -142,18 +188,14 @@ export class DebuggerHelper { * Waits for sources to be populated in the sources panel */ async waitForSources(): Promise { - await this.page.waitForSelector('.jp-DebuggerSources-body >> .jp-Editor', { - state: 'visible' - }); + await this.page + .locator('.jp-DebuggerSources-body >> .jp-Editor') + .first() + .waitFor({ state: 'visible' }); } - private async _getPanel( - selector: string - ): Promise | null> { - const panel = await this.sidebar.getContentPanel('right'); - if (panel) { - return panel.$(selector); - } - return null; + private async _getPanel(selector: string): Promise { + const panel = this.sidebar.getContentPanelLocator('right'); + return panel.locator(selector); } } diff --git a/galata/src/helpers/filebrowser.ts b/galata/src/helpers/filebrowser.ts index 3137db282dc3..9891b8ed8a29 100644 --- a/galata/src/helpers/filebrowser.ts +++ b/galata/src/helpers/filebrowser.ts @@ -54,9 +54,7 @@ export class FileBrowserHelper { await this.openDirectory(dirPath); } - await Utils.waitForCondition(async () => { - return await this.isFileListedInBrowser(fileName); - }); + await Utils.waitForCondition(() => this.isFileListedInBrowser(fileName)); } /** @@ -66,10 +64,11 @@ export class FileBrowserHelper { * @returns File status */ async isFileListedInBrowser(fileName: string): Promise { - const item = await this.page.$( - `xpath=${this.xpBuildFileSelector(fileName)}` - ); - return item !== null; + const item = this.page + .getByRole('region', { name: 'File Browser Section' }) + .getByRole('listitem', { name: new RegExp(`^Name: ${fileName}`) }); + + return (await item.count()) > 0; } /** @@ -106,10 +105,10 @@ export class FileBrowserHelper { await this.revealFileInBrowser(filePath); const name = path.basename(filePath); - const fileItem = await this.page.$( - `xpath=${this.xpBuildFileSelector(name)}` - ); - if (fileItem) { + const fileItem = this.page + .getByRole('region', { name: 'File Browser Section' }) + .getByRole('listitem', { name: new RegExp(`^Name: ${name}`) }); + if (await fileItem.count()) { if (factory) { await fileItem.click({ button: 'right' }); await this.page @@ -127,7 +126,7 @@ export class FileBrowserHelper { // to know that from the DOM). await this.page .getByRole('main') - .getByRole('tab', { name }) + .getByRole('tab', { name: new RegExp(`^${name}`) }) .last() .waitFor({ state: 'visible' @@ -145,10 +144,10 @@ export class FileBrowserHelper { * @returns Action success status */ async openHomeDirectory(): Promise { - const homeButton = await this.page.$( - '.jp-FileBrowser .jp-FileBrowser-crumbs span' - ); - if (!homeButton) { + const homeButton = this.page + .locator('.jp-FileBrowser .jp-FileBrowser-crumbs span') + .first(); + if (!(await homeButton.count())) { return false; } await homeButton.click(); @@ -210,7 +209,7 @@ export class FileBrowserHelper { */ async refresh(): Promise { const page = this.page; - const item = page + const button = page .locator('#filebrowser') .locator( '.jp-ToolbarButtonComponent[data-command="filebrowser:refresh"]' @@ -222,7 +221,7 @@ export class FileBrowserHelper { await Promise.race([ page.waitForTimeout(2000), this.contents.waitForAPIResponse(async () => { - await item.click(); + await button.click(); }) ]); // wait for DOM rerender @@ -230,18 +229,18 @@ export class FileBrowserHelper { } protected async _openDirectory(dirName: string): Promise { - const item = await this.page.$( - `xpath=${this.xpBuildDirectorySelector(dirName)}` - ); - if (item === null) { - return false; - } + const item = this.page + .getByRole('region', { name: 'File Browser Section' }) + .getByRole('listitem', { name: new RegExp(`^Name: ${dirName}`) }); + await Utils.waitForCondition(async () => (await item.count()) > 0); await this.contents.waitForAPIResponse(async () => { - await item.click({ clickCount: 2 }); + await item.dblclick(); }); - // wait for DOM rerender - await this.page.waitForTimeout(200); + await this.page + .getByRole('region', { name: 'File Browser Section' }) + .getByText(`/${dirName}/`) + .waitFor(); return true; } diff --git a/galata/src/helpers/menu.ts b/galata/src/helpers/menu.ts index ae87903e3480..f57cdc3857cf 100644 --- a/galata/src/helpers/menu.ts +++ b/galata/src/helpers/menu.ts @@ -1,7 +1,7 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { ElementHandle, Page } from '@playwright/test'; +import { ElementHandle, Locator, Page } from '@playwright/test'; import * as Utils from '../utils'; /** @@ -15,17 +15,14 @@ export class MenuHelper { */ async closeAll(): Promise { const page = this.page; - const existingMenus = await page.$$('.lm-Menu'); - const numOpenMenus = existingMenus.length; + const existingMenus = page.locator('.lm-Menu'); + const numOpenMenus = await existingMenus.count(); // close menus for (let i = 0; i < numOpenMenus; ++i) { await page.keyboard.press('Escape'); await page.waitForTimeout(100); - await page.waitForFunction( - (menuCount: number) => { - return document.querySelectorAll('.lm-Menu').length === menuCount; - }, - numOpenMenus - (i + 1) + await Utils.waitForCondition( + async () => (await existingMenus.count()) === numOpenMenus - (i + 1) ); } } @@ -35,12 +32,22 @@ export class MenuHelper { * * @param label Menu label * @returns Handle to the menu or null + * + * @deprecated You should use locator instead {@link getMenuBarItemLocator} */ getMenuBarItem(label: string): Promise | null> { - return this.page.$( - `xpath=//li[./div[text()="${label}" and ${Utils.xpContainsClass( - 'lm-MenuBar-itemLabel' - )}]]` + return this.getMenuBarItemLocator(label).elementHandle(); + } + + /** + * Get the locator on a menu from its label. + * + * @param label Menu label + * @returns Locator to the menu + */ + getMenuBarItemLocator(label: string): Locator { + return this.page.locator( + `li:has(div.lm-MenuBar-itemLabel:text-is("${label}"))` ); } @@ -49,14 +56,26 @@ export class MenuHelper { * * @param selector Element over which the menu should be opened * @returns Handle to the menu or null + * + * @deprecated You should use locator instead {@link openContextMenuLocator} */ async openContextMenu( selector: string ): Promise | null> { + return (await this.openContextMenuLocator(selector)).elementHandle(); + } + + /** + * Open a context menu and get its locator. + * + * @param selector Element over which the menu should be opened + * @returns Locator to the menu + */ + async openContextMenuLocator(selector: string): Promise { await this.page.click(selector, { button: 'right' }); - return await this.page.$('.lm-Menu'); + return this.page.locator('.lm-Menu'); } /** @@ -65,30 +84,43 @@ export class MenuHelper { * The separator used is '>'; e.g. to look for the new Notebook item 'File>New>Notebook'. * * @param path Menu item path - * @returns Handle to the menu item + * @returns Handle to the menu item or null + * + * @deprecated You should use locator instead {@link getMenuItemLocator} */ async getMenuItem(path: string): Promise | null> { + return (await this.getMenuItemLocator(path))?.elementHandle() ?? null; + } + + /** + * Get the locator on a menu item from its path. + * + * The separator used is '>'; e.g. to look for the new Notebook item 'File>New>Notebook'. + * + * @param path Menu item path + * @returns Locator to the menu item or null + */ + async getMenuItemLocator(path: string): Promise { const page = this.page; const parts = path.split('>'); const numParts = parts.length; - let subMenu: ElementHandle | null = null; + let subMenu: Locator | null = null; for (let i = 0; i < numParts; ++i) { const part = parts[i]; const menuItem = i === 0 - ? await this.getMenuBarItem(part) - : await this.getMenuItemInMenu(subMenu!, part); + ? this.getMenuBarItemLocator(part) + : await this.getMenuItemLocatorInMenu(subMenu!, part); if (menuItem) { if (i === numParts - 1) { return menuItem; } else { if (i === 0) { - subMenu = await page.$('.lm-Menu.lm-MenuBar-menu'); + subMenu = page.locator('.lm-Menu.lm-MenuBar-menu'); } else { - const newMenus = await page.$$('.lm-Menu'); - subMenu = - newMenus?.length > 0 ? newMenus[newMenus.length - 1] : null; + const newMenus = page.locator('.lm-Menu'); + subMenu = (await newMenus.count()) > 0 ? newMenus.last() : null; } if (!subMenu) { return null; @@ -107,7 +139,9 @@ export class MenuHelper { * * @param parentMenu Menu handle * @param label Item label - * @returns Handle to the menu item + * @returns Handle to the menu item or null + * + * @deprecated You should use locator instead {@link getMenuItemLocatorInMenu} */ async getMenuItemInMenu( parentMenu: ElementHandle, @@ -124,13 +158,33 @@ export class MenuHelper { return items.length > 0 ? items[0] : null; } + /** + * Get a menu item locator from its label. + * + * @param parentMenu Menu locator + * @param label Item label + * @returns Locator to the menu item or null + */ + async getMenuItemLocatorInMenu( + parentMenu: Locator, + label: string + ): Promise { + const items = parentMenu.locator( + `ul li:has(div.lm-Menu-itemLabel:text-is("${label}"))` + ); + if ((await items.count()) > 1) { + throw new Error(`More than one menu item matches label '${label}'`); + } + return (await items.count()) > 0 ? items.first() : null; + } + /** * Whether any menus are opened or not * * @returns Opened menus status */ async isAnyOpen(): Promise { - return (await this.page.$('.lm-Menu')) !== null; + return (await this.page.locator('.lm-Menu').count()) > 0; } /** @@ -140,7 +194,7 @@ export class MenuHelper { * @returns Opened menu status */ async isOpen(path: string): Promise { - return (await this.getMenuItem(path)) !== null; + return (await (await this.getMenuItemLocator(path))?.isVisible()) ?? false; } /** @@ -148,45 +202,56 @@ export class MenuHelper { * * @param path Menu path * @returns Handle to the opened menu + * + * @deprecated You should use locator instead {@link openLocator} */ async open(path: string): Promise | null> { + return (await this.openLocator(path))?.elementHandle() ?? null; + } + + /** + * Open a menu from its path + * + * @param path Menu path + * @returns Locator to the opened menu + */ + async openLocator(path: string): Promise { await this.closeAll(); const page = this.page; const parts = path.split('>'); const numParts = parts.length; - let subMenu: ElementHandle | null = null; + let subMenu: Locator | null = null; for (let i = 0; i < numParts; ++i) { const part = parts[i]; const menuItem = i === 0 - ? await this.getMenuBarItem(part) - : await this.getMenuItemInMenu(subMenu!, part); + ? this.getMenuBarItemLocator(part) + : await this.getMenuItemLocatorInMenu(subMenu!, part); if (menuItem) { if (i === 0) { await menuItem.click(); - subMenu = await page.waitForSelector('.lm-Menu.lm-MenuBar-menu', { - state: 'visible' - }); + subMenu = page.locator('.lm-Menu.lm-MenuBar-menu'); + await subMenu.waitFor({ state: 'visible' }); } else { - const existingMenus = await page.$$('.lm-Menu'); + const expectedMenusCount = + (await page.locator('.lm-Menu').count()) + 1; await menuItem.hover(); - await page.waitForFunction( - ({ n, item }) => { - return ( - document.querySelectorAll('.lm-Menu').length === n && - item.classList.contains('lm-mod-active') - ); - }, - { n: existingMenus.length + 1, item: menuItem } - ); + await Utils.waitForCondition(async () => { + return ( + (await page.locator('.lm-Menu').count()) === expectedMenusCount && + (await Utils.getLocatorClassList(menuItem)).includes( + 'lm-mod-active' + ) + ); + }); await page.waitForTimeout(200); // Fetch a new list of menus, and fetch the last one. // We are assuming the last menu is the most recently opened. - const newMenus = await page.$$('.lm-Menu'); - subMenu = newMenus[newMenus.length - 1]; + const newMenus = page.locator('.lm-Menu'); + subMenu = newMenus.last(); } } } @@ -198,11 +263,22 @@ export class MenuHelper { * Get the handle to the last opened menu * * @returns Handle to the opened menu + * + * @deprecated You should use locator instead {@link getOpenMenuLocator} */ async getOpenMenu(): Promise | null> { - const openMenus = await this.page.$$('.lm-Widget.lm-Menu .lm-Menu-content'); - if (openMenus.length > 0) { - return openMenus[openMenus.length - 1]; + return (await this.getOpenMenuLocator())?.elementHandle() ?? null; + } + + /** + * Get the locator to the last opened menu + * + * @returns Handle to the opened menu + */ + async getOpenMenuLocator(): Promise { + const openMenus = this.page.locator('.lm-Widget.lm-Menu .lm-Menu-content'); + if ((await openMenus.count()) > 0) { + return openMenus.last(); } return null; @@ -220,9 +296,9 @@ export class MenuHelper { path = parts.slice(0, numParts - 1).join('>'); // open parent menu - const parentMenu = await this.open(path); + const parentMenu = await this.openLocator(path); const menuItem = - parentMenu && (await this.getMenuItemInMenu(parentMenu, label)); + parentMenu && (await this.getMenuItemLocatorInMenu(parentMenu, label)); if (menuItem) { await menuItem.click(); diff --git a/galata/src/helpers/notebook.ts b/galata/src/helpers/notebook.ts index 5a36e5a5e798..6bf4f4ecb495 100644 --- a/galata/src/helpers/notebook.ts +++ b/galata/src/helpers/notebook.ts @@ -3,7 +3,7 @@ import type * as nbformat from '@jupyterlab/nbformat'; import type { NotebookPanel } from '@jupyterlab/notebook'; -import { ElementHandle, Page } from '@playwright/test'; +import { ElementHandle, Locator, Page } from '@playwright/test'; import * as path from 'path'; import { ContentsHelper } from '../contents'; import type { INotebookRunCallback } from '../extension'; @@ -16,7 +16,7 @@ import { MenuHelper } from './menu'; /** * Maximal number of retries to get a cell */ -const MAX_RETRIES = 3; +const MAX_RETRIES = 5; /** * Notebook helpers @@ -37,8 +37,8 @@ export class NotebookHelper { * @returns Notebook opened status */ async isOpen(name: string): Promise { - const tab = await this.activity.getTab(name); - return tab !== null; + const tab = this.activity.getTabLocator(name); + return (await tab.count()) > 0; } /** @@ -57,7 +57,7 @@ export class NotebookHelper { * @returns Notebook active status */ async isAnyActive(): Promise { - return (await this.getNotebookInPanel()) !== null; + return (await this.getNotebookInPanelLocator()) !== null; } /** @@ -98,14 +98,30 @@ export class NotebookHelper { * * @param name Notebook name * @returns Handle to the Notebook panel + * + * @deprecated You should use locator instead {@link getNotebookInPanelLocator} */ async getNotebookInPanel( name?: string ): Promise | null> { - const nbPanel = await this.activity.getPanel(name); + return ( + (await this.getNotebookInPanelLocator(name))?.elementHandle() ?? null + ); + } + + /** + * Get the locator to a notebook panel + * + * @param name Notebook name + * @returns Locator to the Notebook panel + */ + async getNotebookInPanelLocator(name?: string): Promise { + const nbPanel = await this.activity.getPanelLocator(name); - if (nbPanel) { - return await nbPanel.$('.jp-NotebookPanel-notebook'); + if (nbPanel && (await nbPanel.count())) { + if (await nbPanel.locator('.jp-NotebookPanel-notebook').count()) { + return nbPanel.locator('.jp-NotebookPanel-notebook').first(); + } } return null; @@ -116,15 +132,25 @@ export class NotebookHelper { * * @param name Notebook name * @returns Handle to the Notebook toolbar + * + * @deprecated You should use locator instead {@link getToolbarLocator} */ async getToolbar(name?: string): Promise | null> { - const nbPanel = await this.activity.getPanel(name); - - if (nbPanel) { - return await nbPanel.$('.jp-Toolbar'); - } + return (await this.getToolbarLocator(name))?.elementHandle() ?? null; + } - return null; + /** + * Get the notebook toolbar locator + * + * @param name Notebook name + * @returns Locator to the Notebook toolbar + */ + async getToolbarLocator(name?: string): Promise { + return ( + (await this.activity.getPanelLocator(name)) + ?.locator('.jp-Toolbar') + .first() ?? null + ); } /** @@ -133,21 +159,41 @@ export class NotebookHelper { * @param itemIndex Toolbar item index * @param notebookName Notebook name * @returns Handle to the notebook toolbar item + * + * @deprecated You should use locator instead {@link getToolbarItemLocatorByIndex} */ async getToolbarItemByIndex( itemIndex: number, notebookName?: string ): Promise | null> { + return ( + ( + await this.getToolbarItemLocatorByIndex(itemIndex, notebookName) + )?.elementHandle() ?? null + ); + } + + /** + * Get the locator to a notebook toolbar item from its index + * + * @param itemIndex Toolbar item index + * @param notebookName Notebook name + * @returns locator to the notebook toolbar item + */ + async getToolbarItemLocatorByIndex( + itemIndex: number, + notebookName?: string + ): Promise { if (itemIndex === -1) { return null; } - const toolbar = await this.getToolbar(notebookName); + const toolbar = await this.getToolbarLocator(notebookName); if (toolbar) { - const toolbarItems = await toolbar.$$('.jp-Toolbar-item'); - if (itemIndex < toolbarItems.length) { - return toolbarItems[itemIndex]; + const toolbarItems = toolbar.locator('.jp-Toolbar-item'); + if (itemIndex < (await toolbarItems.count())) { + return toolbarItems.nth(itemIndex); } } @@ -160,19 +206,39 @@ export class NotebookHelper { * @param itemId Toolbar item id * @param notebookName Notebook name * @returns Handle to the notebook toolbar item + * + * @deprecated You should use locator instead {@link getToolbarItemLocator} */ async getToolbarItem( itemId: galata.NotebookToolbarItemId, notebookName?: string ): Promise | null> { - const toolbar = await this.getToolbar(notebookName); + return ( + ( + await this.getToolbarItemLocator(itemId, notebookName) + )?.elementHandle() ?? null + ); + } + + /** + * Get the locator to a notebook toolbar item from its id + * + * @param itemId Toolbar item id + * @param notebookName Notebook name + * @returns Locator to the notebook toolbar item + */ + async getToolbarItemLocator( + itemId: galata.NotebookToolbarItemId, + notebookName?: string + ): Promise { + const toolbar = await this.getToolbarLocator(notebookName); if (toolbar) { const itemIndex = await this.page.evaluate(async (itemId: string) => { return window.galata.getNotebookToolbarItemIndex(itemId); }, itemId); - return this.getToolbarItemByIndex(itemIndex); + return this.getToolbarItemLocatorByIndex(itemIndex); } return null; @@ -189,7 +255,7 @@ export class NotebookHelper { itemId: galata.NotebookToolbarItemId, notebookName?: string ): Promise { - const toolbarItem = await this.getToolbarItem(itemId, notebookName); + const toolbarItem = await this.getToolbarItemLocator(itemId, notebookName); if (toolbarItem) { await toolbarItem.click(); @@ -406,9 +472,9 @@ export class NotebookHelper { } const page = this.page; - const tab = await this.activity.getTab(); + const tab = this.activity.getTabLocator(); - if (!tab) { + if (!(await tab.count())) { return false; } @@ -418,27 +484,24 @@ export class NotebookHelper { } } - const closeIcon = await tab.$('.lm-TabBar-tabCloseIcon'); - if (!closeIcon) { + const closeIcon = tab.locator('.lm-TabBar-tabCloseIcon'); + if (!(await closeIcon.count())) { return false; } await closeIcon.click(); // close save prompt - const dialogSelector = '.jp-Dialog .jp-Dialog-content'; - const dialog = await page.$(dialogSelector); - if (dialog) { - const dlgBtnSelector = revertChanges - ? 'button.jp-mod-accept.jp-mod-warn' // discard - : 'button.jp-mod-accept:not(.jp-mod-warn)'; // save - const dlgBtn = await dialog.$(dlgBtnSelector); - - if (dlgBtn) { - await dlgBtn.click(); - } + const dialog = page.locator('.jp-Dialog .jp-Dialog-content'); + const dlgBtnSelector = revertChanges + ? 'button.jp-mod-accept.jp-mod-warn' // discard + : 'button.jp-mod-accept:not(.jp-mod-warn)'; // save + const dlgBtn = dialog.locator(dlgBtnSelector); + + if (await dlgBtn.count()) { + await dlgBtn.click(); } - await page.waitForSelector(dialogSelector, { state: 'hidden' }); + await dialog.waitFor({ state: 'hidden' }); return true; } @@ -449,14 +512,11 @@ export class NotebookHelper { * @returns Number of cells */ getCellCount = async (): Promise => { - const notebook = await this.getNotebookInPanel(); + const notebook = await this.getNotebookInPanelLocator(); if (!notebook) { return -1; } - const scroller = (await notebook.$( - '.jp-WindowedPanel-outer' - )) as ElementHandle; - + const scroller = notebook.locator('.jp-WindowedPanel-outer'); const scrollTop = await scroller.evaluate(node => node.scrollTop); // Scroll to bottom @@ -474,11 +534,10 @@ export class NotebookHelper { node => node.scrollHeight - node.clientHeight ); } while (scrollHeight > previousScrollHeight); - - const lastCell = await notebook.$$('div.jp-Cell >> nth=-1'); + const lastCell = notebook.locator('div.jp-Cell').last(); const count = parseInt( - (await lastCell[0].getAttribute('data-windowed-list-index')) ?? '0', + (await lastCell.getAttribute('data-windowed-list-index')) ?? '0', 10 ) + 1; @@ -495,100 +554,79 @@ export class NotebookHelper { * * @param cellIndex Cell index * @returns Handle to the cell + * + * @deprecated You should use locator instead {@link getCellLocator} */ async getCell(cellIndex: number): Promise | null> { - const notebook = await this.getNotebookInPanel(); + return (await this.getCellLocator(cellIndex))?.elementHandle() ?? null; + } + + /** + * Get a cell locator + * + * @param cellIndex Cell index + * @param name Notebook name + * @returns Handle to the cell + */ + async getCellLocator(cellIndex: number): Promise { + const notebook = await this.getNotebookInPanelLocator(); if (!notebook) { return null; } - const scroller = (await notebook.$( - '.jp-WindowedPanel-outer' - )) as ElementHandle; - const viewport = (await notebook.$( - '.jp-WindowedPanel-viewport' - )) as ElementHandle; - - const allCells = await notebook.$$('div.jp-Cell'); - const filters = await Promise.all(allCells.map(c => c.isVisible())); - const cells = allCells.filter((c, i) => filters[i]); - - const firstCell = cells[0]; - const lastCell = cells[cells.length - 1]; + const cells = notebook.locator('.jp-Cell:visible'); let firstIndex = parseInt( - (await firstCell.getAttribute('data-windowed-list-index')) ?? '0', + (await cells.first().getAttribute('data-windowed-list-index')) ?? '', 10 ); let lastIndex = parseInt( - (await lastCell.getAttribute('data-windowed-list-index')) ?? '0', + (await cells.last().getAttribute('data-windowed-list-index')) ?? '', 10 ); - if (cellIndex < firstIndex) { // Scroll up - let scrollTop = - (await firstCell.boundingBox())?.y ?? - (await scroller.evaluate(node => node.scrollTop - node.clientHeight)); - + const viewport = await notebook + .locator('.jp-WindowedPanel-outer') + .first() + .boundingBox(); + await this.page.mouse.move(viewport!.x, viewport!.y); do { - await scroller.evaluate((node, scrollTarget) => { - node.scrollTo({ top: scrollTarget }); - }, scrollTop); + await this.page.mouse.wheel(0, -100); await this.page.waitForTimeout(50); - - const cells = await notebook.$$('div.jp-Cell'); - const isVisible = await Promise.all(cells.map(c => c.isVisible())); - const firstCell = isVisible.findIndex(visibility => visibility); - firstIndex = parseInt( - (await cells[firstCell].getAttribute('data-windowed-list-index')) ?? - '0', + (await cells.first().getAttribute('data-windowed-list-index')) ?? '', 10 ); - scrollTop = - (await cells[firstCell].boundingBox())?.y ?? - (await scroller.evaluate(node => node.scrollTop - node.clientHeight)); - } while (scrollTop > 0 && firstIndex > cellIndex); + } while (cellIndex < firstIndex); + lastIndex = parseInt( + (await cells.last().getAttribute('data-windowed-list-index')) ?? '', + 10 + ); } else if (cellIndex > lastIndex) { - const clientHeight = await scroller.evaluate(node => node.clientHeight); // Scroll down - const viewportBox = await viewport.boundingBox(); - let scrollHeight = viewportBox!.y + viewportBox!.height; - let previousScrollHeight = 0; - + const viewport = await notebook + .locator('.jp-WindowedPanel-outer') + .first() + .boundingBox(); + await this.page.mouse.move(viewport!.x, viewport!.y); do { - previousScrollHeight = scrollHeight; - await scroller.evaluate((node, scrollTarget) => { - node.scrollTo({ top: scrollTarget }); - }, scrollHeight); + await this.page.mouse.wheel(0, 100); await this.page.waitForTimeout(50); - - const cells = await notebook.$$('div.jp-Cell'); - const isVisible = await Promise.all(cells.map(c => c.isVisible())); - const lastCell = isVisible.lastIndexOf(true); - lastIndex = parseInt( - (await cells[lastCell].getAttribute('data-windowed-list-index')) ?? - '0', + (await cells.last().getAttribute('data-windowed-list-index')) ?? '', 10 ); - - const viewportBox = await viewport.boundingBox(); - scrollHeight = viewportBox!.y + viewportBox!.height; - // Avoid jitter - scrollHeight = Math.max( - previousScrollHeight + clientHeight, - scrollHeight - ); - } while (scrollHeight > previousScrollHeight && lastIndex < cellIndex); + } while (lastIndex < cellIndex); + firstIndex = parseInt( + (await cells.first().getAttribute('data-windowed-list-index')) ?? '', + 10 + ); } if (firstIndex <= cellIndex && cellIndex <= lastIndex) { - return ( - await notebook.$$( - `div.jp-Cell[data-windowed-list-index="${cellIndex}"]` - ) - )[0]; + return notebook.locator( + `.jp-Cell[data-windowed-list-index="${cellIndex}"]` + ); } else { return null; } @@ -599,25 +637,38 @@ export class NotebookHelper { * * @param cellIndex Cell index * @returns Handle to the cell input + * + * @deprecated You should use locator instead {@link getCellInputLocator} */ async getCellInput( cellIndex: number ): Promise | null> { - const cell = await this.getCell(cellIndex); + return (await this.getCellInputLocator(cellIndex))?.elementHandle() ?? null; + } + + /** + * Get the locator to the input of a cell + * + * @param cellIndex Cell index + * @returns Locator to the cell input + */ + async getCellInputLocator(cellIndex: number): Promise { + const cell = await this.getCellLocator(cellIndex); if (!cell) { return null; } - const cellEditor = await cell.$('.jp-InputArea-editor'); - if (!cellEditor) { + const cellEditor = cell.locator('.jp-InputArea-editor'); + if (!(await cellEditor.count())) { return null; } - const isRenderedMarkdown = await cellEditor.evaluate(editor => - editor.classList.contains('lm-mod-hidden') - ); + const isRenderedMarkdown = ( + await Utils.getLocatorClassList(cellEditor) + ).includes('lm-mod-hidden'); + if (isRenderedMarkdown) { - return await cell.$('.jp-MarkdownOutput'); + return cell.locator('.jp-MarkdownOutput'); } return cellEditor; @@ -656,16 +707,33 @@ export class NotebookHelper { * * @param cellIndex Cell index * @returns Handle to the cell input expander + * + * @deprecated You should use locator instead {@link getCellInputExpanderLocator} */ async getCellInputExpander( cellIndex: number ): Promise | null> { - const cell = await this.getCell(cellIndex); + return ( + (await this.getCellInputExpanderLocator(cellIndex))?.elementHandle() ?? + null + ); + } + + /** + * Get the locator to the input expander of a cell + * + * @param cellIndex Cell index + * @returns Locator to the cell input expander + */ + async getCellInputExpanderLocator( + cellIndex: number + ): Promise { + const cell = await this.getCellLocator(cellIndex); if (!cell) { return null; } - return await cell.$('.jp-InputCollapser'); + return cell.locator('.jp-InputCollapser'); } /** @@ -675,12 +743,12 @@ export class NotebookHelper { * @returns Cell input expanded status */ async isCellInputExpanded(cellIndex: number): Promise { - const cell = await this.getCell(cellIndex); + const cell = await this.getCellLocator(cellIndex); if (!cell) { return null; } - return (await cell.$('.jp-InputPlaceholder')) === null; + return (await cell.locator('.jp-InputPlaceholder').count()) > 0; } /** @@ -696,7 +764,7 @@ export class NotebookHelper { return false; } - const inputExpander = await this.getCellInputExpander(cellIndex); + const inputExpander = await this.getCellInputExpanderLocator(cellIndex); if (!inputExpander) { return false; } @@ -711,18 +779,35 @@ export class NotebookHelper { * * @param cellIndex Cell index * @returns Handle to the cell output expander + * + * @deprecated You should use locator instead {@link getCellOutputExpanderLocator} */ async getCellOutputExpander( cellIndex: number ): Promise | null> { - const cell = await this.getCell(cellIndex); + return ( + (await this.getCellInputExpanderLocator(cellIndex))?.elementHandle() ?? + null + ); + } + + /** + * Get the locator to a cell output expander + * + * @param cellIndex Cell index + * @returns Handle to the cell output expander + */ + async getCellOutputExpanderLocator( + cellIndex: number + ): Promise { + const cell = await this.getCellLocator(cellIndex); if (!cell) { return null; } const cellType = await this.getCellType(cellIndex); - return cellType === 'code' ? await cell.$('.jp-OutputCollapser') : null; + return cellType === 'code' ? cell.locator('.jp-OutputCollapser') : null; } /** @@ -732,12 +817,12 @@ export class NotebookHelper { * @returns Cell output expanded status */ async isCellOutputExpanded(cellIndex: number): Promise { - const cell = await this.getCell(cellIndex); + const cell = await this.getCellLocator(cellIndex); if (!cell) { return null; } - return (await cell.$('.jp-OutputPlaceholder')) === null; + return (await cell.locator('.jp-OutputPlaceholder').count()) > 0; } /** @@ -753,7 +838,7 @@ export class NotebookHelper { return false; } - const outputExpander = await this.getCellOutputExpander(cellIndex); + const outputExpander = await this.getCellOutputExpanderLocator(cellIndex); if (!outputExpander) { return false; } @@ -768,22 +853,36 @@ export class NotebookHelper { * * @param cellIndex Cell index * @returns Output cell handle + * + * @deprecated You should use locator instead {@link getCellOutputLocator} */ async getCellOutput( cellIndex: number ): Promise | null> { - const cell = await this.getCell(cellIndex); + return ( + (await this.getCellOutputLocator(cellIndex))?.elementHandle() ?? null + ); + } + + /** + * Get the locator on a given output cell + * + * @param cellIndex Cell index + * @returns Locator cell handle + */ + async getCellOutputLocator(cellIndex: number): Promise { + const cell = await this.getCellLocator(cellIndex); if (!cell) { return null; } - const codeCellOutput = await cell.$('.jp-Cell-outputArea'); - if (codeCellOutput) { + const codeCellOutput = cell.locator('.jp-Cell-outputArea'); + if (await codeCellOutput.count()) { return codeCellOutput; } - const mdCellOutput = await cell.$('.jp-MarkdownOutput'); - if (mdCellOutput) { + const mdCellOutput = cell.locator('.jp-MarkdownOutput'); + if (await mdCellOutput.count()) { return mdCellOutput; } @@ -797,20 +896,17 @@ export class NotebookHelper { * @returns List of text outputs */ async getCellTextOutput(cellIndex: number): Promise { - const cellOutput = await this.getCellOutput(cellIndex); + const cellOutput = await this.getCellOutputLocator(cellIndex); if (!cellOutput) { return null; } - const textOutputs = await cellOutput.$$('.jp-OutputArea-output'); - if (textOutputs.length > 0) { + const textOutputs = cellOutput.locator('.jp-OutputArea-output'); + const textOutputsNum = await textOutputs.count(); + if (textOutputsNum > 0) { const outputs: string[] = []; - for (const textOutput of textOutputs) { - outputs.push( - (await ( - await textOutput.getProperty('textContent') - ).jsonValue()) as string - ); + for (let i = 0; i < textOutputsNum; i++) { + outputs.push((await textOutputs.nth(i).textContent()) ?? ''); } return outputs; @@ -829,59 +925,77 @@ export class NotebookHelper { * @returns Editing mode */ async isCellInEditingMode(cellIndex: number): Promise { - const cell = await this.getCell(cellIndex); + const cell = await this.getCellLocator(cellIndex); if (!cell) { return false; } - const cellEditor = await cell.$('.jp-InputArea-editor'); - if (cellEditor) { - return await cellEditor.evaluate(editor => - editor.classList.contains('jp-mod-focused') + const cellEditor = cell.locator('.jp-InputArea-editor'); + if (await cellEditor.count()) { + return (await Utils.getLocatorClassList(cellEditor)).includes( + 'jp-mod-focused' ); } return false; } - /** - * Enter the editing mode on a given cell - * - * @param cellIndex Cell index - * @returns Action success status - */ - async enterCellEditingMode(cellIndex: number): Promise { - const cell = await this.getCell(cellIndex); - if (!cell) { + private async _setCellMode( + cell: Locator, + mode: 'Edit' | 'Command' + ): Promise { + const isCellActive = (await cell.getAttribute('class')) + ?.split(/\s/) + .includes('jp-mod-active'); + const modeLocator = this.page.getByText(`Mode: ${mode}`, { exact: true }); + if ((await modeLocator.count()) == 1 && isCellActive) { + return false; + } + const cellInput = cell.locator('.jp-Cell-inputArea'); + if (!(await cellInput.count())) { return false; } - const cellInput = await cell.$('.jp-Cell-inputArea'); - if (cellInput) { - let isMarkdown = false; - const cellType = await this.getCellType(cellIndex); - if (cellType === 'markdown') { - const renderedMarkdown = await cell.$('.jp-MarkdownOutput'); - if (renderedMarkdown) { - isMarkdown = true; - } + let isMarkdown = false; + const cellType = await this.getCellLocatorType(cell); + if (cellType === 'markdown') { + const renderedMarkdown = cell.locator('.jp-MarkdownOutput'); + if (await renderedMarkdown.count()) { + isMarkdown = true; } + } + if (mode == 'Edit') { if (isMarkdown) { await cellInput.dblclick(); } - - const cellEditor = await cellInput.$('.jp-InputArea-editor'); - if (!cellEditor) { + await cell.locator('.jp-Cell-inputArea').dblclick(); + const cellEditor = cellInput.locator('.jp-InputArea-editor'); + if (!cellEditor.count()) { return false; } await cellEditor.click(); + } else { + await cell.locator('.jp-Cell-inputArea').press('Escape'); + } - return true; + return true; + } + + /** + * Enter the editing mode on a given cell + * + * @param cellIndex Cell index + * @returns Action success status + */ + async enterCellEditingMode(cellIndex: number): Promise { + const cell = await this.getCellLocator(cellIndex); + if (!cell) { + return false; } - return false; + return this._setCellMode(cell, 'Edit'); } /** @@ -891,12 +1005,14 @@ export class NotebookHelper { * @returns Action success status */ async leaveCellEditingMode(cellIndex: number): Promise { - if (await this.isCellInEditingMode(cellIndex)) { - await this.page.keyboard.press('Escape'); - return true; + const cell = await this.getCellLocator(cellIndex); + if (!cell) { + return false; } - return false; + await this._setCellMode(cell, 'Command'); + + return true; } /** @@ -917,15 +1033,8 @@ export class NotebookHelper { return false; } - const cell = await this.getCell(cellIndex); - const gutters = await cell!.$$( - '.cm-gutters > .cm-gutter.cm-breakpoint-gutter > .cm-gutterElement' - ); - if (gutters.length < lineNumber) { - return false; - } - await gutters[lineNumber].click(); - return true; + const cell = await this.getCellLocator(cellIndex); + return this._clickOnGutter(cell!, lineNumber); } /** @@ -934,11 +1043,11 @@ export class NotebookHelper { * @param cellIndex */ async isCellGutterPresent(cellIndex: number): Promise { - const cell = await this.getCell(cellIndex); + const cell = await this.getCellLocator(cellIndex); if (!cell) { return false; } - return (await cell.$('.cm-gutters')) !== null; + return await cell.locator('.cm-gutters')?.isVisible(); } /** @@ -946,13 +1055,8 @@ export class NotebookHelper { * * @param cellIndex */ - async waitForCellGutter(cellIndex: number): Promise { - const cell = await this.getCell(cellIndex); - if (cell) { - await this.page.waitForSelector('.cm-gutters', { - state: 'attached' - }); - } + waitForCellGutter(cellIndex: number): Promise { + return Utils.waitForCondition(() => this.isCellGutterPresent(cellIndex)); } /** @@ -969,19 +1073,20 @@ export class NotebookHelper { return false; } - const panel = await this.activity.getPanel(); - await panel!.waitForSelector( - '.cm-gutters > .cm-gutter.cm-breakpoint-gutter > .cm-gutterElement', - { state: 'attached' } - ); - const gutters = await panel!.$$( - '.cm-gutters > .cm-gutter.cm-breakpoint-gutter > .cm-gutterElement' - ); - if (gutters.length < lineNumber) { + const panel = await this.activity.getPanelLocator(); + if (!panel) { return false; } - await gutters[lineNumber].click(); - return true; + await Utils.waitForCondition( + async () => + (await panel + .locator( + '.cm-gutters > .cm-gutter.cm-breakpoint-gutter > .cm-gutterElement' + ) + .count()) > 0 + ); + + return this._clickOnGutter(panel!, lineNumber); } /** @@ -989,11 +1094,11 @@ export class NotebookHelper { * */ async isCodeGutterPresent(): Promise { - const panel = await this.activity.getPanel(); - if (!panel) { + const panel = await this.activity.getPanelLocator(); + if (!(panel && (await panel.count()))) { return false; } - return (await panel.$('.cm-gutters')) !== null; + return (await panel.locator('.cm-gutters')?.isVisible()) !== null; } /** @@ -1001,13 +1106,35 @@ export class NotebookHelper { * * @param cellIndex */ - async waitForCodeGutter(): Promise { - const panel = await this.activity.getPanel(); - if (panel) { - await this.page.waitForSelector('.cm-gutters', { - state: 'attached' - }); + waitForCodeGutter(): Promise { + return Utils.waitForCondition(() => this.isCodeGutterPresent()); + } + + protected async _clickOnGutter( + panel: Locator, + line: number + ): Promise { + const gutters = panel.locator( + '.cm-gutters > .cm-gutter.cm-breakpoint-gutter > .cm-gutterElement' + ); + if ((await gutters.count()) < line) { + return false; } + + // Sometime the breakpoint is not activated when clicking on the gutter, it can be + // useful to try it several times. + const gutter = gutters.nth(line); + for (let i = 0; i < 3; i++) { + await gutter.click({ position: { x: 5, y: 5 } }); + await Utils.waitForCondition( + async () => ((await gutter.textContent())?.length ?? 0) > 0, + 500 + ); + if ((await gutter.textContent())?.length) { + break; + } + } + return true; } /** @@ -1018,7 +1145,7 @@ export class NotebookHelper { * @returns Action success status */ async selectCells(startIndex: number, endIndex?: number): Promise { - const startCell = await this.getCell(startIndex); + const startCell = await this.getCellLocator(startIndex); if (!startCell) { return false; } @@ -1028,7 +1155,7 @@ export class NotebookHelper { await startCell.click({ position: clickPosition }); if (endIndex !== undefined) { - const endCell = await this.getCell(endIndex); + const endCell = await this.getCellLocator(endIndex); if (!endCell) { return false; } @@ -1108,7 +1235,10 @@ export class NotebookHelper { return false; } - await this.setCellType(cellIndex, cellType); + const r = await this.setCellType(cellIndex, cellType); + if (!r) { + return false; + } if ( !(await this.isCellSelected(cellIndex)) && @@ -1117,14 +1247,18 @@ export class NotebookHelper { return false; } - await this.enterCellEditingMode(cellIndex); + const cell = await this.getCellLocator(cellIndex); - const keyboard = this.page.keyboard; - await keyboard.press('Control+A'); - // give CodeMirror time to style properly - await keyboard.type(source, { delay: cellType === 'code' ? 100 : 0 }); + if (!cell) { + return false; + } - await this.leaveCellEditingMode(cellIndex); + await this._setCellMode(cell, 'Edit'); + await cell.getByRole('textbox').press('Control+A'); + await cell + .getByRole('textbox') + .type(source, { delay: cellType === 'code' ? 100 : 0 }); + await this._setCellMode(cell, 'Command'); // give CodeMirror time to style properly if (cellType === 'code') { @@ -1145,13 +1279,13 @@ export class NotebookHelper { cellIndex: number, cellType: nbformat.CellType ): Promise { - const nbPanel = await this.activity.getPanel(); - if (!nbPanel) { + const nbPanel = await this.activity.getPanelLocator(); + if (!(nbPanel && (await nbPanel.count()))) { return false; } if ((await this.getCellType(cellIndex)) === cellType) { - return false; + return true; } if (!(await this.selectCells(cellIndex))) { @@ -1159,14 +1293,14 @@ export class NotebookHelper { } await this.clickToolbarItem('cellType'); - const selectInput = await nbPanel.$('.jp-Notebook-toolbarCellTypeDropdown'); - if (!selectInput) { + const selectInput = nbPanel.locator('.jp-Notebook-toolbarCellTypeDropdown'); + if (!(await selectInput.count())) { return false; } // Legay select - const select = await selectInput.$('select'); - if (select) { + const select = selectInput.locator('select'); + if (await select.count()) { await select.selectOption(cellType); } else { await selectInput.evaluate((el, cellType) => { @@ -1175,14 +1309,14 @@ export class NotebookHelper { } // Wait for the new cell to be rendered - let cell: ElementHandle | null; + let cell: Locator | null; let counter = 1; do { await this.page.waitForTimeout(50); - cell = await this.getCell(cellIndex); - } while (cell === null && counter++ < MAX_RETRIES); + cell = await this.getCellLocator(cellIndex); + } while (!cell?.isVisible() && counter++ < MAX_RETRIES); - return true; + return counter < MAX_RETRIES; } /** @@ -1192,18 +1326,23 @@ export class NotebookHelper { * @returns Cell type */ async getCellType(cellIndex: number): Promise { - const notebook = await this.getNotebookInPanel(); - if (!notebook) { - return null; - } - - const cell = await this.getCell(cellIndex); + const cell = await this.getCellLocator(cellIndex); if (!cell) { return null; } - const classList = await Utils.getElementClassList(cell); + return this.getCellLocatorType(cell); + } + + /** + * Get the cell type of a cell from its locator + * + * @param cell Cell locator + * @returns Cell type + */ + async getCellLocatorType(cell: Locator): Promise { + const classList = await Utils.getLocatorClassList(cell); if (classList.indexOf('jp-CodeCell') !== -1) { return 'code'; @@ -1256,22 +1395,20 @@ export class NotebookHelper { await this.menu.clickMenuItem('File>New>Notebook'); const page = this.page; - await page.waitForSelector('.jp-Dialog'); + await page.locator('.jp-Dialog').waitFor(); await page.click('.jp-Dialog .jp-mod-accept'); - const activeTab = await this.activity.getTab(); - if (!activeTab) { + const activeTab = this.activity.getTabLocator(); + if (!(await activeTab.count())) { return null; } - const label = await activeTab.$('div.lm-TabBar-tabLabel'); - if (!label) { + const label = activeTab.locator('div.lm-TabBar-tabLabel'); + if (!(await label.count())) { return null; } - const assignedName = (await ( - await label.getProperty('textContent') - ).jsonValue()) as string; + const assignedName = await label.textContent(); if (!name) { return assignedName; @@ -1282,9 +1419,9 @@ export class NotebookHelper { `${currentDir}/${assignedName}`, `${currentDir}/${name}` ); - const renamedTab = await this.activity.getTab(name); + const renamedTab = this.activity.getTabLocator(name); - return renamedTab ? name : null; + return (await renamedTab.count()) ? name : null; } private _runCallbacksExposed = 0; diff --git a/galata/src/helpers/sidebar.ts b/galata/src/helpers/sidebar.ts index ca6a58e4111e..406935710ac9 100644 --- a/galata/src/helpers/sidebar.ts +++ b/galata/src/helpers/sidebar.ts @@ -2,7 +2,7 @@ // Distributed under the terms of the Modified BSD License. import type { ISettingRegistry } from '@jupyterlab/settingregistry'; -import { ElementHandle, Page } from '@playwright/test'; +import { ElementHandle, Locator, Page } from '@playwright/test'; import type { IPluginNameToInterfaceMap } from '../extension'; import { galata } from '../galata'; import * as Utils from '../utils'; @@ -24,7 +24,7 @@ export class SidebarHelper { * @returns Opened status */ isOpen = async (side: galata.SidebarPosition = 'left'): Promise => { - return (await this.getContentPanel(side)) !== null; + return (await this.getContentPanelLocator(side).count()) > 0; }; /** @@ -34,10 +34,10 @@ export class SidebarHelper { * @returns Tab opened status */ async isTabOpen(id: galata.SidebarTabId): Promise { - const tabButton = await this.page.$( + const tabButton = this.page.locator( `${this.buildTabSelector(id)}.lm-mod-current` ); - return tabButton !== null; + return (await tabButton.count()) > 0; } /** @@ -114,19 +114,19 @@ export class SidebarHelper { * @param id Tab id */ async toggleTabPosition(id: galata.SidebarTabId): Promise { - const tab = await this.getTab(id); + const tab = this.getTabLocator(id); - if (!tab) { + if (!(await tab.count())) { return; } await tab.click({ button: 'right' }); - const switchMenuItem = await this.page.waitForSelector( - '.lm-Menu-content .lm-Menu-item[data-command="sidebar:switch"]', - { state: 'visible' } + const switchMenuItem = this.page.locator( + '.lm-Menu-content .lm-Menu-item[data-command="sidebar:switch"]' ); - if (switchMenuItem) { + await switchMenuItem.waitFor({ state: 'visible' }); + if (await switchMenuItem.count()) { await switchMenuItem.click(); } } @@ -175,11 +175,23 @@ export class SidebarHelper { * * @param id Tab id * @returns Tab handle + * + * @deprecated You should use locator instead {@link getTabLocator} */ async getTab( id: galata.SidebarTabId ): Promise | null> { - return await this.page.$(this.buildTabSelector(id)); + return await this.getTabLocator(id).elementHandle(); + } + + /** + * Get the locator on a given tab + * + * @param id Tab id + * @returns Tab locator + */ + getTabLocator(id: galata.SidebarTabId): Locator { + return this.page.locator(this.buildTabSelector(id)); } /** @@ -193,8 +205,8 @@ export class SidebarHelper { return; } - const tabButton = await this.page.$(this.buildTabSelector(id)); - if (tabButton === null) { + const tabButton = this.getTabLocator(id); + if (!((await tabButton.count()) === 1)) { throw new Error(`Unable to find the tab ${id} button`); } await tabButton.click(); @@ -206,11 +218,23 @@ export class SidebarHelper { * * @param side Position * @returns Panel handle + * + * @deprecated You should use locator instead {@link getContentPanelLocator} */ async getContentPanel( side: galata.SidebarPosition = 'left' ): Promise | null> { - return await this.page.$( + return await this.getContentPanelLocator(side).elementHandle(); + } + + /** + * Get the locator on a sidebar content panel + * + * @param side Position + * @returns Panel handle + */ + getContentPanelLocator(side: galata.SidebarPosition = 'left'): Locator { + return this.page.locator( `#jp-${side}-stack .lm-StackedPanel-child:not(.lm-mod-hidden)` ); } @@ -220,11 +244,23 @@ export class SidebarHelper { * * @param side Position * @returns Tab bar handle + * + * @deprecated You should use locator instead {@link getTabBarLocator} */ async getTabBar( side: galata.SidebarPosition = 'left' ): Promise | null> { - return await this.page.$(`.jp-SideBar.jp-mod-${side}`); + return await this.getTabBarLocator(side).elementHandle(); + } + + /** + * Get the locator of the tab bar of the sidebar + * + * @param side Position + * @returns Tab bar locator + */ + getTabBarLocator(side: galata.SidebarPosition = 'left'): Locator { + return this.page.locator(`.jp-SideBar.jp-mod-${side}`); } /** @@ -317,15 +353,14 @@ export class SidebarHelper { } protected async _waitForTabActivate( - tab: ElementHandle, + tab: Locator, activate = true ): Promise { - await this.page.waitForFunction( - ({ tab, activate }) => { - const current = tab.classList.contains('lm-mod-current'); - return activate ? current : !current; - }, - { tab, activate } - ); + await Utils.waitForCondition(async () => { + const current = (await Utils.getLocatorClassList(tab)).includes( + 'lm-mod-current' + ); + return activate ? current : !current; + }); } } diff --git a/galata/src/helpers/statusbar.ts b/galata/src/helpers/statusbar.ts index 560bf88b24b4..3203c62762b2 100644 --- a/galata/src/helpers/statusbar.ts +++ b/galata/src/helpers/statusbar.ts @@ -37,9 +37,7 @@ export class StatusBarHelper { } await this.menu.clickMenuItem('View>Show Status Bar'); - await this.page.waitForSelector('#jp-main-statusbar', { - state: 'visible' - }); + await this.page.locator('#jp-main-statusbar').waitFor({ state: 'visible' }); } /** @@ -52,8 +50,6 @@ export class StatusBarHelper { } await this.menu.clickMenuItem('View>Show Status Bar'); - await this.page.waitForSelector('#jp-main-statusbar', { - state: 'hidden' - }); + await this.page.locator('#jp-main-statusbar').waitFor({ state: 'hidden' }); } } diff --git a/galata/src/helpers/theme.ts b/galata/src/helpers/theme.ts index 9d349b61e811..3e75d33ed041 100644 --- a/galata/src/helpers/theme.ts +++ b/galata/src/helpers/theme.ts @@ -45,6 +45,6 @@ export class ThemeHelper { await window.galata.setTheme(themeName); }, themeName); - await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.locator('#jupyterlab-splash').waitFor({ state: 'detached' }); } } diff --git a/galata/src/jupyterlabpage.ts b/galata/src/jupyterlabpage.ts index 623abe64f535..c6757f4f1a04 100644 --- a/galata/src/jupyterlabpage.ts +++ b/galata/src/jupyterlabpage.ts @@ -227,7 +227,9 @@ export interface IJupyterLabPage { * * @param element Element or selector to watch */ - waitForTransition(element: ElementHandle | string): Promise; + waitForTransition( + element: ElementHandle | Locator | string + ): Promise; /** * Factory for active activity tab xpath @@ -599,7 +601,7 @@ export class JupyterLabPage implements IJupyterLabPage { * @returns Whether this operation succeeds or not */ async setSimpleMode(simple: boolean): Promise { - const toggle = await this.page.$( + const toggle = this.page.locator( '#jp-single-document-mode button.jp-switch' ); if (toggle) { @@ -640,7 +642,7 @@ export class JupyterLabPage implements IJupyterLabPage { * @param element Element or selector to watch */ async waitForTransition( - element: ElementHandle | string + element: ElementHandle | Locator | string ): Promise { return Utils.waitForTransition(this.page, element); } diff --git a/galata/src/utils.ts b/galata/src/utils.ts index db163f81c25d..249c1e46e7af 100644 --- a/galata/src/utils.ts +++ b/galata/src/utils.ts @@ -2,7 +2,7 @@ // Distributed under the terms of the Modified BSD License. import { URLExt } from '@jupyterlab/coreutils'; -import { ElementHandle, Page } from '@playwright/test'; +import { ElementHandle, Locator, Page } from '@playwright/test'; import * as fs from 'fs-extra'; import * as path from 'path'; @@ -76,6 +76,8 @@ export async function getBaseUrl(page: Page): Promise { * * @param element Element handle * @returns Classes list + * + * @deprecated You should use locator instead {@link getLocatorClassList} */ export async function getElementClassList( element: ElementHandle @@ -95,6 +97,21 @@ export async function getElementClassList( return []; } +/** + * Get the classes of an locator + * + * @param locator Element locator + * @returns Classes list + */ +export async function getLocatorClassList(locator: Locator): Promise { + const className = await locator.getAttribute('class'); + if (className) { + return className.split(/\s/); + } + + return []; +} + /** * List the content of a local directory * @@ -195,23 +212,37 @@ export async function waitForCondition( */ export async function waitForTransition( page: Page, - element: ElementHandle | string + element: ElementHandle | Locator | string ): Promise { - const el = typeof element === 'string' ? await page.$(element) : element; - - if (el) { - return page.evaluate(el => { + let el = typeof element === 'string' ? page.locator(element) : element; + try { + return (el as Locator).evaluate(elem => { return new Promise(resolve => { const onEndHandler = () => { - el.removeEventListener('transitionend', onEndHandler); + elem.removeEventListener('transitionend', onEndHandler); resolve(); }; - el.addEventListener('transitionend', onEndHandler); + elem.addEventListener('transitionend', onEndHandler); }); - }, el); + }); + } catch { + if (el) { + console.warn( + 'ElementHandle are deprecated, you should call "WaitForTransition()" \ + with a Locator instead' + ); + return page.evaluate(el => { + return new Promise(resolve => { + const onEndHandler = () => { + el.removeEventListener('transitionend', onEndHandler); + resolve(); + }; + el.addEventListener('transitionend', onEndHandler); + }); + }, el as ElementHandle); + } + return Promise.reject(); } - - return Promise.reject(); } // Selector builders diff --git a/galata/test/benchmark/notebook.spec.ts b/galata/test/benchmark/notebook.spec.ts index 3214e4e98950..aad8dadbbd23 100644 --- a/galata/test/benchmark/notebook.spec.ts +++ b/galata/test/benchmark/notebook.spec.ts @@ -112,20 +112,20 @@ test.describe('Benchmark', () => { const openTime = await perf.measure(async () => { // Open the notebook and wait for the spinner await Promise.all([ - page.waitForSelector('[role="main"] >> .jp-SpinnerContent'), + page.locator('[role="main"] >> .jp-SpinnerContent').waitFor(), page.dblclick(`#filebrowser >> text=${file}`) ]); // Wait for spinner to be hidden - await page.waitForSelector('[role="main"] >> .jp-SpinnerContent', { - state: 'hidden' - }); + await page + .locator('[role="main"] >> .jp-SpinnerContent') + .waitFor({ state: 'hidden' }); }); // Check the notebook is correctly opened - let panel = await page.$('[role="main"] >> .jp-NotebookPanel'); + let panel = page.locator('[role="main"] >> .jp-NotebookPanel'); // Get only the document node to avoid noise from kernel and debugger in the toolbar - let document = await panel.$('.jp-Notebook'); + let document = panel.locator('.jp-Notebook'); // Wait for the cell toolbar to be visible in code cell. if (file === codeNotebook) { @@ -158,11 +158,13 @@ test.describe('Benchmark', () => { // Open text file const fromTime = await perf.measure(async () => { await page.dblclick(`#filebrowser >> text=${textFile}`); - await page.waitForSelector( - `div[role="main"] >> .lm-DockPanel-tabBar >> text=${path.basename( - textFile - )}` - ); + await page + .locator( + `div[role="main"] >> .lm-DockPanel-tabBar >> text=${path.basename( + textFile + )}` + ) + .waitFor(); }); let editorPanel = page.locator( @@ -186,9 +188,6 @@ test.describe('Benchmark', () => { }); // Check the notebook is correctly opened - panel = await page.$('[role="main"] >> .jp-NotebookPanel'); - // Get only the document node to avoid noise from kernel and debugger in the toolbar - document = await panel.$('.jp-Notebook'); expect(await document.screenshot()).toMatchSnapshot( `${file.replace('.', '-')}.png` ); diff --git a/galata/test/documentation/commands.test.ts b/galata/test/documentation/commands.test.ts index 327999b5fc17..55d820c5e390 100644 --- a/galata/test/documentation/commands.test.ts +++ b/galata/test/documentation/commands.test.ts @@ -1,8 +1,7 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { test } from '@jupyterlab/galata'; -import { expect } from '@playwright/test'; +import { expect, test } from '@jupyterlab/galata'; import * as fs from 'fs-extra'; test('All commands must have a default label', async ({ page }, testInfo) => { diff --git a/galata/test/documentation/commands.test.ts-snapshots/commandsList-documentation-linux.json b/galata/test/documentation/commands.test.ts-snapshots/commandsList-documentation-linux.json index 80a7cf4dc2ce..505aee20c919 100644 --- a/galata/test/documentation/commands.test.ts-snapshots/commandsList-documentation-linux.json +++ b/galata/test/documentation/commands.test.ts-snapshots/commandsList-documentation-linux.json @@ -31,13 +31,6 @@ "Ctrl Shift ," ] }, - { - "id": "application:toggle-side-tabbar", - "label": "Toggle Sidebar Element", - "shortcuts": [ - "Alt 1" - ] - }, { "id": "application:close", "label": "Close Tab", @@ -116,6 +109,14 @@ "id": "application:toggle-side-tabbar", "label": "Show Left Activity Bar", "caption": "", + "shortcuts": [ + "Alt 1" + ] + }, + { + "id": "application:toggle-sidebar-widget", + "label": "Toggle Sidebar Element", + "caption": "", "shortcuts": [] }, { diff --git a/galata/test/documentation/customization.test.ts b/galata/test/documentation/customization.test.ts index 5518625a1200..feddc2333d83 100644 --- a/galata/test/documentation/customization.test.ts +++ b/galata/test/documentation/customization.test.ts @@ -2,6 +2,7 @@ // Distributed under the terms of the Modified BSD License. import { expect, galata, test } from '@jupyterlab/galata'; +import { filterContent } from './utils'; test.use({ autoGoto: false, @@ -14,7 +15,7 @@ test.describe.configure({ mode: 'serial' }); test.describe('Default', () => { test('should use default layout', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await page.goto(); await page.addStyleTag({ content: `.jp-LabShell.jp-mod-devMode { @@ -26,7 +27,7 @@ test.describe('Default', () => { await page.menu.clickMenuItem('File>New>Terminal'); - await page.waitForSelector('.jp-Terminal'); + await page.locator('.jp-Terminal').waitFor(); expect(await page.screenshot()).toMatchSnapshot( 'default-terminal-position-single.png' @@ -48,7 +49,7 @@ test.describe('Default', () => { ); await page.dblclick('text=Lorenz.ipynb'); - await page.waitForSelector('div[role="main"] >> text=Lorenz.ipynb'); + await page.locator('div[role="main"] >> text=Lorenz.ipynb').waitFor(); // Wait for kernel to settle on idle await page @@ -66,7 +67,7 @@ test.describe('Default', () => { }); test('should use default menu bar', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await page.goto(); await page.addStyleTag({ content: `.jp-LabShell.jp-mod-devMode { @@ -78,7 +79,7 @@ test.describe('Default', () => { await page.click('text=Tabs'); - await page.waitForSelector('#jp-mainmenu-tabs'); + await page.locator('#jp-mainmenu-tabs').waitFor(); expect( await page.screenshot({ clip: { x: 0, y: 0, width: 800, height: 200 } }) @@ -86,7 +87,7 @@ test.describe('Default', () => { }); test('should use default context menu', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await page.goto(); await page.addStyleTag({ content: `.jp-LabShell.jp-mod-devMode { @@ -190,7 +191,7 @@ test.describe('Customized', () => { } }); test('should use customized layout', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await page.goto(); await page.addStyleTag({ content: `.jp-LabShell.jp-mod-devMode { @@ -202,7 +203,7 @@ test.describe('Customized', () => { await page.menu.clickMenuItem('File>New>Terminal'); - await page.waitForSelector('.jp-Terminal'); + await page.locator('.jp-Terminal').waitFor(); await page.sidebar.setWidth(271, 'right'); @@ -226,9 +227,9 @@ test.describe('Customized', () => { ); await page.dblclick('text=Lorenz.ipynb'); - await page.waitForSelector('div[role="main"] >> text=Lorenz.ipynb'); + await page.locator('div[role="main"] >> text=Lorenz.ipynb').waitFor(); - await page.waitForSelector('text=Python 3 (ipykernel) | Idle'); + await page.locator('text=Python 3 (ipykernel) | Idle').waitFor(); expect( await page @@ -238,7 +239,7 @@ test.describe('Customized', () => { }); test('should use customized menu bar', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await page.goto(); await page.addStyleTag({ content: `.jp-LabShell.jp-mod-devMode { @@ -250,7 +251,7 @@ test.describe('Customized', () => { await page.click('text=Tabs'); - await page.waitForSelector('#jp-mainmenu-tabs'); + await page.locator('#jp-mainmenu-tabs').waitFor(); expect( await page.screenshot({ clip: { x: 0, y: 0, width: 800, height: 200 } }) @@ -258,7 +259,7 @@ test.describe('Customized', () => { }); test('should use customized context menu', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await page.goto(); await page.addStyleTag({ content: `.jp-LabShell.jp-mod-devMode { diff --git a/galata/test/documentation/customization.test.ts-snapshots/customized-terminal-position-single-documentation-linux.png b/galata/test/documentation/customization.test.ts-snapshots/customized-terminal-position-single-documentation-linux.png index 71135fa78be7..9802e71cc01d 100644 Binary files a/galata/test/documentation/customization.test.ts-snapshots/customized-terminal-position-single-documentation-linux.png and b/galata/test/documentation/customization.test.ts-snapshots/customized-terminal-position-single-documentation-linux.png differ diff --git a/galata/test/documentation/customization.test.ts-snapshots/default-terminal-position-single-documentation-linux.png b/galata/test/documentation/customization.test.ts-snapshots/default-terminal-position-single-documentation-linux.png index a47b18c674c1..b2fa65aa0553 100644 Binary files a/galata/test/documentation/customization.test.ts-snapshots/default-terminal-position-single-documentation-linux.png and b/galata/test/documentation/customization.test.ts-snapshots/default-terminal-position-single-documentation-linux.png differ diff --git a/galata/test/documentation/debugger.test.ts b/galata/test/documentation/debugger.test.ts index e5c898640773..658778222867 100644 --- a/galata/test/documentation/debugger.test.ts +++ b/galata/test/documentation/debugger.test.ts @@ -79,8 +79,7 @@ test.describe('Debugger', () => { const runButton = await page .locator('.jp-Toolbar-item') .locator('[data-command="notebook:run-cell-and-select-next"]') - .getByRole('button') - .elementHandle(); + .getByRole('button'); // Inject mouse pointer await page.evaluate( @@ -197,9 +196,8 @@ test.describe('Debugger', () => { await createNotebook(page); - const sidebar = await page.waitForSelector( - '[data-id="jp-debugger-sidebar"]' - ); + const sidebar = page.locator('[data-id="jp-debugger-sidebar"]'); + await sidebar.waitFor(); await sidebar.click(); await page.sidebar.setWidth(251, 'right'); @@ -293,7 +291,7 @@ test.describe('Debugger', () => { // Wait to be stopped on the breakpoint await page.debugger.waitForCallStack(); - const breakpointsPanel = await page.debugger.getBreakPointsPanel(); + const breakpointsPanel = await page.debugger.getBreakPointsPanelLocator(); expect(await breakpointsPanel.innerText()).toMatch(/ipykernel.*\/\d+.py/); // Don't compare screenshot as the kernel id varies @@ -342,7 +340,7 @@ async function createNotebook(page: IJupyterLabPageFixture) { await page.sidebar.setWidth(); - await page.waitForSelector('text=Python 3 (ipykernel) | Idle'); + await page.locator('text=Python 3 (ipykernel) | Idle').waitFor(); } async function setBreakpoint(page: IJupyterLabPageFixture) { diff --git a/galata/test/documentation/export_notebook.test.ts b/galata/test/documentation/export_notebook.test.ts index 8787ebd5af95..6e9e770f8a4d 100644 --- a/galata/test/documentation/export_notebook.test.ts +++ b/galata/test/documentation/export_notebook.test.ts @@ -20,7 +20,7 @@ test.describe('Export Notebook', () => { ); await page.dblclick('text=Lorenz.ipynb'); - await page.waitForSelector('text=Python 3 (ipykernel) | Idle'); + await page.locator('text=Python 3 (ipykernel) | Idle').waitFor(); await page.click('text=File'); await page.click( @@ -29,7 +29,7 @@ test.describe('Export Notebook', () => { // Wait for Latex renderer // note: requires the a11y/assistive-mml MathJax extension - await page.waitForSelector('text=(σ, β, ρ)'); + await page.locator('text=(σ, β, ρ)').waitFor(); expect( await page.screenshot({ clip: { y: 5, x: 0, width: 700, height: 700 } }) diff --git a/galata/test/documentation/extension_manager.test.ts b/galata/test/documentation/extension_manager.test.ts index 52b4dd1da88f..a8c311df85e0 100644 --- a/galata/test/documentation/extension_manager.test.ts +++ b/galata/test/documentation/extension_manager.test.ts @@ -96,7 +96,7 @@ test.describe('Extension Manager', () => { }); // We can not wait for extension kept by the keyword as they are already in the DOM - await page.waitForSelector('text=No entries'); + await page.locator('text=No entries').waitFor(); expect( await page.screenshot({ clip: { y: 31, x: 0, width: 283, height: 600 } }) @@ -194,9 +194,11 @@ async function openExtensionSidebar(page: IJupyterLabPageFixture) { ), page.click('button:has-text("Yes")') ]); - await page.waitForSelector( - '.jp-extensionmanager-view >> .jp-AccordionPanel-title[aria-expanded="false"] >> text=Warning' - ); + await page + .locator( + '.jp-extensionmanager-view >> .jp-AccordionPanel-title[aria-expanded="false"] >> text=Warning' + ) + .waitFor(); await page.sidebar.setWidth(); } diff --git a/galata/test/documentation/general.test.ts b/galata/test/documentation/general.test.ts index 359aa4d7e96d..bf1710cabc4c 100644 --- a/galata/test/documentation/general.test.ts +++ b/galata/test/documentation/general.test.ts @@ -3,7 +3,12 @@ import { expect, galata, test } from '@jupyterlab/galata'; import path from 'path'; -import { generateArrow, positionMouse, positionMouseOver } from './utils'; +import { + filterContent, + generateArrow, + positionMouse, + positionMouseOver +} from './utils'; test.use({ autoGoto: false, @@ -13,7 +18,7 @@ test.use({ test.describe('General', () => { test('Welcome', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await page.goto(); await page.addStyleTag({ content: `.jp-LabShell.jp-mod-devMode { @@ -52,7 +57,7 @@ test.describe('General', () => { await page.notebook.run(); - const cell = await page.$( + const cell = page.locator( '[aria-label="Code Cell Content with Output"] >> text=interactive' ); await cell.click(); @@ -60,7 +65,9 @@ test.describe('General', () => { await page.click('text=Create New View for Cell Output'); // Emulate drag and drop - const viewerHandle = await page.$('div[role="main"] >> text=lorenz.py'); + const viewerHandle = page.locator( + '.lm-TabBar-tabLabel:text-is("lorenz.py")' + ); await viewerHandle.click(); const viewerBBox = await viewerHandle.boundingBox(); @@ -76,7 +83,7 @@ test.describe('General', () => { }); test('Left Sidebar', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await page.goto(); await page.addStyleTag({ content: `.jp-LabShell.jp-mod-devMode { @@ -88,7 +95,7 @@ test.describe('General', () => { await page.dblclick('[aria-label="File Browser Section"] >> text=data'); // Wait for the `data` folder to load to have something to blur - await page.waitForSelector('text=1024px'); + await page.locator('text=1024px').waitFor(); await page.evaluate(() => { (document.activeElement as HTMLElement).blur(); @@ -196,7 +203,7 @@ test.describe('General', () => { await expect( page.locator('.jp-ActiveCellTool .jp-InputPrompt') ).toHaveClass(/lm-mod-hidden/); - await (await page.notebook.getCellInput(1))?.click(); + await (await page.notebook.getCellInputLocator(1))?.click(); await page.keyboard.type(' content'); await expect( page.locator('.jp-ActiveCellTool .jp-ActiveCellTool-Content pre') @@ -256,9 +263,10 @@ test.describe('General', () => { await page.click('text=File'); await page.mouse.move(70, 40); - const fileMenuNewItem = await page.waitForSelector( - '.lm-Menu ul[role="menu"] >> text=New' - ); + const fileMenuNewItem = page + .locator('.lm-Menu ul[role="menu"]') + .getByText('New', { exact: true }); + await fileMenuNewItem.waitFor(); await fileMenuNewItem.click(); // Inject mouse @@ -298,14 +306,14 @@ test.describe('General', () => { await page.click('text=Lorenz.ipynb', { button: 'right' }); await page.hover('text=Copy Shareable Link'); - const itemHandle = await page.$('text=Copy Shareable Link'); + const itemLocator = page.locator('text=Copy Shareable Link'); // Inject mouse await page.evaluate( ([mouse]) => { document.body.insertAdjacentHTML('beforeend', mouse); }, - [await positionMouseOver(itemHandle, { top: 0.5, left: 0.55 })] + [await positionMouseOver(itemLocator, { top: 0.5, left: 0.55 })] ); expect( @@ -447,8 +455,7 @@ test.describe('General', () => { const trustPromise = page.evaluate(() => { return window.jupyterapp.commands.execute('notebook:trust'); }); - const dialogSelector = '.jp-Dialog-content'; - await page.waitForSelector(dialogSelector); + await page.locator('.jp-Dialog-content').waitFor(); // Accept option to trust the notebook await page.click('.jp-Dialog-button.jp-mod-accept'); // Wait until dialog is gone @@ -469,14 +476,15 @@ test.describe('General', () => { ); await page.dblclick('text=Data.ipynb'); - const heading = await page.waitForSelector( - 'h2[id="Open-a-CSV-file-using-Pandas"]' - ); - const anchor = await heading.$('text=¶'); + const heading = page.locator('h2[id="Open-a-CSV-file-using-Pandas"]'); + await heading.waitFor(); + const anchor = heading.locator('text=¶'); await heading.hover(); // Get parent cell which includes the heading - const cell = await heading.evaluateHandle(node => node.closest('.jp-Cell')); + const cell = await heading.evaluateHandle((node: HTMLElement) => + node.closest('.jp-Cell') + ); // Inject mouse await page.evaluate( @@ -492,7 +500,7 @@ test.describe('General', () => { ] ); - expect(await cell.screenshot()).toMatchSnapshot( + expect(await cell!.screenshot()).toMatchSnapshot( 'notebook_heading_anchor_link.png' ); }); @@ -525,7 +533,7 @@ test.describe('General', () => { await page.click('#jp-mainmenu-file-new >> text=Terminal'); // Wait for the xterm.js element to be added in the DOM - await page.waitForSelector('.jp-Terminal-body'); + await page.locator('.jp-Terminal-body').waitFor(); await page.keyboard.type('cd $JUPYTERLAB_GALATA_ROOT_DIR'); await page.keyboard.press('Enter'); @@ -578,7 +586,7 @@ test.describe('General', () => { await page.keyboard.press('Control+Shift+C'); expect( - await (await page.$('#modal-command-palette')).screenshot() + await page.locator('#modal-command-palette').screenshot() ).toMatchSnapshot('command_palette.png'); }); @@ -673,7 +681,7 @@ test.describe('General', () => { await page.notebook.run(); // Need to wait for altair to update the canvas - await page.waitForSelector('summary'); + await page.locator('summary').waitFor(); // The menu button '...' color of Altair is flaky increase threshold tolerance expect(await page.screenshot()).toMatchSnapshot('file_formats_altair.png', { diff --git a/galata/test/documentation/internationalization.test.ts b/galata/test/documentation/internationalization.test.ts index bc0f5466e986..acb70d72c4ea 100644 --- a/galata/test/documentation/internationalization.test.ts +++ b/galata/test/documentation/internationalization.test.ts @@ -2,6 +2,7 @@ // Distributed under the terms of the Modified BSD License. import { expect, galata, test } from '@jupyterlab/galata'; +import { filterContent } from './utils'; test.use({ autoGoto: false, @@ -11,6 +12,7 @@ test.use({ test.describe('Internationalization', () => { test('Menu', async ({ page }) => { + await galata.Mock.freezeContentLastModified(page, filterContent); await page.goto(); await page.sidebar.setWidth(); @@ -40,7 +42,7 @@ test.describe('Internationalization', () => { }); test('UI in Chinese', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await page.goto(); await page.dblclick('[aria-label="File Browser Section"] >> text=data'); @@ -51,13 +53,11 @@ test.describe('Internationalization', () => { await Promise.all([ page.waitForNavigation(), - page.waitForSelector('#jupyterlab-splash'), + page.locator('#jupyterlab-splash').waitFor(), page.click('button:has-text("Change and reload")') ]); - await page.waitForSelector('#jupyterlab-splash', { - state: 'detached' - }); + await page.locator('#jupyterlab-splash').waitFor({ state: 'detached' }); await page.addStyleTag({ content: `.jp-LabShell.jp-mod-devMode { @@ -66,7 +66,7 @@ test.describe('Internationalization', () => { }); // Wait for the launcher to be loaded - await page.waitForSelector('text=README.md'); + await page.locator('text=README.md').waitFor(); await page.sidebar.setWidth(); diff --git a/galata/test/documentation/internationalization.test.ts-snapshots/language-settings-documentation-linux.png b/galata/test/documentation/internationalization.test.ts-snapshots/language-settings-documentation-linux.png index 78035e785477..713e443e161e 100644 Binary files a/galata/test/documentation/internationalization.test.ts-snapshots/language-settings-documentation-linux.png and b/galata/test/documentation/internationalization.test.ts-snapshots/language-settings-documentation-linux.png differ diff --git a/galata/test/documentation/overview.test.ts b/galata/test/documentation/overview.test.ts index 45e08d239233..2dbb9e0c22dd 100644 --- a/galata/test/documentation/overview.test.ts +++ b/galata/test/documentation/overview.test.ts @@ -1,7 +1,13 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { expect, galata, test } from '@jupyterlab/galata'; +import { + expect, + galata, + IJupyterLabPageFixture, + test +} from '@jupyterlab/galata'; +import { filterContent } from './utils'; test.use({ autoGoto: false, @@ -14,7 +20,7 @@ test.describe.configure({ mode: 'serial' }); test.describe('Overview', () => { test('Overview', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await openOverview(page); expect(await page.screenshot()).toMatchSnapshot('interface_jupyterlab.png'); @@ -36,7 +42,7 @@ test.describe('Overview', () => { }); test('Tabs menu', async ({ page }) => { - await galata.Mock.freezeContentLastModified(page); + await galata.Mock.freezeContentLastModified(page, filterContent); await openOverview(page); await page.click('text="Tabs"'); @@ -47,7 +53,7 @@ test.describe('Overview', () => { }); }); -async function openOverview(page) { +async function openOverview(page: IJupyterLabPageFixture) { await page.goto(); await page.addStyleTag({ content: `.jp-LabShell.jp-mod-devMode { @@ -83,7 +89,7 @@ async function openOverview(page) { ); // Move notebook panel - const notebookHandle = await page.$('div[role="main"] >> text=Data.ipynb'); + const notebookHandle = page.locator('div[role="main"] >> text=Data.ipynb'); await notebookHandle.click(); const notebookBBox = await notebookHandle.boundingBox(); @@ -96,10 +102,10 @@ async function openOverview(page) { await page.mouse.up(); // Move md panel - const mdHandle = await page.$('div[role="main"] >> text=jupyterlab.md'); + const mdHandle = page.locator('div[role="main"] >> text=jupyterlab.md'); await mdHandle.click(); const mdBBox = await mdHandle.boundingBox(); - const panelHandle = await page.activity.getPanel(); + const panelHandle = await page.activity.getPanelLocator(); const panelBBox = await panelHandle.boundingBox(); await page.mouse.move( diff --git a/galata/test/documentation/utils.ts b/galata/test/documentation/utils.ts index 9de1de24bc22..233298ad1ab4 100644 --- a/galata/test/documentation/utils.ts +++ b/galata/test/documentation/utils.ts @@ -3,10 +3,24 @@ * Distributed under the terms of the Modified BSD License. */ -import { ElementHandle, Page } from '@playwright/test'; +import { ElementHandle, Locator, Page } from '@playwright/test'; import fs from 'fs'; import path from 'path'; +/** + * Filter directory content + * + * @param array Array of content models + * @returns Filtered array + */ +export function filterContent(array: any[]): any[] { + return array.filter( + item => + item['type'] !== 'directory' || + !(item['name'] as string).startsWith('test-') + ); +} + /** * Generate a SVG arrow to inject in a HTML document. * @@ -74,12 +88,12 @@ export interface IPositionInElement { /** * Generate a SVG mouse pointer to inject in a HTML document over a DOM element. * - * @param element A playwright handle for the target DOM element + * @param element A playwright handle or locator for the target DOM element * @param position A position within the target element (default: bottom right quarter). * @returns The svg to inject in the page */ export async function positionMouseOver( - element: ElementHandle, + element: ElementHandle | Locator, position: IPositionInElement = {} ): Promise { const top = position.top ?? 0.75; diff --git a/galata/test/galata/contents.spec.ts b/galata/test/galata/contents.spec.ts index a689abc91017..3502ef1bfbbc 100644 --- a/galata/test/galata/contents.spec.ts +++ b/galata/test/galata/contents.spec.ts @@ -18,11 +18,9 @@ test.describe('Contents API Tests', () => { // Upload removed existing tmpPath, so we need to get inside await page.dblclick(`text=${tmpPath}`); - expect(await page.waitForSelector('text=sub_folder')).toBeTruthy(); - expect(await page.waitForSelector('text=upload_image.png')).toBeTruthy(); - expect( - await page.waitForSelector('text=upload_notebook.ipynb') - ).toBeTruthy(); + await expect(page.locator('text=sub_folder')).toHaveCount(1); + await expect(page.locator('text=upload_image.png')).toHaveCount(1); + await expect(page.locator('text=upload_notebook.ipynb')).toHaveCount(1); }); test('File operations', async ({ page, tmpPath }) => { diff --git a/galata/test/galata/fixture.spec.ts b/galata/test/galata/fixture.spec.ts index f23cb2e8fe35..f2e2da112b59 100644 --- a/galata/test/galata/fixture.spec.ts +++ b/galata/test/galata/fixture.spec.ts @@ -48,8 +48,8 @@ test.describe('mockSettings', () => { page.click('.lm-Menu ul[role="menu"] >> text=JupyterLab Light') ]); - await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); - await page.waitForSelector('div[role="main"] >> text=Launcher'); + await page.locator('#jupyterlab-splash').waitFor({ state: 'detached' }); + await page.locator('div[role="main"] >> text=Launcher').waitFor(); expect(((await response.json()) as any).raw).toMatch(/JupyterLab Light/); @@ -98,7 +98,7 @@ test.describe('mockState', () => { test('should return the mocked state', async ({ page }) => { expect( - await page.waitForSelector( + await page.locator( '[aria-label="Running Sessions section"] >> text=Open Tabs' ) ).toBeTruthy(); @@ -120,9 +120,9 @@ test.describe('sessions', () => { expect(sessions.size).toEqual(1); await page.menu.clickMenuItem('File>New>Console'); - await page.waitForSelector('.jp-Dialog'); + await page.locator('.jp-Dialog').waitFor(); await page.click('.jp-Dialog .jp-mod-accept'); - await page.waitForSelector('text= | Idle'); + await page.locator('text= | Idle').waitFor(); expect(sessions.size).toEqual(2); }); diff --git a/galata/test/galata/jupyterlabpage.spec.ts b/galata/test/galata/jupyterlabpage.spec.ts index c2081e3a9a50..cbbd97aea7b5 100644 --- a/galata/test/galata/jupyterlabpage.spec.ts +++ b/galata/test/galata/jupyterlabpage.spec.ts @@ -64,7 +64,7 @@ test.describe('listeners', () => { }); await page.menu.clickMenuItem('File>New>Text File'); - await page.waitForSelector(`[role="main"] >> text=${DEFAULT_NAME}`); + await page.locator(`[role="main"] >> text=${DEFAULT_NAME}`).waitFor(); await Promise.all([ page.locator('.jp-Dialog').waitFor(), @@ -86,7 +86,7 @@ test.describe('listeners', () => { }); await page.menu.clickMenuItem('File>New>Text File'); - await page.waitForSelector(`[role="main"] >> text=${DEFAULT_NAME}`); + await page.locator(`[role="main"] >> text=${DEFAULT_NAME}`).waitFor(); await Promise.all([ page.locator('.jp-Dialog').waitFor(), @@ -114,7 +114,7 @@ test.describe('listeners', () => { }); await page.menu.clickMenuItem('File>New>Text File'); - await page.waitForSelector(`[role="main"] >> text=${DEFAULT_NAME}`); + await page.locator(`[role="main"] >> text=${DEFAULT_NAME}`).waitFor(); await Promise.all([ page.locator('.jp-Dialog').waitFor(), diff --git a/galata/test/galata/notebook.spec.ts b/galata/test/galata/notebook.spec.ts index 939f6faa3f71..c0ca3b10701d 100644 --- a/galata/test/galata/notebook.spec.ts +++ b/galata/test/galata/notebook.spec.ts @@ -121,7 +121,7 @@ test.describe('Notebook Tests', () => { expect(cellOutput4).toBeTruthy(); expect(parseFloat(cellOutput4![0])).toBeGreaterThan(1.5); - const panel = await page.activity.getPanel(); + const panel = await page.activity.getPanelLocator(); expect(await panel!.screenshot()).toMatchSnapshot('example-run.png'); }); @@ -182,7 +182,7 @@ test.describe('Access cells in windowed notebook', () => { await page.filebrowser.open(target); await page.locator('#jp-main-statusbar').getByText('Idle').waitFor(); - expect(await page.notebook.getCell(12)).toBeTruthy(); + expect(await page.notebook.getCellLocator(12)).toBeTruthy(); }); test('getCell above the viewport', async ({ page, tmpPath }) => { @@ -196,8 +196,8 @@ test.describe('Access cells in windowed notebook', () => { await page.locator('#jp-main-statusbar').getByText('Idle').waitFor(); await page.waitForTimeout(50); - await page.notebook.getCell(12); + await page.notebook.getCellLocator(12); - expect(await page.notebook.getCell(0)).toBeTruthy(); + expect(await page.notebook.getCellLocator(0)).toBeTruthy(); }); }); diff --git a/galata/test/jupyterlab/collapsible-headings.test.ts b/galata/test/jupyterlab/collapsible-headings.test.ts index d125a2ccc3aa..6a431f2e16db 100644 --- a/galata/test/jupyterlab/collapsible-headings.test.ts +++ b/galata/test/jupyterlab/collapsible-headings.test.ts @@ -27,23 +27,23 @@ test.describe('Collapsible Headings; showHCB', () => { }); test('Show Collapser Unselected; showHCB', async ({ page }) => { - expect(await (await page.notebook.getCell(0)).screenshot()).toMatchSnapshot( - 'showHCB_heading_unselected.png' - ); + expect( + await (await page.notebook.getCellLocator(0))!.screenshot() + ).toMatchSnapshot('showHCB_heading_unselected.png'); }); test('Show Collapser Selected; showHCB', async ({ page }) => { await page.notebook.selectCells(0); - expect(await (await page.notebook.getCell(0)).screenshot()).toMatchSnapshot( - 'showHCB_heading_selected.png' - ); + expect( + await (await page.notebook.getCellLocator(0))!.screenshot() + ).toMatchSnapshot('showHCB_heading_selected.png'); }); test('Collapse Heading; showHCB', async ({ page }) => { await page.notebook.selectCells(0); await page.click('text=# Heading 1Heading 1¶ >> button'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('showHCB_collapse_heading.png'); }); @@ -52,7 +52,7 @@ test.describe('Collapsible Headings; showHCB', () => { await page.click('text=# Heading 1Heading 1¶ >> button'); await page.click('text=# Heading 1Heading 1¶ >> button'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('showHCB_expand_heading_via_collapser.png'); }); }); @@ -76,23 +76,23 @@ test.describe('Collapsible Headings; no_showHCB', () => { }); test('Show Collapser Unselected; no_showHCB', async ({ page }) => { - expect(await (await page.notebook.getCell(0)).screenshot()).toMatchSnapshot( - 'no_showHCB_heading_unselected.png' - ); + expect( + await (await page.notebook.getCellLocator(0))!.screenshot() + ).toMatchSnapshot('no_showHCB_heading_unselected.png'); }); test('Show Collapser Selected; no_showHCB', async ({ page }) => { await page.notebook.selectCells(0); - expect(await (await page.notebook.getCell(0)).screenshot()).toMatchSnapshot( - 'no_showHCB_heading_selected.png' - ); + expect( + await (await page.notebook.getCellLocator(0))!.screenshot() + ).toMatchSnapshot('no_showHCB_heading_selected.png'); }); test('Collapse Heading; no_showHCB', async ({ page }) => { await page.notebook.selectCells(0); await page.click('text=# Heading 1Heading 1¶ >> button'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('no_showHCB_collapse_heading.png'); }); @@ -101,7 +101,7 @@ test.describe('Collapsible Headings; no_showHCB', () => { await page.click('text=# Heading 1Heading 1¶ >> button'); await page.click('text=# Heading 1Heading 1¶ >> button'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('no_showHCB_expand_heading_via_collapser.png'); }); }); @@ -128,7 +128,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.notebook.selectCells(6); await page.keyboard.press('ArrowLeft'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('jump_previous_header.png'); }); @@ -137,7 +137,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('ArrowLeft'); await page.keyboard.press('ArrowLeft'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('collapse_previous_header.png'); }); @@ -148,7 +148,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('ArrowLeft'); await page.keyboard.press('ArrowLeft'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('collapse_previous_headers.png'); }); @@ -164,7 +164,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('ArrowRight'); await page.keyboard.press('ArrowLeft'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('reexpand_headers_01.png'); }); @@ -177,7 +177,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('ArrowDown'); await page.keyboard.press('ArrowRight'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('reexpand_headers_02.png'); }); @@ -191,13 +191,13 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('ArrowLeft'); await page.keyboard.press('ArrowLeft'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('reexpand_headers_03a.png'); await page.keyboard.press('ArrowRight'); await page.keyboard.press('ArrowRight'); await page.keyboard.press('ArrowRight'); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('reexpand_headers_03b.png'); }); @@ -211,7 +211,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('Shift+Enter'); await page.notebook.selectCells(2); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('add_header_below_01.png'); }); @@ -228,7 +228,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('Shift+Enter'); await page.notebook.selectCells(0); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('add_header_below_02.png'); }); @@ -244,7 +244,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('Shift+Enter'); await page.notebook.selectCells(2); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('add_header_below_03.png'); }); @@ -254,7 +254,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('Shift+A'); await page.waitForTimeout(200); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('add_header_above_01.png'); }); @@ -264,7 +264,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('Shift+A'); await page.waitForTimeout(200); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('add_header_above_02.png'); }); @@ -274,7 +274,7 @@ test.describe('Collapsible Headings; keyboard navigation', () => { await page.keyboard.press('Shift+A'); await page.waitForTimeout(200); expect( - await (await page.notebook.getNotebookInPanel()).screenshot() + await (await page.notebook.getNotebookInPanelLocator())!.screenshot() ).toMatchSnapshot('add_header_above_03.png'); }); }); diff --git a/galata/test/jupyterlab/completer.test.ts b/galata/test/jupyterlab/completer.test.ts index a297a9611039..fa391b63e308 100644 --- a/galata/test/jupyterlab/completer.test.ts +++ b/galata/test/jupyterlab/completer.test.ts @@ -82,10 +82,9 @@ test.describe('Completer', () => { await page.keyboard.press('Tab'); completer = page.locator(COMPLETER_SELECTOR); await completer.waitFor(); - await page.waitForSelector('.jp-Completer-loading-bar'); - await page.waitForSelector('.jp-Completer-loading-bar', { - state: 'detached' - }); + await page + .locator('.jp-Completer-loading-bar') + .waitFor({ state: 'detached' }); await session?.detach(); const imageName = 'completer-with-doc-panel.png'; expect(await completer.screenshot()).toMatchSnapshot(imageName); @@ -158,8 +157,8 @@ test.describe('Completer', () => { await page.click('button:has-text("Select")'); - await page.waitForSelector('[aria-label="Code Cell Content"]'); - await page.waitForSelector('text=| Idle'); + await page.locator('[aria-label="Code Cell Content"]').waitFor(); + await page.locator('text=| Idle').waitFor(); await page.keyboard.type('import getopt\ngetopt.'); await page.keyboard.press('Tab'); diff --git a/galata/test/jupyterlab/contextmenu.test.ts b/galata/test/jupyterlab/contextmenu.test.ts index b535eb308c19..898d3478877d 100644 --- a/galata/test/jupyterlab/contextmenu.test.ts +++ b/galata/test/jupyterlab/contextmenu.test.ts @@ -59,7 +59,7 @@ test.describe('Application Context Menu', () => { expect(await page.menu.isAnyOpen()).toBe(true); const imageName = 'folder.png'; - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName); }); @@ -74,7 +74,7 @@ test.describe('Application Context Menu', () => { expect(await page.menu.isAnyOpen()).toBe(true); const imageName = 'file.png'; - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName); }); @@ -84,9 +84,9 @@ test.describe('Application Context Menu', () => { }) => { await page.notebook.openByPath(`${tmpPath}/${testNotebook}`); // Wait for kernel to be idle - expect( - await page.waitForSelector(`#jp-main-statusbar >> text=Idle`) - ).toBeTruthy(); + await expect( + page.locator(`#jp-main-statusbar >> text=Idle`).first() + ).toHaveCount(1); await page.click(`.jp-DirListing-item span:has-text("${testNotebook}")`, { button: 'right' @@ -95,7 +95,7 @@ test.describe('Application Context Menu', () => { expect(await page.menu.isAnyOpen()).toBe(true); const imageName = 'running-notebook.png'; - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName); }); @@ -110,13 +110,13 @@ test.describe('Application Context Menu', () => { expect(await page.menu.isAnyOpen()).toBe(true); await page.hover('text=Open With'); - await page.waitForSelector( - '.lm-Menu li[role="menuitem"]:has-text("Editor")' - ); + await page + .locator('.lm-Menu li[role="menuitem"]:has-text("Editor")') + .waitFor(); const imageName = `file-openwith.png`; // Get the last menu -> will be submenu - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName); }); @@ -129,7 +129,7 @@ test.describe('Application Context Menu', () => { expect(await page.menu.isAnyOpen()).toBe(true); const imageName = `tab-launcher.png`; - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName); }); @@ -150,7 +150,7 @@ test.describe('Application Context Menu', () => { expect(await page.menu.isAnyOpen()).toBe(true); const imageName = `tab-notebook.png`; - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName); }); @@ -161,7 +161,7 @@ test.describe('Application Context Menu', () => { expect(await page.menu.isAnyOpen()).toBe(true); const imageName = `notebook-md.png`; - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName); }); @@ -172,7 +172,7 @@ test.describe('Application Context Menu', () => { expect(await page.menu.isAnyOpen()).toBe(true); const imageName = `notebook-code.png`; - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName); }); }); @@ -189,7 +189,7 @@ test.describe('Application Context Menu', () => { expect(await page.menu.isAnyOpen()).toBe(true); const imageName = `fileeditor.png`; - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName); }); @@ -206,16 +206,16 @@ test.describe('Application Context Menu', () => { }); expect(await page.menu.isAnyOpen()).toBe(true); let imageName = `console-prompt.png`; - let menu = await page.menu.getOpenMenu(); - expect.soft(await menu.screenshot()).toMatchSnapshot(imageName); + let menu = await page.menu.getOpenMenuLocator(); + expect.soft(await menu?.screenshot()).toMatchSnapshot(imageName); // Over console content await page.click('.jp-CodeConsole-content', { button: 'right' }); imageName = `console-content.png`; - menu = await page.menu.getOpenMenu(); + menu = await page.menu.getOpenMenuLocator(); expect(await page.menu.isAnyOpen()).toBe(true); - expect(await menu.screenshot()).toMatchSnapshot(imageName); + expect(await menu?.screenshot()).toMatchSnapshot(imageName); }); }); diff --git a/galata/test/jupyterlab/debugger.test.ts b/galata/test/jupyterlab/debugger.test.ts index 7344423e3155..51b00b119039 100644 --- a/galata/test/jupyterlab/debugger.test.ts +++ b/galata/test/jupyterlab/debugger.test.ts @@ -12,6 +12,18 @@ async function openNotebook(page: IJupyterLabPageFixture, tmpPath, fileName) { await page.notebook.openByPath(`${tmpPath}/${fileName}`); } +test('Move Debugger to right', async ({ page }) => { + await page.sidebar.moveTabToRight('jp-debugger-sidebar'); + expect(await page.sidebar.getTabPosition('jp-debugger-sidebar')).toBe( + 'right' + ); +}); + +test('Open Debugger on right', async ({ page }) => { + await page.sidebar.openTab('jp-debugger-sidebar'); + expect(await page.sidebar.isTabOpen('jp-debugger-sidebar')).toBeTruthy(); +}); + test.describe('Debugger Tests', () => { test.afterEach(async ({ page }) => { await page.debugger.switchOff(); @@ -19,18 +31,6 @@ test.describe('Debugger Tests', () => { await page.notebook.close(); }); - test('Move Debugger to right', async ({ page }) => { - await page.sidebar.moveTabToRight('jp-debugger-sidebar'); - expect(await page.sidebar.getTabPosition('jp-debugger-sidebar')).toBe( - 'right' - ); - }); - - test('Open Debugger on right', async ({ page }) => { - await page.sidebar.openTab('jp-debugger-sidebar'); - expect(await page.sidebar.isTabOpen('jp-debugger-sidebar')).toBeTruthy(); - }); - test('Start debug session', async ({ page, tmpPath }) => { await openNotebook(page, tmpPath, 'code_notebook.ipynb'); @@ -41,10 +41,10 @@ test.describe('Debugger Tests', () => { await page.notebook.clickCellGutter(0, 2); await page.debugger.waitForBreakPoints(); - const breakpointsPanel = await page.debugger.getBreakPointsPanel(); + const breakpointsPanel = await page.debugger.getBreakPointsPanelLocator(); expect(await breakpointsPanel.innerText()).toMatch(/ipykernel/); - const callStackPanel = await page.debugger.getCallStackPanel(); + const callStackPanel = await page.debugger.getCallStackPanelLocator(); expect(await callStackPanel.innerText()).toBe(''); // don't add await, run will be blocked by the breakpoint @@ -54,13 +54,13 @@ test.describe('Debugger Tests', () => { expect(await callStackPanel.innerText()).toMatch(/ipykernel/); await page.debugger.waitForVariables(); - const variablesPanel = await page.debugger.getVariablesPanel(); + const variablesPanel = await page.debugger.getVariablesPanelLocator(); expect(await variablesPanel.screenshot()).toMatchSnapshot( 'start-debug-session-variables.png' ); await page.debugger.waitForSources(); - const sourcesPanel = await page.debugger.getSourcePanel(); + const sourcesPanel = await page.debugger.getSourcePanelLocator(); expect(await sourcesPanel.screenshot()).toMatchSnapshot( 'start-debug-session-sources.png' ); @@ -92,16 +92,16 @@ test.describe('Debugger Tests', () => { await page.debugger.waitForCallStack(); await page.debugger.waitForVariables(); - const variablesPanel = await page.debugger.getVariablesPanel(); + const variablesPanel = await page.debugger.getVariablesPanelLocator(); expect(await variablesPanel.screenshot()).toMatchSnapshot( 'image-debug-session-global-variables.png' ); await page.debugger.renderVariable(globalVar); - let richVariableTab = await page.activity.getPanel( + let richVariableTab = await page.activity.getPanelLocator( `${globalVar} - ${notebookName}` ); - expect(await richVariableTab.screenshot()).toMatchSnapshot( + expect(await richVariableTab?.screenshot()).toMatchSnapshot( 'image-debug-session-global-rich-variable.png' ); @@ -112,10 +112,10 @@ test.describe('Debugger Tests', () => { await page.debugger.waitForVariables(); await page.debugger.renderVariable(localVar); - richVariableTab = await page.activity.getPanel( + richVariableTab = await page.activity.getPanelLocator( `${localVar} - ${notebookName}` ); - expect(await richVariableTab.screenshot()).toMatchSnapshot( + expect(await richVariableTab?.screenshot()).toMatchSnapshot( 'image-debug-session-local-rich-variable.png' ); }); @@ -127,13 +127,13 @@ test.describe('Debugger Tests', () => { button: 'right' }); - const menu = await page.menu.getOpenMenu(); - await (await menu.$('[data-command="fileeditor:create-console"]')).click(); + const menu = await page.menu.getOpenMenuLocator(); + await menu?.locator('[data-command="fileeditor:create-console"]')?.click(); - await page.waitForSelector('.jp-Dialog-body'); - const select = await page.$('.jp-Dialog-body >> select'); - const option = await select.$('option:has-text("ipykernel")'); - await select.selectOption(option); + await page.locator('.jp-Dialog-body').waitFor(); + const select = page.locator('.jp-Dialog-body >> select'); + const option = select.locator('option:has-text("ipykernel")'); + await select.selectOption(await option.textContent()); await page.click('div.jp-Dialog-content >> button:has-text("Select")'); // activate the script tab @@ -144,10 +144,10 @@ test.describe('Debugger Tests', () => { await page.notebook.clickCodeGutter(2); await page.debugger.waitForBreakPoints(); - const breakpointsPanel = await page.debugger.getBreakPointsPanel(); + const breakpointsPanel = await page.debugger.getBreakPointsPanelLocator(); expect(await breakpointsPanel.innerText()).toMatch(/ipykernel/); - const callStackPanel = await page.debugger.getCallStackPanel(); + const callStackPanel = await page.debugger.getCallStackPanelLocator(); expect(await callStackPanel.innerText()).toBe(''); // don't add await, run will be blocked by the breakpoint @@ -157,13 +157,13 @@ test.describe('Debugger Tests', () => { expect(await callStackPanel.innerText()).toMatch(/ipykernel/); await page.debugger.waitForVariables(); - const variablesPanel = await page.debugger.getVariablesPanel(); + const variablesPanel = await page.debugger.getVariablesPanelLocator(); expect(await variablesPanel.screenshot()).toMatchSnapshot( 'start-debug-session-script-variables.png' ); await page.debugger.waitForSources(); - const sourcesPanel = await page.debugger.getSourcePanel(); + const sourcesPanel = await page.debugger.getSourcePanelLocator(); expect(await sourcesPanel.screenshot()).toMatchSnapshot( 'start-debug-session-script-sources.png' ); @@ -315,14 +315,14 @@ test.describe('Debugger Variables', () => { async function createNotebook(page: IJupyterLabPageFixture) { await page.notebook.createNew(); - await page.waitForSelector('text=Python 3 (ipykernel) | Idle'); + await page.locator('text=Python 3 (ipykernel) | Idle').waitFor(); } async function setBreakpoint(page: IJupyterLabPageFixture) { await page.notebook.setCell( 0, 'code', - 'global_var = 1\ndef add(a, b):\nlocal_var = a + b\nreturn local_var' + 'global_var = 1\ndef add(a, b):\n local_var = a + b\n return local_var' ); await page.notebook.run(); await page.notebook.addCell('code', 'result = add(1, 2)\nprint(result)'); diff --git a/galata/test/jupyterlab/file-search.test.ts b/galata/test/jupyterlab/file-search.test.ts index 77ca273c03fa..7c72ae594b10 100644 --- a/galata/test/jupyterlab/file-search.test.ts +++ b/galata/test/jupyterlab/file-search.test.ts @@ -65,9 +65,6 @@ test('Search with a text and replacement', async ({ page }) => { test('Populate search box with selected text', async ({ page }) => { const imageName = 'text-editor-search-from-selection.png'; - // Enter first cell - await page.notebook.enterCellEditingMode(0); - // Go to first line await page.keyboard.press('PageUp'); // Select first line @@ -94,11 +91,11 @@ test('Populate search box with selected text', async ({ page }) => { await expect(page.locator('.cm-search.cm-panel')).toHaveCount(0); // Expect the first match to be highlighted - await page.waitForSelector('text=1/2'); + await page.locator('text=1/2').waitFor(); - const tabHandle = await page.activity.getPanel(DEFAULT_NAME); + const tabHandle = await page.activity.getPanelLocator(DEFAULT_NAME); - expect(await tabHandle.screenshot()).toMatchSnapshot(imageName); + expect(await tabHandle?.screenshot()).toMatchSnapshot(imageName); }); test.describe('File search from selection', () => { diff --git a/galata/test/jupyterlab/history.test.ts b/galata/test/jupyterlab/history.test.ts index 5492fb76a7f4..247c9fc40559 100644 --- a/galata/test/jupyterlab/history.test.ts +++ b/galata/test/jupyterlab/history.test.ts @@ -40,8 +40,8 @@ test.describe('test kernel history keybindings', () => { // input 1+2 const imageName = 'history.png'; - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); }); diff --git a/galata/test/jupyterlab/inline-completer.test.ts b/galata/test/jupyterlab/inline-completer.test.ts index 499e14ffaf96..0578e16dbc20 100644 --- a/galata/test/jupyterlab/inline-completer.test.ts +++ b/galata/test/jupyterlab/inline-completer.test.ts @@ -61,8 +61,8 @@ test.describe('Inline Completer', () => { expect(await completer.screenshot()).toMatchSnapshot(imageName); // Should hide on moving cursor away - const toolbar = await page.notebook.getToolbar(); - await toolbar.hover(); + const toolbar = await page.notebook.getToolbarLocator(); + await toolbar!.hover(); await completer.waitFor({ state: 'hidden' }); }); }); @@ -179,8 +179,8 @@ test.describe('Inline Completer', () => { await page.keyboard.press('Tab'); - const cellEditor = await page.notebook.getCellInput(2); - const text = await cellEditor.textContent(); + const cellEditor = await page.notebook.getCellInputLocator(2); + const text = await cellEditor!.textContent(); expect(text).toMatch(/estion.*/); }); }); @@ -207,9 +207,9 @@ test.describe('Inline Completer', () => { await page.keyboard.type('gg'); await expect(ghostText).toHaveText(/estion.*/); - const cellEditor = await page.notebook.getCellInput(2); + const cellEditor = await page.notebook.getCellInputLocator(2); const imageName = 'editor-with-ghost-text.png'; - expect(await cellEditor.screenshot()).toMatchSnapshot(imageName); + expect(await cellEditor!.screenshot()).toMatchSnapshot(imageName); // Ghost text should hide await page.keyboard.press('Escape'); diff --git a/galata/test/jupyterlab/launcher.test.ts b/galata/test/jupyterlab/launcher.test.ts index 901f7a3ecc95..9bce8c2cbc73 100644 --- a/galata/test/jupyterlab/launcher.test.ts +++ b/galata/test/jupyterlab/launcher.test.ts @@ -12,10 +12,10 @@ test.use({ test.describe('Dynamic Text Spacing', () => { test('should Use Dynamic Text Spacing', async ({ page }) => { await page.goto(); - await page.waitForSelector('.jp-LauncherCard-label'); let element = page.locator('div.jp-LauncherCard-label'); for (let i = 0; i < (await element.count()); i++) { + await element.nth(i).waitFor(); let height = await element .nth(i) .evaluate(el => diff --git a/galata/test/jupyterlab/menus.test.ts b/galata/test/jupyterlab/menus.test.ts index f8ddc8188042..cc8e29239fbf 100644 --- a/galata/test/jupyterlab/menus.test.ts +++ b/galata/test/jupyterlab/menus.test.ts @@ -28,11 +28,10 @@ test.describe('General Tests', () => { menuPaths.forEach(menuPath => { test(`Open menu item ${menuPath}`, async ({ page }) => { await page.goto(); - await page.menu.open(menuPath); + await page.menu.openLocator(menuPath); expect(await page.menu.isOpen(menuPath)).toBeTruthy(); - const imageName = `opened-menu-${menuPath.replace(/>/g, '-')}.png`; - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName.toLowerCase()); }); }); @@ -51,17 +50,17 @@ test.describe('General Tests', () => { await page.goto(); const menuPath = 'Settings>Language'; - await page.menu.open(menuPath); + await page.menu.openLocator(menuPath); expect(await page.menu.isOpen(menuPath)).toBeTruthy(); const imageName = `opened-menu-settings-language.png`; - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); expect(await menu.screenshot()).toMatchSnapshot(imageName.toLowerCase()); }); test('Close all menus', async ({ page }) => { await page.goto(); - await page.menu.open('File>New'); + await page.menu.openLocator('File>New'); await page.menu.closeAll(); expect(await page.menu.isAnyOpen()).toEqual(false); }); diff --git a/galata/test/jupyterlab/metadataform.test.ts b/galata/test/jupyterlab/metadataform.test.ts index df495f2255f7..d67499a6d53c 100644 --- a/galata/test/jupyterlab/metadataform.test.ts +++ b/galata/test/jupyterlab/metadataform.test.ts @@ -17,9 +17,7 @@ test.use({ tmpPath: 'metadataform-test', waitForApplication: async ({ baseURL }, use, testInfo) => { const simpleWait = async (page: Page): Promise => { - await page.waitForSelector('#jupyterlab-splash', { - state: 'detached' - }); + await page.locator('#jupyterlab-splash').waitFor({ state: 'detached' }); }; void use(simpleWait); } diff --git a/galata/test/jupyterlab/notebook-create.test.ts b/galata/test/jupyterlab/notebook-create.test.ts index e374d3060ff6..0fad699207d0 100644 --- a/galata/test/jupyterlab/notebook-create.test.ts +++ b/galata/test/jupyterlab/notebook-create.test.ts @@ -56,16 +56,16 @@ test.describe('Notebook Create', () => { menuPaths.forEach(menuPath => { test(`Open menu item ${menuPath}`, async ({ page, sessions }) => { // Wait for kernel to be idle as some menu depend of kernel information - expect( - await page.waitForSelector(`#jp-main-statusbar >> text=Idle`) - ).toBeTruthy(); + await expect( + page.locator(`#jp-main-statusbar >> text=Idle`).first() + ).toHaveCount(1); - await page.menu.open(menuPath); + await page.menu.openLocator(menuPath); expect(await page.menu.isOpen(menuPath)).toBeTruthy(); const imageName = `opened-menu-${menuPath.replace(/>/g, '-')}.png`; - const menu = await page.menu.getOpenMenu(); - expect(await menu.screenshot()).toMatchSnapshot(imageName.toLowerCase()); + const menu = await page.menu.getOpenMenuLocator(); + expect(await menu!.screenshot()).toMatchSnapshot(imageName.toLowerCase()); }); }); @@ -78,18 +78,18 @@ test.describe('Notebook Create', () => { expect((await page.notebook.getCellTextOutput(2))[0]).toBe('8'); await expect(page.locator(TRUSTED_SELECTOR)).toHaveCount(1); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Toggle Dark theme', async ({ page }) => { await populateNotebook(page); await page.notebook.run(); await page.theme.setDarkTheme(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); const imageName = 'dark-theme.png'; - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); }); diff --git a/galata/test/jupyterlab/notebook-edit.test.ts b/galata/test/jupyterlab/notebook-edit.test.ts index 59075eeaff04..4180c4eb7b9a 100644 --- a/galata/test/jupyterlab/notebook-edit.test.ts +++ b/galata/test/jupyterlab/notebook-edit.test.ts @@ -23,9 +23,9 @@ test.describe('Notebook Edit', () => { await page.notebook.addCell('code', '2 ** 3'); await page.notebook.runCell(1, true); const imageName = 'run-cell.png'; - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Re-edit after execution', async ({ page }) => { @@ -34,9 +34,9 @@ test.describe('Notebook Edit', () => { await page.notebook.setCell(1, 'code', '2 ** 6'); const imageName = 'reedit-cell.png'; - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Execute again', async ({ page }) => { @@ -45,9 +45,9 @@ test.describe('Notebook Edit', () => { await page.notebook.setCell(1, 'code', '2 ** 6'); const imageName = 'execute-again.png'; - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Copy-Paste cell', async ({ page }) => { @@ -58,9 +58,9 @@ test.describe('Notebook Edit', () => { await page.menu.clickMenuItem('Edit>Copy Cell'); await page.notebook.selectCells(0); await page.menu.clickMenuItem('Edit>Paste Cell Above'); - let nbPanel = await page.notebook.getNotebookInPanel(); + let nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Cut-Paste cell', async ({ page }) => { @@ -71,9 +71,9 @@ test.describe('Notebook Edit', () => { await page.menu.clickMenuItem('Edit>Cut Cell'); await page.notebook.selectCells(0); await page.menu.clickMenuItem('Edit>Paste Cell Below'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Paste-Replace cell', async ({ page }) => { @@ -84,9 +84,9 @@ test.describe('Notebook Edit', () => { await page.menu.clickMenuItem('Edit>Copy Cell'); await page.notebook.selectCells(2); await page.menu.clickMenuItem('Edit>Paste Cell and Replace'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Delete cell', async ({ page }) => { @@ -95,9 +95,9 @@ test.describe('Notebook Edit', () => { const imageName = 'delete-cell.png'; await page.notebook.selectCells(2); await page.menu.clickMenuItem('Edit>Delete Cell'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Select all cells', async ({ page }) => { @@ -105,9 +105,9 @@ test.describe('Notebook Edit', () => { const imageName = 'select-all-cells.png'; await page.notebook.selectCells(2); await page.menu.clickMenuItem('Edit>Select All Cells'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Deselect all cells', async ({ page }) => { @@ -115,9 +115,9 @@ test.describe('Notebook Edit', () => { const imageName = 'deselect-all-cells.png'; await page.notebook.selectCells(1, 2); await page.menu.clickMenuItem('Edit>Deselect All Cells'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Move cells up', async ({ page }) => { @@ -125,9 +125,9 @@ test.describe('Notebook Edit', () => { const imageName = 'move-cell-up.png'; await page.notebook.selectCells(1); await page.menu.clickMenuItem('Edit>Move Cell Up'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Move cells down', async ({ page }) => { @@ -135,9 +135,9 @@ test.describe('Notebook Edit', () => { const imageName = 'move-cell-down.png'; await page.notebook.selectCells(0); await page.menu.clickMenuItem('Edit>Move Cell Down'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Split cell', async ({ page }) => { @@ -150,7 +150,7 @@ test.describe('Notebook Edit', () => { await page.keyboard.press('Home'); await page.menu.clickMenuItem('Edit>Split Cell'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); @@ -162,8 +162,8 @@ test.describe('Notebook Edit', () => { const imageName = 'merge-cells.png'; await page.notebook.selectCells(1, 2); await page.menu.clickMenuItem('Edit>Merge Selected Cells'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); }); diff --git a/galata/test/jupyterlab/notebook-markdown.test.ts b/galata/test/jupyterlab/notebook-markdown.test.ts index 7476ee9e51c3..690b5d11dce6 100644 --- a/galata/test/jupyterlab/notebook-markdown.test.ts +++ b/galata/test/jupyterlab/notebook-markdown.test.ts @@ -11,13 +11,13 @@ async function enterEditingModeForScreenshot( cellIndex: number ) { await page.notebook.enterCellEditingMode(cellIndex); - const cell = await page.notebook.getCell(cellIndex); + const cell = await page.notebook.getCellLocator(cellIndex); // Make sure cursor is consistently in the same position to avoid screenshot flake await page.keyboard.press('Home'); await page.keyboard.press('PageUp'); // Add some timeout to stabilize codemirror bounding box const cellBox = await cell.boundingBox(); - const cellNew = await page.notebook.getCell(cellIndex); + const cellNew = await page.notebook.getCellLocator(cellIndex); const cellNewBox = await cellNew.boundingBox(); if ( cellBox.x != cellNewBox.x || @@ -42,9 +42,9 @@ test.describe('Notebook Markdown', () => { await page.notebook.openByPath(`${tmpPath}/${fileName}`); const imageName = 'highlight-latex.png'; await page.notebook.enterCellEditingMode(0); - const cell = await page.notebook.getCell(0); + const cell = await page.notebook.getCellLocator(0); - expect(await (await cell.$('.jp-Editor')).screenshot()).toMatchSnapshot( + expect(await cell!.locator('.jp-Editor').screenshot()).toMatchSnapshot( imageName ); }); @@ -54,9 +54,9 @@ test.describe('Notebook Markdown', () => { const imageName = 'do-not-highlight-not-latex.png'; await enterEditingModeForScreenshot(page, 1); - const cell = await page.notebook.getCell(1); + const cell = await page.notebook.getCellLocator(1); - expect(await (await cell.$('.jp-Editor')).screenshot()).toMatchSnapshot( + expect(await cell!.locator('.jp-Editor').screenshot()).toMatchSnapshot( imageName ); }); @@ -69,9 +69,9 @@ test.describe('Notebook Markdown', () => { const imageName = 'do-not-highlight-standalone-dollar.png'; await enterEditingModeForScreenshot(page, 2); - const cell = await page.notebook.getCell(2); + const cell = await page.notebook.getCellLocator(2); - expect(await (await cell.$('.jp-Editor')).screenshot()).toMatchSnapshot( + expect(await cell!.locator('.jp-Editor').screenshot()).toMatchSnapshot( imageName ); }); @@ -79,14 +79,14 @@ test.describe('Notebook Markdown', () => { test('Render a MermaidJS flowchart', async ({ page, tmpPath }) => { await page.notebook.openByPath(`${tmpPath}/${fileName}`); const imageName = 'render-mermaid-flowchart.png'; - const cell = await page.notebook.getCell(3); - expect(await cell.screenshot()).toMatchSnapshot(imageName); + const cell = await page.notebook.getCellLocator(3); + expect(await cell!.screenshot()).toMatchSnapshot(imageName); }); test('Render a MermaidJS error', async ({ page, tmpPath }) => { await page.notebook.openByPath(`${tmpPath}/${fileName}`); const imageName = 'render-mermaid-error.png'; - const cell = await page.notebook.getCell(4); - expect(await cell.screenshot()).toMatchSnapshot(imageName); + const cell = await page.notebook.getCellLocator(4); + expect(await cell!.screenshot()).toMatchSnapshot(imageName); }); }); diff --git a/galata/test/jupyterlab/notebook-max-outputs.test.ts b/galata/test/jupyterlab/notebook-max-outputs.test.ts index 7790f14cce58..5414fe4b2479 100644 --- a/galata/test/jupyterlab/notebook-max-outputs.test.ts +++ b/galata/test/jupyterlab/notebook-max-outputs.test.ts @@ -48,7 +48,7 @@ input('Your age:') `); await page.menu.clickMenuItem('Run>Run All Cells'); - await page.waitForSelector('.jp-Stdin >> text=Your age:'); + await page.locator('.jp-Stdin >> text=Your age:').waitFor(); expect(await page.locator('.jp-RenderedMarkdown').count()).toBeGreaterThan( MAX_OUTPUTS ); @@ -73,7 +73,7 @@ for i in range(10): `); await page.menu.clickMenuItem('Run>Run All Cells'); - await page.waitForSelector('.jp-Stdin >> text=Your age:'); + await page.locator('.jp-Stdin >> text=Your age:').waitFor(); await page.keyboard.type('42'); await page.keyboard.press('Enter'); diff --git a/galata/test/jupyterlab/notebook-mobile.test.ts b/galata/test/jupyterlab/notebook-mobile.test.ts index a8bcd8e32972..2705fe85bde8 100644 --- a/galata/test/jupyterlab/notebook-mobile.test.ts +++ b/galata/test/jupyterlab/notebook-mobile.test.ts @@ -14,14 +14,11 @@ test.describe('Notebook Layout on Mobile', () => { test('Execute code cell', async ({ page }) => { await page.sidebar.close('left'); - // TODO: calling `setCell` just once leads to very flaky test - // See https://github.com/jupyterlab/jupyterlab/issues/15252 for more information - await page.notebook.setCell(0, 'code', 'print("hello")'); await page.notebook.setCell(0, 'code', 'print("hello")'); await page.notebook.addCell('code', '2 * 3'); await page.notebook.runCellByCell(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); const imageName = 'mobile-layout.png'; - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); }); diff --git a/galata/test/jupyterlab/notebook-replace.test.ts b/galata/test/jupyterlab/notebook-replace.test.ts index 02bd238470f2..945bd2b14ae7 100644 --- a/galata/test/jupyterlab/notebook-replace.test.ts +++ b/galata/test/jupyterlab/notebook-replace.test.ts @@ -28,7 +28,7 @@ test.describe('Notebook Search and Replace', () => { await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); await page.click('button[title="Show Replace"]'); @@ -36,11 +36,11 @@ test.describe('Notebook Search and Replace', () => { await page.click('button:has-text("Replace")'); - await page.waitForSelector('text=1/20'); + await page.locator('text=1/20').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot('replace-in-cell.png'); + expect(await nbPanel!.screenshot()).toMatchSnapshot('replace-in-cell.png'); }); test('Substitute groups of regular expressions', async ({ page }) => { @@ -48,17 +48,17 @@ test.describe('Notebook Search and Replace', () => { await page.click('button[title="Use Regular Expression"]'); await page.fill('[placeholder="Find"]', 'text/(\\w+)'); - await page.waitForSelector('text=1/3'); + await page.locator('text=1/3').waitFor(); await page.click('button[title="Show Replace"]'); await page.fill('[placeholder="Replace"]', 'script/$1'); - const cell = await page.notebook.getCell(2); + const cell = await page.notebook.getCellLocator(2); await expect(page.locator('body')).not.toContainText('script/plain'); await page.click('button:has-text("Replace")'); - await page.waitForSelector('text=1/2'); + await page.locator('text=1/2').waitFor(); - await cell.waitForSelector('text=script/plain'); + await cell!.locator('text=script/plain').waitFor(); await expect(page.locator('body')).toContainText('script/plain'); }); @@ -68,7 +68,7 @@ test.describe('Notebook Search and Replace', () => { await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); // Click next button await page.click('button[title^="Next Match"]', { @@ -81,13 +81,13 @@ test.describe('Notebook Search and Replace', () => { await page.click('button:has-text("Replace")'); - await page.waitForSelector('text=5/20'); + await page.locator('text=5/20').waitFor(); - const cell = await page.notebook.getCell(1); + const cell = await page.notebook.getCellLocator(1); - expect(await (await cell.$('.jp-Editor')).screenshot()).toMatchSnapshot( - 'replace-in-markdown-rendered-cell.png' - ); + expect( + await cell!.locator('.jp-Editor').first().screenshot() + ).toMatchSnapshot('replace-in-markdown-rendered-cell.png'); }); test('Replace all', async ({ page }) => { @@ -96,7 +96,7 @@ test.describe('Notebook Search and Replace', () => { await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); await page.click('button[title="Show Replace"]'); @@ -104,11 +104,11 @@ test.describe('Notebook Search and Replace', () => { await page.click('button:has-text("Replace All")'); - await page.waitForSelector('text=-/-'); + await page.locator('text=-/-').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot('replace-all.png'); + expect(await nbPanel!.screenshot()).toMatchSnapshot('replace-all.png'); }); test('Replace step-by-step across cell boundaries', async ({ page }) => { @@ -125,24 +125,24 @@ test.describe('Notebook Search and Replace', () => { // TODO: Next Match press count should be one less // (the -/4 state should not be necessary). - await page.waitForSelector('text=-/4'); + await page.locator('text=-/4').waitFor(); await page.click('button[title^="Next Match"]', { clickCount: 3 }); - await page.waitForSelector('text=1/4'); + await page.locator('text=1/4').waitFor(); await page.click('button:has-text("Replace")'); - await page.waitForSelector('text=1/3'); + await page.locator('text=1/3').waitFor(); await page.click('button:has-text("Replace")'); // At this point we should be in the second cell - await page.waitForSelector('text=1/2'); + await page.locator('text=1/2').waitFor(); await page.click('button:has-text("Replace")'); - await page.waitForSelector('text=1/1'); + await page.locator('text=1/1').waitFor(); await page.click('button:has-text("Replace")'); - await page.waitForSelector('text=-/-'); + await page.locator('text=-/-').waitFor(); }); }); diff --git a/galata/test/jupyterlab/notebook-run-mermaid.test.ts b/galata/test/jupyterlab/notebook-run-mermaid.test.ts index 7fc22b3b56a6..a20778eab131 100644 --- a/galata/test/jupyterlab/notebook-run-mermaid.test.ts +++ b/galata/test/jupyterlab/notebook-run-mermaid.test.ts @@ -51,11 +51,11 @@ test.describe('Notebook Run Mermaid', () => { const imageName = 'run-cells-mermaid.png'; await page.notebook.run(); - await page.waitForSelector('.jp-RenderedMermaid'); + await page.locator('.jp-RenderedMermaid').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); expect(await nbDiskContent(page, nbPath)).toContain(SVG_MIME_TYPE); }); @@ -73,11 +73,11 @@ test.describe('Notebook Run Mermaid', () => { const imageName = 'run-cells-dark-mermaid.png'; await page.notebook.run(); - await page.waitForSelector('.jp-RenderedMermaid'); + await page.locator('.jp-RenderedMermaid').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); expect(await nbDiskContent(page, nbPath)).toContain(SVG_MIME_TYPE); }); }); diff --git a/galata/test/jupyterlab/notebook-run-vega.test.ts b/galata/test/jupyterlab/notebook-run-vega.test.ts index 231b9efc9e35..7072fac81f05 100644 --- a/galata/test/jupyterlab/notebook-run-vega.test.ts +++ b/galata/test/jupyterlab/notebook-run-vega.test.ts @@ -51,11 +51,11 @@ test.describe('Notebook Run Vega', () => { const imageName = 'run-cells-vega.png'; await page.notebook.run(); - await page.waitForSelector('.vega-embed'); + await page.locator('.vega-embed').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); expect(await nbDiskContent(page, nbPath)).toContain(PNG_MIME_TYPE); }); @@ -73,11 +73,11 @@ test.describe('Notebook Run Vega', () => { const imageName = 'run-cells-dark-vega.png'; await page.notebook.run(); - await page.waitForSelector('.vega-embed'); + await page.locator('.vega-embed').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); expect(await nbDiskContent(page, nbPath)).toContain(PNG_MIME_TYPE); }); }); diff --git a/galata/test/jupyterlab/notebook-run.test.ts b/galata/test/jupyterlab/notebook-run.test.ts index fb44dc4fe2c8..b45ba6403578 100644 --- a/galata/test/jupyterlab/notebook-run.test.ts +++ b/galata/test/jupyterlab/notebook-run.test.ts @@ -43,7 +43,7 @@ test.describe.serial('Notebook Run', () => { await page.notebook.runCellByCell({ onBeforeScroll: async () => { - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); if (nbPanel) { captures.push(await nbPanel.screenshot()); numNBImages++; @@ -54,8 +54,8 @@ test.describe.serial('Notebook Run', () => { // Save outputs for the next tests await page.notebook.save(); - const nbPanel = await page.notebook.getNotebookInPanel(); - captures.push(await nbPanel.screenshot()); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); + captures.push(await nbPanel!.screenshot()); numNBImages++; for (let c = 0; c < numNBImages; ++c) { diff --git a/galata/test/jupyterlab/notebook-scroll-no-windowing.test.ts b/galata/test/jupyterlab/notebook-scroll-no-windowing.test.ts index cb3a18fce136..4ce259b1323b 100644 --- a/galata/test/jupyterlab/notebook-scroll-no-windowing.test.ts +++ b/galata/test/jupyterlab/notebook-scroll-no-windowing.test.ts @@ -181,30 +181,29 @@ test.describe('Notebook scroll on execution (no windowing)', () => { test('should scroll when advancing if top is only marginally visible', async ({ page }) => { - const notebook = await page.notebook.getNotebookInPanel(); - const thirdCell = await page.notebook.getCell(2); + const notebook = await page.notebook.getNotebookInPanelLocator(); + const thirdCell = await page.notebook.getCellLocator(2); - await positionCellPartiallyBelowViewport(page, notebook, thirdCell, 0.01); + await positionCellPartiallyBelowViewport(page, notebook!, thirdCell!, 0.01); // Select second cell await page.notebook.selectCells(1); - const thirdCellLocator = page.locator( - '.jp-Cell[data-windowed-list-index="2"]' - ); // The third cell should be positioned at the bottom, revealing between 0 to 2% of its content. - await expect(thirdCellLocator).toBeInViewport({ ratio: 0.0 }); - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.02 }); + await expect(thirdCell!).toBeInViewport({ ratio: 0.0 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.02 }); // Only a small fraction of notebook viewport should be taken up by that cell - expect(await notebookViewportRatio(notebook, thirdCell)).toBeLessThan(0.1); + expect(await notebookViewportRatio(notebook!, thirdCell!)).toBeLessThan( + 0.1 + ); // Run second cell await page.notebook.runCell(1); // After running the second cell, the third cell should be revealed, in at least 10% - await expect(thirdCellLocator).toBeInViewport({ ratio: 0.1 }); + await expect(thirdCell!).toBeInViewport({ ratio: 0.1 }); // The third cell should now occupy about half of the notebook viewport - expect(await notebookViewportRatio(notebook, thirdCell)).toBeGreaterThan( + expect(await notebookViewportRatio(notebook!, thirdCell!)).toBeGreaterThan( 0.4 ); }); @@ -212,53 +211,47 @@ test.describe('Notebook scroll on execution (no windowing)', () => { test('should not scroll when advancing if top is non-marginally visible', async ({ page }) => { - const notebook = await page.notebook.getNotebookInPanel(); - const thirdCell = await page.notebook.getCell(2); + const notebook = await page.notebook.getNotebookInPanelLocator(); + const thirdCell = await page.notebook.getCellLocator(2); - await positionCellPartiallyBelowViewport(page, notebook, thirdCell, 0.15); + await positionCellPartiallyBelowViewport(page, notebook!, thirdCell!, 0.15); // Select second cell await page.notebook.selectCells(1); - const thirdCellLocator = page.locator( - '.jp-Cell[data-windowed-list-index="2"]' - ); // The third cell should be positioned at the bottom, revealing between 10 to 20% of its content. - await expect(thirdCellLocator).toBeInViewport({ ratio: 0.1 }); - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + await expect(thirdCell!).toBeInViewport({ ratio: 0.1 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.2 }); // This cell should initially take up between 30% and 50% of the notebook viewport - let spaceTaken = await notebookViewportRatio(notebook, thirdCell); + let spaceTaken = await notebookViewportRatio(notebook!, thirdCell!); expect(spaceTaken).toBeGreaterThan(0.3); expect(spaceTaken).toBeLessThan(0.5); // Run second cell await page.notebook.runCell(1); // After running the second cell, the third cell should not be scrolled - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.2 }); // The cell should still take up between 30% and 50% of the notebook viewport - spaceTaken = await notebookViewportRatio(notebook, thirdCell); + spaceTaken = await notebookViewportRatio(notebook!, thirdCell!); expect(spaceTaken).toBeGreaterThan(0.3); expect(spaceTaken).toBeLessThan(0.5); }); test('should not scroll when running in-place', async ({ page }) => { - const notebook = await page.notebook.getNotebookInPanel(); - const thirdCell = await page.notebook.getCell(2); + const notebook = await page.notebook.getNotebookInPanelLocator(); + const thirdCell = await page.notebook.getCellLocator(2); - await positionCellPartiallyBelowViewport(page, notebook, thirdCell, 0.15); + await positionCellPartiallyBelowViewport(page, notebook!, thirdCell!, 0.15); // Select third cell await page.notebook.enterCellEditingMode(2); - const thirdCellLocator = page.locator( - '.jp-Cell[data-windowed-list-index="2"]' - ); // The third cell should be positioned at the bottom, revealing between 10 to 20% of its content. - await expect(thirdCellLocator).toBeInViewport({ ratio: 0.1 }); - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + await expect(thirdCell!).toBeInViewport({ ratio: 0.1 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.2 }); // Run third cell in-place await page.notebook.runCell(2, true); // After running the third cell it should not be scrolled - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.2 }); // The galata implementation of `page.notebook.runCell(2, true);` // first switches to command mode before cell execution, @@ -266,7 +259,7 @@ test.describe('Notebook scroll on execution (no windowing)', () => { await page.keyboard.press('Control+Enter'); // After running the third cell it should not be scrolled - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.2 }); }); }); @@ -302,8 +295,8 @@ test.describe('Notebook scroll over long outputs (no windowing)', () => { await renderedMarkdownLocator.waitFor({ state: 'hidden', timeout: 100 }); // Scroll to the last cell - const lastCell = await page.notebook.getCell(10); - await lastCell.scrollIntoViewIfNeeded(); + const lastCell = await page.notebook.getCellLocator(10); + await lastCell!.scrollIntoViewIfNeeded(); // Get the outer window const outer = page.locator('.jp-WindowedPanel-outer'); diff --git a/galata/test/jupyterlab/notebook-scroll.test.ts b/galata/test/jupyterlab/notebook-scroll.test.ts index d50d835e6970..8b96679c135d 100644 --- a/galata/test/jupyterlab/notebook-scroll.test.ts +++ b/galata/test/jupyterlab/notebook-scroll.test.ts @@ -181,30 +181,29 @@ test.describe('Notebook scroll on execution (with windowing)', () => { test('should scroll when advancing if top is only marginally visible', async ({ page }) => { - const notebook = await page.notebook.getNotebookInPanel(); - const thirdCell = await page.notebook.getCell(2); + const notebook = await page.notebook.getNotebookInPanelLocator(); + const thirdCell = await page.notebook.getCellLocator(2); - await positionCellPartiallyBelowViewport(page, notebook, thirdCell, 0.01); + await positionCellPartiallyBelowViewport(page, notebook!, thirdCell!, 0.01); // Select second cell await page.notebook.selectCells(1); - const thirdCellLocator = page.locator( - '.jp-Cell[data-windowed-list-index="2"]' - ); // The third cell should be positioned at the bottom, revealing between 0 to 2% of its content. - await expect(thirdCellLocator).toBeInViewport({ ratio: 0.0 }); - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.02 }); + await expect(thirdCell!).toBeInViewport({ ratio: 0.0 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.02 }); // Only a small fraction of notebook viewport should be taken up by that cell - expect(await notebookViewportRatio(notebook, thirdCell)).toBeLessThan(0.1); + expect(await notebookViewportRatio(notebook!, thirdCell!)).toBeLessThan( + 0.1 + ); // Run second cell await page.notebook.runCell(1); // After running the second cell, the third cell should be revealed, in at least 10% - await expect(thirdCellLocator).toBeInViewport({ ratio: 0.1 }); + await expect(thirdCell!).toBeInViewport({ ratio: 0.1 }); // The third cell should now occupy about half of the notebook viewport - expect(await notebookViewportRatio(notebook, thirdCell)).toBeGreaterThan( + expect(await notebookViewportRatio(notebook!, thirdCell!)).toBeGreaterThan( 0.4 ); }); @@ -212,53 +211,47 @@ test.describe('Notebook scroll on execution (with windowing)', () => { test('should not scroll when advancing if top is non-marginally visible', async ({ page }) => { - const notebook = await page.notebook.getNotebookInPanel(); - const thirdCell = await page.notebook.getCell(2); + const notebook = await page.notebook.getNotebookInPanelLocator(); + const thirdCell = await page.notebook.getCellLocator(2); - await positionCellPartiallyBelowViewport(page, notebook, thirdCell, 0.15); + await positionCellPartiallyBelowViewport(page, notebook!, thirdCell!, 0.15); // Select second cell await page.notebook.selectCells(1); - const thirdCellLocator = page.locator( - '.jp-Cell[data-windowed-list-index="2"]' - ); // The third cell should be positioned at the bottom, revealing between 10 to 20% of its content. - await expect(thirdCellLocator).toBeInViewport({ ratio: 0.1 }); - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + await expect(thirdCell!).toBeInViewport({ ratio: 0.1 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.2 }); // This cell should initially take up between 30% and 50% of the notebook viewport - let spaceTaken = await notebookViewportRatio(notebook, thirdCell); + let spaceTaken = await notebookViewportRatio(notebook!, thirdCell!); expect(spaceTaken).toBeGreaterThan(0.3); expect(spaceTaken).toBeLessThan(0.5); // Run second cell await page.notebook.runCell(1); // After running the second cell, the third cell should not be scrolled - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.2 }); // The cell should still take up between 30% and 50% of the notebook viewport - spaceTaken = await notebookViewportRatio(notebook, thirdCell); + spaceTaken = await notebookViewportRatio(notebook!, thirdCell!); expect(spaceTaken).toBeGreaterThan(0.3); expect(spaceTaken).toBeLessThan(0.5); }); test('should not scroll when running in-place', async ({ page }) => { - const notebook = await page.notebook.getNotebookInPanel(); - const thirdCell = await page.notebook.getCell(2); + const notebook = await page.notebook.getNotebookInPanelLocator(); + const thirdCell = await page.notebook.getCellLocator(2); - await positionCellPartiallyBelowViewport(page, notebook, thirdCell, 0.15); + await positionCellPartiallyBelowViewport(page, notebook!, thirdCell!, 0.15); // Select third cell await page.notebook.enterCellEditingMode(2); - const thirdCellLocator = page.locator( - '.jp-Cell[data-windowed-list-index="2"]' - ); // The third cell should be positioned at the bottom, revealing between 10 to 20% of its content. - await expect(thirdCellLocator).toBeInViewport({ ratio: 0.1 }); - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + await expect(thirdCell!).toBeInViewport({ ratio: 0.1 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.2 }); // Run third cell in-place await page.notebook.runCell(2, true); // After running the third cell it should not be scrolled - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.2 }); // The galata implementation of `page.notebook.runCell(2, true);` // first switches to command mode before cell execution, @@ -266,7 +259,7 @@ test.describe('Notebook scroll on execution (with windowing)', () => { await page.keyboard.press('Control+Enter'); // After running the third cell it should not be scrolled - await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); + await expect(thirdCell!).not.toBeInViewport({ ratio: 0.2 }); }); }); @@ -302,8 +295,8 @@ test.describe('Notebook scroll over long outputs (with windowing)', () => { await renderedMarkdownLocator.waitFor({ state: 'hidden', timeout: 100 }); // Scroll to the last cell - const lastCell = await page.notebook.getCell(10); - await lastCell.scrollIntoViewIfNeeded(); + const lastCell = await page.notebook.getCellLocator(10); + await lastCell!.scrollIntoViewIfNeeded(); // Get the outer window const outer = page.locator('.jp-WindowedPanel-outer'); diff --git a/galata/test/jupyterlab/notebook-search-highlight.test.ts b/galata/test/jupyterlab/notebook-search-highlight.test.ts index d282f76d50eb..bba6a848afc1 100644 --- a/galata/test/jupyterlab/notebook-search-highlight.test.ts +++ b/galata/test/jupyterlab/notebook-search-highlight.test.ts @@ -34,7 +34,7 @@ test('Open and close Search dialog, then add new code cell', async ({ }, TEST_NEEDLE); // wait for the search to complete - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); expect(await page.locator(HIGHLIGHTS_LOCATOR).count()).toBeGreaterThanOrEqual( 4 ); diff --git a/galata/test/jupyterlab/notebook-search.test.ts b/galata/test/jupyterlab/notebook-search.test.ts index a67b7d02d7c6..b9d88044262b 100644 --- a/galata/test/jupyterlab/notebook-search.test.ts +++ b/galata/test/jupyterlab/notebook-search.test.ts @@ -35,11 +35,11 @@ test.describe('Notebook Search', () => { await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot('search.png'); + expect(await nbPanel!.screenshot()).toMatchSnapshot('search.png'); }); test('Should open search box in edit mode', async ({ page }) => { @@ -91,7 +91,7 @@ test.describe('Notebook Search', () => { 'one notebook withr\n\n\nThis is a multi' ); - await page.waitForSelector('text=1/1'); + await page.locator('text=1/1').waitFor(); // Show replace buttons to check for visual regressions await page.click('button[title="Show Replace"]'); @@ -125,7 +125,7 @@ test.describe('Notebook Search', () => { }); // Expect the first match to be highlighted - await page.waitForSelector('text=1/2'); + await page.locator('text=1/2').waitFor(); // Enter first cell again await page.notebook.enterCellEditingMode(0); @@ -162,7 +162,7 @@ test.describe('Notebook Search', () => { // Search for "test" await page.keyboard.press('Control+f'); await page.fill('[placeholder="Find"]', 'test'); - await page.waitForSelector('text=1/2'); + await page.locator('text=1/2').waitFor(); // Close search box await page.keyboard.press('Escape'); @@ -174,7 +174,7 @@ test.describe('Notebook Search', () => { // Expect the text to be set in the input field await expect(inputWithTestLocator).toBeVisible(); // Expect the search to be active again - await page.waitForSelector('text=1/2'); + await page.locator('text=1/2').waitFor(); }); test('Clear search when box is empty', async ({ page }) => { @@ -243,14 +243,14 @@ test.describe('Notebook Search', () => { await page.click('.jp-Dialog .jp-mod-accept'); } - await page.waitForSelector('text=1/29'); + await page.locator('text=1/29').waitFor(); - const cell = await page.notebook.getCell(5); - await cell.scrollIntoViewIfNeeded(); + const cell = await page.notebook.getCellLocator(5); + await cell!.scrollIntoViewIfNeeded(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot( + expect(await nbPanel!.screenshot()).toMatchSnapshot( 'search-within-outputs.png' ); }); @@ -265,10 +265,10 @@ test.describe('Notebook Search', () => { await page.click('text=Search in 1 Selected Cell'); - await page.waitForSelector('text=1/4'); + await page.locator('text=1/4').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); - expect(await nbPanel.screenshot()).toMatchSnapshot( + const nbPanel = await page.notebook.getNotebookInPanelLocator(); + expect(await nbPanel!.screenshot()).toMatchSnapshot( 'search-in-selected-cells.png' ); }); @@ -280,36 +280,40 @@ test.describe('Notebook Search', () => { await page.click('text=Search in 1 Selected Cell'); // Bring focus to first cell without switching away from command mode - let cell = await page.notebook.getCell(0); - await (await cell.$('.jp-InputPrompt')).click(); + let cell = await page.notebook.getCellLocator(0); + await cell!.locator('.jp-InputPrompt').click(); // Select two cells below await page.keyboard.press('Shift+ArrowDown'); await page.keyboard.press('Shift+ArrowDown'); // Expect the filter text to be updated - await page.waitForSelector('text=Search in 3 Selected Cells'); + await page.locator('text=Search in 3 Selected Cells').waitFor(); // Reset selection, switch to third cell, preserving command mode - cell = await page.notebook.getCell(2); - await (await cell.$('.jp-InputPrompt')).click(); + cell = await page.notebook.getCellLocator(2); + await cell!.locator('.jp-InputPrompt').click(); - await page.waitForSelector('text=Search in 1 Selected Cell'); + await page.locator('text=Search in 1 Selected Cell').waitFor(); + // Wait for the counter to be properly updated + await page + .locator('.jp-DocumentSearch-index-counter:has-text("1/10")') + .waitFor(); // Select cell above await page.keyboard.press('Shift+ArrowUp'); // Expect updated text - await page.waitForSelector('text=Search in 2 Selected Cells'); + await page.locator('text=Search in 2 Selected Cells').waitFor(); // Expect 15 matches; this is 6/15, not 1/15 because current match is set // in second cell and when selection is extended, it does not move; keeping // the current match when extending the selection is desired as user may use // it as a reference, especially when it was set as closest to the cursor. - await page.waitForSelector('text=6/15'); + await page.locator('text=6/15').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); - expect(await nbPanel.screenshot()).toMatchSnapshot( + const nbPanel = await page.notebook.getNotebookInPanelLocator(); + expect(await nbPanel!.screenshot()).toMatchSnapshot( 'search-in-two-selected-cells.png' ); }); @@ -322,11 +326,11 @@ test.describe('Notebook Search', () => { await page.fill('[placeholder="Find"]', 'with'); await page.click('button[title="Show Search Filters"]'); await page.click('text=Search in 1 Selected Cell'); - await page.waitForSelector('text=1/4'); + await page.locator('text=1/4').waitFor(); // Bring focus to first cell without switching to edit mode - let cell = await page.notebook.getCell(0); - await (await cell.$('.jp-Editor')).click(); + let cell = await page.notebook.getCellLocator(0); + await cell!.locator('.jp-Editor').click(); // Switch back to command mode await page.keyboard.press('Escape'); @@ -336,22 +340,21 @@ test.describe('Notebook Search', () => { await page.keyboard.press('Shift+ArrowDown'); // Expect the filter text to be updated - await page.waitForSelector('text=Search in 3 Selected Cells'); + await page.locator('text=Search in 3 Selected Cells').waitFor(); // Expect 19 matches - await page.waitForSelector('text=1/19'); + await page.locator('text=1/19').waitFor(); }); test('Search in selected text', async ({ page }) => { await page.keyboard.press('Control+f'); await page.fill('[placeholder="Find"]', 'text/'); - await page.waitForSelector('text=1/3'); + await page.locator('text=1/3').waitFor(); // Activate third cell - const cell = await page.notebook.getCell(2); - const editor = await cell.$('.jp-Editor'); - await editor.click(); + const cell = await page.notebook.getCellLocator(2); + await cell!.locator('.jp-Editor').click(); // Select 7 lines await page.keyboard.press('Control+Home'); @@ -363,11 +366,11 @@ test.describe('Notebook Search', () => { await page.click('button[title="Show Search Filters"]'); await page.click('text=Search in 7 Selected Lines'); - await page.waitForSelector('text=1/2'); + await page.locator('text=1/2').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot( + expect(await nbPanel!.screenshot()).toMatchSnapshot( 'search-in-selected-text.png' ); }); @@ -375,16 +378,15 @@ test.describe('Notebook Search', () => { test('Highlights are visible when text is selected', async ({ page }) => { await page.keyboard.press('Control+f'); await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); - const cell = await page.notebook.getCell(0); - const editor = await cell.$('.jp-Editor'); - await editor.click(); + const cell = await page.notebook.getCellLocator(0); + await cell!.locator('.jp-Editor').click(); // Select text (to see if the highlights will still be visible) await page.keyboard.press('Control+A'); - expect(await (await cell.$('.jp-Editor')).screenshot()).toMatchSnapshot( + expect(await cell!.locator('.jp-Editor').screenshot()).toMatchSnapshot( 'highlight-visible-under-selection.png' ); }); @@ -395,14 +397,14 @@ test.describe('Notebook Search', () => { await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); // Click next button await page.click('button[title^="Next Match"]'); - const cell = await page.notebook.getCell(0); + const cell = await page.notebook.getCellLocator(0); - expect(await (await cell.$('.jp-Editor')).screenshot()).toMatchSnapshot( + expect(await cell!.locator('.jp-Editor').screenshot()).toMatchSnapshot( 'highlight-next-in-editor.png' ); }); @@ -413,16 +415,16 @@ test.describe('Notebook Search', () => { await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); // Click next button await page.click('button[title^="Next Match"]', { clickCount: 4 }); - const cell = await page.notebook.getCell(1); + const cell = await page.notebook.getCellLocator(1); - expect(await cell.screenshot()).toMatchSnapshot('highlight-next-cell.png'); + expect(await cell!.screenshot()).toMatchSnapshot('highlight-next-cell.png'); }); test('Highlight previous hit', async ({ page }) => { @@ -431,39 +433,39 @@ test.describe('Notebook Search', () => { await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); // Click previous button await page.click('button[title^="Previous Match"]'); // Should cycle back - await page.waitForSelector('text=21/21'); + await page.locator('text=21/21').waitFor(); // Click previous button twice await page.click('button[title^="Previous Match"]'); await page.click('button[title^="Previous Match"]'); // Should move up by two - await page.waitForSelector('text=19/21'); + await page.locator('text=19/21').waitFor(); - const hit = await page.notebook.getCell(2); - expect(await hit.screenshot()).toMatchSnapshot( + const hit = await page.notebook.getCellLocator(2); + expect(await hit!.screenshot()).toMatchSnapshot( 'highlight-previous-element.png' ); }); test('Search from cursor', async ({ page }) => { - const cell = await page.notebook.getCell(5); - await cell.click(); + const cell = await page.notebook.getCellLocator(5); + await cell!.click(); await page.keyboard.press('Escape'); - await cell.scrollIntoViewIfNeeded(); + await cell!.scrollIntoViewIfNeeded(); // Open search box await page.keyboard.press('Control+f'); await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=20/21'); + await page.locator('text=20/21').waitFor(); // Click previous button await page.click('button[title^="Previous Match"]'); - await page.waitForSelector('text=19/21'); + await page.locator('text=19/21').waitFor(); }); test('Highlight on markdown rendered state change', async ({ page }) => { @@ -472,18 +474,18 @@ test.describe('Notebook Search', () => { await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); // Click next button await page.click('button[title^="Next Match"]', { clickCount: 4 }); - const cell = await page.notebook.getCell(1); + const cell = await page.notebook.getCellLocator(1); - await cell.dblclick(); + await cell!.dblclick(); - expect(await (await cell.$('.jp-Editor')).screenshot()).toMatchSnapshot( + expect(await cell!.locator('.jp-Editor').screenshot()).toMatchSnapshot( 'highlight-markdown-switch-state.png' ); }); @@ -497,12 +499,12 @@ test.describe('Notebook Search', () => { // Wait until search has settled before entering a cell for edition // as this can lead to selection of active result near that cell // (rather than at the beginning of the notebook) - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); await page.notebook.setCell(5, 'code', 'with'); - const cell = await page.notebook.getCell(5); - expect(await cell.screenshot()).toMatchSnapshot('search-typing.png'); + const cell = await page.notebook.getCellLocator(5); + expect(await cell!.screenshot()).toMatchSnapshot('search-typing.png'); }); test('Search new outputs', async ({ page }) => { @@ -515,14 +517,14 @@ test.describe('Notebook Search', () => { await page.click('text=Search Cell Outputs'); - await page.waitForSelector('text=1/29'); + await page.locator('text=1/29').waitFor(); - const cell = await page.notebook.getCell(5); + const cell = await page.notebook.getCellLocator(5); - await cell.click(); + await cell!.click(); await page.notebook.runCell(5); - expect(await cell.screenshot()).toMatchSnapshot('search-new-outputs.png'); + expect(await cell!.screenshot()).toMatchSnapshot('search-new-outputs.png'); }); test('Search on new cell', async ({ page }) => { @@ -531,16 +533,16 @@ test.describe('Notebook Search', () => { await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); - const cell = await page.notebook.getCell(5); - await cell.click(); + const cell = await page.notebook.getCellLocator(5); + await cell!.click(); await page.notebook.clickToolbarItem('insert'); await page.notebook.setCell(6, 'code', 'with'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot( + expect(await nbPanel!.screenshot()).toMatchSnapshot( 'search-on-new-cell.png' ); }); @@ -551,21 +553,21 @@ test.describe('Notebook Search', () => { await page.fill('[placeholder="Find"]', 'with'); - await page.waitForSelector('text=1/21'); + await page.locator('text=1/21').waitFor(); - const cell = await page.notebook.getCell(5); - await cell.click(); + const cell = await page.notebook.getCellLocator(5); + await cell!.click(); await page.keyboard.press('Escape'); - await cell.scrollIntoViewIfNeeded(); + await cell!.scrollIntoViewIfNeeded(); await page.keyboard.press('d'); await page.keyboard.press('d'); - await page.waitForSelector('text=1/19'); + await page.locator('text=1/19').waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot( + expect(await nbPanel!.screenshot()).toMatchSnapshot( 'search-on-deleted-cell.png' ); }); @@ -615,8 +617,8 @@ test.describe('Auto search in multiple selection', async () => { test('Toggles search in cell selection', async ({ page }) => { // Bring focus to first cell without switching away from command mode - let cell = await page.notebook.getCell(0); - await (await cell.$('.jp-InputPrompt')).click(); + let cell = await page.notebook.getCellLocator(0); + await cell!.locator('.jp-InputPrompt').click(); // Open search box and show filters await page.keyboard.press('Control+f'); await page.click('button[title="Show Search Filters"]'); @@ -637,8 +639,8 @@ test.describe('Auto search in multiple selection', async () => { test('Toggles search in line selection', async ({ page }) => { // Activate third cell - const cell = await page.notebook.getCell(2); - const editor = await cell.$('.jp-Editor'); + const cell = await page.notebook.getCellLocator(2); + const editor = cell!.locator('.jp-Editor'); await editor.click(); // Select 1st line @@ -678,8 +680,8 @@ test.describe('Auto search in any selection', async () => { test('Toggles search in cell selection', async ({ page }) => { // Bring focus to first cell without switching away from command mode - let cell = await page.notebook.getCell(0); - await (await cell.$('.jp-InputPrompt')).click(); + let cell = await page.notebook.getCellLocator(0); + await cell!.locator('.jp-InputPrompt').click(); // Open search box and show filters await page.keyboard.press('Control+f'); await page.click('button[title="Show Search Filters"]'); @@ -693,8 +695,8 @@ test.describe('Auto search in any selection', async () => { test('Toggles search in line selection', async ({ page }) => { // Activate third cell - const cell = await page.notebook.getCell(2); - const editor = await cell.$('.jp-Editor'); + const cell = await page.notebook.getCellLocator(2); + const editor = cell!.locator('.jp-Editor'); await editor.click(); // Open search box and show filters diff --git a/galata/test/jupyterlab/notebook-toolbar.test.ts b/galata/test/jupyterlab/notebook-toolbar.test.ts index a709c486b829..0e040c2abf14 100644 --- a/galata/test/jupyterlab/notebook-toolbar.test.ts +++ b/galata/test/jupyterlab/notebook-toolbar.test.ts @@ -80,9 +80,9 @@ test.describe('Notebook Toolbar', () => { await page.notebook.clickToolbarItem('insert'); await page.notebook.selectCells(2); await page.notebook.clickToolbarItem('insert'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Copy-Paste cell', async ({ page }) => { @@ -91,18 +91,18 @@ test.describe('Notebook Toolbar', () => { await page.notebook.clickToolbarItem('copy'); await page.notebook.selectCells(0); await page.notebook.clickToolbarItem('paste'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Cut cell', async ({ page }) => { const imageName = 'cut-cell.png'; await page.notebook.selectCells(1); await page.notebook.clickToolbarItem('cut'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Paste cell', async ({ page }) => { @@ -113,21 +113,21 @@ test.describe('Notebook Toolbar', () => { const imageName = 'paste-cell.png'; await page.notebook.selectCells(1); await page.notebook.clickToolbarItem('paste'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - await expect - .soft(page.locator('.jp-Notebook-cell.jp-mod-active .jp-cell-toolbar')) - .toBeVisible(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + await expect( + page.locator('.jp-Notebook-cell.jp-mod-active .jp-cell-toolbar') + ).toBeVisible(); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Delete cells', async ({ page }) => { const imageName = 'delete-cell.png'; await page.notebook.selectCells(1, 2); await page.menu.clickMenuItem('Edit>Delete Cells'); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Run cell', async ({ page }) => { @@ -135,11 +135,11 @@ test.describe('Notebook Toolbar', () => { await page.notebook.selectCells(2); await page.notebook.clickToolbarItem('run'); - await page.waitForSelector('text=8'); + await page.getByText('8', { exact: true }).waitFor(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); test('Change cell type to Markdown', async ({ page }) => { @@ -149,9 +149,9 @@ test.describe('Notebook Toolbar', () => { await page.keyboard.press('m'); await page.keyboard.press('Enter'); await page.notebook.selectCells(2); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); - expect(await nbPanel.screenshot()).toMatchSnapshot(imageName); + expect(await nbPanel!.screenshot()).toMatchSnapshot(imageName); }); }); @@ -159,19 +159,19 @@ test('Toolbar items act on owner widget', async ({ page }) => { // Given two side-by-side notebooks and the second being active const file1 = 'notebook1.ipynb'; await page.notebook.createNew(file1); - const panel1 = await page.activity.getPanel(file1); - const tab1 = await page.activity.getTab(file1); + const panel1 = await page.activity.getPanelLocator(file1); + const tab1 = page.activity.getTabLocator(file1); // FIXME Calling a second time `page.notebook.createNew` is not robust await page.menu.clickMenuItem('File>New>Notebook'); try { - await page.waitForSelector('.jp-Dialog', { timeout: 5000 }); + await page.locator('.jp-Dialog').waitFor({ timeout: 5000 }); await page.click('.jp-Dialog .jp-mod-accept'); } catch (reason) { // no-op } - const tab2 = await page.activity.getTab(); + const tab2 = page.activity.getTabLocator(); const tab2BBox = await tab2.boundingBox(); await page.mouse.move( @@ -186,9 +186,10 @@ test('Toolbar items act on owner widget', async ({ page }) => { expect(classlist.split(' ')).not.toContain('jp-mod-current'); // When clicking on toolbar item of the first file - await ( - await panel1.$('jp-button[data-command="notebook:insert-cell-below"]') - ).click(); + await panel1 + ?.locator('jp-button[data-command="notebook:insert-cell-below"]') + .first() + .click(); // Then the first file is activated and the action is performed const classlistEnd = await tab1.getAttribute('class'); diff --git a/galata/test/jupyterlab/notebook-trust.test.ts b/galata/test/jupyterlab/notebook-trust.test.ts index 776ffd8cbd51..7b4a76f6f286 100644 --- a/galata/test/jupyterlab/notebook-trust.test.ts +++ b/galata/test/jupyterlab/notebook-trust.test.ts @@ -42,10 +42,9 @@ test.describe('Notebook Trust', () => { }); await page.hover('text=Open With'); await page.click('.lm-Menu li[role="menuitem"]:has-text("Editor")'); - const editorContent = await page.waitForSelector( - '.jp-FileEditor .cm-content' - ); - await editorContent.waitForSelector('text=TEST_TEXT'); + const editorContent = page.locator('.jp-FileEditor .cm-content'); + await editorContent.waitFor(); + await editorContent.locator('text=TEST_TEXT').waitFor(); const originalContent = await page.evaluate(async () => { await window.jupyterapp.commands.execute('fileeditor:select-all'); await window.jupyterapp.commands.execute('fileeditor:cut'); diff --git a/galata/test/jupyterlab/notification.test.ts b/galata/test/jupyterlab/notification.test.ts index fc1bf7cd33b6..b6db7d7dabdf 100644 --- a/galata/test/jupyterlab/notification.test.ts +++ b/galata/test/jupyterlab/notification.test.ts @@ -27,7 +27,7 @@ test.describe('Toast', () => { }); }, type); - await page.waitForSelector('.Toastify__toast'); + await page.locator('.Toastify__toast').waitFor(); expect( await page.locator('.Toastify__toast').screenshot({ @@ -70,7 +70,8 @@ test.describe('Toast', () => { }); }); - const handle = await page.waitForSelector('.Toastify__toast'); + const handle = page.locator('.Toastify__toast'); + await handle.waitFor(); expect(await handle.screenshot({ animations: 'disabled' })).toMatchSnapshot( { @@ -79,7 +80,7 @@ test.describe('Toast', () => { ); await Promise.all([ - handle.waitForElementState('hidden'), + handle.last().waitFor({ state: 'hidden' }), page.click('.Toastify__toast >> text=Button 2') ]); @@ -113,7 +114,8 @@ test.describe('Toast', () => { }); }, displayType); - const handle = await page.waitForSelector('.Toastify__toast'); + const handle = page.locator('.Toastify__toast').first(); + await handle.waitFor(); expect( await handle.screenshot({ animations: 'disabled' }) @@ -134,7 +136,7 @@ test.describe('Toast', () => { }); }); - await page.waitForSelector('.Toastify__toast'); + await page.locator('.Toastify__toast').first().waitFor(); expect( await page.locator('.Toastify__toast-body').innerHTML() @@ -151,7 +153,7 @@ test.describe('Toast', () => { }); }); - await page.waitForSelector('.Toastify__toast >> text=Simple note'); + await page.locator('.Toastify__toast >> text=Simple note').waitFor(); await page.evaluate(id => { return window.jupyterapp.commands.execute( @@ -177,12 +179,12 @@ test.describe('Toast', () => { }); }); - await page.waitForSelector('.Toastify__toast >> text=Simple note'); + await page.locator('.Toastify__toast >> text=Simple note').waitFor(); await Promise.all([ - page.waitForSelector('.Toastify__toast >> text=Simple note', { - state: 'detached' - }), + page + .locator('.Toastify__toast >> text=Simple note') + .waitFor({ state: 'detached' }), page.evaluate(id => { return window.jupyterapp.commands.execute( 'apputils:dismiss-notification', diff --git a/galata/test/jupyterlab/output-scrolling.test.ts b/galata/test/jupyterlab/output-scrolling.test.ts index fc27a406ed8e..0ba0ef573136 100644 --- a/galata/test/jupyterlab/output-scrolling.test.ts +++ b/galata/test/jupyterlab/output-scrolling.test.ts @@ -35,9 +35,9 @@ test.describe('Output Scrolling', () => { /jp-mod-outputsScrolled/ ); - const cell = await page.notebook.getCell(0); + const cell = await page.notebook.getCellLocator(0); - expect(await (await cell.$('.jp-OutputArea')).screenshot()).toMatchSnapshot( + expect(await cell!.locator('.jp-OutputArea').screenshot()).toMatchSnapshot( 'cell-output-area-scrolling-mode.png' ); @@ -55,8 +55,8 @@ test.describe('Output Scrolling', () => { await page .locator(`${cellSelector} >> nth=1 >> .jp-OutputArea-promptOverlay`) .hover(); - const cell = await page.notebook.getCell(1); - expect(await cell.screenshot()).toMatchSnapshot( + const cell = await page.notebook.getCellLocator(1); + expect(await cell!.screenshot()).toMatchSnapshot( 'prompt-overlay-hover-normal.png' ); await page.click( @@ -65,7 +65,7 @@ test.describe('Output Scrolling', () => { await expect(page.locator(`${cellSelector} >> nth=1`)).toHaveClass( /jp-mod-outputsScrolled/ ); - expect(await cell.screenshot()).toMatchSnapshot( + expect(await cell!.screenshot()).toMatchSnapshot( 'prompt-overlay-hover-scroll.png' ); await page.click( diff --git a/galata/test/jupyterlab/outputarea-stdin.test.ts b/galata/test/jupyterlab/outputarea-stdin.test.ts index 2fefbf3a8dcd..d1ce2812a5e2 100644 --- a/galata/test/jupyterlab/outputarea-stdin.test.ts +++ b/galata/test/jupyterlab/outputarea-stdin.test.ts @@ -40,33 +40,33 @@ test.describe('Stdin for ipdb', () => { await page.keyboard.press('Control+Enter'); // enter a bunch of nonsense commands into the stdin attached to ipdb - await page.waitForSelector(ACTIVE_INPUT); + await page.locator(ACTIVE_INPUT).waitFor(); await page.keyboard.insertText('foofoo'); await page.keyboard.press('Enter'); - await page.waitForSelector(ACTIVE_INPUT); + await page.locator(ACTIVE_INPUT).waitFor(); await page.keyboard.insertText('barbar'); await page.keyboard.press('Enter'); - await page.waitForSelector(ACTIVE_INPUT); + await page.locator(ACTIVE_INPUT).waitFor(); await page.keyboard.insertText('bazbaz'); await page.keyboard.press('Enter'); // search for the first nonsense command - await page.waitForSelector(ACTIVE_INPUT); + await page.locator(ACTIVE_INPUT).waitFor(); await page.keyboard.insertText('foo'); await page.keyboard.press('Control+ArrowUp'); // Mask out random kernel temporary file path // e.g. `/tmp/ipykernel_104185/2235509928.py` - const filePath = await page.$( + const filePath = page.locator( '.jp-OutputArea-output :text-matches("/tmp/")' ); await filePath.evaluate(node => (node.textContent = '/tmp/masked.py')); const imageName = 'stdin-history-search.png'; - const cell = await page.notebook.getCell(1); - expect(await cell.screenshot()).toMatchSnapshot(imageName); + const cell = await page.notebook.getCellLocator(1); + expect(await cell!.screenshot()).toMatchSnapshot(imageName); // Check that the input remains focused and cursor is at the end. await page.keyboard.insertText('x'); @@ -89,7 +89,7 @@ test.describe('Stdin for ipdb', () => { // for it to complete (as it should stay waiting for input). await page.keyboard.press('Control+Enter'); - await page.waitForSelector(testCase.selector); + await page.locator(testCase.selector).waitFor(); await page.focus(testCase.selector); for (const letter of alphabet) { @@ -114,7 +114,7 @@ test.describe('Stdin for ipdb', () => { await page.keyboard.press('Control+Enter'); // Wait for first input - await page.waitForSelector('.jp-Stdin-input'); + await page.locator('.jp-Stdin-input').waitFor(); // Note: this test does not wait for subsequent inputs on purpose @@ -134,8 +134,8 @@ test.describe('Stdin for ipdb', () => { await page.keyboard.press('Enter'); } - const cellInput = await page.notebook.getCellInput(0); - const editor = await cellInput.$('.cm-content'); + const cellInput = await page.notebook.getCellInputLocator(0); + const editor = cellInput!.locator('.cm-content'); const contentAfter = await editor.evaluate((e: any) => e.cmView.view.state.doc.toString() ); diff --git a/galata/test/jupyterlab/save.test.ts b/galata/test/jupyterlab/save.test.ts index 5b49125e0217..75141b1ae36c 100644 --- a/galata/test/jupyterlab/save.test.ts +++ b/galata/test/jupyterlab/save.test.ts @@ -16,9 +16,9 @@ for (const type of ['Text', 'Notebook', 'Markdown']) { type === 'Notebook' ? `File>New>${type}` : `File>New>${type} File` ); - await page.waitForSelector( - `[role="main"] >> text=${DEFAULT_NAME}${EXTENSION[type]}` - ); + await page + .locator(`[role="main"] >> text=${DEFAULT_NAME}${EXTENSION[type]}`) + .waitFor(); if (type === 'Notebook') { // Select the kernel @@ -29,7 +29,7 @@ for (const type of ['Text', 'Notebook', 'Markdown']) { type === 'Markdown' ? `File>Save ${type} File` : `File>Save ${type}` ); - await page.waitForSelector('.jp-Dialog >> text=Rename file'); + await page.locator('.jp-Dialog >> text=Rename file').waitFor(); await expect( page.locator('.jp-Dialog >> input[placeholder="File name"]') @@ -51,9 +51,9 @@ for (const type of ['Text', 'Notebook', 'Markdown']) { type === 'Notebook' ? `File>New>${type}` : `File>New>${type} File` ); - await page.waitForSelector( - `[role="main"] >> text=${DEFAULT_NAME}${EXTENSION[type]}` - ); + await page + .locator(`[role="main"] >> text=${DEFAULT_NAME}${EXTENSION[type]}`) + .waitFor(); if (type === 'Notebook') { // Select the kernel diff --git a/galata/test/jupyterlab/settings.test.ts b/galata/test/jupyterlab/settings.test.ts index d13140bb1f1a..7691a468e774 100644 --- a/galata/test/jupyterlab/settings.test.ts +++ b/galata/test/jupyterlab/settings.test.ts @@ -1,8 +1,8 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { test } from '@jupyterlab/galata'; -import { expect } from '@playwright/test'; +import { IJupyterLabPageFixture, test } from '@jupyterlab/galata'; +import { expect, Locator } from '@playwright/test'; test('Open the settings editor with a specific search query', async ({ page @@ -35,54 +35,56 @@ test('Open the settings editor with a specific search query', async ({ test.describe('change font-size', () => { const ipynbFileName = 'create_test.ipynb'; - const createNewCodeCell = async page => { + const createNewCodeCell = async (page: IJupyterLabPageFixture) => { await page.notebook.createNew(ipynbFileName); await page.notebook.addCell('code', '2 + 2'); }; - const createMarkdownFile = async page => { - await page.waitForSelector('div[role="main"] >> text=Launcher'); + const createMarkdownFile = async (page: IJupyterLabPageFixture) => { + await page.locator('div[role="main"] >> text=Launcher').waitFor(); await page.click('.jp-LauncherCard[title="Create a new markdown file"]'); - return await page.waitForSelector('.jp-FileEditor .cm-content'); + await page.locator('.jp-FileEditor .cm-content').waitFor(); + return page.locator('.jp-FileEditor .cm-content'); }; - const inputMarkdownFile = async (page, markdownFile) => { + const inputMarkdownFile = async ( + page: IJupyterLabPageFixture, + markdownFile: Locator + ) => { await markdownFile.focus(); await markdownFile.type('markdown cell'); await page.keyboard.press('Control'); await page.keyboard.press('s'); }; - const getCodeCellFontSize = async page => { - const cellElement = await page.$( + const getCodeCellFontSize = async (page: IJupyterLabPageFixture) => { + const cellElement = page.locator( 'div.lm-Widget.jp-Cell.jp-CodeCell.jp-Notebook-cell.jp-mod-noOutputs.jp-mod-active.jp-mod-selected .cm-line' ); - const computedStyle = await page.evaluate( - el => getComputedStyle(el), - cellElement + const computedStyle = await cellElement.evaluate(el => + getComputedStyle(el) ); return parseInt(computedStyle.fontSize); }; - const getMarkdownFontSize = async page => { - await page.waitForSelector('.jp-RenderedHTMLCommon'); - const markdownElement = await page.$('.jp-RenderedHTMLCommon'); - const computedStyle = await page.evaluate( - el => getComputedStyle(el), - markdownElement + const getMarkdownFontSize = async (page: IJupyterLabPageFixture) => { + const markdownElement = page.locator('.jp-RenderedHTMLCommon'); + await markdownElement.waitFor(); + const computedStyle = await markdownElement.evaluate(el => + getComputedStyle(el) ); return parseInt(computedStyle.fontSize); }; - const getFileListFontSize = async page => { - await page.waitForSelector( + const getFileListFontSize = async (page: IJupyterLabPageFixture) => { + const itemElement = page.locator( '.jp-DirListing-content .jp-DirListing-itemText' ); - const itemElement = await page.$( - '.jp-DirListing-content .jp-DirListing-itemText' - ); - const computedStyle = await page.evaluate( - el => getComputedStyle(el), - itemElement + await itemElement.waitFor(); + const computedStyle = await itemElement.evaluate(el => + getComputedStyle(el) ); return parseInt(computedStyle.fontSize); }; - const changeCodeFontSize = async (page, menuOption) => { + const changeCodeFontSize = async ( + page: IJupyterLabPageFixture, + menuOption + ) => { await page.click('text=Settings'); await page.click('.lm-Menu ul[role="menu"] >> text=Theme'); await page.click(`.lm-Menu ul[role="menu"] >> text="${menuOption}"`); @@ -93,15 +95,14 @@ test.describe('change font-size', () => { const fontSize = await getCodeCellFontSize(page); await changeCodeFontSize(page, 'Increase Code Font Size'); - await page.waitForSelector('div[role="main"] >> text=Launcher'); - await page.waitForSelector('.jp-Notebook-cell'); + await page.locator('div[role="main"] >> text=Launcher').waitFor(); + await page.locator('.jp-Notebook-cell').first().waitFor(); - const cellElement = await page.$( + const cellElement = page.locator( 'div.lm-Widget.jp-Cell.jp-CodeCell.jp-Notebook-cell.jp-mod-noOutputs.jp-mod-active.jp-mod-selected .cm-line' ); - const computedStyle = await page.evaluate( - el => getComputedStyle(el), - cellElement + const computedStyle = await cellElement.evaluate(el => + getComputedStyle(el) ); expect(computedStyle.fontSize).toEqual(`${fontSize + 1}px`); }); @@ -111,15 +112,14 @@ test.describe('change font-size', () => { const fontSize = await getCodeCellFontSize(page); await changeCodeFontSize(page, 'Decrease Code Font Size'); - await page.waitForSelector('div[role="main"] >> text=Launcher'); - await page.waitForSelector('.jp-Notebook-cell'); + await page.locator('div[role="main"] >> text=Launcher').waitFor(); + await page.locator('.jp-Notebook-cell').first().waitFor(); - const cellElement = await page.$( + const cellElement = page.locator( 'div.lm-Widget.jp-Cell.jp-CodeCell.jp-Notebook-cell.jp-mod-noOutputs.jp-mod-active.jp-mod-selected .cm-line' ); - const computedStyle = await page.evaluate( - el => getComputedStyle(el), - cellElement + const computedStyle = await cellElement.evaluate(el => + getComputedStyle(el) ); expect(computedStyle.fontSize).toEqual(`${fontSize - 1}px`); }); @@ -134,11 +134,10 @@ test.describe('change font-size', () => { await changeCodeFontSize(page, 'Increase Content Font Size'); - await page.waitForSelector('.jp-FileEditor .cm-content'); - const fileElement = await page.$('.jp-RenderedHTMLCommon'); - const computedStyle = await page.evaluate( - el => getComputedStyle(el), - fileElement + await page.locator('.jp-FileEditor .cm-content').waitFor(); + const fileElement = page.locator('.jp-RenderedHTMLCommon'); + const computedStyle = await fileElement.evaluate(el => + getComputedStyle(el) ); expect(computedStyle.fontSize).toEqual(`${fontSize + 1}px`); }); @@ -153,11 +152,10 @@ test.describe('change font-size', () => { await changeCodeFontSize(page, 'Decrease Content Font Size'); - await page.waitForSelector('.jp-FileEditor .cm-content'); - const fileElement = await page.$('.jp-RenderedHTMLCommon'); - const computedStyle = await page.evaluate( - el => getComputedStyle(el), - fileElement + await page.locator('.jp-FileEditor .cm-content').waitFor(); + const fileElement = page.locator('.jp-RenderedHTMLCommon'); + const computedStyle = await fileElement.evaluate(el => + getComputedStyle(el) ); expect(computedStyle.fontSize).toEqual(`${fontSize - 1}px`); }); @@ -167,15 +165,14 @@ test.describe('change font-size', () => { const fontSize = await getFileListFontSize(page); await changeCodeFontSize(page, 'Increase UI Font Size'); - await page.waitForSelector( - '.jp-DirListing-content .jp-DirListing-itemText' - ); - const fileElement = await page.$( + await page + .locator('.jp-DirListing-content .jp-DirListing-itemText') + .waitFor(); + const fileElement = page.locator( '.jp-DirListing-content .jp-DirListing-itemText' ); - const computedStyle = await page.evaluate( - el => getComputedStyle(el), - fileElement + const computedStyle = await fileElement.evaluate(el => + getComputedStyle(el) ); expect(computedStyle.fontSize).toEqual(`${fontSize + 1}px`); }); @@ -185,15 +182,14 @@ test.describe('change font-size', () => { const fontSize = await getFileListFontSize(page); await changeCodeFontSize(page, 'Decrease UI Font Size'); - await page.waitForSelector( - '.jp-DirListing-content .jp-DirListing-itemText' - ); - const fileElement = await page.$( + await page + .locator('.jp-DirListing-content .jp-DirListing-itemText') + .waitFor(); + const fileElement = page.locator( '.jp-DirListing-content .jp-DirListing-itemText' ); - const computedStyle = await page.evaluate( - el => getComputedStyle(el), - fileElement + const computedStyle = await fileElement.evaluate(el => + getComputedStyle(el) ); expect(computedStyle.fontSize).toEqual(`${fontSize - 1}px`); }); diff --git a/galata/test/jupyterlab/sidebars.test.ts b/galata/test/jupyterlab/sidebars.test.ts index 65e26d0b8303..5220d82fdcac 100644 --- a/galata/test/jupyterlab/sidebars.test.ts +++ b/galata/test/jupyterlab/sidebars.test.ts @@ -2,6 +2,7 @@ // Distributed under the terms of the Modified BSD License. import { expect, galata, Handle, test } from '@jupyterlab/galata'; +import { Locator } from '@playwright/test'; const sidebarIds: galata.SidebarTabId[] = [ 'filebrowser', @@ -16,14 +17,13 @@ const sidebarIds: galata.SidebarTabId[] = [ * By default we only have icons, but we should test for the * styling of labels which are used downstream (e.g. sidecar). */ -async function mockLabelOnFirstTab(tabbar: Handle, text: string) { - await tabbar.$eval( - '.lm-TabBar-tabLabel', - (node: HTMLElement, text: string) => { +async function mockLabelOnFirstTab(tabbar: Locator, text: string) { + await tabbar + .locator('.lm-TabBar-tabLabel') + .first() + .evaluate((node: HTMLElement, text: string) => { node.innerText = text; - }, - text - ); + }, text); } test.describe('Sidebars', () => { @@ -34,7 +34,9 @@ test.describe('Sidebars', () => { const imageName = `opened-sidebar-${sidebarId.replace('.', '-')}.png`; const position = await page.sidebar.getTabPosition(sidebarId); - const sidebar = await page.sidebar.getContentPanel(position); + const sidebar = page.sidebar.getContentPanelLocator( + position ?? undefined + ); expect(await sidebar.screenshot()).toMatchSnapshot( imageName.toLowerCase() ); @@ -44,11 +46,14 @@ test.describe('Sidebars', () => { test('File Browser has no unused rules', async ({ page }) => { await page.sidebar.openTab('filebrowser'); const clickMenuItem = async (command): Promise => { - const contextmenu = await page.menu.openContextMenu( + const contextmenu = await page.menu.openContextMenuLocator( '.jp-DirListing-headerItem' ); - const item = await page.menu.getMenuItemInMenu(contextmenu, command); - await item.click(); + const item = await page.menu.getMenuItemLocatorInMenu( + contextmenu, + command + ); + await item?.click(); }; await clickMenuItem('Show File Checkboxes'); await clickMenuItem('Show File Size Column'); @@ -75,7 +80,7 @@ test.describe('Sidebars', () => { test('Left light tabbar (with text)', async ({ page }) => { await page.theme.setLightTheme(); const imageName = 'left-light-tabbar-with-text.png'; - const tabbar = await page.sidebar.getTabBar(); + const tabbar = page.sidebar.getTabBarLocator(); await mockLabelOnFirstTab(tabbar, 'File Browser'); expect(await tabbar.screenshot()).toMatchSnapshot(imageName.toLowerCase()); }); @@ -83,7 +88,7 @@ test.describe('Sidebars', () => { test('Right dark tabbar (with text)', async ({ page }) => { await page.theme.setDarkTheme(); const imageName = 'right-dark-tabbar-with-text.png'; - const tabbar = await page.sidebar.getTabBar('right'); + const tabbar = page.sidebar.getTabBarLocator('right'); await mockLabelOnFirstTab(tabbar, 'Property Inspector'); expect(await tabbar.screenshot()).toMatchSnapshot(imageName.toLowerCase()); }); diff --git a/galata/test/jupyterlab/terminal.test.ts b/galata/test/jupyterlab/terminal.test.ts index e026415b6dad..674a2f76a050 100644 --- a/galata/test/jupyterlab/terminal.test.ts +++ b/galata/test/jupyterlab/terminal.test.ts @@ -11,7 +11,7 @@ const TERMINAL_THEME_ATTRIBUTE = 'data-term-theme'; test.describe('Terminal', () => { test.beforeEach(async ({ page }) => { await page.menu.clickMenuItem('File>New>Terminal'); - await page.waitForSelector(TERMINAL_SELECTOR); + await page.locator(TERMINAL_SELECTOR).waitFor(); }); test.describe('Open', () => { @@ -90,7 +90,7 @@ test.describe('Terminal', () => { }); test('Terminal should open in Launcher cwd', async ({ page, tmpPath }) => { - await page.waitForSelector(`.jp-Launcher-cwd > h3:has-text("${tmpPath}")`); + await page.locator(`.jp-Launcher-cwd > h3:has-text("${tmpPath}")`).waitFor(); await page.locator('[role="main"] >> p:has-text("Terminal")').click(); @@ -105,7 +105,7 @@ test('Terminal should open in Launcher cwd', async ({ page, tmpPath }) => { }); test('Terminal web link', async ({ page, tmpPath }) => { - await page.waitForSelector(`.jp-Launcher-cwd > h3:has-text("${tmpPath}")`); + await page.locator(`.jp-Launcher-cwd > h3:has-text("${tmpPath}")`).waitFor(); await page.locator('[role="main"] >> p:has-text("Terminal")').click(); diff --git a/galata/test/jupyterlab/texteditor.test.ts b/galata/test/jupyterlab/texteditor.test.ts index 501a083bf874..87a5832ac60f 100644 --- a/galata/test/jupyterlab/texteditor.test.ts +++ b/galata/test/jupyterlab/texteditor.test.ts @@ -10,10 +10,10 @@ test.describe('Text Editor Tests', () => { const imageName = 'text-editor.png'; await page.menu.clickMenuItem('File>New>Text File'); - await page.waitForSelector(`[role="main"] >> text=${DEFAULT_NAME}`); + await page.locator(`[role="main"] >> text=${DEFAULT_NAME}`).waitFor(); - const tabHandle = await page.activity.getPanel(DEFAULT_NAME); - expect(await tabHandle.screenshot()).toMatchSnapshot(imageName); + const tabHandle = await page.activity.getPanelLocator(DEFAULT_NAME); + expect(await tabHandle?.screenshot()).toMatchSnapshot(imageName); }); test('Changing a text editor settings', async ({ page }) => { @@ -37,9 +37,9 @@ test.describe('Text Editor Tests', () => { await page.activity.activateTab(DEFAULT_NAME); - const tabHandle = await page.activity.getPanel(DEFAULT_NAME); + const tabHandle = await page.activity.getPanelLocator(DEFAULT_NAME); - expect(await tabHandle.screenshot()).toMatchSnapshot(imageName); + expect(await tabHandle?.screenshot()).toMatchSnapshot(imageName); }); test('Selection in highlighted line', async ({ page }) => { @@ -55,7 +55,7 @@ test.describe('Text Editor Tests', () => { await page.menu.clickMenuItem('File>New>Text File'); - await page.waitForSelector(`[role="main"] >> text=${DEFAULT_NAME}`); + await page.locator(`[role="main"] >> text=${DEFAULT_NAME}`).waitFor(); await page.type( '.cm-content', @@ -78,7 +78,7 @@ test.describe('Text Editor Tests', () => { const imageName = 'go-to-line-editor.png'; await page.menu.clickMenuItem('File>New>Text File'); - await page.waitForSelector(`[role="main"] >> text=${DEFAULT_NAME}`); + await page.locator(`[role="main"] >> text=${DEFAULT_NAME}`).waitFor(); await page.type( '.cm-content', @@ -100,7 +100,7 @@ ut elit.` await page.keyboard.type('#2:8#'); - const tabHandle = await page.activity.getPanel(DEFAULT_NAME); - expect(await tabHandle.screenshot()).toMatchSnapshot(imageName); + const tabHandle = await page.activity.getPanelLocator(DEFAULT_NAME); + expect(await tabHandle?.screenshot()).toMatchSnapshot(imageName); }); }); diff --git a/galata/test/jupyterlab/toc-running.test.ts b/galata/test/jupyterlab/toc-running.test.ts index c3abea4aba23..e4a3fe395862 100644 --- a/galata/test/jupyterlab/toc-running.test.ts +++ b/galata/test/jupyterlab/toc-running.test.ts @@ -13,9 +13,11 @@ test.describe('ToC Running indicator', () => { await page.sidebar.openTab('table-of-contents'); // Wait until the last heading has loaded into the ToC - await page.waitForSelector( - '.jp-TableOfContents-content[data-document-type="notebook"] >> text=Title 1.3' - ); + await page + .locator( + '.jp-TableOfContents-content[data-document-type="notebook"] >> text=Title 1.3' + ) + .waitFor(); }); test.beforeAll(async ({ request, tmpPath }) => { @@ -33,11 +35,11 @@ test.describe('ToC Running indicator', () => { }); test('should display running indicators', async ({ page }) => { - const tocPanel = await page.sidebar.getContentPanel( - await page.sidebar.getTabPosition('table-of-contents') + const tocPanel = page.sidebar.getContentPanelLocator( + (await page.sidebar.getTabPosition('table-of-contents')) ?? undefined ); const executed = page.notebook.run(); - await tocPanel.waitForSelector('[data-running="1"]'); + await tocPanel.locator('[data-running="1"]').waitFor(); expect(await tocPanel.screenshot()).toMatchSnapshot( 'toc-running-indicators.png' ); @@ -46,11 +48,11 @@ test.describe('ToC Running indicator', () => { }); test('should display error indicators', async ({ page }) => { - const tocPanel = await page.sidebar.getContentPanel( - await page.sidebar.getTabPosition('table-of-contents') + const tocPanel = page.sidebar.getContentPanelLocator( + (await page.sidebar.getTabPosition('table-of-contents')) ?? undefined ); const executed = page.notebook.run(); - await tocPanel.waitForSelector('[data-running="-0.5"]'); + await tocPanel.locator('[data-running="-0.5"]').waitFor(); expect(await tocPanel.screenshot()).toMatchSnapshot( 'toc-running-indicator-error.png' ); @@ -61,8 +63,8 @@ test.describe('ToC Running indicator', () => { test('should display running indicator on first visible top level', async ({ page }) => { - const tocPanel = await page.sidebar.getContentPanel( - await page.sidebar.getTabPosition('table-of-contents') + const tocPanel = page.sidebar.getContentPanelLocator( + (await page.sidebar.getTabPosition('table-of-contents')) ?? undefined ); await page.notebook.run(); @@ -73,7 +75,7 @@ test.describe('ToC Running indicator', () => { const executed = page.notebook.runCell(5); - await tocPanel.waitForSelector('[data-running="1"]'); + await tocPanel.locator('[data-running="1"]').waitFor(); expect(await tocPanel.screenshot()).toMatchSnapshot( 'toc-running-indicator-top-level.png' ); diff --git a/galata/test/jupyterlab/toc-scrolling.test.ts b/galata/test/jupyterlab/toc-scrolling.test.ts index f9ba1c2bbe79..afeec8d49295 100644 --- a/galata/test/jupyterlab/toc-scrolling.test.ts +++ b/galata/test/jupyterlab/toc-scrolling.test.ts @@ -39,18 +39,21 @@ test.describe('Table of Contents scrolling to heading', () => { await page.keyboard.press('Enter'); await page.getByText('Mode: Edit').waitFor(); - await page.sidebar.getContentPanel( - await page.sidebar.getTabPosition('table-of-contents') + const contentPanel = page.sidebar.getContentPanelLocator( + (await page.sidebar.getTabPosition('table-of-contents')) ?? undefined ); + await contentPanel.waitFor(); await page .locator('.jp-TableOfContents-tree') .getByText('the last one') .click(); + await page.waitForTimeout(100); + // Should switch to command mode await expect.soft(page.getByText('Mode: Command')).toBeVisible(); - const nbPanel = await page.notebook.getNotebookInPanel(); + const nbPanel = await page.notebook.getNotebookInPanelLocator(); expect .soft(await nbPanel!.screenshot()) .toMatchSnapshot('scrolled-to-bottom-heading.png'); @@ -68,6 +71,7 @@ test.describe('Table of Contents scrolling to heading', () => { .locator('.jp-TableOfContents-tree') .getByText('the last one') .click(); + await page.waitForTimeout(100); expect(await nbPanel!.screenshot()).toMatchSnapshot( 'scrolled-to-bottom-heading.png' diff --git a/galata/test/jupyterlab/toc.test.ts b/galata/test/jupyterlab/toc.test.ts index 1ba96cd119ce..4e2de186237a 100644 --- a/galata/test/jupyterlab/toc.test.ts +++ b/galata/test/jupyterlab/toc.test.ts @@ -37,8 +37,8 @@ test.describe('Table of Contents', () => { test('Open Table of Contents panel', async ({ page }) => { const imageName = 'toc-panel.png'; - const tocPanel = await page.sidebar.getContentPanel( - await page.sidebar.getTabPosition('table-of-contents') + const tocPanel = page.sidebar.getContentPanelLocator( + (await page.sidebar.getTabPosition('table-of-contents')) ?? undefined ); expect(await tocPanel.screenshot()).toMatchSnapshot(imageName); @@ -47,16 +47,16 @@ test.describe('Table of Contents', () => { test('Toggle list', async ({ page }) => { await page.notebook.selectCells(0); - const tocPanel = await page.sidebar.getContentPanel( - await page.sidebar.getTabPosition('table-of-contents') + const tocPanel = page.sidebar.getContentPanelLocator( + (await page.sidebar.getTabPosition('table-of-contents')) ?? undefined ); - const numberingButton = await tocPanel.$$( + const numberingButton = tocPanel.locator( 'jp-button[data-command="toc:display-numbering"]' ); - expect(numberingButton.length).toBe(1); + await expect(numberingButton).toHaveCount(1); const imageName = 'toggle-numbered-list.png'; - await numberingButton[0].click(); + await numberingButton.click(); expect(await tocPanel.screenshot()).toMatchSnapshot(imageName); }); @@ -64,8 +64,8 @@ test.describe('Table of Contents', () => { test('Notebook context menu', async ({ page }) => { await page.notebook.selectCells(0); - const tocPanel = await page.sidebar.getContentPanel( - await page.sidebar.getTabPosition('table-of-contents') + const tocPanel = page.sidebar.getContentPanelLocator( + (await page.sidebar.getTabPosition('table-of-contents')) ?? undefined ); await Promise.all([ @@ -79,11 +79,11 @@ test.describe('Table of Contents', () => { }) ]); - const menu = await page.menu.getOpenMenu(); + const menu = await page.menu.getOpenMenuLocator(); - await ( - await menu.$('text=Select and Run Cell(s) for this Heading') - ).click(); + await menu + ?.locator('text=Select and Run Cell(s) for this Heading') + ?.click(); await page .locator('.jp-TableOfContents-tree >> text="2. HTML title"') diff --git a/galata/test/jupyterlab/utils.ts b/galata/test/jupyterlab/utils.ts index 964261a2abad..2e455e254c8c 100644 --- a/galata/test/jupyterlab/utils.ts +++ b/galata/test/jupyterlab/utils.ts @@ -1,7 +1,7 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { IJupyterLabPageFixture } from '@jupyterlab/galata'; -import { ElementHandle, Locator } from '@playwright/test'; +import { Locator } from '@playwright/test'; const OUTER_SELECTOR = '.jp-WindowedPanel-outer'; const DRAGGABLE_AREA = '.jp-InputArea-prompt'; @@ -10,12 +10,10 @@ const DRAGGABLE_AREA = '.jp-InputArea-prompt'; * Measure how much of the **notebook** viewport does a cell take up. */ export async function notebookViewportRatio( - notebook: ElementHandle, - cell: ElementHandle + notebook: Locator, + cell: Locator ): Promise { - const scroller = (await notebook.$( - OUTER_SELECTOR - )) as ElementHandle; + const scroller = notebook.locator(OUTER_SELECTOR).first(); const n = await scroller.boundingBox(); const c = await cell.boundingBox(); const cellBottom = c.y + c.height; @@ -36,13 +34,11 @@ export async function notebookViewportRatio( */ export async function positionCellPartiallyBelowViewport( page: IJupyterLabPageFixture, - notebook: ElementHandle, - cell: ElementHandle, + notebook: Locator, + cell: Locator, ratio: number ): Promise { - const scroller = (await notebook.$( - OUTER_SELECTOR - )) as ElementHandle; + const scroller = notebook.locator(OUTER_SELECTOR).first(); const notebookBbox = await scroller.boundingBox(); const cellBbox = await cell.boundingBox(); await page.mouse.move(notebookBbox.x, notebookBbox.y); diff --git a/galata/test/jupyterlab/windowed-notebook.test.ts b/galata/test/jupyterlab/windowed-notebook.test.ts index c36864b890cf..de25a7f0efc6 100644 --- a/galata/test/jupyterlab/windowed-notebook.test.ts +++ b/galata/test/jupyterlab/windowed-notebook.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { expect, galata, test } from '@jupyterlab/galata'; -import { ElementHandle } from '@playwright/test'; +import { Locator } from '@playwright/test'; import * as path from 'path'; const fileName = 'windowed_notebook.ipynb'; @@ -28,21 +28,19 @@ test.beforeEach(async ({ page, tmpPath }) => { ); }); -async function getInnerHeight(panel: ElementHandle) { +async function getInnerHeight(panel: Locator) { return parseInt( - await panel.$eval( - '.jp-WindowedPanel-inner', - node => (node as HTMLElement).style.height - ), + await panel + .locator('.jp-WindowedPanel-inner') + .evaluate(node => (node as HTMLElement).style.height), 10 ); } -async function getWindowHeight(panel: ElementHandle) { +async function getWindowHeight(panel: Locator) { return parseInt( - await panel.$eval( - '.jp-WindowedPanel-viewport', - node => (node as HTMLElement).style.minHeight - ), + await panel + .locator('.jp-WindowedPanel-viewport') + .evaluate(node => (node as HTMLElement).style.minHeight), 10 ); } @@ -56,10 +54,10 @@ test('should update window height on resize', async ({ page, tmpPath }) => { ); await page.notebook.openByPath(`${tmpPath}/${notebookName}`); - const notebook = await page.notebook.getNotebookInPanel(); + const notebook = await page.notebook.getNotebookInPanelLocator(); // Measure height when the notebook is open but launcher closed - const fullHeight = await getWindowHeight(notebook); + const fullHeight = await getWindowHeight(notebook!); // Add a new launcher below the notebook await page.evaluate(async () => { @@ -67,7 +65,7 @@ test('should update window height on resize', async ({ page, tmpPath }) => { window.jupyterapp.shell.add(widget, 'main', { mode: 'split-bottom' }); }); // Measure height after splitting the dock area - const heightAfterSplit = await getWindowHeight(notebook); + const heightAfterSplit = await getWindowHeight(notebook!); expect(heightAfterSplit).toBeLessThan(fullHeight); @@ -78,7 +76,7 @@ test('should update window height on resize', async ({ page, tmpPath }) => { await resizeHandle.dragTo(page.locator('#jp-main-statusbar')); // Measure height after resizing - const heightAfterResize = await getWindowHeight(notebook); + const heightAfterResize = await getWindowHeight(notebook!); expect(heightAfterResize).toBeGreaterThan(heightAfterSplit); }); @@ -89,15 +87,15 @@ test('should not update height when hiding', async ({ page, tmpPath }) => { // Wait to ensure the rendering logic is stable. await page.waitForTimeout(200); - const notebook = await page.notebook.getNotebookInPanel(); - const initialHeight = await getInnerHeight(notebook); + const notebook = await page.notebook.getNotebookInPanelLocator(); + const initialHeight = await getInnerHeight(notebook!); expect(initialHeight).toBeGreaterThan(0); // Add a new launcher covering the notebook. await page.menu.clickMenuItem('File>New Launcher'); - const innerHeight = await getInnerHeight(notebook); + const innerHeight = await getInnerHeight(notebook!); expect(innerHeight).toEqual(initialHeight); }); @@ -111,14 +109,14 @@ test('should hide first inactive code cell when scrolling down', async ({ // Activate >second< cell await page.notebook.selectCells(1); // Test if the >first< (now inactive) cell gets detached - const h = await page.notebook.getNotebookInPanel(); - const firstCellSelector = '.jp-Cell[data-windowed-list-index="0"]'; - const firstCell = await h!.waitForSelector(firstCellSelector); + const h = await page.notebook.getNotebookInPanelLocator(); + const firstCell = h!.locator('.jp-Cell[data-windowed-list-index="0"]'); + await firstCell.waitFor(); const bbox = await h!.boundingBox(); await page.mouse.move(bbox!.x, bbox!.y); await Promise.all([ - firstCell.waitForElementState('hidden'), + firstCell.waitFor({ state: 'hidden' }), page.mouse.wheel(0, 600) ]); @@ -135,25 +133,25 @@ test('should reattached inactive code cell when scrolling back into the viewport // Activate >second< cell await page.notebook.selectCells(1); // Test if the >first< (now inactive) cell gets re-attached - const h = await page.notebook.getNotebookInPanel(); - const firstCellSelector = '.jp-Cell[data-windowed-list-index="0"]'; - const firstCell = await h!.waitForSelector(firstCellSelector); + const h = await page.notebook.getNotebookInPanelLocator(); + const firstCell = h!.locator('.jp-Cell[data-windowed-list-index="0"]'); + await firstCell.waitFor(); const bbox = await h!.boundingBox(); await page.mouse.move(bbox!.x, bbox!.y); await Promise.all([ - firstCell.waitForElementState('hidden'), - h!.waitForSelector('.jp-MarkdownCell[data-windowed-list-index="6"]'), + firstCell.waitFor({ state: 'hidden' }), + h!.locator('.jp-MarkdownCell[data-windowed-list-index="6"]').waitFor(), page.mouse.wheel(0, 1200) ]); await Promise.all([ - firstCell.waitForElementState('visible'), + firstCell.waitFor({ state: 'visible' }), page.mouse.wheel(0, -1200) ]); // Check that the input area is back - expect(await firstCell.waitForSelector('.jp-InputArea')).toBeDefined(); + await expect(firstCell.locator('.jp-InputArea')).toHaveCount(1); }); test('should not detach active code cell input when scrolling down', async ({ @@ -163,19 +161,19 @@ test('should not detach active code cell input when scrolling down', async ({ await page.notebook.openByPath(`${tmpPath}/${fileName}`); await page.notebook.selectCells(0); - const h = await page.notebook.getNotebookInPanel(); - const firstCellSelector = '.jp-Cell[data-windowed-list-index="0"]'; - const firstCell = await h!.waitForSelector(firstCellSelector); + const h = await page.notebook.getNotebookInPanelLocator(); + const firstCell = h!.locator('.jp-Cell[data-windowed-list-index="0"]'); + await firstCell.waitFor(); const bbox = await h!.boundingBox(); await page.mouse.move(bbox!.x, bbox!.y); await Promise.all([ - firstCell.waitForElementState('hidden'), + firstCell.waitFor({ state: 'hidden' }), page.mouse.wheel(0, 1200) ]); // Check the input is still defined - expect(await firstCell.waitForSelector('.jp-InputArea')).toBeDefined(); + await expect(firstCell.locator('.jp-InputArea')).toHaveCount(1); }); for (const cellType of ['code', 'markdown']) { @@ -187,14 +185,14 @@ for (const cellType of ['code', 'markdown']) { await page.notebook.setCellType(0, cellType); await page.notebook.enterCellEditingMode(0); - const h = await page.notebook.getNotebookInPanel(); - const firstCellSelector = '.jp-Cell[data-windowed-list-index="0"]'; - const firstCell = await h!.waitForSelector(firstCellSelector); + const h = await page.notebook.getNotebookInPanelLocator(); + const firstCell = h!.locator('.jp-Cell[data-windowed-list-index="0"]'); + await firstCell.waitFor(); const bbox = await h!.boundingBox(); await page.mouse.move(bbox!.x, bbox!.y); await Promise.all([ - firstCell.waitForElementState('hidden'), + firstCell.waitFor({ state: 'hidden' }), page.mouse.wheel(0, 1200) ]); @@ -202,11 +200,11 @@ for (const cellType of ['code', 'markdown']) { await page.keyboard.type('TEST', { delay: 150 }); // Expect the cell to become visible again - await firstCell.waitForElementState('visible'); + await firstCell.waitFor({ state: 'visible' }); // Expect the text to populate the cell editor - const firstCellInput = await page.notebook.getCellInput(0); - expect(await firstCellInput.textContent()).toContain('TEST'); + const firstCellInput = await page.notebook.getCellInputLocator(0); + expect(await firstCellInput!.textContent()).toContain('TEST'); }); } @@ -218,19 +216,17 @@ test('should scroll back to the cell below the active cell on arrow down key', a // Activate the first cell. await page.notebook.selectCells(0); - const h = await page.notebook.getNotebookInPanel(); - const firstCell = await h!.waitForSelector( - '.jp-Cell[data-windowed-list-index="0"]' - ); - const secondCell = await h!.waitForSelector( - '.jp-Cell[data-windowed-list-index="1"]' - ); + const h = await page.notebook.getNotebookInPanelLocator(); + const firstCell = h!.locator('.jp-Cell[data-windowed-list-index="0"]'); + const secondCell = h!.locator('.jp-Cell[data-windowed-list-index="1"]'); + await firstCell.waitFor(); + await secondCell.waitFor(); const bbox = await h!.boundingBox(); await page.mouse.move(bbox!.x, bbox!.y); await Promise.all([ - firstCell.waitForElementState('hidden'), - secondCell.waitForElementState('hidden'), + firstCell.waitFor({ state: 'hidden' }), + secondCell.waitFor({ state: 'hidden' }), page.mouse.wheel(0, 1200) ]); @@ -238,7 +234,7 @@ test('should scroll back to the cell below the active cell on arrow down key', a await page.keyboard.press('ArrowDown'); // Expect the second cell to become visible again. - await secondCell.waitForElementState('visible'); + await secondCell.waitFor({ state: 'visible' }); }); test('should detach a markdown code cell when scrolling out of the viewport', async ({ @@ -247,20 +243,20 @@ test('should detach a markdown code cell when scrolling out of the viewport', as }) => { await page.notebook.openByPath(`${tmpPath}/${fileName}`); - const h = await page.notebook.getNotebookInPanel(); - const mdCellSelector = '.jp-MarkdownCell[data-windowed-list-index="2"]'; - const mdCell = await h!.waitForSelector(mdCellSelector); + const h = await page.notebook.getNotebookInPanelLocator(); + const mdCell = h!.locator('.jp-MarkdownCell[data-windowed-list-index="2"]'); + await mdCell.waitFor(); const bbox = await h!.boundingBox(); await page.mouse.move(bbox!.x, bbox!.y); await Promise.all([ - mdCell.waitForElementState('hidden'), + mdCell.waitFor({ state: 'hidden' }), page.mouse.wheel(0, 1200) ]); let found = true; try { - await h!.waitForSelector(mdCellSelector, { timeout: 150 }); + await mdCell.waitFor({ timeout: 150 }); } catch (r) { found = false; } @@ -273,15 +269,15 @@ test('should reattach a markdown code cell when scrolling back into the viewport }) => { await page.notebook.openByPath(`${tmpPath}/${fileName}`); - const h = await page.notebook.getNotebookInPanel(); - const mdCellSelector = '.jp-MarkdownCell[data-windowed-list-index="2"]'; - const mdCell = await h!.waitForSelector(mdCellSelector); + const h = await page.notebook.getNotebookInPanelLocator(); + const mdCell = h!.locator('.jp-MarkdownCell[data-windowed-list-index="2"]'); + await mdCell.waitFor(); const bbox = await h!.boundingBox(); await page.mouse.move(bbox!.x, bbox!.y); await Promise.all([ - mdCell.waitForElementState('hidden'), - h!.waitForSelector('.jp-MarkdownCell[data-windowed-list-index="6"]'), + mdCell.waitFor({ state: 'hidden' }), + h!.locator('.jp-MarkdownCell[data-windowed-list-index="6"]').waitFor(), page.mouse.wheel(0, 1200) ]); @@ -289,7 +285,7 @@ test('should reattach a markdown code cell when scrolling back into the viewport await page.mouse.wheel(0, -1200); - expect(await h!.waitForSelector(mdCellSelector)).toBeDefined(); + await expect(mdCell).toBeVisible(); }); test('should remove all cells including hidden outputs artifacts', async ({ @@ -298,12 +294,12 @@ test('should remove all cells including hidden outputs artifacts', async ({ }) => { await page.notebook.openByPath(`${tmpPath}/${fileName}`); - const h = await page.notebook.getNotebookInPanel(); + const h = await page.notebook.getNotebookInPanelLocator(); const bbox = await h!.boundingBox(); await page.mouse.move(bbox!.x, bbox!.y); await Promise.all([ - h!.waitForSelector('.jp-MarkdownCell[data-windowed-list-index="6"]'), + h!.locator('.jp-MarkdownCell[data-windowed-list-index="6"]').waitFor(), page.mouse.wheel(0, 1200) ]); @@ -314,14 +310,14 @@ test('should remove all cells including hidden outputs artifacts', async ({ await page.keyboard.press('d'); // Check that the notebook only contains one cell - expect(await (await h!.$('.jp-WindowedPanel-inner'))!.textContent()).toEqual( + expect(await h!.locator('.jp-WindowedPanel-inner')!.textContent()).toEqual( '[ ]:' ); // Check there are no hidden cells let found = true; try { - await h!.waitForSelector('.jp-Cell', { state: 'hidden', timeout: 150 }); + await h!.locator('.jp-Cell').waitFor({ state: 'hidden', timeout: 150 }); } catch (r) { found = false; } diff --git a/galata/test/jupyterlab/workspace.test.ts b/galata/test/jupyterlab/workspace.test.ts index 5c124446e75a..081f42482006 100644 --- a/galata/test/jupyterlab/workspace.test.ts +++ b/galata/test/jupyterlab/workspace.test.ts @@ -13,9 +13,7 @@ test.use({ tmpPath: 'workspace-test', waitForApplication: async ({ baseURL }, use, testInfo) => { const simpleWait = async (page: Page): Promise => { - await page.waitForSelector('#jupyterlab-splash', { - state: 'detached' - }); + await page.locator('#jupyterlab-splash').waitFor({ state: 'detached' }); }; void use(simpleWait); } @@ -151,9 +149,11 @@ test.describe('Workspace', () => { ).toBeVisible(); // Wait for the kernel to be ready so it does not unfocus the menu - await page.waitForSelector('text= | Idle'); + await page.locator('text= | Idle').waitFor(); - await expect(page.menu.getMenuItem(`Tabs>${mdFile}`)).toBeDefined(); + const menuItem = await page.menu.getMenuItemLocator(`Tabs>${mdFile}`); + expect(menuItem).toBeDefined(); + expect(menuItem).toBeNull(); }); test('should clone the default workspace', async ({ page, tmpPath }) => { @@ -169,7 +169,7 @@ test.describe('Workspace', () => { /api\/workspaces/.test(response.request().url()) && response.request().postDataJSON().data['terminal:1'] ), - page.waitForSelector('[role="main"] >> .jp-Terminal'), + page.locator('[role="main"] >> .jp-Terminal').waitFor(), page.menu.clickMenuItem('File>New>Terminal') ]); @@ -205,7 +205,7 @@ test.describe('Workspace', () => { /api\/workspaces/.test(response.request().url()) && response.request().postDataJSON().data['terminal:1'] ), - page.waitForSelector('[role="main"] >> .jp-Terminal'), + page.locator('[role="main"] >> .jp-Terminal').waitFor(), page.menu.clickMenuItem('File>New>Terminal') ]); diff --git a/packages/ui-components/src/components/toolbar.tsx b/packages/ui-components/src/components/toolbar.tsx index ff118e2dc29f..407a3ccac810 100644 --- a/packages/ui-components/src/components/toolbar.tsx +++ b/packages/ui-components/src/components/toolbar.tsx @@ -239,6 +239,7 @@ export class Toolbar extends Widget { layout.insertWidget(j, widget); Private.nameProperty.set(widget, name); + widget.node.dataset['jpItemName'] = name; return true; }