Skip to content

Commit

Permalink
E2E test: Positron editors POM (#5845)
Browse files Browse the repository at this point in the history
Replace MS editors POM with one based solely on Playwright

### QA Notes

All tests should pass.
  • Loading branch information
testlabauto authored Dec 21, 2024
1 parent 18a1951 commit bdc131e
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 23 deletions.
1 change: 1 addition & 0 deletions test/automation/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ export * from './positron/positronQuickaccess';
export * from './positron/positronOutline';
export * from './positron/positronClipboard';
export * from './positron/positronExtensions';
export * from './positron/positronEditors';
// --- End Positron ---
export { getDevElectronPath, getBuildElectronPath, getBuildVersion } from './electron';
32 changes: 32 additions & 0 deletions test/automation/src/positron/positronEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,36 @@ export class PositronEditor {

return topValue;
}

async waitForTypeInEditor(filename: string, text: string, selectorPrefix = ''): Promise<any> {
if (text.includes('\n')) {
throw new Error('waitForTypeInEditor does not support new lines, use either a long single line or dispatchKeybinding(\'Enter\')');
}
const editor = [selectorPrefix || '', EDITOR(filename)].join(' ');

await expect(this.code.driver.page.locator(editor)).toBeVisible();

const textarea = `${editor} textarea`;
await expect(this.code.driver.page.locator(textarea)).toBeFocused();

await this.code.driver.page.locator(textarea).fill(text);

await this.waitForEditorContents(filename, c => c.indexOf(text) > -1, selectorPrefix);
}

async waitForEditorContents(filename: string, accept: (contents: string) => boolean, selectorPrefix = ''): Promise<any> {
const selector = [selectorPrefix || '', `${EDITOR(filename)} .view-lines`].join(' ');
const locator = this.code.driver.page.locator(selector);

let content = '';
await expect(async () => {
content = (await locator.textContent())?.replace(/\u00a0/g, ' ') || '';
if (!accept(content)) {
throw new Error(`Content did not match condition: ${content}`);
}
}).toPass();

return content;
}

}
59 changes: 59 additions & 0 deletions test/automation/src/positron/positronEditors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* 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 { Code } from '../code';


export class PositronEditors {

activeEditor = this.code.driver.page.locator('div.tab.tab-actions-right.active.selected');
editorIcon = this.code.driver.page.locator('.monaco-icon-label.file-icon');
editorPart = this.code.driver.page.locator('.split-view-view .part.editor');

constructor(private code: Code) { }

async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise<void> {
await expect(this.code.driver.page.locator(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][data-resource-name$="${fileName}"]`)).toBeVisible();
}

async newUntitledFile(): Promise<void> {
if (process.platform === 'darwin') {
await this.code.driver.page.keyboard.press('Meta+N');
} else {
await this.code.driver.page.keyboard.press('Control+N');
}

await this.waitForEditorFocus('Untitled-1');
}

async waitForEditorFocus(fileName: string): Promise<void> {
await this.waitForActiveTab(fileName, undefined);
await this.waitForActiveEditor(fileName);
}

async waitForActiveEditor(fileName: string): Promise<any> {
const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] textarea`;
await expect(this.code.driver.page.locator(selector)).toBeFocused();
}

async selectTab(fileName: string): Promise<void> {

// Selecting a tab and making an editor have keyboard focus
// is critical to almost every test. As such, we try our
// best to retry this task in case some other component steals
// focus away from the editor while we attempt to get focus

await expect(async () => {
await this.code.driver.page.locator(`.tabs-container div.tab[data-resource-name$="${fileName}"]`).click();
await this.code.driver.page.keyboard.press(process.platform === 'darwin' ? 'Meta+1' : 'Control+1'); // make editor really active if click failed somehow
await this.waitForEditorFocus(fileName);
}).toPass();
}

async waitForTab(fileName: string, isDirty: boolean = false): Promise<void> {
await expect(this.code.driver.page.locator(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[data-resource-name$="${fileName}"]`)).toBeVisible();
}
}
3 changes: 3 additions & 0 deletions test/automation/src/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { PositronWelcome } from './positron/positronWelcome';
import { PositronTerminal } from './positron/positronTerminal';
import { PositronViewer } from './positron/positronViewer';
import { PositronEditor } from './positron/positronEditor';
import { PositronEditors } from './positron/positronEditors';
import { PositronTestExplorer } from './positron/positronTestExplorer';
import { PositronQuickAccess } from './positron/positronQuickaccess';
import { PositronOutline } from './positron/positronOutline';
Expand Down Expand Up @@ -102,6 +103,7 @@ export class Workbench {
readonly positronClipboard: PositronClipboard;
readonly positronQuickInput: PositronQuickInput;
readonly positronExtensions: PositronExtensions;
readonly positronEditors: PositronEditors;
// --- End Positron ---

constructor(code: Code) {
Expand Down Expand Up @@ -150,6 +152,7 @@ export class Workbench {
this.positronOutline = new PositronOutline(code, this.positronQuickaccess);
this.positronClipboard = new PositronClipboard(code);
this.positronExtensions = new PositronExtensions(code, this.positronQuickaccess);
this.positronEditors = new PositronEditors(code);
// --- End Positron ---
}
}
2 changes: 1 addition & 1 deletion test/e2e/areas/console/console-ansi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ test.describe('Console ANSI styling', { tag: [tags.CRITICAL, tags.CONSOLE, tags.
await expect(link).toContainText(fileName, { useInnerText: true });

await link.click();
await app.workbench.editors.waitForActiveTab(fileName);
await app.workbench.positronEditors.waitForActiveTab(fileName);
}).toPass({ timeout: 60000 });
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ Data_Frame = data('mtcars')`;
}).toPass();

// Now move focus out of the the data explorer pane
await app.workbench.editors.newUntitledFile();
await app.workbench.positronEditors.newUntitledFile();
await app.workbench.positronQuickaccess.runCommand('workbench.panel.positronVariables.focus');
await app.workbench.positronVariables.doubleClickVariableRow('Data_Frame');

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/areas/data-explorer/data-explorer-r.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ test.describe('Data Explorer - R ', {
}).toPass();

// Now move focus out of the the data explorer pane
await app.workbench.editors.newUntitledFile();
await app.workbench.positronEditors.newUntitledFile();
await app.workbench.positronQuickaccess.runCommand('workbench.panel.positronVariables.focus');
await app.workbench.positronVariables.doubleClickVariableRow('Data_Frame');

Expand Down
28 changes: 14 additions & 14 deletions test/e2e/areas/top-action-bar/top-action-bar-save.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ test.describe('Top Action Bar - Save Actions', {
await app.workbench.positronQuickaccess.runCommand('workbench.action.closeAllEditors', { keepOpen: false });
await app.workbench.positronQuickaccess.openFile(join(app.workspacePathOrFolder, fileName));
await app.workbench.positronQuickaccess.runCommand('workbench.action.keepEditor', { keepOpen: false });
await app.workbench.editors.selectTab(fileName);
await app.workbench.positronEditors.selectTab(fileName);
await app.workbench.editor.waitForTypeInEditor(fileName, 'Puppies frolicking in a meadow of wildflowers');
// The file is now "dirty" and the save buttons should be enabled
await app.workbench.editors.waitForTab(fileName, true);
await app.workbench.positronEditors.waitForTab(fileName, true);
await expect(async () => {
expect(await app.workbench.positronTopActionBar.saveButton.isEnabled()).toBeTruthy();
expect(await app.workbench.positronTopActionBar.saveAllButton.isEnabled()).toBeTruthy();
}).toPass({ timeout: 10000 });
await app.workbench.positronTopActionBar.saveButton.click();
// The file is now saved, so the file should no longer be "dirty"
await app.workbench.editors.waitForTab(fileName, false);
await app.workbench.positronEditors.waitForTab(fileName, false);
await expect(async () => {
// The Save button stays enabled even when the active file is not "dirty"
expect(await app.workbench.positronTopActionBar.saveButton.isEnabled()).toBeTruthy();
Expand All @@ -62,21 +62,21 @@ test.describe('Top Action Bar - Save Actions', {
await app.workbench.positronQuickaccess.runCommand('workbench.action.keepEditor', { keepOpen: false });
await app.workbench.positronQuickaccess.openFile(join(app.workspacePathOrFolder, fileName2));
await app.workbench.positronQuickaccess.runCommand('workbench.action.keepEditor', { keepOpen: false });
await app.workbench.editors.selectTab(fileName1);
await app.workbench.editor.waitForTypeInEditor(fileName1, text);
await app.workbench.editors.selectTab(fileName2);
await app.workbench.editor.waitForTypeInEditor(fileName2, text);
await app.workbench.positronEditors.selectTab(fileName1);
await app.workbench.positronEditor.waitForTypeInEditor(fileName1, text);
await app.workbench.positronEditors.selectTab(fileName2);
await app.workbench.positronEditor.waitForTypeInEditor(fileName2, text);
// The files are now "dirty" and the save buttons should be enabled
await app.workbench.editors.waitForTab(fileName1, true);
await app.workbench.editors.waitForTab(fileName2, true);
await app.workbench.positronEditors.waitForTab(fileName1, true);
await app.workbench.positronEditors.waitForTab(fileName2, true);
await expect(async () => {
expect(await app.workbench.positronTopActionBar.saveButton.isEnabled()).toBeTruthy();
expect(await app.workbench.positronTopActionBar.saveAllButton.isEnabled()).toBeTruthy();
}).toPass({ timeout: 10000 });
await app.workbench.positronTopActionBar.saveAllButton.click();
// The files are now saved, so the files should no longer be "dirty"
await app.workbench.editors.waitForTab(fileName1, false);
await app.workbench.editors.waitForTab(fileName2, false);
await app.workbench.positronEditors.waitForTab(fileName1, false);
await app.workbench.positronEditors.waitForTab(fileName2, false);
await expect(async () => {
// The Save button stays enabled even when the active file is not "dirty"
expect(await app.workbench.positronTopActionBar.saveButton.isEnabled()).toBeTruthy();
Expand All @@ -91,10 +91,10 @@ test.describe('Top Action Bar - Save Actions', {
// Open a new file and type in some text
await app.workbench.positronQuickaccess.runCommand('workbench.action.closeAllEditors', { keepOpen: false });
await app.workbench.positronQuickaccess.runCommand('workbench.action.files.newUntitledFile', { keepOpen: false });
await app.workbench.editors.selectTab(fileName);
await app.workbench.editor.waitForTypeInEditor(fileName, text);
await app.workbench.positronEditors.selectTab(fileName);
await app.workbench.positronEditor.waitForTypeInEditor(fileName, text);
// The file is now "dirty" and the save buttons should be enabled
await app.workbench.editors.waitForTab(fileName, true);
await app.workbench.positronEditors.waitForTab(fileName, true);
await expect(async () => {
expect(await app.workbench.positronTopActionBar.saveButton.isEnabled()).toBeTruthy();
expect(await app.workbench.positronTopActionBar.saveAllButton.isEnabled()).toBeTruthy();
Expand Down
12 changes: 6 additions & 6 deletions test/e2e/areas/welcome/welcome.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ test.describe('Welcome Page', { tag: [tags.WELCOME] }, () => {

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

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

await app.workbench.positronQuickaccess.runCommand('View: Close Editor');
});
Expand All @@ -85,7 +85,7 @@ test.describe('Welcome Page', { tag: [tags.WELCOME] }, () => {

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

await expect(app.workbench.editors.activeEditor.locator(app.workbench.editors.editorIcon)).toHaveClass(/ipynb-ext-file-icon/);
await expect(app.workbench.positronEditors.activeEditor.locator(app.workbench.positronEditors.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);
Expand All @@ -100,7 +100,7 @@ test.describe('Welcome Page', { tag: [tags.WELCOME] }, () => {
await app.workbench.positronPopups.clickOnModalDialogPopupOption(expectedInterpreterVersion);

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

// console is the active view in the bottom panel
await expect(app.workbench.positronLayouts.panelViewsTab.and(app.code.driver.page.locator('.checked'))).toHaveText('Console');
Expand All @@ -113,7 +113,7 @@ test.describe('Welcome Page', { tag: [tags.WELCOME] }, () => {

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

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

test('Click on R console from the Welcome page [C684756]', async function ({ app, r }) {
Expand All @@ -124,7 +124,7 @@ test.describe('Welcome Page', { tag: [tags.WELCOME] }, () => {
await app.workbench.positronPopups.clickOnModalDialogPopupOption(expectedInterpreterVersion);

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

// console is the active view in the bottom panel
await expect(app.workbench.positronLayouts.panelViewsTab.and(app.code.driver.page.locator('.checked'))).toHaveText('Console');
Expand All @@ -135,7 +135,7 @@ test.describe('Welcome Page', { tag: [tags.WELCOME] }, () => {

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

await expect(app.workbench.editors.activeEditor.locator(app.workbench.editors.editorIcon)).toHaveClass(/ipynb-ext-file-icon/);
await expect(app.workbench.positronEditors.activeEditor.locator(app.workbench.positronEditors.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);
Expand Down

0 comments on commit bdc131e

Please sign in to comment.