diff --git a/test/e2e/infra/fixtures/interpreter.ts b/test/e2e/infra/fixtures/interpreter.ts index b247cea065c..15dba94d84d 100644 --- a/test/e2e/infra/fixtures/interpreter.ts +++ b/test/e2e/infra/fixtures/interpreter.ts @@ -87,8 +87,8 @@ export class Interpreter { if (waitForReady) { interpreterType === 'Python' - ? await this.console.waitForReady('>>>', 30000) - : await this.console.waitForReady('>', 30000); + ? await this.console.waitForReadyAndStarted('>>>', 30000) + : await this.console.waitForReadyAndStarted('>', 30000); } }); } @@ -306,8 +306,8 @@ export class Interpreter { await this.console.waitForConsoleContents('restarted'); interpreterType === 'Python' - ? await this.console.waitForReady('>>>', 10000) - : await this.console.waitForReady('>', 10000); + ? await this.console.waitForReadyAndStarted('>>>', 10000) + : await this.console.waitForReadyAndStarted('>', 10000); }); } diff --git a/test/e2e/infra/test-runner/test-tags.ts b/test/e2e/infra/test-runner/test-tags.ts index 26a4fe91d36..a7b33ec58c9 100644 --- a/test/e2e/infra/test-runner/test-tags.ts +++ b/test/e2e/infra/test-runner/test-tags.ts @@ -18,13 +18,13 @@ export enum TestTags { // feature tags - EDITOR_ACTION_BAR = '@:editor-action-bar', APPS = '@:apps', CONNECTIONS = '@:connections', CONSOLE = '@:console', CRITICAL = '@:critical', DATA_EXPLORER = '@:data-explorer', DUCK_DB = '@:duck-db', + EDITOR_ACTION_BAR = '@:editor-action-bar', HELP = '@:help', HTML = '@:html', INTERPRETER = '@:interpreter', diff --git a/test/e2e/infra/workbench.ts b/test/e2e/infra/workbench.ts index 04584da65f0..33e5a5aca76 100644 --- a/test/e2e/infra/workbench.ts +++ b/test/e2e/infra/workbench.ts @@ -31,6 +31,7 @@ import { Clipboard } from '../pages/clipboard'; import { QuickInput } from '../pages/quickInput'; import { Extensions } from '../pages/extensions'; import { Settings } from '../pages/settings'; +import { EditorActionBar } from '../pages/editorActionBar'; export interface Commands { runCommand(command: string, options?: { exactLabelMatch?: boolean }): Promise; @@ -65,6 +66,7 @@ export class Workbench { readonly extensions: Extensions; readonly editors: Editors; readonly settings: Settings; + readonly editorActionBar: EditorActionBar; constructor(code: Code) { @@ -96,6 +98,7 @@ export class Workbench { this.clipboard = new Clipboard(code); this.extensions = new Extensions(code, this.quickaccess); this.settings = new Settings(code, this.editors, this.editor, this.quickaccess); + this.editorActionBar = new EditorActionBar(code.driver.page, this.viewer, this.quickaccess); } } diff --git a/test/e2e/pages/console.ts b/test/e2e/pages/console.ts index c8930df805b..8bbaec7be22 100644 --- a/test/e2e/pages/console.ts +++ b/test/e2e/pages/console.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ -import { expect, Locator } from '@playwright/test'; +import test, { expect, Locator } from '@playwright/test'; import { Code } from '../infra/code'; import { QuickAccess } from './quickaccess'; import { QuickInput } from './quickInput'; @@ -71,37 +71,39 @@ export class Console { if (waitForReady) { desiredInterpreterType === InterpreterType.Python - ? await this.waitForReady('>>>', 40000) - : await this.waitForReady('>', 40000); + ? await this.waitForReadyAndStarted('>>>', 40000) + : await this.waitForReadyAndStarted('>', 40000); } return; } - async executeCode(languageName: string, code: string, prompt: string): Promise { + async executeCode(languageName: 'Python' | 'R', code: string): Promise { + await test.step(`Execute ${languageName} code in console: ${code}`, async () => { - await expect(async () => { - // Kind of hacky, but activate console in case focus was previously lost - await this.activeConsole.click(); - await this.quickaccess.runCommand('workbench.action.executeCode.console', { keepOpen: true }); + await expect(async () => { + // Kind of hacky, but activate console in case focus was previously lost + await this.activeConsole.click(); + await this.quickaccess.runCommand('workbench.action.executeCode.console', { keepOpen: true }); - }).toPass(); + }).toPass(); - await this.quickinput.waitForQuickInputOpened(); - await this.quickinput.type(languageName); - await this.quickinput.waitForQuickInputElements(e => e.length === 1 && e[0] === languageName); - await this.code.driver.page.keyboard.press('Enter'); + await this.quickinput.waitForQuickInputOpened(); + await this.quickinput.type(languageName); + await this.quickinput.waitForQuickInputElements(e => e.length === 1 && e[0] === languageName); + await this.code.driver.page.keyboard.press('Enter'); - await this.quickinput.waitForQuickInputOpened(); - const unescapedCode = code - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r'); - await this.quickinput.type(unescapedCode); - await this.code.driver.page.keyboard.press('Enter'); - await this.quickinput.waitForQuickInputClosed(); + await this.quickinput.waitForQuickInputOpened(); + const unescapedCode = code + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r'); + await this.quickinput.type(unescapedCode); + await this.code.driver.page.keyboard.press('Enter'); + await this.quickinput.waitForQuickInputClosed(); - // The console will show the prompt after the code is done executing. - await this.waitForReady(prompt); - await this.maximizeConsole(); + // The console will show the prompt after the code is done executing. + await this.waitForReady(languageName === 'Python' ? '>>>' : '>'); + await this.maximizeConsole(); + }); } async logConsoleContents() { @@ -129,9 +131,17 @@ export class Console { async waitForReady(prompt: string, timeout = 30000): Promise { const activeLine = this.code.driver.page.locator(`${ACTIVE_CONSOLE_INSTANCE} .active-line-number`); - await expect(activeLine).toHaveText(prompt, { timeout }); - await this.waitForConsoleContents('started', { timeout }); + } + + async waitForReadyAndStarted(prompt: string, timeout = 30000): Promise { + this.waitForReady(prompt, timeout); + await this.waitForConsoleContents('started'); + } + + async waitForReadyAndRestarted(prompt: string, timeout = 30000): Promise { + this.waitForReady(prompt, timeout); + await this.waitForConsoleContents('restarted'); } /** diff --git a/test/e2e/pages/editor.ts b/test/e2e/pages/editor.ts index 050337a95d0..f67630fb9c7 100644 --- a/test/e2e/pages/editor.ts +++ b/test/e2e/pages/editor.ts @@ -6,7 +6,7 @@ import { expect, FrameLocator, Locator } from '@playwright/test'; import { Code } from '../infra/code'; -// currently a dupe of declaration in ../editor.ts but trying not to modifiy that file +// currently a dupe of declaration in ../editor.ts but trying not to modify that file const EDITOR = (filename: string) => `.monaco-editor[data-uri$="${filename}"]`; const CURRENT_LINE = '.view-overlays .current-line'; const PLAY_BUTTON = '.codicon-play'; @@ -17,6 +17,7 @@ const INNER_FRAME = '#active-frame'; export class Editor { viewerFrame = this.code.driver.page.frameLocator(OUTER_FRAME).frameLocator(INNER_FRAME); + playButton = this.code.driver.page.locator(PLAY_BUTTON); getEditorViewerLocator(locator: string,): Locator { return this.viewerFrame.locator(locator); diff --git a/test/e2e/pages/editorActionBar.ts b/test/e2e/pages/editorActionBar.ts new file mode 100644 index 00000000000..15366c21e4e --- /dev/null +++ b/test/e2e/pages/editorActionBar.ts @@ -0,0 +1,182 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +import test, { expect, Page } from '@playwright/test'; +import { Viewer } from './viewer'; +import { QuickAccess } from './quickaccess'; + + +export class EditorActionBar { + + previewButton = this.page.getByLabel('Preview', { exact: true }); + openChangesButton = this.page.getByLabel('Open Changes'); + splitEditorRightButton = this.page.getByLabel('Split Editor Right', { exact: true }); + splitEditorDownButton = this.page.getByLabel('Split Editor Down', { exact: true }); + openInViewerButton = this.page.getByLabel('Open in Viewer'); + + constructor(private page: Page, private viewer: Viewer, private quickaccess: QuickAccess) { + } + + // --- Actions --- + + /** + * Action: Click the "Split Editor" button. Handles pressing the 'Alt' key for 'down' direction. + * @param direction 'down' or 'right' + */ + async clickSplitEditorButton(direction: 'down' | 'right') { + if (direction === 'down') { + await this.page.keyboard.down('Alt'); + await this.page.getByLabel('Split Editor Down').click(); + await this.page.keyboard.up('Alt'); + } + else { + this.splitEditorRightButton.click(); + } + } + + /** + * Action: Set the summary position to the specified side. + * @param isWeb whether the test is running in the web or desktop app + * @param position select 'Left' or 'Right' to position the summary + */ + async selectSummaryOn(isWeb: boolean, position: 'Left' | 'Right') { + if (isWeb) { + await this.page.getByLabel('More actions', { exact: true }).click(); + await this.page.getByRole('menuitemcheckbox', { name: `Summary on ${position}` }).hover(); + await this.page.keyboard.press('Enter'); + } + else { + await this.quickaccess.runCommand(`workbench.action.positronDataExplorer.summaryOn${position}`); + } + } + + /** + * Action: Click a menu item in the "Customize Notebook" dropdown. + * @param menuItem a menu item to click in the "Customize Notebook" dropdown + */ + async clickCustomizeNotebookMenuItem(menuItem: string) { + const role = menuItem.includes('Line Numbers') ? 'menuitemcheckbox' : 'menuitem'; + const dropdownButton = this.page.getByLabel('Customize Notebook...'); + await dropdownButton.evaluate((button) => { + (button as HTMLElement).dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true })); + }); + + const toggleMenuItem = this.page.getByRole(role, { name: menuItem }); + await toggleMenuItem.hover(); + await this.page.waitForTimeout(500); + await toggleMenuItem.click(); + } + + // --- Verifications --- + + /** + * Verify: Check that the editor is split in the specified direction (on the correct plane) + * @param direction the direction the editor was split + * @param tabName the name of the tab to verify + */ + async verifySplitEditor(direction: 'down' | 'right', tabName: string,) { + await test.step(`Verify split editor: ${direction}`, async () => { + // Verify 2 tabs + await expect(this.page.getByRole('tab', { name: tabName })).toHaveCount(2); + const splitTabs = this.page.getByRole('tab', { name: tabName }); + const firstTabBox = await splitTabs.nth(0).boundingBox(); + const secondTabBox = await splitTabs.nth(1).boundingBox(); + + if (direction === 'right') { + // Verify tabs are on the same X plane + expect(firstTabBox).not.toBeNull(); + expect(secondTabBox).not.toBeNull(); + expect(firstTabBox!.y).toBeCloseTo(secondTabBox!.y, 1); + expect(firstTabBox!.x).not.toBeCloseTo(secondTabBox!.x, 1); + } + else { + // Verify tabs are on the same Y plane + expect(firstTabBox).not.toBeNull(); + expect(secondTabBox).not.toBeNull(); + expect(firstTabBox!.x).toBeCloseTo(secondTabBox!.x, 1); + expect(firstTabBox!.y).not.toBeCloseTo(secondTabBox!.y, 1); + } + + // Close one tab + await splitTabs.first().getByLabel('Close').click(); + }); + } + + /** + * Verify: Check that the "open in new window" contains the specified title + * @param isWeb whether the test is running in the web or desktop app + * @param windowTitle the title to verify in the new window + */ + async verifyOpenInNewWindow(isWeb: boolean, windowTitle: string) { + if (!isWeb) { + await test.step(`Verify "open new window" contains: ${windowTitle}`, async () => { + const [newPage] = await Promise.all([ + this.page.context().waitForEvent('page'), + this.page.getByLabel('Move into new window').first().click(), + ]); + await newPage.waitForLoadState(); + await expect(newPage.getByText(windowTitle)).toBeVisible(); + }); + } + } + + /** + * Verify: Check that the preview renders the specified heading + * @param heading the heading to verify in the preview + */ + async verifyPreviewRendersHtml(heading: string) { + await test.step('Verify "preview" renders html', async () => { + await this.page.getByLabel('Preview', { exact: true }).click(); + const viewerFrame = this.viewer.getViewerFrame().frameLocator('iframe'); + await expect(viewerFrame.getByRole('heading', { name: heading })).toBeVisible({ timeout: 30000 }); + }); + } + + /** + * Verify: Check that the "open in viewer" renders the specified title + * @param isWeb whether the test is running in the web or desktop app + * @param title the title to verify in the viewer + */ + async verifyOpenViewerRendersHtml(isWeb: boolean, title: string) { + await test.step('verify "open in viewer" renders html', async () => { + const viewerFrame = this.page.locator('iframe.webview').contentFrame().locator('#active-frame').contentFrame(); + const cellLocator = isWeb + ? viewerFrame.frameLocator('iframe').getByRole('cell', { name: title }) + : viewerFrame.getByRole('cell', { name: title }); + + await expect(cellLocator).toBeVisible({ timeout: 30000 }); + }); + } + + /** + * Verify: Check that the summary is positioned on the specified side + * @param position the side to verify the summary is positioned + */ + async verifySummaryPosition(position: 'Left' | 'Right') { + await test.step(`Verify summary position: ${position}`, async () => { + // Get the summary and table locators. + const summaryLocator = this.page.locator('div.column-summary').first(); + const tableLocator = this.page.locator('div.data-grid-column-headers'); + + // Ensure both the summary and table elements are visible + await expect(summaryLocator).toBeVisible(); + await expect(tableLocator).toBeVisible(); + + // Get the bounding boxes for both elements + const summaryBox = await summaryLocator.boundingBox(); + const tableBox = await tableLocator.boundingBox(); + + // Validate bounding boxes are available + if (!summaryBox || !tableBox) { + throw new Error('Bounding boxes could not be retrieved for summary or table.'); + } + + // Validate positions based on the expected position + position === 'Left' + ? expect(summaryBox.x).toBeLessThan(tableBox.x) + : expect(summaryBox.x).toBeGreaterThan(tableBox.x); + }); + } +} diff --git a/test/e2e/pages/settings.ts b/test/e2e/pages/settings.ts index 489e9b2bd6e..e49b9cb5f78 100644 --- a/test/e2e/pages/settings.ts +++ b/test/e2e/pages/settings.ts @@ -16,6 +16,7 @@ export class Settings { async addUserSettings(settings: [key: string, value: string][]): Promise { await this.openUserSettingsFile(); const file = 'settings.json'; + await this.editors.saveOpenedFile(); await this.code.driver.page.keyboard.press('ArrowRight'); await this.editor.waitForTypeInEditor(file, settings.map(v => `"${v[0]}": ${v[1]},`).join('')); await this.editors.saveOpenedFile(); diff --git a/test/e2e/pages/utils/packageManager.ts b/test/e2e/pages/utils/packageManager.ts index 9e55aa6770d..415b139fd1a 100644 --- a/test/e2e/pages/utils/packageManager.ts +++ b/test/e2e/pages/utils/packageManager.ts @@ -36,9 +36,8 @@ export class PackageManager { await test.step(`${action}: ${packageName}`, async () => { const command = this.getCommand(packageInfo.type, packageName, action); const expectedOutput = this.getExpectedOutput(packageName, action); - const prompt = packageInfo.type === 'Python' ? '>>> ' : '> '; - await this.app.workbench.console.executeCode(packageInfo.type, command, prompt); + await this.app.workbench.console.executeCode(packageInfo.type, command); await expect(this.app.code.driver.page.getByText(expectedOutput)).toBeVisible(); }); } diff --git a/test/e2e/tests/_test.setup.ts b/test/e2e/tests/_test.setup.ts index c792ff1876d..725132493bf 100644 --- a/test/e2e/tests/_test.setup.ts +++ b/test/e2e/tests/_test.setup.ts @@ -137,6 +137,8 @@ export const test = base.extend({ }, { scope: 'test' }], + // ex: await packages.manage('ipykernel', 'install'); + // ex: await packages.manage('renv', 'uninstall'); packages: [async ({ app }, use) => { const packageManager = new PackageManager(app); await use(packageManager); @@ -145,10 +147,34 @@ export const test = base.extend({ devTools: [async ({ app }, use) => { await app.workbench.quickaccess.runCommand('workbench.action.toggleDevTools'); await use(); + }, { scope: 'test' }], + + // ex: await openFile('workspaces/basic-rmd-file/basicRmd.rmd'); + openFile: async ({ app }, use) => { + await use(async (filePath: string) => { + await test.step(`Open file: ${path.basename(filePath)}`, async () => { + await app.workbench.quickaccess.openFile(path.join(app.workspacePathOrFolder, filePath)); + }); + }); + }, + + // ex: await openDataFile(app, 'workspaces/large_r_notebook/spotify.ipynb'); + openDataFile: async ({ app }, use) => { + await use(async (filePath: string) => { + await test.step(`Open data file: ${path.basename(filePath)}`, async () => { + await app.workbench.quickaccess.openDataFile(path.join(app.workspacePathOrFolder, filePath)); + }); + }); }, - { scope: 'test' }], + // ex: await runCommand('workbench.action.files.save'); + runCommand: async ({ app }, use) => { + await use(async (command: string) => { + await app.workbench.quickaccess.runCommand(command); + }); + }, + // ex: await userSettings.set([['editor.actionBar.enabled', 'true']], false); userSettings: [async ({ app }, use) => { const userSettings = new UserSettingsFixtures(app); @@ -340,6 +366,9 @@ interface TestFixtures { packages: PackageManager; autoTestFixture: any; devTools: void; + openFile: (filePath: string) => Promise; + openDataFile: (filePath: string) => Promise; + runCommand: (command: string) => Promise; } interface WorkerFixtures { diff --git a/test/e2e/tests/action-bar/editor-action-bar.test.ts b/test/e2e/tests/action-bar/editor-action-bar.test.ts deleted file mode 100644 index 943a61164ae..00000000000 --- a/test/e2e/tests/action-bar/editor-action-bar.test.ts +++ /dev/null @@ -1,201 +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 { test, expect, tags } from '../_test.setup'; -import { Application } from '../../infra'; -import { Page } from '@playwright/test'; -import path = require('path'); - - -test.use({ - suiteId: __filename -}); - -test.describe('Editor Action Bar', { - tag: [tags.WEB, tags.WIN, tags.EDITOR_ACTION_BAR, tags.EDITOR] -}, () => { - - test.beforeAll(async function ({ userSettings }) { - await userSettings.set([['editor.actionBar.enabled', 'true']], false); - }); - - test.afterEach(async function ({ app }) { - await app.workbench.quickaccess.runCommand('workbench.action.closeAllEditors'); - }); - - test('R Markdown Document [C1080703]', { - tag: [tags.R_MARKDOWN] - }, async function ({ app, page }) { - await openFile(app, 'workspaces/basic-rmd-file/basicRmd.rmd'); - await verifyPreviewRendersHtml(app, 'Getting startedAnchor'); - await verifySplitEditor(page, 'basicRmd.rmd'); - await verifyOpenInNewWindow(page, 'This post examines the features'); - }); - - test('Quarto Document [C1080700]', { - tag: [tags.QUARTO] - }, async function ({ app, page }) { - await openFile(app, 'workspaces/quarto_basic/quarto_basic.qmd'); - await verifyPreviewRendersHtml(app, 'Diamond sizes'); - await verifyOpenChanges(page); - await verifySplitEditor(page, 'quarto_basic.qmd'); - await verifyOpenInNewWindow(page, 'Diamond sizes'); - }); - - test('HTML Document [C1080701]', { tag: [tags.HTML] }, async function ({ app, page }) { - await openFile(app, 'workspaces/dash-py-example/data/OilandGasMetadata.html'); - await verifyOpenViewerRendersHtml(app); - await verifySplitEditor(page, 'OilandGasMetadata.html'); - await verifyOpenInNewWindow(page, ' Oil & Gas Wells - Metadata'); - }); - - test('Jupyter Notebook [C1080702]', { - 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'); - - if (app.web) { - await verifyToggleLineNumbers(page); - await verifyToggleBreadcrumb(page); - } - - await verifySplitEditor(page, 'spotify.ipynb'); - }); -}); - - -// Helper functions -async function openFile(app, filePath: string) { - const fileName = path.basename(filePath); - await test.step(`open file: ${fileName}`, async () => { - await app.workbench.quickaccess.openFile(path.join(app.workspacePathOrFolder, filePath)); - }); -} - -async function openNotebook(app: Application, filePath: string) { - await test.step('open jupyter notebook', async () => { - await app.workbench.quickaccess.openDataFile( - path.join(app.workspacePathOrFolder, filePath) - ); - }); -} - -async function verifySplitEditor(page, tabName: string) { - await test.step(`verify "split editor" opens another tab`, async () => { - // Split editor right - // Sometimes in CI the click doesn't register, wrapping these actions to reduce flake - await expect(async () => { - await page.getByLabel('Split Editor Right', { exact: true }).click(); - await expect(page.getByRole('tab', { name: tabName })).toHaveCount(2); - }).toPass({ timeout: 10000 }); - - // Close one tab - await page.getByRole('tab', { name: tabName }).getByLabel('Close').first().click(); - - // Split editor down - // Sometimes in CI the click doesn't register, wrapping these actions to reduce flake - await expect(async () => { - await page.keyboard.down('Alt'); - await page.getByLabel('Split Editor Down').click(); - await page.keyboard.up('Alt'); - await expect(page.getByRole('tab', { name: tabName })).toHaveCount(2); - }).toPass({ timeout: 10000 }); - - }); -} - -async function verifyOpenInNewWindow(page, expectedText: string) { - await test.step(`verify "open new window" contains: ${expectedText}`, async () => { - const [newPage] = await Promise.all([ - page.context().waitForEvent('page'), - page.getByLabel('Move into new window').first().click(), - ]); - await newPage.waitForLoadState(); - await expect(newPage.getByText(expectedText)).toBeVisible(); - }); -} - -async function clickCustomizeNotebookMenuItem(page, menuItem: string) { - const role = menuItem.includes('Line Numbers') ? 'menuitemcheckbox' : 'menuitem'; - const dropdownButton = page.getByLabel('Customize Notebook...'); - await dropdownButton.evaluate((button) => { - (button as HTMLElement).dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true })); - }); - - const toggleMenuItem = page.getByRole(role, { name: menuItem }); - await toggleMenuItem.hover(); - await page.waitForTimeout(500); - await toggleMenuItem.click(); -} - -async function verifyLineNumbersVisibility(page, isVisible: boolean) { - for (const lineNum of [1, 2, 3, 4, 5]) { - const lineNumbers = expect(page.locator('.line-numbers').getByText(lineNum.toString(), { exact: true })); - isVisible ? await lineNumbers.toBeVisible() : await lineNumbers.not.toBeVisible(); - } -} - -async function verifyOpenChanges(page: Page) { - await test.step('verify "open changes" shows diff', async () => { - - // make change & save - await page.getByText('date', { exact: true }).click(); - await page.keyboard.press('X'); - await bindPlatformHotkey(page, 'S'); - - // click open changes & verify - await page.getByLabel('Open Changes').click(); - await expect(page.getByLabel('Revert Block')).toBeVisible(); - await expect(page.getByLabel('Stage Block')).toBeVisible(); - await page.getByRole('tab', { name: 'quarto_basic.qmd (Working' }).getByLabel('Close').click(); - - // undo changes & save - await bindPlatformHotkey(page, 'Z'); - await bindPlatformHotkey(page, 'S'); - }); -} - -async function bindPlatformHotkey(page: Page, key: string) { - await page.keyboard.press(process.platform === 'darwin' ? `Meta+${key}` : `Control+${key}`); -} - -async function verifyOpenViewerRendersHtml(app: Application) { - await test.step('verify "open in viewer" renders html', async () => { - await app.code.driver.page.getByLabel('Open in Viewer').click(); - const viewerFrame = app.code.driver.page.locator('iframe.webview').contentFrame().locator('#active-frame').contentFrame(); - const cellLocator = app.web - ? viewerFrame.frameLocator('iframe').getByRole('cell', { name: 'Oil, Gas, and Other Regulated' }) - : viewerFrame.getByRole('cell', { name: 'Oil, Gas, and Other Regulated' }); - - await expect(cellLocator).toBeVisible({ timeout: 30000 }); - }); -} - -async function verifyPreviewRendersHtml(app: Application, heading: string) { - await test.step('verify "preview" renders html', async () => { - await app.code.driver.page.getByLabel('Preview', { exact: true }).click(); - const viewerFrame = app.workbench.viewer.getViewerFrame().frameLocator('iframe'); - await expect(viewerFrame.getByRole('heading', { name: heading })).toBeVisible({ timeout: 30000 }); - }); -} - -async function verifyToggleLineNumbers(page: Page) { - await test.step('verify "customize notebook > toggle line numbers" (web only)', async () => { - await verifyLineNumbersVisibility(page, false); - await clickCustomizeNotebookMenuItem(page, 'Toggle Notebook Line Numbers'); - await verifyLineNumbersVisibility(page, true); - }); -} - -async function verifyToggleBreadcrumb(page: Page) { - await test.step('verify "customize notebook > toggle breadcrumbs" (web only)', async () => { - const breadcrumbs = page.locator('.monaco-breadcrumbs'); - - await expect(breadcrumbs).toBeVisible(); - await clickCustomizeNotebookMenuItem(page, 'Toggle Breadcrumbs'); - await expect(breadcrumbs).not.toBeVisible(); - }); -} diff --git a/test/e2e/tests/connections/connections-db.test.ts b/test/e2e/tests/connections/connections-db.test.ts index abc7b9df355..b2d586ab8d0 100644 --- a/test/e2e/tests/connections/connections-db.test.ts +++ b/test/e2e/tests/connections/connections-db.test.ts @@ -91,7 +91,6 @@ test.describe('SQLite DB Connection', { await app.workbench.console.executeCode( 'R', `con <- connections::connection_open(RSQLite::SQLite(), tempfile())`, - '>' ); }); @@ -111,7 +110,6 @@ test.describe('SQLite DB Connection', { await app.workbench.console.executeCode( 'R', `DBI::dbWriteTable(con, 'mtcars', mtcars)`, - '>' ); // refresh and mtcars should exist diff --git a/test/e2e/tests/console/console-python.test.ts b/test/e2e/tests/console/console-python.test.ts index a529ce8928c..5a92b22f864 100644 --- a/test/e2e/tests/console/console-python.test.ts +++ b/test/e2e/tests/console/console-python.test.ts @@ -22,8 +22,7 @@ test.describe('Console Pane: Python', { tag: [tags.WEB, tags.WIN, tags.CONSOLE] await app.workbench.console.consoleRestartButton.click(); await app.workbench.quickaccess.runCommand('workbench.action.toggleAuxiliaryBar'); - await app.workbench.console.waitForReady('>>>'); - await app.workbench.console.waitForConsoleContents('restarted'); + await app.workbench.console.waitForReadyAndRestarted('>>>'); await expect(app.workbench.console.consoleRestartButton).not.toBeVisible(); }).toPass(); }); @@ -39,8 +38,7 @@ test.describe('Console Pane: Python', { tag: [tags.WEB, tags.WIN, tags.CONSOLE] await app.workbench.console.barRestartButton.click(); await app.workbench.quickaccess.runCommand('workbench.action.toggleAuxiliaryBar'); - await app.workbench.console.waitForReady('>>>'); - await app.workbench.console.waitForConsoleContents('restarted'); + await app.workbench.console.waitForReadyAndStarted('>>>'); }); test('Verify cancel button on console bar [C...]', { diff --git a/test/e2e/tests/console/console-r.test.ts b/test/e2e/tests/console/console-r.test.ts index 632151d9624..57587d0b68f 100644 --- a/test/e2e/tests/console/console-r.test.ts +++ b/test/e2e/tests/console/console-r.test.ts @@ -22,8 +22,7 @@ test.describe('Console Pane: R', { await app.workbench.console.barClearButton.click(); await app.workbench.console.barPowerButton.click(); await app.workbench.console.consoleRestartButton.click(); - await app.workbench.console.waitForReady('>'); - await app.workbench.console.waitForConsoleContents('restarted'); + await app.workbench.console.waitForReadyAndRestarted('>'); await expect(app.workbench.console.consoleRestartButton).not.toBeVisible(); }).toPass(); }); @@ -32,8 +31,7 @@ test.describe('Console Pane: R', { await expect(async () => { await app.workbench.console.barClearButton.click(); await app.workbench.console.barRestartButton.click(); - await app.workbench.console.waitForReady('>'); - await app.workbench.console.waitForConsoleContents('restarted'); + await app.workbench.console.waitForReadyAndRestarted('>'); }).toPass(); }); diff --git a/test/e2e/tests/data-explorer/100x100-pandas.test.ts b/test/e2e/tests/data-explorer/100x100-pandas.test.ts index e8e3945245e..e9355b9e5b8 100644 --- a/test/e2e/tests/data-explorer/100x100-pandas.test.ts +++ b/test/e2e/tests/data-explorer/100x100-pandas.test.ts @@ -20,7 +20,6 @@ test('Data Explorer 100x100 - Python - Pandas [C557563]', { await testDataExplorer( app, 'Python', - '>>>', [ 'import pandas as pd', `${dataFrameName} = pd.read_parquet("${parquetFilePath(app)}")`, diff --git a/test/e2e/tests/data-explorer/100x100-polars.test.ts b/test/e2e/tests/data-explorer/100x100-polars.test.ts index 6c7de9559b8..3162fea0403 100644 --- a/test/e2e/tests/data-explorer/100x100-polars.test.ts +++ b/test/e2e/tests/data-explorer/100x100-polars.test.ts @@ -20,7 +20,6 @@ test('Data Explorer 100x100 - Python - Polars [C674520]', { await testDataExplorer( app, 'Python', - '>>>', [ 'import polars', `${dataFrameName} = polars.read_parquet("${parquetFilePath(app)}")`, diff --git a/test/e2e/tests/data-explorer/100x100-r.test.ts b/test/e2e/tests/data-explorer/100x100-r.test.ts index 1a4142487d2..00a448767f2 100644 --- a/test/e2e/tests/data-explorer/100x100-r.test.ts +++ b/test/e2e/tests/data-explorer/100x100-r.test.ts @@ -21,7 +21,6 @@ test('Data Explorer 100x100 - R [C674521]', { await testDataExplorer( app, 'R', - '>', [ 'library(arrow)', `${dataFrameName} <- read_parquet("${parquetFilePath(app)}")`, 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 0f40cc78a20..be7dd53f1df 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 @@ -22,7 +22,7 @@ data = {'Name':['Jai', 'Princi', 'Gaurav', 'Anuj'], df = pd.DataFrame(data)`; logger.log('Sending code to console'); - await app.workbench.console.executeCode('Python', script, '>>>'); + await app.workbench.console.executeCode('Python', script); logger.log('Opening data grid'); await expect(async () => { @@ -62,7 +62,7 @@ data = { df2 = pd.DataFrame(data)`; logger.log('Sending code to console'); - await app.workbench.console.executeCode('Python', script, '>>>'); + await app.workbench.console.executeCode('Python', script); logger.log('Opening data grid'); await expect(async () => { @@ -192,7 +192,7 @@ Data_Frame = pd.DataFrame({ "gear": [4, 4, 4, 3, 3], "carb": [4, 4, 1, 1, 2] })`; - await app.workbench.console.executeCode('Python', script, '>>>'); + await app.workbench.console.executeCode('Python', script); await app.workbench.quickaccess.runCommand('workbench.panel.positronVariables.focus'); await expect(async () => { @@ -217,7 +217,7 @@ Data_Frame = pd.DataFrame({ const script = `import pandas as pd df = pd.DataFrame({'x': ["a ", "a", " ", ""]})`; - await app.workbench.console.executeCode('Python', script, '>>>'); + await app.workbench.console.executeCode('Python', script); await expect(async () => { await app.workbench.variables.doubleClickVariableRow('df'); diff --git a/test/e2e/tests/data-explorer/data-explorer-r.test.ts b/test/e2e/tests/data-explorer/data-explorer-r.test.ts index 93abe3bc3a0..7bfbdfd5be8 100644 --- a/test/e2e/tests/data-explorer/data-explorer-r.test.ts +++ b/test/e2e/tests/data-explorer/data-explorer-r.test.ts @@ -22,7 +22,7 @@ test.describe('Data Explorer - R ', { )`; logger.log('Sending code to console'); - await app.workbench.console.executeCode('R', script, '>'); + await app.workbench.console.executeCode('R', script); logger.log('Opening data grid'); await expect(async () => { @@ -82,7 +82,7 @@ test.describe('Data Explorer - R ', { // Regression test for https://github.com/posit-dev/positron/issues/4197 // and https://github.com/posit-dev/positron/issues/5714 const script = `Data_Frame <- mtcars`; - await app.workbench.console.executeCode('R', script, '>'); + await app.workbench.console.executeCode('R', script); await app.workbench.quickaccess.runCommand('workbench.panel.positronVariables.focus'); await expect(async () => { @@ -105,7 +105,7 @@ test.describe('Data Explorer - R ', { test('R - Check blank spaces in data explorer [C1078834]', async function ({ app, r }) { const script = `df = data.frame(x = c("a ", "a", " ", ""))`; - await app.workbench.console.executeCode('R', script, '>'); + await app.workbench.console.executeCode('R', script); await expect(async () => { await app.workbench.variables.doubleClickVariableRow('df'); diff --git a/test/e2e/tests/data-explorer/helpers/100x100.ts b/test/e2e/tests/data-explorer/helpers/100x100.ts index 681fc8e643d..5777224f7f4 100644 --- a/test/e2e/tests/data-explorer/helpers/100x100.ts +++ b/test/e2e/tests/data-explorer/helpers/100x100.ts @@ -11,7 +11,6 @@ import { Application } from '../../../infra'; export const testDataExplorer = async ( app: Application, language: 'Python' | 'R', - prompt: string, commands: string[], dataFrameName: string, tsvFilePath: string @@ -21,7 +20,6 @@ export const testDataExplorer = async ( await app.workbench.console.executeCode( language, commands[i], - prompt ); } diff --git a/test/e2e/tests/data-explorer/sparklines.test.ts b/test/e2e/tests/data-explorer/sparklines.test.ts index dcda21658ae..9c32b76bd26 100644 --- a/test/e2e/tests/data-explorer/sparklines.test.ts +++ b/test/e2e/tests/data-explorer/sparklines.test.ts @@ -23,14 +23,14 @@ test.describe('Data Explorer - Sparklines', { }); test('Python Pandas - Verifies downward trending graph [C830552]', async ({ app, python }) => { - await app.workbench.console.executeCode('Python', pythonScript, '>>>'); + await app.workbench.console.executeCode('Python', pythonScript); await openDataExplorerColumnProfile(app, 'pythonData'); await verifyGraphBarHeights(app); }); test('R - Verifies downward trending graph [C830553]', async ({ app, r }) => { - await app.workbench.console.executeCode('R', rScript, '>'); + await app.workbench.console.executeCode('R', rScript); await openDataExplorerColumnProfile(app, 'rData'); await verifyGraphBarHeights(app); }); diff --git a/test/e2e/tests/editor-action-bar/data-files.test.ts b/test/e2e/tests/editor-action-bar/data-files.test.ts new file mode 100644 index 00000000000..4f58dae5b77 --- /dev/null +++ b/test/e2e/tests/editor-action-bar/data-files.test.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Application } from '../../infra'; +import { EditorActionBar } from '../../pages/editorActionBar'; +import { test, expect, tags } from '../_test.setup'; + +let editorActionBar: EditorActionBar; + +const testCases = [ + { + title: 'R - Load data frame via variables pane', + openFile: 'workspaces/generate-data-frames-r/simple-data-frames.r', + variable: 'df', + tabName: 'Data: df', + }, + { + title: 'Python - Load data frame via variables pane', + openFile: 'workspaces/generate-data-frames-py/simple-data-frames.py', + variable: 'df', + tabName: 'Data: df', + }, + { + title: 'Open parquet file via DuckDB', + openDataFile: 'data-files/100x100/100x100.parquet', + tabName: 'Data: 100x100.parquet', + }, + { + title: 'Open CSV file via DuckDB', + openDataFile: 'data-files/flights/flights.csv', + tabName: 'Data: flights.csv' + }]; + +test.use({ + suiteId: __filename +}); + +test.describe('Editor Action Bar: Data Files', { + tag: [tags.WEB, tags.WIN, tags.EDITOR_ACTION_BAR, tags.DATA_EXPLORER] +}, () => { + + test.beforeAll(async function ({ app, userSettings }) { + editorActionBar = app.workbench.editorActionBar; + await userSettings.set([['editor.actionBar.enabled', 'true']], false); + }); + + test.afterEach(async function ({ runCommand }) { + await runCommand('workbench.action.closeAllEditors'); + await runCommand('Console: Clear Console'); + }); + + for (const testCase of testCases) { + test(testCase.title, async function ({ app, interpreter, openDataFile, openFile }) { + // Set interpreter + const language = testCase.title.startsWith('R') ? 'R' : 'Python'; + await interpreter.set(language); + + // Open file + testCase.openFile + ? await openFile(testCase.openFile) + : await openDataFile(testCase.openDataFile!); + + // Open data explorer via variable pane + if (testCase.variable) { + await openDataExplorerViaVariablePane(app, testCase.variable, testCase.tabName); + } + + // Verify action bar behavior + await editorActionBar.selectSummaryOn(app.web, 'Left'); + await editorActionBar.verifySummaryPosition('Left'); + + await editorActionBar.selectSummaryOn(app.web, 'Right'); + await editorActionBar.verifySummaryPosition('Right'); + + await editorActionBar.clickSplitEditorButton('right'); + await editorActionBar.verifySplitEditor('right', testCase.tabName); + + await editorActionBar.clickSplitEditorButton('down'); + await editorActionBar.verifySplitEditor('down', testCase.tabName); + + await editorActionBar.verifyOpenInNewWindow(app.web, `${testCase.tabName} — qa-example-content`); + }); + } +}); + + +async function openDataExplorerViaVariablePane(app: Application, variable: string, tabName: string) { + await test.step('Open data explorer via variable pane', async () => { + await app.workbench.editor.playButton.click(); + await app.workbench.variables.doubleClickVariableRow(variable); + await app.code.driver.page.getByRole('tablist').locator('.tab').first().click(); + await app.code.driver.page.getByLabel('Close').first().click(); + await expect(app.code.driver.page.getByText(tabName, { exact: true })).toBeVisible(); + }); +} diff --git a/test/e2e/tests/editor-action-bar/document-files.test.ts b/test/e2e/tests/editor-action-bar/document-files.test.ts new file mode 100644 index 00000000000..2e459a170c7 --- /dev/null +++ b/test/e2e/tests/editor-action-bar/document-files.test.ts @@ -0,0 +1,141 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +import { expect, Page } from '@playwright/test'; +import { test, tags } from '../_test.setup'; +import { EditorActionBar } from '../../pages/editorActionBar'; +import { Application } from '../../infra'; + +let editorActionBar: EditorActionBar; + +test.use({ + suiteId: __filename +}); + +test.describe('Editor Action Bar: Document Files', { + tag: [tags.WEB, tags.WIN, tags.EDITOR_ACTION_BAR, tags.EDITOR] +}, () => { + + test.beforeAll(async function ({ userSettings, app }) { + editorActionBar = app.workbench.editorActionBar; + await userSettings.set([['editor.actionBar.enabled', 'true']], false); + }); + + test.afterEach(async function ({ runCommand }) { + await runCommand('workbench.action.closeAllEditors'); + }); + + test('R Markdown Document [C1080703]', { + tag: [tags.R_MARKDOWN] + }, async function ({ app, openFile }) { + await openFile('workspaces/basic-rmd-file/basicRmd.rmd'); + await verifyPreviewRendersHtml('Getting startedAnchor'); + await verifySplitEditor('basicRmd.rmd'); + await verifyOpenInNewWindow(app, 'This post examines the features'); + }); + + test('Quarto Document [C1080700]', { + tag: [tags.QUARTO] + }, async function ({ app, page, openFile }) { + await openFile('workspaces/quarto_basic/quarto_basic.qmd'); + await verifyPreviewRendersHtml('Diamond sizes'); + await verifyOpenChanges(page); + await verifySplitEditor('quarto_basic.qmd'); + await verifyOpenInNewWindow(app, 'Diamond sizes'); + }); + + test('HTML Document [C1080701]', { tag: [tags.HTML] }, async function ({ app, page, openFile }) { + await openFile('workspaces/dash-py-example/data/OilandGasMetadata.html'); + await verifyOpenViewerRendersHtml(app, 'Oil, Gas, and Other Regulated'); + await verifySplitEditor('OilandGasMetadata.html'); + await verifyOpenInNewWindow(app, ' Oil & Gas Wells - Metadata'); + }); + + test('Jupyter Notebook [C1080702]', { + tag: [tags.NOTEBOOKS], + }, async function ({ app, page, openDataFile }) { + await openDataFile('workspaces/large_r_notebook/spotify.ipynb'); + + if (app.web) { + await verifyToggleLineNumbers(page); + await verifyToggleBreadcrumb(page); + } + + await verifySplitEditor('spotify.ipynb'); + }); +}); + + +// Helper functions + +async function verifyPreviewRendersHtml(heading: string) { + await editorActionBar.previewButton.click(); + await editorActionBar.verifyPreviewRendersHtml(heading); +} + +async function verifySplitEditor(tabName: string) { + await editorActionBar.clickSplitEditorButton('right'); + await editorActionBar.verifySplitEditor('right', tabName); + + await editorActionBar.clickSplitEditorButton('down'); + await editorActionBar.verifySplitEditor('down', tabName); +} + +async function verifyOpenInNewWindow(app: Application, title: string) { + await editorActionBar.verifyOpenInNewWindow(app.web, title); +} + +async function verifyOpenViewerRendersHtml(app: Application, title: string) { + await editorActionBar.openInViewerButton.click(); + await editorActionBar.verifyOpenViewerRendersHtml(app.web, title); +} + +async function verifyOpenChanges(page: Page) { + await test.step('verify "open changes" shows diff', async () => { + async function bindPlatformHotkey(page: Page, key: string) { + await page.keyboard.press(process.platform === 'darwin' ? `Meta+${key}` : `Control+${key}`); + } + + // make change & save + await page.getByText('date', { exact: true }).click(); + await page.keyboard.press('X'); + await bindPlatformHotkey(page, 'S'); + + // click open changes & verify + await page.getByLabel('Open Changes').click(); + await expect(page.getByLabel('Revert Block')).toBeVisible(); + await expect(page.getByLabel('Stage Block')).toBeVisible(); + await page.getByRole('tab', { name: 'quarto_basic.qmd (Working' }).getByLabel('Close').click(); + + // undo changes & save + await bindPlatformHotkey(page, 'Z'); + await bindPlatformHotkey(page, 'S'); + }); +} + +async function verifyToggleLineNumbers(page: Page) { + async function verifyLineNumbersVisibility(page: Page, isVisible: boolean) { + for (const lineNum of [1, 2, 3, 4, 5]) { + const lineNumbers = expect(page.locator('.line-numbers').getByText(lineNum.toString(), { exact: true })); + isVisible ? await lineNumbers.toBeVisible() : await lineNumbers.not.toBeVisible(); + } + } + + await test.step('verify "customize notebook > toggle line numbers" (web only)', async () => { + await verifyLineNumbersVisibility(page, false); + await editorActionBar.clickCustomizeNotebookMenuItem('Toggle Notebook Line Numbers'); + await verifyLineNumbersVisibility(page, true); + }); +} + +async function verifyToggleBreadcrumb(page: Page) { + await test.step('verify "customize notebook > toggle breadcrumbs" (web only)', async () => { + const breadcrumbs = page.locator('.monaco-breadcrumbs'); + + await expect(breadcrumbs).toBeVisible(); + await editorActionBar.clickCustomizeNotebookMenuItem('Toggle Breadcrumbs'); + await expect(breadcrumbs).not.toBeVisible(); + }); +} diff --git a/test/e2e/tests/help/help.test.ts b/test/e2e/tests/help/help.test.ts index f58959465da..cd2e603c7e4 100644 --- a/test/e2e/tests/help/help.test.ts +++ b/test/e2e/tests/help/help.test.ts @@ -18,7 +18,7 @@ test.describe('Help', { tag: [tags.HELP, tags.WEB] }, () => { }); test('Python - Verifies basic help functionality [C633814]', { tag: [tags.WIN] }, async function ({ app, python }) { - await app.workbench.console.executeCode('Python', `?load`, '>>>'); + await app.workbench.console.executeCode('Python', `?load`); await expect(async () => { const helpFrame = await app.workbench.help.getHelpFrame(0); @@ -28,7 +28,7 @@ test.describe('Help', { tag: [tags.HELP, tags.WEB] }, () => { }); test('R - Verifies basic help functionality [C633813]', { tag: [tags.WIN] }, async function ({ app, r }) { - await app.workbench.console.executeCode('R', `?load()`, '>'); + await app.workbench.console.executeCode('R', `?load()`); await expect(async () => { const helpFrame = await app.workbench.help.getHelpFrame(1); diff --git a/test/e2e/tests/new-project-wizard/new-project-python.test.ts b/test/e2e/tests/new-project-wizard/new-project-python.test.ts index f7121f5d230..3a9019b6711 100644 --- a/test/e2e/tests/new-project-wizard/new-project-python.test.ts +++ b/test/e2e/tests/new-project-wizard/new-project-python.test.ts @@ -104,7 +104,7 @@ async function createNewProject(app: Application, options: CreateProjectOptions) async function verifyProjectCreation(app: Application, projectTitle: string) { await test.step(`Verify project created`, async () => { await expect(app.code.driver.page.getByLabel('Folder Commands')).toHaveText(projectTitle, { timeout: 60000 }); // this is really slow on windows CI for some reason - await app.workbench.console.waitForReady('>>>', 90000); + await app.workbench.console.waitForReadyAndStarted('>>>', 90000); }); } diff --git a/test/e2e/tests/plots/plots.test.ts b/test/e2e/tests/plots/plots.test.ts index c9413950f8f..f3ae00abeb0 100644 --- a/test/e2e/tests/plots/plots.test.ts +++ b/test/e2e/tests/plots/plots.test.ts @@ -44,7 +44,7 @@ test.describe('Plots', { tag: [tags.PLOTS, tags.EDITOR] }, () => { }, async function ({ app, logger, headless, logsPath }, testInfo) { // modified snippet from https://www.geeksforgeeks.org/python-pandas-dataframe/ logger.log('Sending code to console'); - await app.workbench.console.executeCode('Python', pythonDynamicPlot, '>>>'); + await app.workbench.console.executeCode('Python', pythonDynamicPlot); await app.workbench.plots.waitForCurrentPlot(); const buffer = await app.workbench.plots.getCurrentPlotAsBuffer(); @@ -83,7 +83,7 @@ test.describe('Plots', { tag: [tags.PLOTS, tags.EDITOR] }, () => { tag: [tags.CRITICAL, tags.WEB, tags.WIN] }, async function ({ app, logger, logsPath }, testInfo) { logger.log('Sending code to console'); - await app.workbench.console.executeCode('Python', pythonStaticPlot, '>>>'); + await app.workbench.console.executeCode('Python', pythonStaticPlot); await app.workbench.plots.waitForCurrentStaticPlot(); const buffer = await app.workbench.plots.getCurrentStaticPlotAsBuffer(); @@ -113,9 +113,9 @@ test.describe('Plots', { tag: [tags.PLOTS, tags.EDITOR] }, () => { await expect(plots.zoomPlotButton).not.toBeVisible(); // create plots separately so that the order is known - await app.workbench.console.executeCode('Python', pythonPlotActions1, '>>>'); + await app.workbench.console.executeCode('Python', pythonPlotActions1); await plots.waitForCurrentStaticPlot(); - await app.workbench.console.executeCode('Python', pythonPlotActions2, '>>>'); + await app.workbench.console.executeCode('Python', pythonPlotActions2); await plots.waitForCurrentPlot(); // expand the plot pane to show the action bar @@ -152,7 +152,7 @@ test.describe('Plots', { tag: [tags.PLOTS, tags.EDITOR] }, () => { test('Python - Verifies saving a Python plot [C557005]', { tag: [tags.WIN] }, async function ({ app, logger }) { await test.step('Sending code to console to create plot', async () => { - await app.workbench.console.executeCode('Python', pythonDynamicPlot, '>>>'); + await app.workbench.console.executeCode('Python', pythonDynamicPlot); await app.workbench.plots.waitForCurrentPlot(); await app.workbench.layouts.enterLayout('fullSizedAuxBar'); }); @@ -292,7 +292,7 @@ test.describe('Plots', { tag: [tags.PLOTS, tags.EDITOR] }, () => { tag: [tags.CRITICAL, tags.WEB, tags.WIN] }, async function ({ app, logger, headless, logsPath }, testInfo) { logger.log('Sending code to console'); - await app.workbench.console.executeCode('R', rBasicPlot, '>'); + await app.workbench.console.executeCode('R', rBasicPlot); await app.workbench.plots.waitForCurrentPlot(); const buffer = await app.workbench.plots.getCurrentPlotAsBuffer(); @@ -329,7 +329,7 @@ test.describe('Plots', { tag: [tags.PLOTS, tags.EDITOR] }, () => { test('R - Verifies saving an R plot [C557006]', { tag: [tags.WIN] }, async function ({ app, logger }) { await test.step('Sending code to console to create plot', async () => { - await app.workbench.console.executeCode('R', rSavePlot, '>'); + await app.workbench.console.executeCode('R', rSavePlot); await app.workbench.plots.waitForCurrentPlot(); }); diff --git a/test/e2e/tests/r-pkg-development/r-pkg-development.test.ts b/test/e2e/tests/r-pkg-development/r-pkg-development.test.ts index 222fa9417f5..10fe860e620 100644 --- a/test/e2e/tests/r-pkg-development/r-pkg-development.test.ts +++ b/test/e2e/tests/r-pkg-development/r-pkg-development.test.ts @@ -36,7 +36,7 @@ test.describe('R Package Development', { tag: [tags.WEB, tags.R_PKG_DEVELOPMENT] await app.workbench.quickInput.clickOkOnQuickInput(); // Wait for the console to be ready - await app.workbench.console.waitForReady('>', 45000); + await app.workbench.console.waitForReadyAndStarted('>', 45000); }); await test.step('Test R Package', async () => { diff --git a/test/e2e/tests/reticulate/reticulate.test.ts b/test/e2e/tests/reticulate/reticulate.test.ts index d33bdbcc9af..5c1019cc24f 100644 --- a/test/e2e/tests/reticulate/reticulate.test.ts +++ b/test/e2e/tests/reticulate/reticulate.test.ts @@ -46,7 +46,7 @@ test.describe('Reticulate', { // Prompt did not appear } - await app.workbench.console.waitForReady('>>>'); + await app.workbench.console.waitForReadyAndStarted('>>>'); await verifyReticulateFunctionality(app, interpreter, false); @@ -73,7 +73,7 @@ test.describe('Reticulate', { await app.code.driver.page.locator('.positron-console').getByRole('button', { name: 'Restart R' }).click(); - await app.workbench.console.waitForReady('>'); + await app.workbench.console.waitForReadyAndStarted('>'); await app.code.driver.page.locator('.positron-console').locator('.action-bar-button-drop-down-arrow').click(); @@ -83,7 +83,7 @@ test.describe('Reticulate', { await app.code.driver.page.locator('.positron-console').getByRole('button', { name: 'Restart Python' }).click(); - await app.workbench.console.waitForReady('>>>'); + await app.workbench.console.waitForReadyAndStarted('>>>'); await verifyReticulateFunctionality(app, interpreter, sequential); diff --git a/test/e2e/tests/test-explorer/test-explorer.test.ts b/test/e2e/tests/test-explorer/test-explorer.test.ts index ed58436d3f6..acdcc8c56ba 100644 --- a/test/e2e/tests/test-explorer/test-explorer.test.ts +++ b/test/e2e/tests/test-explorer/test-explorer.test.ts @@ -36,7 +36,7 @@ test.describe('Test Explorer', { tag: [tags.TEST_EXPLORER, tags.WEB] }, () => { await app.workbench.quickInput.waitForQuickInputOpened(); await app.workbench.quickInput.type(path.join(app.workspacePathOrFolder, 'workspaces', 'r_testing')); await app.workbench.quickInput.clickOkOnQuickInput(); - await app.workbench.console.waitForReady('>', 10000); + await app.workbench.console.waitForReadyAndStarted('>', 10000); }).toPass({ timeout: 50000 }); await app.workbench.testExplorer.clickTestExplorerIcon(); diff --git a/test/e2e/tests/variables/variables-expanded.test.ts b/test/e2e/tests/variables/variables-expanded.test.ts index b2fab9f1494..0097ae22b6e 100644 --- a/test/e2e/tests/variables/variables-expanded.test.ts +++ b/test/e2e/tests/variables/variables-expanded.test.ts @@ -18,7 +18,7 @@ test.describe('Variables - Expanded View', { tag: [tags.WEB, tags.VARIABLES] }, test('Python - should display children values and types when variable is expanded [C1078836]', async function ({ app, python }) { const variables = app.workbench.variables; - await app.workbench.console.executeCode('Python', script, '>>>'); + await app.workbench.console.executeCode('Python', script); await app.workbench.layouts.enterLayout('fullSizedAuxBar'); await variables.expandVariable('df'); @@ -34,7 +34,7 @@ test.describe('Variables - Expanded View', { tag: [tags.WEB, tags.VARIABLES] }, // workaround for https://github.com/posit-dev/positron/issues/5718 await app.workbench.popups.closeAllToasts(); - await app.workbench.console.executeCode('R', 'df2 <- data.frame(b=rep(1:1000000))', '>'); + await app.workbench.console.executeCode('R', 'df2 <- data.frame(b=rep(1:1000000))'); await app.workbench.layouts.enterLayout('fullSizedAuxBar'); await variables.expandVariable('df2'); diff --git a/test/e2e/tests/variables/variables-pane.test.ts b/test/e2e/tests/variables/variables-pane.test.ts index 6bbe61a72f3..838d6ec296f 100644 --- a/test/e2e/tests/variables/variables-pane.test.ts +++ b/test/e2e/tests/variables/variables-pane.test.ts @@ -18,7 +18,7 @@ test.describe('Variables Pane', { test('Python - Verifies Variables pane basic function [C628634]', async function ({ app, logger, python }) { const executeCode = async (code: string) => { - await app.workbench.console.executeCode('Python', code, '>>>'); + await app.workbench.console.executeCode('Python', code); }; await executeCode('x=1'); @@ -49,7 +49,7 @@ test.describe('Variables Pane', { test('R - Verifies Variables pane basic function [C628635]', async function ({ app, logger, r }) { const executeCode = async (code: string) => { - await app.workbench.console.executeCode('R', code, '>'); + await app.workbench.console.executeCode('R', code); }; await executeCode('x=1'); diff --git a/test/e2e/tests/viewer/viewer.test.ts b/test/e2e/tests/viewer/viewer.test.ts index 343e35dcbea..e367b56c102 100644 --- a/test/e2e/tests/viewer/viewer.test.ts +++ b/test/e2e/tests/viewer/viewer.test.ts @@ -43,7 +43,7 @@ test.describe('Viewer', { tag: [tags.VIEWER] }, () => { test('R - Verify Viewer functionality with modelsummary [C784889]', { tag: [tags.WEB] }, async function ({ app, logger, r }) { logger.log('Sending code to console'); - await app.workbench.console.executeCode('R', rModelSummaryScript, '>'); + await app.workbench.console.executeCode('R', rModelSummaryScript); let billDepthLocator; if (!app.web) { billDepthLocator = app.workbench.viewer.getViewerLocator('tr').filter({ hasText: 'bill_depth_mm' }); @@ -59,7 +59,7 @@ test.describe('Viewer', { tag: [tags.VIEWER] }, () => { }, async function ({ app, logger, r }) { logger.log('Sending code to console'); - await app.workbench.console.executeCode('R', rReactableScript, '>'); + await app.workbench.console.executeCode('R', rReactableScript); const datsun710 = app.workbench.viewer.getViewerLocator('div.rt-td-inner').filter({ hasText: 'Datsun 710' }); @@ -72,7 +72,7 @@ test.describe('Viewer', { tag: [tags.VIEWER] }, () => { }, async function ({ app, logger, r }) { logger.log('Sending code to console'); - await app.workbench.console.executeCode('R', rReprexScript, '>'); + await app.workbench.console.executeCode('R', rReprexScript); const rnorm = app.workbench.viewer.getViewerLocator('code.sourceCode').filter({ hasText: 'rbinom' });