Skip to content

Commit

Permalink
e2e tests: add expanded variables test (#5109)
Browse files Browse the repository at this point in the history
### Intent

Add a test to verify that variables display correct values and types
when expanded.

### Approach

The test runs a Python script to create a polars data frame with various
data types. It expands each variable in the UI and asserts that the
displayed values and types match the expected data.

### QA Notes

Tagged to run on PRs. (test runs in under 15-20s)
  • Loading branch information
midleman authored Oct 24, 2024
1 parent a0290f3 commit 69b1efb
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 14 deletions.
4 changes: 3 additions & 1 deletion test/automation/src/playwrightDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export class PlaywrightDriver {
constructor(
private readonly application: playwright.Browser | playwright.ElectronApplication,
private readonly context: playwright.BrowserContext,
private readonly page: playwright.Page,
// --- Start Positron ---
readonly page: playwright.Page,
// --- End Positron ---
private readonly serverProcess: ChildProcess | undefined,
private readonly whenLoaded: Promise<unknown>,
private readonly options: LaunchOptions
Expand Down
73 changes: 67 additions & 6 deletions test/automation/src/positron/positronVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@
import { Code } from '../code';
import * as os from 'os';
import { IElement } from '../driver';
import { Locator } from '@playwright/test';
import { expect, Locator } from '@playwright/test';

interface FlatVariables {
value: string;
type: string;
}

const VARIABLE_ITEMS = '.variables-instance[style*="z-index: 1"] .list .variable-item';
const VARIABLE_ITEMS = '.variable-item';
const VARIABLE_NAMES = 'name-column';
const VARIABLE_DETAILS = 'details-column';
const VARIABLES_NAME_COLUMN = '.variables-instance[style*="z-index: 1"] .variable-item .name-column';
const VARIABLES_SECTION = '[aria-label="Variables Section"]';
const VARIABLES_INTERPRETER = '.positron-variables-container .action-bar-button-text';
const VARIABLE_CHEVRON_ICON = '.gutter .expand-collapse-icon';
const VARIABLE_INDENTED = '.name-column-indenter[style*="margin-left: 40px"]';

/*
* Reuseable Positron variables functionality for tests to leverage.
Expand Down Expand Up @@ -49,7 +51,6 @@ export class PositronVariables {
}

async waitForVariableRow(variableName: string): Promise<Locator> {

const desiredRow = this.code.driver.getLocator(`${VARIABLES_NAME_COLUMN} .name-value:text("${variableName}")`);
await desiredRow.waitFor({ state: 'attached' });
return desiredRow;
Expand All @@ -61,19 +62,79 @@ export class PositronVariables {
await desiredRow.dblclick();
}

async openVariables() {

async toggleVariablesView() {
const isMac = os.platform() === 'darwin';
const modifier = isMac ? 'Meta' : 'Control';

await this.code.driver.getKeyboard().press(`${modifier}+Alt+B`);

await this.code.waitForElement(VARIABLES_SECTION);
}

async toggleVariable({ variableName, action }: { variableName: string; action: 'expand' | 'collapse' }) {
await this.waitForVariableRow(variableName);
const variable = this.code.driver.page.locator('.name-value', { hasText: variableName });

const chevronIcon = variable.locator('..').locator(VARIABLE_CHEVRON_ICON);
const isExpanded = await chevronIcon.evaluate((el) => el.classList.contains('codicon-chevron-down'));

// perform action based on the 'action' parameter
if (action === 'expand' && !isExpanded) {
await chevronIcon.click();
} else if (action === 'collapse' && isExpanded) {
await chevronIcon.click();
}

const expectedClass = action === 'expand'
? /codicon-chevron-down/
: /codicon-chevron-right/;

await expect(chevronIcon).toHaveClass(expectedClass);
}

async expandVariable(variableName: string) {
await this.toggleVariable({ variableName, action: 'expand' });
}

async collapseVariable(variableName: string) {
await this.toggleVariable({ variableName, action: 'collapse' });
}

async getVariablesInterpreter(): Promise<IElement> {
const interpreter = await this.code.waitForElement(VARIABLES_INTERPRETER);
return interpreter;
}

/**
* Gets the data (value and type) for the children of a parent variable.
* NOTE: it assumes that either ALL variables are collapsed or ONLY the parent variable is expanded.
*
* @param parentVariable the parent variable to get the children of
* @param collapseParent whether to collapse the parent variable after getting the children data
* @returns a map of the children's name, value, and type
*/
async getVariableChildren(parentVariable: string, collapseParent = true): Promise<{ [key: string]: { value: string; type: string } }> {
await this.expandVariable(parentVariable);
const variable = this.code.driver.page.locator(`.name-value:text-is("${parentVariable}")`);

// get the children of the parent variable, which are indented
const children = await variable.locator('..').locator('..').locator('..').locator('..').locator(VARIABLE_ITEMS)
.filter({ has: this.code.driver.page.locator(VARIABLE_INDENTED) }).all();

// create a map of the children's name, value, and type
const result: { [key: string]: { value: string; type: string } } = {};
for (const child of children) {
const childName = await child.locator('.name-value').textContent() || '';
const childValue = await child.locator('.details-column .value').textContent() || '';
const childType = await child.locator('.details-column .right-column').textContent() || '';

if (childName) {
result[childName] = { value: childValue, type: childType };
}
}

// collapse the parent variable if the flag is set
if (collapseParent) { await this.collapseVariable(parentVariable); }

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ df = pd.DataFrame(data)`;
}).toPass({ timeout: 60000 });

await app.workbench.positronDataExplorer.closeDataExplorer();
await app.workbench.positronVariables.openVariables();
await app.workbench.positronVariables.toggleVariablesView();

});

Expand Down Expand Up @@ -145,7 +145,7 @@ df2 = pd.DataFrame(data)`;
await app.workbench.positronSideBar.closeSecondarySideBar();

await app.workbench.positronDataExplorer.closeDataExplorer();
await app.workbench.positronVariables.openVariables();
await app.workbench.positronVariables.toggleVariablesView();

});
// This test is not dependent on the previous test, so it refreshes the python environment
Expand Down Expand Up @@ -215,7 +215,7 @@ df2 = pd.DataFrame(data)`;
const app = this.app as Application;

await app.workbench.positronDataExplorer.closeDataExplorer();
await app.workbench.positronVariables.openVariables();
await app.workbench.positronVariables.toggleVariablesView();
await app.workbench.quickaccess.runCommand('workbench.action.closeAllEditors', { keepOpen: false });

});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('Data Explorer - Very Large Data Frame #win', () => {
const app = this.app as Application;

await app.workbench.positronDataExplorer.closeDataExplorer();
await app.workbench.positronVariables.openVariables();
await app.workbench.positronVariables.toggleVariablesView();

});

Expand Down Expand Up @@ -135,7 +135,7 @@ describe('Data Explorer - Very Large Data Frame #win', () => {
const app = this.app as Application;

await app.workbench.positronDataExplorer.closeDataExplorer();
await app.workbench.positronVariables.openVariables();
await app.workbench.positronVariables.toggleVariablesView();

});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('Data Explorer - XLSX #web #win', () => {
const app = this.app as Application;

await app.workbench.positronDataExplorer.closeDataExplorer();
await app.workbench.positronVariables.openVariables();
await app.workbench.positronVariables.toggleVariablesView();

});

Expand Down Expand Up @@ -68,7 +68,7 @@ describe('Data Explorer - XLSX #web #win', () => {
const app = this.app as Application;

await app.workbench.positronDataExplorer.closeDataExplorer();
await app.workbench.positronVariables.openVariables();
await app.workbench.positronVariables.toggleVariablesView();

});

Expand Down
56 changes: 56 additions & 0 deletions test/smoke/src/areas/positron/variables/variables-expanded.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* 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, PositronPythonFixtures } from '../../../../../automation';
import { setupAndStartApp } from '../../../test-runner/test-hooks';

describe('Variables - Expanded View #web #win', () => {
setupAndStartApp();

beforeEach(async function () {
const app = this.app as Application;
await PositronPythonFixtures.SetupFixtures(app);
await app.workbench.positronConsole.executeCode('Python', script, '>>>');
await app.workbench.positronLayouts.enterLayout('fullSizedAuxBar');

});

it('Python - should display children values and types when variable is expanded', async function () {
const app = this.app as Application;
const variables = app.workbench.positronVariables;

await variables.expandVariable('df');
for (const variable of Object.keys(expectedData)) {
const actualData = await variables.getVariableChildren(variable);
expect(actualData).toEqual(expectedData[variable]);
}
});
});

const script = `
import polars as pl
from datetime import date
df = pl.DataFrame(
{
"foo": [1, 2, 3],
"bar": [6.0, 7.0, 8.0],
"ham": [date(2020, 1, 2), date(2021, 3, 4), date(2022, 5, 6)],
"green": [None, 2, 3],
"eggs": [0.5, None, 2.5],
"cheese": [True, None, False],
}
)
`;

const expectedData = {
foo: { 0: { type: "int", value: "1" }, 1: { type: "int", value: "2" }, 2: { type: "int", value: "3" } },
bar: { 0: { type: "float", value: "6.0" }, 1: { type: "float", value: "7.0" }, 2: { type: "float", value: "8.0" } },
ham: { 0: { type: "date", value: "datetime.date(2020, 1, 2)" }, 1: { type: "date", value: "datetime.date(2021, 3, 4)" }, 2: { type: "date", value: "datetime.date(2022, 5, 6)" } },
green: { 0: { type: "NoneType", value: "None" }, 1: { type: "int", value: "2" }, 2: { type: "int", value: "3" } },
eggs: { 0: { type: "float", value: "0.5" }, 1: { type: "NoneType", value: "None" }, 2: { type: "float", value: "2.5" } },
cheese: { 0: { type: "bool", value: "True" }, 1: { type: "NoneType", value: "None" }, 2: { type: "bool", value: "False" } },
};

0 comments on commit 69b1efb

Please sign in to comment.