Skip to content

Commit

Permalink
Welcome page smoke tests (#4092)
Browse files Browse the repository at this point in the history
Adds smoke tests for verifying the Welcome page content and that the
Start section buttons work.

Changed the `PositronPopup` to allow reusing the modal dialog title
locator.

Fixed the modal popup clicking on an option. It wasn't reliable with
plot tests and needed to support regex to locate the primary
interpreter.

Signed-off-by: Tim Mok <[email protected]>
Co-authored-by: Nick Strayer <[email protected]>
Co-authored-by: Jon Vanausdeln <[email protected]>
  • Loading branch information
3 people authored Jul 26, 2024
1 parent 3d8649a commit 79b6f0d
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 4 deletions.
5 changes: 5 additions & 0 deletions test/automation/src/editors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
import { Code } from './code';

export class Editors {
// --- Start Positron ---
activeEditor = this.code.driver.getLocator('div.tab.tab-actions-right.active.selected');
editorIcon = this.code.driver.getLocator('.monaco-icon-label.file-icon');
editorPart = this.code.driver.getLocator('.split-view-view .part.editor');
// --- End Positron ---

constructor(private code: Code) { }

Expand Down
1 change: 1 addition & 0 deletions test/automation/src/positron/positronNotebooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const MARKDOWN_TEXT = '#preview';
* Reuseable Positron notebook functionality for tests to leverage. Includes selecting the notebook's interpreter.
*/
export class PositronNotebooks {
kernelLabel = this.code.driver.getLocator(KERNEL_LABEL);

constructor(private code: Code, private quickinput: QuickInput, private quickaccess: QuickAccess, private notebook: Notebook) { }

Expand Down
17 changes: 13 additions & 4 deletions test/automation/src/positron/positronPopups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { Code } from '../code';
const POSITRON_MODAL_DIALOG_BOX = '.positron-modal-dialog-box';
const POSITRON_MODAL_DIALOG_BOX_OK = '.positron-modal-dialog-box .ok-cancel-action-bar .positron-button.action-bar-button.default';
const POSITRON_MODAL_DIALOG_BOX_CANCEL = '.positron-modal-dialog-box .ok-cancel-action-bar .positron-button.action-bar-button:not(.default)';
const POSITRON_MODAL_DIALOG_BOX_MISSING_R_PACKAGE_TITLE = '.positron-modal-dialog-box .simple-title-bar-title';
const POSITRON_MODAL_DIALOG_BOX_TITLE = '.positron-modal-dialog-box .simple-title-bar-title';
const POSITRON_MODAL_DIALOG_POPUP_OPTION = '.positron-modal-popup .positron-modal-popup-children';
const NOTIFICATION_TOAST = '.notification-toast';

/*
Expand Down Expand Up @@ -57,7 +58,7 @@ export class PositronPopups {
this.code.logger.log('Checking for install Renv modal dialog box');
// fail fast if the renv install modal is not present
await this.code.waitForTextContent(
POSITRON_MODAL_DIALOG_BOX_MISSING_R_PACKAGE_TITLE,
POSITRON_MODAL_DIALOG_BOX_TITLE,
'Missing R package',
undefined,
50
Expand Down Expand Up @@ -102,8 +103,16 @@ export class PositronPopups {
* Can be called after a DropDownListBox is clicked. Selects the option with the given label.
* @param label The label of the option to select.
*/
async clickOnModalDialogPopupOption(label: string) {
const el = this.code.driver.getLocator('.positron-modal-popup .positron-button .title').filter({ hasText: label });
async clickOnModalDialogPopupOption(label: string | RegExp) {
const el = this.code.driver.getLocator(POSITRON_MODAL_DIALOG_POPUP_OPTION).getByText(label);
await el.click();
}

/**
* Waits for the modal dialog box title to match the given title.
* @param title The title to wait for.
*/
async waitForModalDialogTitle(title: string) {
await this.code.waitForTextContent(POSITRON_MODAL_DIALOG_BOX_TITLE, title);
}
}
43 changes: 43 additions & 0 deletions test/automation/src/positron/positronWelcome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/


import { Code } from '../code';

const LOGO = '.product-logo';
const TITLE = '.gettingStartedCategoriesContainer div.header div .positron';
const FOOTER = '.gettingStartedCategoriesContainer div.footer';
const START_SECTION = '.positron-welcome-page-open';
const HELP_TITLE = '.positron-welcome-page-help';
const OPEN_SECTION = '.categories-column.categories-column-right .index-list.start-container';
const RECENT_SECTION = '.categories-column.categories-column-right .index-list.recently-opened';

const HEADING_ROLE = 'heading';
const BUTTON_ROLE = 'button';
const LINK_ROLE = 'link';

export class PositronWelcome {

logo = this.code.driver.getLocator(LOGO);
title = this.code.driver.getLocator(TITLE);
footer = this.code.driver.getLocator(FOOTER);
startSection = this.code.driver.getLocator(START_SECTION);
startTitle = this.startSection.getByRole(HEADING_ROLE);
startButtons = this.startSection.getByRole(BUTTON_ROLE);
helpSection = this.code.driver.getLocator(HELP_TITLE);
helpTitle = this.helpSection.getByRole(HEADING_ROLE);
helpLinks = this.helpSection.getByRole(LINK_ROLE);
openSection = this.code.driver.getLocator(OPEN_SECTION);
openTitle = this.openSection.getByRole(HEADING_ROLE);
openButtons = this.openSection.getByRole(BUTTON_ROLE);
recentSection = this.code.driver.getLocator(RECENT_SECTION);
recentTitle = this.recentSection.getByRole(HEADING_ROLE);
newNotebookButton = this.startButtons.getByText('New Notebook');
newFileButton = this.startButtons.getByText('New File');
newConsoleButton = this.startButtons.getByText('New Console');
newProjectButton = this.startButtons.getByText('New Project');

constructor(private code: Code) { }
}
3 changes: 3 additions & 0 deletions test/automation/src/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { PositronHelp } from './positron/positronHelp';
import { PositronTopActionBar } from './positron/positronTopActionBar';
import { PositronLayouts } from './positron/positronLayouts';
import { PositronOutput } from './positron/positronOutput';
import { PositronWelcome } from './positron/positronWelcome';
// --- End Positron ---

export interface Commands {
Expand Down Expand Up @@ -82,6 +83,7 @@ export class Workbench {
readonly positronTopActionBar: PositronTopActionBar;
readonly positronLayouts: PositronLayouts;
readonly positronOutput: PositronOutput;
readonly positronWelcome: PositronWelcome;
// --- End Positron ---

constructor(code: Code) {
Expand Down Expand Up @@ -120,6 +122,7 @@ export class Workbench {
this.positronTopActionBar = new PositronTopActionBar(code);
this.positronLayouts = new PositronLayouts(code, this);
this.positronOutput = new PositronOutput(code, this.quickaccess, this.quickinput);
this.positronWelcome = new PositronWelcome(code);
// --- End Positron ---
}
}
179 changes: 179 additions & 0 deletions test/smoke/src/areas/positron/welcome/welcome.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/


import { expect } from '@playwright/test';
import { Application, Logger, PositronPythonFixtures, PositronRFixtures } from '../../../../../automation';
import { installAllHandlers } from '../../../utils';

/*
* Welcome test cases.
*/

export function setup(logger: Logger) {
describe('Welcome Page', () => {
installAllHandlers(logger);

before(async function () {
await PositronPythonFixtures.SetupFixtures(this.app as Application);
});

beforeEach(async function () {
const app = this.app as Application;

await app.workbench.quickaccess.runCommand('Help: Welcome');
app.workbench.editors.waitForActiveEditor('Welcome');
});

afterEach(async function () {
const app = this.app as Application;
await app.workbench.quickaccess.runCommand('View: Close All Editors');
});

it('Verify Welcome page header and footer [C684750]', async function () {
const app = this.app as Application;

await expect(app.workbench.positronWelcome.logo).toBeVisible();

// product name in release is 'Positron' and in dev is 'Positron Dev'
await expect(app.workbench.positronWelcome.title).toHaveText([/(Positron)|(Positron Dev)/, 'an IDE for data science']);

await expect(app.workbench.positronWelcome.footer).toHaveText('Show welcome page on startup');
});

it('Verify Welcome page content [C610960]', async function () {
const app = this.app as Application;
const OPEN_BUTTONS_LABELS = process.platform === 'darwin' ?
['Open...', 'New Folder...', 'New Folder from Git...']
: ['Open File...', 'Open Folder...', 'New Folder...', 'New Folder from Git...'];

await expect(app.workbench.positronWelcome.startTitle).toHaveText('Start');

await expect(app.workbench.positronWelcome.startButtons).toHaveText(['New Notebook', 'New File', 'New Console', 'New Project']);

await expect(app.workbench.positronWelcome.helpTitle).toHaveText('Help');

await expect(app.workbench.positronWelcome.helpLinks).toHaveText(['Positron Documentation', 'Positron Community', 'Report a bug']);

await expect(app.workbench.positronWelcome.openTitle).toHaveText('Open');

await expect(app.workbench.positronWelcome.openButtons).toHaveText(OPEN_BUTTONS_LABELS);

await app.workbench.quickaccess.runCommand('File: Clear Recently Opened...');

await expect(app.workbench.positronWelcome.recentTitle).toHaveText('Recent');

// 'open a folder' is a button so there is no character space because of its padding
await expect(app.workbench.positronWelcome.recentSection.locator('.empty-recent')).toHaveText('You have no recent folders,open a folderto start.');
});

it('Click on new project from the Welcome page [C684751]', async function () {
const app = this.app as Application;

await app.workbench.positronWelcome.newProjectButton.click();
await app.workbench.positronPopups.popupCurrentlyOpen();

await app.workbench.positronPopups.waitForModalDialogBox();

// confirm New Project dialog box is open
await app.workbench.positronPopups.waitForModalDialogTitle('Create New Project');

await app.workbench.positronPopups.clickCancelOnModalDialogBox();
});

describe('Python', () => {
before(async function () {
await PositronPythonFixtures.SetupFixtures(this.app as Application);
});

it('Create a new Python file from the Welcome page [C684752]', async function () {
const app = this.app as Application;

await app.workbench.positronWelcome.newFileButton.click();

await app.workbench.quickinput.selectQuickInputElementContaining('Python File');

await expect(app.workbench.editors.activeEditor.locator(app.workbench.editors.editorIcon)).toHaveClass(/python-lang-file-icon/);

await app.workbench.quickaccess.runCommand('View: Close Editor');
});

it('Create a new Python notebook from the Welcome page [C684753]', async function () {
const app = this.app as Application;

await app.workbench.positronWelcome.newNotebookButton.click();

await app.workbench.positronPopups.clickOnModalDialogPopupOption('Python Notebook');

await expect(app.workbench.editors.activeEditor.locator(app.workbench.editors.editorIcon)).toHaveClass(/ipynb-ext-file-icon/);

const expectedInterpreterVersion = new RegExp(`Python ${process.env.POSITRON_PY_VER_SEL}`, 'i');
await expect(app.workbench.positronNotebooks.kernelLabel).toHaveText(expectedInterpreterVersion);
});

it('Click on Python console from the Welcome page [C684754]', async function () {
const app = this.app as Application;

await app.workbench.positronWelcome.newConsoleButton.click();
await app.workbench.positronPopups.popupCurrentlyOpen();

const expectedInterpreterVersion = new RegExp(`Python ${process.env.POSITRON_PY_VER_SEL}`, 'i');
await app.workbench.positronPopups.clickOnModalDialogPopupOption(expectedInterpreterVersion);

// editor is hidden because bottom panel is maximized
await expect(app.workbench.editors.editorPart).not.toBeVisible();

// console is the active view in the bottom panel
await expect(app.workbench.positronLayouts.panelViewsTab.and(app.code.driver.getLocator('.checked'))).toHaveText('Console');
});
});

describe('R', () => {
before(async function () {
await PositronRFixtures.SetupFixtures(this.app as Application);
});

it('Create a new R file from the Welcome page [C684755]', async function () {
const app = this.app as Application;

await app.workbench.positronWelcome.newFileButton.click();

await app.workbench.quickinput.selectQuickInputElementContaining('R File');

await expect(app.workbench.editors.activeEditor.locator(app.workbench.editors.editorIcon)).toHaveClass(/r-lang-file-icon/);
});

it('Click on R console from the Welcome page [C684756]', async function () {
const app = this.app as Application;

await app.workbench.positronWelcome.newConsoleButton.click();
await app.workbench.positronPopups.popupCurrentlyOpen();

const expectedInterpreterVersion = new RegExp(`R ${process.env.POSITRON_R_VER_SEL}`, 'i');
await app.workbench.positronPopups.clickOnModalDialogPopupOption(expectedInterpreterVersion);

// editor is hidden because bottom panel is maximized
await expect(app.workbench.editors.editorPart).not.toBeVisible();

// console is the active view in the bottom panel
await expect(app.workbench.positronLayouts.panelViewsTab.and(app.code.driver.getLocator('.checked'))).toHaveText('Console');
});

it('Create a new R notebook from the Welcome page [C684757]', async function () {
const app = this.app as Application;

await app.workbench.positronWelcome.newNotebookButton.click();

await app.workbench.positronPopups.clickOnModalDialogPopupOption('R Notebook');

await expect(app.workbench.editors.activeEditor.locator(app.workbench.editors.editorIcon)).toHaveClass(/ipynb-ext-file-icon/);

const expectedInterpreterVersion = new RegExp(`R ${process.env.POSITRON_R_VER_SEL}`, 'i');
await expect(app.workbench.positronNotebooks.kernelLabel).toHaveText(expectedInterpreterVersion);
});
});
});

}
2 changes: 2 additions & 0 deletions test/smoke/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { setup as setupConsoleInputTest } from './areas/positron/console/console
import { setup as setupConsoleANSITest } from './areas/positron/console/consoleANSI.test';
import { setup as setupConsoleOutputLogTest } from './areas/positron/output/consoleOutputLog.test';
import { setup as setupBasicRMarkdownTest } from './areas/positron/rmarkdown/rmarkdown.test';
import { setup as setupWelcomeTest } from './areas/positron/welcome/welcome.test';
// --- End Positron ---

const rootPath = path.join(__dirname, '..', '..', '..');
Expand Down Expand Up @@ -456,5 +457,6 @@ describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => {
setupConsoleANSITest(logger);
setupConsoleOutputLogTest(logger);
setupBasicRMarkdownTest(logger);
setupWelcomeTest(logger);
// --- End Positron ---
});

0 comments on commit 79b6f0d

Please sign in to comment.