Skip to content

e2e-test: add visual-mode test #6191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/vs/workbench/services/dialogs/common/dialogService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ export class DialogService extends Disposable implements IDialogService {
prompt<T>(prompt: IPromptWithDefaultCancel<T>): Promise<IPromptResult<T>>;
prompt<T>(prompt: IPrompt<T>): Promise<IPromptResult<T>>;
async prompt<T>(prompt: IPrompt<T> | IPromptWithCustomCancel<T> | IPromptWithDefaultCancel<T>): Promise<IPromptResult<T> | IPromptResultWithCancel<T>> {
if (this.skipDialogs()) {
throw new Error(`DialogService: refused to show dialog in tests. Contents: ${prompt.message}`);
}
// if (this.skipDialogs()) {
// throw new Error(`DialogService: refused to show dialog in tests. Contents: ${prompt.message}`);
// }

const handle = this.model.show({ promptArgs: { prompt } });

Expand Down
48 changes: 48 additions & 0 deletions test/e2e/infra/fixtures/keyboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import test, { Page } from '@playwright/test';

export enum Hotkeys {
COPY = 'Cmd+C',
PASTE = 'Cmd+V',
CUT = 'Cmd+X',
SELECT_ALL = 'Cmd+A',
SAVE = 'Cmd+S',
UNDO = 'Cmd+Z',
OPEN_FILE = 'Cmd+O',
FIND = 'Cmd+F',
CLOSE_TAB = 'Cmd+W',
FIRST_TAB = 'Cmd+1',
SWITCH_TAB_LEFT = 'Cmd+Shift+[',
SWITCH_TAB_RIGHT = 'Cmd+Shift+]',
CLOSE_ALL_EDITORS = 'Cmd+K Cmd+W', // space indicates a sequence of keys
VISUAL_MODE = 'Cmd+Shift+F4',
}

export class Keyboard {
constructor(private page: Page) { }

private getModifierKey(): string {
return process.platform === 'darwin' ? 'Meta' : 'Control';
}

async hotKeys(action: Hotkeys) {
await test.step(`Press hotkeys: ${action}`, async () => {
const modifierKey = this.getModifierKey();

// Split command if there are multiple sequential key presses
const keySequences = action.split(' ').map(keys => keys.replace(/Cmd/g, modifierKey));

for (const key of keySequences) {
await this.page.keyboard.press(key);
}
})
}

async press(keys: string) {
await this.page.keyboard.press(keys);
}

async type(text: string) {
await this.page.keyboard.type(text);
}
}

1 change: 1 addition & 0 deletions test/e2e/infra/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export * from '../pages/scm';
// fixtures
export * from './fixtures/userSettings';
export * from './fixtures/interpreter';
export * from './fixtures/keyboard';

// test-runner
export * from './test-runner';
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/infra/test-runner/test-tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ export enum TestTags {
OUTPUT = '@:output',
PLOTS = '@:plots',
PROBLEMS = '@:problems',
REFERENCES = '@:references',
R_MARKDOWN = '@:r-markdown',
R_PKG_DEVELOPMENT = '@:r-pkg-development',
REFERENCES = '@:references',
RETICULATE = '@:reticulate',
SCM = '@:scm',
TEST_EXPLORER = '@:test-explorer',
TOP_ACTION_BAR = '@:top-action-bar',
VARIABLES = '@:variables',
VISUAL_MODE = '@:visual-mode',
WELCOME = '@:welcome',

// platform tags
Expand Down
3 changes: 3 additions & 0 deletions test/e2e/infra/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { Code } from './code';
import { Interpreter } from '../infra/fixtures/interpreter';
import { Keyboard } from '../infra/fixtures/keyboard';
import { Popups } from '../pages/popups';
import { Console } from '../pages/console';
import { Variables } from '../pages/variables';
Expand Down Expand Up @@ -75,6 +76,7 @@ export class Workbench {
readonly problems: Problems;
readonly references: References;
readonly scm: SCM;
readonly keyboard: Keyboard;

constructor(code: Code) {

Expand Down Expand Up @@ -111,6 +113,7 @@ export class Workbench {
this.problems = new Problems(code, this.quickaccess);
this.references = new References(code);
this.scm = new SCM(code, this.layouts);
this.keyboard = new Keyboard(code.driver.page);
}
}

1 change: 0 additions & 1 deletion test/e2e/pages/editorActionBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ export class EditorActionBar {
*/
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 });
});
Expand Down
32 changes: 16 additions & 16 deletions test/e2e/pages/quickaccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,28 +91,28 @@ export class QuickAccess {
this.code.logger.log('QuickAccess: File search succeeded.');
}

async openFile(path: string): Promise<void> {
await test.step('Open file', async () => {
if (!isAbsolute(path)) {
// we require absolute paths to get a single
// result back that is unique and avoid hitting
// the search process to reduce chances of
// search needing longer.
throw new Error('QuickAccess.openFile requires an absolute path');
}
async openFile(path: string, waitForFocus = true): Promise<void> {
if (!isAbsolute(path)) {
// we require absolute paths to get a single
// result back that is unique and avoid hitting
// the search process to reduce chances of
// search needing longer.
throw new Error('QuickAccess.openFile requires an absolute path');
}

const fileName = basename(path);
const fileName = basename(path);

// quick access shows files with the basename of the path
await this.openFileQuickAccessAndWait(path, basename(path));
// quick access shows files with the basename of the path
await this.openFileQuickAccessAndWait(path, basename(path));

// open first element
await this.quickInput.selectQuickInputElement(0);
// open first element
await this.quickInput.selectQuickInputElement(0);

// wait for editor being focused
// wait for editor being focused
if (waitForFocus) {
await this.editors.waitForActiveTab(fileName);
await this.editors.selectTab(fileName);
});
}
}

private async openQuickAccessWithRetry(kind: QuickAccessKind, value?: string): Promise<void> {
Expand Down
21 changes: 15 additions & 6 deletions test/e2e/tests/_test.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import { randomUUID } from 'crypto';
import archiver from 'archiver';

// Local imports
import { Application, Logger, UserSetting, UserSettingsFixtures, createLogger, createApp, TestTags } from '../infra';
import { Application, Logger, UserSetting, UserSettingsFixtures, createLogger, createApp, TestTags, } from '../infra';
import { PackageManager } from '../pages/utils/packageManager';
import { Keyboard } from '../infra/fixtures/keyboard';

// Constants
const TEMP_DIR = `temp-${randomUUID()}`;
Expand Down Expand Up @@ -151,9 +152,9 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({

// ex: await openFile('workspaces/basic-rmd-file/basicRmd.rmd');
openFile: async ({ app }, use) => {
await use(async (filePath: string) => {
await use(async (filePath: string, waitForFocus = true) => {
await test.step(`Open file: ${path.basename(filePath)}`, async () => {
await app.workbench.quickaccess.openFile(path.join(app.workspacePathOrFolder, filePath));
await app.workbench.quickaccess.openFile(path.join(app.workspacePathOrFolder, filePath), waitForFocus);
});
});
},
Expand Down Expand Up @@ -181,6 +182,13 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
});
},

// ex: await keyboard.hotKeys(HotKeys.COPY);
// ex: await keyboard.press('Enter');
keyboard: async ({ page }, use) => {
const keyboard = new Keyboard(page);
await use(keyboard);
},

// ex: await userSettings.set([['editor.actionBar.enabled', 'true']], false);
userSettings: [async ({ app }, use) => {
const userSettings = new UserSettingsFixtures(app);
Expand Down Expand Up @@ -318,7 +326,7 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
// Runs once per worker. If a worker handles multiple specs, these hooks only run for the first spec.
// However, we are using `suiteId` to ensure each suite gets a new worker (and a fresh app
// instance). This also ensures these before/afterAll hooks will run for EACH spec
test.beforeAll(async ({ logger }, testInfo) => {
test.beforeAll('Mark test start in logger', async ({ logger }, testInfo) => {
// since the worker doesn't know or have access to the spec name when it starts,
// we store the spec name in a global variable. this ensures logs are written
// to the correct folder even when the app is scoped to "worker".
Expand All @@ -331,7 +339,7 @@ test.beforeAll(async ({ logger }, testInfo) => {
logger.log('');
});

test.afterAll(async function ({ logger }, testInfo) {
test.afterAll('Mark test end in logger', async function ({ logger }, testInfo) {
try {
logger.log('');
logger.log(`>>> Suite end: '${testInfo.titlePath[0] ?? 'unknown'}' <<<`);
Expand Down Expand Up @@ -384,10 +392,11 @@ interface TestFixtures {
packages: PackageManager;
autoTestFixture: any;
devTools: void;
openFile: (filePath: string) => Promise<void>;
openFile: (filePath: string, waitForFocus?: boolean) => Promise<void>;
openDataFile: (filePath: string) => Promise<void>;
runCommand: (command: string) => Promise<void>;
executeCode: (language: 'Python' | 'R', code: string) => Promise<void>;
keyboard: Keyboard;
}

interface WorkerFixtures {
Expand Down
12 changes: 5 additions & 7 deletions test/e2e/tests/editor-action-bar/document-files.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { expect, Page } from '@playwright/test';
import { test, tags } from '../_test.setup';
import { EditorActionBar } from '../../pages/editorActionBar';
import { Application } from '../../infra';
import { Application, Hotkeys, Keyboard } from '../../infra';

let editorActionBar: EditorActionBar;

Expand Down Expand Up @@ -93,15 +93,13 @@ async function verifyOpenViewerRendersHtml(app: Application, title: string) {
}

async function verifyOpenChanges(page: Page) {
const keyboard = new Keyboard(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');
await keyboard.hotKeys(Hotkeys.SAVE);

// click open changes & verify
await editorActionBar.clickButton('Open Changes');
Expand All @@ -110,8 +108,8 @@ async function verifyOpenChanges(page: Page) {
await page.getByRole('tab', { name: 'quarto_basic.qmd (Working' }).getByLabel('Close').click();

// undo changes & save
await bindPlatformHotkey(page, 'Z');
await bindPlatformHotkey(page, 'S');
await keyboard.hotKeys(Hotkeys.UNDO);
await keyboard.hotKeys(Hotkeys.SAVE);
});
}

Expand Down
16 changes: 5 additions & 11 deletions test/e2e/tests/problems/problems.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { expect } from '@playwright/test';
import { Problems, ProblemSeverity } from '../../infra';
import { Hotkeys, Problems, ProblemSeverity } from '../../infra';
import { test, tags } from '../_test.setup';
import { join } from 'path';

Expand All @@ -16,7 +16,7 @@ test.describe('Problems', {
tag: [tags.PROBLEMS, tags.WEB, tags.WIN]
}, () => {

test('Python - Verify Problems Functionality', async function ({ app, python, openFile }) {
test('Python - Verify Problems Functionality', async function ({ app, python, openFile, keyboard }) {

await test.step('Open file and replace "rows" on line 9 with exclamation point', async () => {
await openFile(join('workspaces', 'chinook-db-py', 'chinook-sqlite.py'));
Expand All @@ -43,10 +43,7 @@ test.describe('Problems', {
}).toPass({ timeout: 20000 });
});

await test.step('Revert error', async () => {
await app.code.driver.page.keyboard.press(process.platform === 'darwin' ? 'Meta+Z' : 'Control+Z');

});
await keyboard.hotKeys(Hotkeys.UNDO);

await test.step('Verify File Squiggly Is Gone', async () => {
const fileSquiggly = Problems.getSelectorInEditor(ProblemSeverity.ERROR);
Expand All @@ -64,7 +61,7 @@ test.describe('Problems', {

});

test('R - Verify Problems Functionality', async function ({ app, r, openFile }) {
test('R - Verify Problems Functionality', async function ({ app, r, openFile, keyboard }) {

await test.step('Open file and replace "albums" on line 5 with exclamation point', async () => {
await openFile(join('workspaces', 'chinook-db-r', 'chinook-sqlite.r'));
Expand All @@ -89,10 +86,7 @@ test.describe('Problems', {
expect(errorLocators.length).toBe(1);
});

await test.step('Revert error', async () => {
await app.code.driver.page.keyboard.press(process.platform === 'darwin' ? 'Meta+Z' : 'Control+Z');

});
await keyboard.hotKeys(Hotkeys.UNDO);

await test.step('Verify File Squiggly Is Gone', async () => {
const fileSquiggly = Problems.getSelectorInEditor(ProblemSeverity.ERROR);
Expand Down
5 changes: 3 additions & 2 deletions test/e2e/tests/scm/scm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import { Hotkeys } from '../../infra';
import { test, tags } from '../_test.setup';
import { join } from 'path';

Expand All @@ -14,7 +15,7 @@ test.describe('Source Content Management', {
tag: [tags.SCM, tags.WEB, tags.WIN]
}, () => {

test('Verify SCM Tracks File Modifications, Staging, and Commit Actions', async function ({ app, openFile }) {
test('Verify SCM Tracks File Modifications, Staging, and Commit Actions', async function ({ app, openFile, keyboard }) {

const file = 'chinook-sqlite.py';
await test.step('Open file and add a new line to it', async () => {
Expand All @@ -28,7 +29,7 @@ test.describe('Source Content Management', {

await app.code.driver.page.keyboard.type('print(df)');

await app.code.driver.page.keyboard.press(process.platform === 'darwin' ? 'Meta+S' : 'Control+S');
await keyboard.hotKeys(Hotkeys.SAVE);

});

Expand Down
Loading
Loading