Skip to content

Commit

Permalink
E2E test: problems checks (#6086)
Browse files Browse the repository at this point in the history
Basic problems tests for Python and R, including facilities for editing
code based on line & "term".

Validates squiggle presence and error counts, un-does changes and
validates errors and squiggles go away.

### QA Notes

All tests should pass

@:problems @:web @:win
  • Loading branch information
testlabauto authored Jan 23, 2025
1 parent 257cbcd commit 0140de6
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 0 deletions.
1 change: 1 addition & 0 deletions test/e2e/infra/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export * from '../pages/extensions';
export * from '../pages/editors';
export * from '../pages/settings';
export * from '../pages/debug';
export * from '../pages/problems';

// fixtures
export * from './fixtures/userSettings';
Expand Down
1 change: 1 addition & 0 deletions test/e2e/infra/test-runner/test-tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export enum TestTags {
TOP_ACTION_BAR = '@:top-action-bar',
VARIABLES = '@:variables',
WELCOME = '@:welcome',
PROBLEMS = '@:problems',

// platform tags
WEB = '@:web',
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 @@ -33,6 +33,7 @@ import { Extensions } from '../pages/extensions';
import { Settings } from '../pages/settings';
import { Debug } from '../pages/debug';
import { EditorActionBar } from '../pages/editorActionBar';
import { Problems } from '../pages/problems';

export interface Commands {
runCommand(command: string, options?: { exactLabelMatch?: boolean }): Promise<any>;
Expand Down Expand Up @@ -69,6 +70,7 @@ export class Workbench {
readonly settings: Settings;
readonly debug: Debug;
readonly editorActionBar: EditorActionBar;
readonly problems: Problems;

constructor(code: Code) {

Expand Down Expand Up @@ -102,6 +104,7 @@ export class Workbench {
this.settings = new Settings(code, this.editors, this.editor, this.quickaccess);
this.debug = new Debug(code);
this.editorActionBar = new EditorActionBar(code.driver.page, this.viewer, this.quickaccess);
this.problems = new Problems(code, this.quickaccess);
}
}

68 changes: 68 additions & 0 deletions test/e2e/pages/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { Code } from '../infra/code';
const EDITOR = (filename: string) => `.monaco-editor[data-uri$="${filename}"]`;
const CURRENT_LINE = '.view-overlays .current-line';
const PLAY_BUTTON = '.codicon-play';
const VIEW_LINES = (filename: string) => `${EDITOR(filename)} .view-lines`;
const LINE_NUMBERS = (filename: string) => `${EDITOR(filename)} .margin .margin-view-overlays .line-numbers`;

const OUTER_FRAME = '.webview';
const INNER_FRAME = '#active-frame';
Expand Down Expand Up @@ -134,4 +136,70 @@ export class Editor {
}).toPass();
}

async clickOnTerm(filename: string, term: string, line: number, doubleClick: boolean = false): Promise<void> {
const selector = await this.getSelector(filename, term, line);
if (doubleClick) {
await this.code.driver.page.locator(selector).dblclick();
} else {
await this.code.driver.page.locator(selector).click();
}
}

private async getSelector(filename: string, term: string, line: number): Promise<string> {
const lineIndex = await this.getViewLineIndex(filename, line);

// Get class names and the correct term index
const { classNames, termIndex } = await this.getClassSelectors(filename, term, lineIndex);

return `${VIEW_LINES(filename)}>:nth-child(${lineIndex}) span span.${classNames[0]}:nth-of-type(${termIndex + 1})`;
}

private async getViewLineIndex(filename: string, line: number): Promise<number> {
const allElements = await this.code.driver.page.locator(LINE_NUMBERS(filename)).all();

// Resolve textContent for all elements first
const elementsWithText = await Promise.all(allElements.map(async (el, index) => ({
el,
text: await el.textContent(),
index
})));

// Find the first element matching the line number
const matchingElement = elementsWithText.find(({ text }) => text === `${line}`);

if (!matchingElement) {
throw new Error(`Line ${line} not found in file ${filename}`);
}

// Return the 1-based index
return matchingElement.index + 1;
}

private async getClassSelectors(filename: string, term: string, viewline: number): Promise<{ classNames: string[], termIndex: number }> {
// Locate all spans inside the line
const allElements = await this.code.driver.page.locator(`${VIEW_LINES(filename)}>:nth-child(${viewline}) span span`).all();

// Resolve all textContent values before filtering
const elementsWithText = await Promise.all(allElements.map(async (el, index) => ({
el,
text: await el.textContent(),
index
})));

// Find the first element that contains the term
const matchingElement = elementsWithText.find(({ text }) => text === term);

if (!matchingElement) {
throw new Error(`No elements found with term "${term}" in file "${filename}"`);
}

// Get the class names of the matching span
const className = await matchingElement.el.evaluate(el => (el as HTMLElement).className) as string;
const classNames = className.split(/\s+/);

// Find the index of this span among all spans
const termIndex = elementsWithText.filter(({ text }) => text !== null).map(({ el }) => el).indexOf(matchingElement.el);

return { classNames, termIndex };
}
}
41 changes: 41 additions & 0 deletions test/e2e/pages/problems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* 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 '../infra/code';
import { QuickAccess } from './quickaccess';

export const enum ProblemSeverity {
WARNING = 0,
ERROR = 1
}

export class Problems {

static PROBLEMS_VIEW_SELECTOR = '.panel .markers-panel';

constructor(private code: Code, private quickaccess: QuickAccess) { }

async showProblemsView(): Promise<any> {
await this.quickaccess.runCommand('workbench.panel.markers.view.focus');
await this.waitForProblemsView();
}

async waitForProblemsView(): Promise<void> {
await expect(this.code.driver.page.locator(Problems.PROBLEMS_VIEW_SELECTOR)).toBeVisible();
}


static getSelectorInProblemsView(problemType: ProblemSeverity): string {
const selector = problemType === ProblemSeverity.WARNING ? 'codicon-warning' : 'codicon-error';
return `div[id="workbench.panel.markers"] .monaco-tl-contents .marker-icon .${selector}`;
}

static getSelectorInEditor(problemType: ProblemSeverity): string {
const selector = problemType === ProblemSeverity.WARNING ? 'squiggly-warning' : 'squiggly-error';
return `.view-overlays .cdr.${selector}`;
}
}
111 changes: 111 additions & 0 deletions test/e2e/tests/problems/problems.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* 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 { Problems, ProblemSeverity } from '../../infra';
import { test, tags } from '../_test.setup';
import { join } from 'path';

test.use({
suiteId: __filename
});

test.describe('Problems', {
tag: [tags.DEBUG, tags.WEB, tags.WIN]
}, () => {

test('Python - Verify Problems Functionality', { tag: [tags.WIN, tags.WEB, tags.PROBLEMS] }, async function ({ app, python, openFile }) {

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'));

await app.workbench.editor.clickOnTerm('chinook-sqlite.py', 'rows', 9, true);

await app.code.driver.page.keyboard.type('!');
});

await test.step('Verify File Squiggly', async () => {
const fileSquiggly = Problems.getSelectorInEditor(ProblemSeverity.ERROR);
await expect(app.code.driver.page.locator(fileSquiggly)).toBeVisible();
});

const errorsSelector = Problems.getSelectorInProblemsView(ProblemSeverity.ERROR);

await app.workbench.problems.showProblemsView();

await test.step('Verify Problems Count', async () => {
const errorLocators = await app.code.driver.page.locator(errorsSelector).all();

expect(errorLocators.length).toBe(4);
});

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

});

await test.step('Verify File Squiggly Is Gone', async () => {
const fileSquiggly = Problems.getSelectorInEditor(ProblemSeverity.ERROR);
await expect(app.code.driver.page.locator(fileSquiggly)).not.toBeVisible();
});

await test.step('Verify Problems Count is 0', async () => {

await expect(async () => {
const errorLocators = await app.code.driver.page.locator(errorsSelector).all();
expect(errorLocators.length).toBe(0);
}).toPass({ timeout: 20000 });

});

});

test('R - Verify Problems Functionality', { tag: [tags.WIN, tags.WEB, tags.PROBLEMS] }, async function ({ app, r, openFile }) {

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'));

await app.workbench.editor.clickOnTerm('chinook-sqlite.r', 'albums', 5, true);

await app.code.driver.page.keyboard.type('!');
});

await test.step('Verify File Squiggly', async () => {
const fileSquiggly = Problems.getSelectorInEditor(ProblemSeverity.ERROR);
await expect(app.code.driver.page.locator(fileSquiggly)).toBeVisible();
});

const errorsSelector = Problems.getSelectorInProblemsView(ProblemSeverity.ERROR);

await app.workbench.problems.showProblemsView();

await test.step('Verify Problems Count', async () => {
const errorLocators = await app.code.driver.page.locator(errorsSelector).all();

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 test.step('Verify File Squiggly Is Gone', async () => {
const fileSquiggly = Problems.getSelectorInEditor(ProblemSeverity.ERROR);
await expect(app.code.driver.page.locator(fileSquiggly)).not.toBeVisible();
});

await test.step('Verify Problems Count is 0', async () => {

await expect(async () => {
const errorLocators = await app.code.driver.page.locator(errorsSelector).all();

expect(errorLocators.length).toBe(0);
}).toPass({ timeout: 20000 });

});

});
});

0 comments on commit 0140de6

Please sign in to comment.