From 326c977a60bfbb86a2635eb9e70a91b4a6db83d2 Mon Sep 17 00:00:00 2001 From: Marie Idleman Date: Tue, 14 Jan 2025 14:28:33 -0600 Subject: [PATCH] e2e-test: refactor select kernel (#5986) This PR updates the logic that selects the kernel in Notebooks to make it easier to read and maintain. ### QA Notes Ran all touched tests on PR. @:notebooks @:plots @:data-explorer @:win --- test/e2e/infra/test-runner/test-tags.ts | 8 ++- test/e2e/pages/notebooks.ts | 58 +++++++++++-------- test/e2e/pages/quickaccess.ts | 2 +- .../action-bar/editor-action-bar.test.ts | 2 +- .../data-explorer-python-pandas.test.ts | 2 +- test/e2e/tests/help/f1.test.ts | 2 +- .../tests/notebook/notebook-create.test.ts | 6 +- .../notebook/notebook-large-python.test.ts | 4 +- .../tests/plots/matplotlib-interact.test.ts | 4 +- .../variables/variables-notebook.test.ts | 6 +- 10 files changed, 55 insertions(+), 39 deletions(-) diff --git a/test/e2e/infra/test-runner/test-tags.ts b/test/e2e/infra/test-runner/test-tags.ts index aa938eaa228..ab1050eb692 100644 --- a/test/e2e/infra/test-runner/test-tags.ts +++ b/test/e2e/infra/test-runner/test-tags.ts @@ -17,6 +17,8 @@ * -> Incorrect: `@tag` */ export enum TestTags { + + // features and functionality EDITOR_ACTION_BAR = '@editor-action-bar', APPS = '@apps', CONNECTIONS = '@connections', @@ -32,7 +34,7 @@ export enum TestTags { EDITOR = '@editor', QUARTO = '@quarto', NEW_PROJECT_WIZARD = '@new-project-wizard', - NOTEBOOK = '@notebook', + NOTEBOOKS = '@notebooks', OUTLINE = '@outline', OUTPUT = '@output', PLOTS = '@plots', @@ -42,7 +44,9 @@ export enum TestTags { TEST_EXPLORER = '@test-explorer', TOP_ACTION_BAR = '@top-action-bar', VARIABLES = '@variables', - WEB = '@web', WELCOME = '@welcome', + + // platform + WEB = '@web', WIN = '@win' } diff --git a/test/e2e/pages/notebooks.ts b/test/e2e/pages/notebooks.ts index eea699d2427..3ce13f2d856 100644 --- a/test/e2e/pages/notebooks.ts +++ b/test/e2e/pages/notebooks.ts @@ -9,10 +9,8 @@ import { QuickAccess } from './quickaccess'; import { basename } from 'path'; import test, { expect } from '@playwright/test'; - -const KERNEL_LABEL = '.kernel-label'; -const KERNEL_ACTION = '.kernel-action-view-item'; -const SELECT_KERNEL_TEXT = 'Select Kernel'; +const KERNEL_DROPDOWN = 'a.kernel-label'; +const KERNEL_LABEL = '.codicon-notebook-kernel-select'; const DETECTING_KERNELS_TEXT = 'Detecting Kernels'; const NEW_NOTEBOOK_COMMAND = 'ipynb.newUntitledIpynb'; const CELL_LINE = '.cell div.view-lines'; @@ -24,45 +22,59 @@ const REVERT_AND_CLOSE = 'workbench.action.revertAndCloseActiveEditor'; const MARKDOWN_TEXT = '#preview'; const ACTIVE_ROW_SELECTOR = `.notebook-editor .monaco-list-row.focused`; - /* * Reuseable Positron notebook functionality for tests to leverage. Includes selecting the notebook's interpreter. */ export class Notebooks { kernelLabel = this.code.driver.page.locator(KERNEL_LABEL); + kernelDropdown = this.code.driver.page.locator(KERNEL_DROPDOWN); frameLocator = this.code.driver.page.frameLocator(OUTER_FRAME).frameLocator(INNER_FRAME); notebookProgressBar = this.code.driver.page.locator('[id="workbench\\.parts\\.editor"]').getByRole('progressbar'); constructor(private code: Code, private quickinput: QuickInput, private quickaccess: QuickAccess) { } - async selectInterpreter(kernelGroup: string, desiredKernel: string) { - await test.step(`Select notebook interpreter: ${desiredKernel}`, async () => { + async selectInterpreter( + kernelGroup: 'Python' | 'R', + desiredKernel = kernelGroup === 'Python' + ? process.env.POSITRON_PY_VER_SEL! + : process.env.POSITRON_R_VER_SEL! + ) { + await test.step(`Select kernel: ${desiredKernel}`, async () => { await expect(this.notebookProgressBar).not.toBeVisible({ timeout: 30000 }); await expect(this.code.driver.page.locator(DETECTING_KERNELS_TEXT)).not.toBeVisible({ timeout: 30000 }); - // Wait for the desired kernel to populate in dropdown, if no show then wait for "Select Kernel" - const kernelLabelLocator = this.code.driver.page.locator(KERNEL_LABEL); try { - await expect(kernelLabelLocator).toHaveText(new RegExp(desiredKernel), { timeout: 10000 }); + // 1. Try finding by text + await expect(this.kernelDropdown.filter({ hasText: desiredKernel })).toBeVisible({ timeout: 5000 }); + this.code.logger.log(`Kernel: found by text: ${desiredKernel}`); + return; } catch (e) { - await expect(kernelLabelLocator).toHaveText(new RegExp(SELECT_KERNEL_TEXT), { timeout: 10000 }); + this.code.logger.log(`Kernel: not found by text: ${desiredKernel}`); } - // Retrieve the matched text for conditional logic - const matchedText = await kernelLabelLocator.textContent() || ''; - - this.code.logger.log(`Matched text: ${matchedText}, Desired kernel: ${desiredKernel}`); - if (!new RegExp(desiredKernel).test(matchedText)) { - await this.code.driver.page.locator(KERNEL_ACTION).click(); - await this.quickinput.waitForQuickInputOpened(); - await this.quickinput.selectQuickInputElementContaining(kernelGroup); - await this.quickinput.selectQuickInputElementContaining(desiredKernel); - await this.quickinput.waitForQuickInputClosed(); + try { + // 2. Try finding by label + const kernelLabelLocator = this.code.driver.page.locator(KERNEL_LABEL); + await expect(kernelLabelLocator).toHaveAttribute('aria-label', new RegExp(desiredKernel), { timeout: 5000 }); + this.code.logger.log(`Kernel: found by label: ${desiredKernel}`); + return; + } catch (e) { + this.code.logger.log(`Kernel: not found by label: ${desiredKernel}`); } - // wait for the kernel to load - await expect(this.code.driver.page.locator('.kernel-action-view-item').locator('.codicon-modifier-spin')).not.toBeVisible({ timeout: 30000 }); + // 3. Open dropdown to select kernel + this.code.logger.log(`Kernel: opening dropdown to select: ${desiredKernel}`); + + await this.code.driver.page.locator(KERNEL_DROPDOWN).click(); + await this.quickinput.waitForQuickInputOpened(); + await this.code.driver.page.getByText('Select Another Kernel...').click(); + await this.quickinput.selectQuickInputElementContaining(`${kernelGroup} Environments...`); + await this.quickinput.selectQuickInputElementContaining(desiredKernel); + await this.quickinput.waitForQuickInputClosed(); + + // Wait for kernel initialization + await expect(this.code.driver.page.locator('.kernel-action-view-item .codicon-modifier-spin')).not.toBeVisible({ timeout: 30000 }); }); } diff --git a/test/e2e/pages/quickaccess.ts b/test/e2e/pages/quickaccess.ts index 305ae1c23bf..7f7fcc6dfe1 100644 --- a/test/e2e/pages/quickaccess.ts +++ b/test/e2e/pages/quickaccess.ts @@ -188,7 +188,7 @@ export class QuickAccess { intervals: [1000], }); - this.code.logger.log('QuickAccess: Command found and successfully executed.'); + this.code.logger.log(`QuickAccess: ${commandId} ✓ success`); await this.quickInput.selectQuickInputElement(0, keepOpen); }); } diff --git a/test/e2e/tests/action-bar/editor-action-bar.test.ts b/test/e2e/tests/action-bar/editor-action-bar.test.ts index 0d55cc163ae..273321e8b70 100644 --- a/test/e2e/tests/action-bar/editor-action-bar.test.ts +++ b/test/e2e/tests/action-bar/editor-action-bar.test.ts @@ -52,7 +52,7 @@ test.describe('Editor Action Bar', { }); test('Jupyter Notebook [C1080702]', { - tag: [tags.NOTEBOOK], + tag: [tags.NOTEBOOKS], annotation: [{ type: 'info', description: 'electron test unable to interact with dropdown native menu' }], }, async function ({ app, page }) { await openNotebook(app, 'workspaces/large_r_notebook/spotify.ipynb'); diff --git a/test/e2e/tests/data-explorer/data-explorer-python-pandas.test.ts b/test/e2e/tests/data-explorer/data-explorer-python-pandas.test.ts index ef923e49c4e..ae3180275f1 100644 --- a/test/e2e/tests/data-explorer/data-explorer-python-pandas.test.ts +++ b/test/e2e/tests/data-explorer/data-explorer-python-pandas.test.ts @@ -134,7 +134,7 @@ df2 = pd.DataFrame(data)`; const filename = 'pandas-update-dataframe.ipynb'; await app.workbench.notebooks.openNotebook(join(app.workspacePathOrFolder, 'workspaces', 'data-explorer-update-datasets', filename)); - await app.workbench.notebooks.selectInterpreter('Python Environments', process.env.POSITRON_PY_VER_SEL!); + await app.workbench.notebooks.selectInterpreter('Python', process.env.POSITRON_PY_VER_SEL!); await app.workbench.notebooks.focusFirstCell(); await app.workbench.notebooks.executeActiveCell(); diff --git a/test/e2e/tests/help/f1.test.ts b/test/e2e/tests/help/f1.test.ts index 7a823e762db..d870b16bf39 100644 --- a/test/e2e/tests/help/f1.test.ts +++ b/test/e2e/tests/help/f1.test.ts @@ -60,7 +60,7 @@ test.describe('F1 Help', { await app.workbench.quickaccess.openDataFile(join(app.workspacePathOrFolder, 'workspaces', 'large_r_notebook', 'spotify.ipynb')); // workaround - await app.workbench.notebooks.selectInterpreter('R Environments', process.env.POSITRON_R_VER_SEL!); + await app.workbench.notebooks.selectInterpreter('R', process.env.POSITRON_R_VER_SEL!); await app.code.driver.page.locator('span').filter({ hasText: 'options(digits = 2)' }).locator('span').first().dblclick(); diff --git a/test/e2e/tests/notebook/notebook-create.test.ts b/test/e2e/tests/notebook/notebook-create.test.ts index fc2c644835b..ed7232e15ed 100644 --- a/test/e2e/tests/notebook/notebook-create.test.ts +++ b/test/e2e/tests/notebook/notebook-create.test.ts @@ -10,13 +10,13 @@ test.use({ }); test.describe('Notebooks', { - tag: [tags.CRITICAL, tags.WEB, tags.WIN, tags.NOTEBOOK] + tag: [tags.CRITICAL, tags.WEB, tags.WIN, tags.NOTEBOOKS] }, () => { test.describe('Python Notebooks', () => { test.beforeEach(async function ({ app, python }) { await app.workbench.layouts.enterLayout('notebook'); await app.workbench.notebooks.createNewNotebook(); - await app.workbench.notebooks.selectInterpreter('Python Environments', process.env.POSITRON_PY_VER_SEL!); + await app.workbench.notebooks.selectInterpreter('Python'); }); test.afterEach(async function ({ app }) { @@ -43,7 +43,7 @@ test.describe('Notebooks', { test.beforeEach(async function ({ app, r }) { await app.workbench.layouts.enterLayout('notebook'); await app.workbench.notebooks.createNewNotebook(); - await app.workbench.notebooks.selectInterpreter('R Environments', process.env.POSITRON_R_VER_SEL!); + await app.workbench.notebooks.selectInterpreter('R'); }); test.afterEach(async function ({ app }) { diff --git a/test/e2e/tests/notebook/notebook-large-python.test.ts b/test/e2e/tests/notebook/notebook-large-python.test.ts index 3e977a0d615..ea8a59e5212 100644 --- a/test/e2e/tests/notebook/notebook-large-python.test.ts +++ b/test/e2e/tests/notebook/notebook-large-python.test.ts @@ -13,7 +13,7 @@ test.use({ // Note that this test is too heavy to pass on web and windows test.describe('Large Python Notebook', { - tag: [tags.NOTEBOOK, tags.WIN] + tag: [tags.NOTEBOOKS, tags.WIN] }, () => { test('Python - Large notebook execution [C983592]', async function ({ app, python }) { @@ -22,7 +22,7 @@ test.describe('Large Python Notebook', { await app.workbench.quickaccess.openDataFile(join(app.workspacePathOrFolder, 'workspaces', 'large_py_notebook', 'spotify.ipynb')); - await notebooks.selectInterpreter('Python Environments', process.env.POSITRON_PY_VER_SEL!); + await notebooks.selectInterpreter('Python'); await notebooks.runAllCells(120000); diff --git a/test/e2e/tests/plots/matplotlib-interact.test.ts b/test/e2e/tests/plots/matplotlib-interact.test.ts index 5b32c77de55..d0450e7be28 100644 --- a/test/e2e/tests/plots/matplotlib-interact.test.ts +++ b/test/e2e/tests/plots/matplotlib-interact.test.ts @@ -11,7 +11,7 @@ test.use({ suiteId: __filename }); -test.describe('Matplotlib Interact', { tag: [tags.PLOTS, tags.NOTEBOOK] }, () => { +test.describe('Matplotlib Interact', { tag: [tags.PLOTS, tags.NOTEBOOKS] }, () => { test('Python - Matplotlib Interact Test [C1067443]', { tag: [tags.CRITICAL, tags.WEB, tags.WIN], @@ -21,7 +21,7 @@ test.describe('Matplotlib Interact', { tag: [tags.PLOTS, tags.NOTEBOOK] }, () => await app.workbench.quickaccess.openDataFile(join(app.workspacePathOrFolder, 'workspaces', 'matplotlib', 'interact.ipynb')); - await notebooks.selectInterpreter('Python Environments', process.env.POSITRON_PY_VER_SEL!); + await notebooks.selectInterpreter('Python'); await notebooks.runAllCells(); diff --git a/test/e2e/tests/variables/variables-notebook.test.ts b/test/e2e/tests/variables/variables-notebook.test.ts index 6050455663d..fd0af4c34ef 100644 --- a/test/e2e/tests/variables/variables-notebook.test.ts +++ b/test/e2e/tests/variables/variables-notebook.test.ts @@ -15,7 +15,7 @@ test.afterEach(async function ({ app }) { }); test.describe('Variables Pane - Notebook', { - tag: [tags.CRITICAL, tags.WEB, tags.VARIABLES, tags.NOTEBOOK] + tag: [tags.CRITICAL, tags.WEB, tags.VARIABLES, tags.NOTEBOOKS] }, () => { test('Python - Verifies Variables pane basic function for notebook [C669188]', async function ({ app, python }) { await app.workbench.notebooks.createNewNotebook(); @@ -23,7 +23,7 @@ test.describe('Variables Pane - Notebook', { // workaround issue where starting multiple interpreters in quick succession can cause startup failure await app.code.wait(1000); - await app.workbench.notebooks.selectInterpreter('Python Environments', process.env.POSITRON_PY_VER_SEL!); + await app.workbench.notebooks.selectInterpreter('Python'); await app.workbench.notebooks.addCodeToFirstCell('y = [2, 3, 4, 5]'); await app.workbench.notebooks.executeCodeInCell(); @@ -41,7 +41,7 @@ test.describe('Variables Pane - Notebook', { test('R - Verifies Variables pane basic function for notebook [C669189]', async function ({ app, r }) { await app.workbench.notebooks.createNewNotebook(); - await app.workbench.notebooks.selectInterpreter('R Environments', process.env.POSITRON_R_VER_SEL!); + await app.workbench.notebooks.selectInterpreter('R'); await app.workbench.notebooks.addCodeToFirstCell('y <- c(2, 3, 4, 5)'); await app.workbench.notebooks.executeCodeInCell();