From 14bff09e3141fc25ab651e27df44796e0c25a488 Mon Sep 17 00:00:00 2001 From: Marie Idleman Date: Fri, 10 Jan 2025 11:55:08 -0600 Subject: [PATCH] e2e-test: simplify interpreter fixtures (#5949) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Summary Per my previous PR https://github.com/posit-dev/positron/pull/5946 there is now a lot of overlap between the R/Python fixtures and interpreter POM. * Removed R/Python fixtures * Moved interpreter POM to Fixtures ### QA Notes ✅ ran [full suite](https://github.com/posit-dev/positron/actions/runs/12714065066) --- .../{pages => infra/fixtures}/interpreter.ts | 57 ++++++++++++++----- test/e2e/infra/fixtures/python.ts | 57 ------------------- test/e2e/infra/fixtures/r.ts | 43 -------------- test/e2e/infra/index.ts | 4 +- test/e2e/infra/workbench.ts | 2 +- test/e2e/pages/console.ts | 10 +++- test/e2e/tests/_test.setup.ts | 14 ++--- 7 files changed, 58 insertions(+), 129 deletions(-) rename test/e2e/{pages => infra/fixtures}/interpreter.ts (85%) delete mode 100644 test/e2e/infra/fixtures/python.ts delete mode 100644 test/e2e/infra/fixtures/r.ts diff --git a/test/e2e/pages/interpreter.ts b/test/e2e/infra/fixtures/interpreter.ts similarity index 85% rename from test/e2e/pages/interpreter.ts rename to test/e2e/infra/fixtures/interpreter.ts index a6e36790a26..2d3c91070c0 100644 --- a/test/e2e/pages/interpreter.ts +++ b/test/e2e/infra/fixtures/interpreter.ts @@ -4,10 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import test, { expect, Locator } from '@playwright/test'; -import { Code, Console } from '../infra'; +import { Console } from '../../pages/console'; +import { Code } from '../code'; const INTERPRETER_INFO_LINE = '.info .container .line'; const INTERPRETER_ACTIONS_SELECTOR = `.interpreter-actions .action-button`; +const DESIRED_PYTHON = process.env.POSITRON_PY_VER_SEL; +const DESIRED_R = process.env.POSITRON_R_VER_SEL; export enum InterpreterType { Python = 'Python', @@ -31,15 +34,45 @@ export class Interpreter { // --- Actions --- + /** + * Action: Start an interpreter via the Quick Access bar. + * @param interpreterType The type of the interpreter to start. + * @param waitForReady Wait for the interpreter to be ready after starting. + */ + startInterpreterViaQuickAccess = async (interpreterType: 'Python' | 'R', waitForReady = true) => { + if (!DESIRED_PYTHON || !DESIRED_R) { + throw new Error('Please set env vars: POSITRON_PYTHON_VER_SEL, POSITRON_R_VER_SEL'); + } + + await test.step(`Select interpreter via Quick Access: ${interpreterType}`, async () => { + interpreterType === 'Python' + ? await this.console.selectInterpreter(InterpreterType.Python, DESIRED_PYTHON) + : await this.console.selectInterpreter(InterpreterType.R, DESIRED_R); + + if (waitForReady) { + interpreterType === 'Python' + ? await this.console.waitForReady('>>>', 30000) + : await this.console.waitForReady('>', 30000); + } + }); + }; + /** * Action: Select an interpreter from the dropdown by the interpreter type and a descriptive string. - * The interpreter type could be 'Python', 'R', etc. - * The string could be 'Python 3.10.4 (Pyenv)', 'R 4.4.0', '/opt/homebrew/bin/python3', etc. * @param interpreterType The type of the interpreter to select. - * @param description The descriptive string of the interpreter to select. + * @param description Description of interpreter to select: 'Python', 'R 4.4.0', '/opt/homebrew/bin/python3', etc. + * @param waitForReady Wait for the interpreter to be ready after selecting. */ - async selectInterpreter(interpreterType: 'Python' | 'R', interpreterDescription: string, waitForInterpreterReady = true) { - await test.step(`Select interpreter: ${interpreterDescription}`, async () => { + async selectInterpreter( + interpreterType: 'Python' | 'R', + interpreterDescription = interpreterType === 'Python' ? DESIRED_PYTHON : DESIRED_R, + waitForReady = true + ) { + if (!DESIRED_PYTHON || !DESIRED_R) { + throw new Error('Please set env vars: POSITRON_PYTHON_VER_SEL, POSITRON_R_VER_SEL'); + } + + await test.step(`Select interpreter via UI: ${interpreterDescription}`, async () => { await this.openInterpreterDropdown(); const selectedPrimaryInterpreter = this.primaryInterpreter.filter({ hasText: interpreterDescription }); @@ -58,7 +91,7 @@ export class Interpreter { await secondaryInterpreterOption.click(); } - if (waitForInterpreterReady) { + if (waitForReady) { interpreterType === 'Python' ? await this.console.waitForReady('>>>', 30000) : await this.console.waitForReady('>', 30000); @@ -68,8 +101,7 @@ export class Interpreter { /** * Action: Restart the primary interpreter - * The interpreter type could be 'Python', 'R', etc. - * The string could be 'Python 3.10.4 (Pyenv)', 'R 4.4.0', '/opt/homebrew/bin/python3', etc. + * @param description The description of interpreter to restart: 'Python', 'R 4.4.0', '/opt/homebrew/bin/python3', etc. */ async restartPrimaryInterpreter(description: string | InterpreterType) { await test.step(`Restart interpreter: ${description}`, async () => { @@ -90,8 +122,7 @@ export class Interpreter { /** * Action: Stop the primary interpreter - * The interpreter type could be 'Python', 'R', etc. - * The string could be 'Python 3.10.4 (Pyenv)', 'R 4.4.0', '/opt/homebrew/bin/python3', etc. + * @param description The description of interpreter to stop: 'Python', 'R 4.4.0', '/opt/homebrew/bin/python3', etc. */ async stopPrimaryInterpreter(description: string | InterpreterType, waitForInterpreterShutdown = true) { await test.step(`Stop interpreter: ${description}`, async () => { @@ -173,8 +204,7 @@ export class Interpreter { /** * Helper: Get the primary interpreter element by a descriptive string or interpreter type. - * The string could be 'Python 3.10.4 (Pyenv)', 'R 4.4.0', '/opt/homebrew/bin/python3', etc. - * @param descriptionOrType The descriptive string of the interpreter to get. + * @param descriptionOrType The descriptive string or interpreter type to filter the primary interpreter by. * @returns The primary interpreter element */ private async getPrimaryInterpreterElement(descriptionOrType: string | InterpreterType) { @@ -189,7 +219,6 @@ export class Interpreter { /** * Helper: Get the interpreter name from the interpreter element. - * Examples: 'Python 3.10.4 (Pyenv)', 'R 4.4.0'. * @param interpreterLocator The locator for the interpreter element. */ private async getInterpreterName(interpreterLocator: Locator) { diff --git a/test/e2e/infra/fixtures/python.ts b/test/e2e/infra/fixtures/python.ts deleted file mode 100644 index 4e8aa9fdd52..00000000000 --- a/test/e2e/infra/fixtures/python.ts +++ /dev/null @@ -1,57 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (C) 2024 Posit Software, PBC. All rights reserved. - * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. - *--------------------------------------------------------------------------------------------*/ - -import { fail } from 'assert'; -import { Application, InterpreterType } from '..'; -import { expect } from '@playwright/test'; - -/* - * Reuseable Positron Python fixture tests can leverage to get a Python interpreter selected. - */ -export class PythonFixtures { - - constructor(private app: Application) { } - - static async SetupFixtures(app: Application, waitForReady = true) { - const fixtures = new PythonFixtures(app); - await fixtures.startPythonInterpreter(waitForReady); - } - - async startPythonInterpreter(waitForReady = true) { - - const desiredPython = process.env.POSITRON_PY_VER_SEL; - if (desiredPython === undefined) { - fail('Please be sure to set env var POSITRON_PY_VER_SEL to the UI text corresponding to the Python version for the test'); - } - - try { - await this.app.workbench.console.selectInterpreter(InterpreterType.Python, desiredPython, waitForReady); - await this.app.workbench.console.waitForReady('>>>', 40000); - } catch (e) { - await this.app.code.driver.takeScreenshot('startPythonInterpreter'); - throw e; - } - await this.app.workbench.console.logConsoleContents(); - } - - async startAndGetPythonInterpreter(installIPyKernelIfPrompted: boolean = false): Promise { - const desiredPython = process.env.POSITRON_PY_VER_SEL; - if (desiredPython === undefined) { - fail('Please be sure to set env var POSITRON_PY_VER_SEL to the UI text corresponding to the Python version for the test'); - } - await this.app.workbench.console.selectInterpreter(InterpreterType.Python, desiredPython); - - if ( - installIPyKernelIfPrompted && - (await this.app.workbench.popups.popupCurrentlyOpen()) - ) { - await this.app.workbench.popups.installIPyKernel(); - } - - await expect(this.app.workbench.console.activeConsole.getByText('>>>')).toBeVisible({ timeout: 30000 }); - await this.app.workbench.console.logConsoleContents(); - } - -} diff --git a/test/e2e/infra/fixtures/r.ts b/test/e2e/infra/fixtures/r.ts deleted file mode 100644 index e5581f21053..00000000000 --- a/test/e2e/infra/fixtures/r.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (C) 2024 Posit Software, PBC. All rights reserved. - * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. - *--------------------------------------------------------------------------------------------*/ - -import { fail } from 'assert'; -import { Application, InterpreterType } from '..'; - -/* - * Reuseable Positron R fixture tests can leverage to get an R interpreter selected. - */ -export class RFixtures { - - constructor(private app: Application) { } - - static async SetupFixtures(app: Application, waitForReady = true) { - const fixtures = new RFixtures(app); - await fixtures.startRInterpreter(waitForReady); - } - - async startRInterpreter(waitForReady = true) { - - const desiredR = process.env.POSITRON_R_VER_SEL; - if (desiredR === undefined) { - fail('Please be sure to set env var POSITRON_R_VER_SEL to the UI text corresponding to the R version for the test'); - } - - - // We currently don't capture fixtures in the Playwright trace, so take a screenshot on failure - try { - await this.app.workbench.console.selectInterpreter(InterpreterType.R, desiredR, waitForReady); - await this.app.workbench.console.waitForReady('>', 40000); - } catch (e) { - await this.app.code.driver.takeScreenshot('startRInterpreter'); - throw e; - } - - await this.app.workbench.console.logConsoleContents(); - - - } - -} diff --git a/test/e2e/infra/index.ts b/test/e2e/infra/index.ts index 2b60e34d683..e0cec32ecef 100644 --- a/test/e2e/infra/index.ts +++ b/test/e2e/infra/index.ts @@ -11,7 +11,6 @@ export * from './workbench'; // pages export * from '../pages/console'; export * from '../pages/popups'; -export * from '../pages/interpreter'; export * from '../pages/variables'; export * from '../pages/dataExplorer'; export * from '../pages/sideBar'; @@ -38,9 +37,8 @@ export * from '../pages/editors'; export * from '../pages/settings'; // fixtures -export * from './fixtures/python'; -export * from './fixtures/r'; export * from './fixtures/userSettings'; +export * from './fixtures/interpreter'; // test-runner export * from './test-runner'; diff --git a/test/e2e/infra/workbench.ts b/test/e2e/infra/workbench.ts index 24bf3860b79..04584da65f0 100644 --- a/test/e2e/infra/workbench.ts +++ b/test/e2e/infra/workbench.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Code } from './code'; -import { Interpreter } from '../pages/interpreter'; +import { Interpreter } from '../infra/fixtures/interpreter'; import { Popups } from '../pages/popups'; import { Console } from '../pages/console'; import { Variables } from '../pages/variables'; diff --git a/test/e2e/pages/console.ts b/test/e2e/pages/console.ts index 03ba22cb0e1..c8930df805b 100644 --- a/test/e2e/pages/console.ts +++ b/test/e2e/pages/console.ts @@ -48,9 +48,7 @@ export class Console { async selectInterpreter(desiredInterpreterType: InterpreterType, desiredInterpreterString: string, waitForReady: boolean = true): Promise { // don't try to start a new interpreter if one is currently starting up - if (waitForReady) { - await this.waitForReadyOrNoInterpreter(); - } + await this.waitForReadyOrNoInterpreter(); let command: string; if (desiredInterpreterType === InterpreterType.Python) { @@ -70,6 +68,12 @@ export class Console { // may include additional items above the desired interpreter string. await this.quickinput.selectQuickInputElementContaining(desiredInterpreterString); await this.quickinput.waitForQuickInputClosed(); + + if (waitForReady) { + desiredInterpreterType === InterpreterType.Python + ? await this.waitForReady('>>>', 40000) + : await this.waitForReady('>', 40000); + } return; } diff --git a/test/e2e/tests/_test.setup.ts b/test/e2e/tests/_test.setup.ts index 435a4796fc3..153d88e80e5 100644 --- a/test/e2e/tests/_test.setup.ts +++ b/test/e2e/tests/_test.setup.ts @@ -21,7 +21,7 @@ import { randomUUID } from 'crypto'; import archiver from 'archiver'; // Local imports -import { Application, Logger, PythonFixtures, RFixtures, UserSetting, UserSettingsFixtures, createLogger, createApp, TestTags } from '../infra'; +import { Application, Logger, UserSetting, UserSettingsFixtures, createLogger, createApp, TestTags } from '../infra'; import { PackageManager } from '../pages/utils/packageManager'; // Constants @@ -109,15 +109,13 @@ export const test = base.extend({ }, { scope: 'worker', auto: true, timeout: 60000 }], interpreter: [async ({ app, page }, use) => { - const setInterpreter = async (interpreterName: 'Python' | 'R') => { + const setInterpreter = async (desiredInterpreter: 'Python' | 'R') => { const currentInterpreter = await page.locator('.top-action-bar-interpreters-manager').textContent() || ''; - if (!currentInterpreter.includes(interpreterName)) { - if (interpreterName === 'Python') { - await PythonFixtures.SetupFixtures(app, false); - } else if (interpreterName === 'R') { - await RFixtures.SetupFixtures(app, false); - } + if (!currentInterpreter.startsWith(desiredInterpreter)) { + desiredInterpreter === 'Python' + ? await app.workbench.interpreter.startInterpreterViaQuickAccess('Python') + : await app.workbench.interpreter.startInterpreterViaQuickAccess('R'); } };