Skip to content

Commit

Permalink
e2e-test: data-explorer-r flake fix (#6099)
Browse files Browse the repository at this point in the history
Just fixing flakes in the `data-explorer-r` test and a little bit of
refactor.

### QA Notes
Ran full suite and it didn't flake!

@:data-explorer @:web @:win
  • Loading branch information
midleman authored Jan 23, 2025
1 parent dceffce commit 6807337
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 87 deletions.
19 changes: 16 additions & 3 deletions test/e2e/pages/dataExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/


import { expect, Locator } from '@playwright/test';
import test, { expect, Locator } from '@playwright/test';
import { Code } from '../infra/code';
import { Workbench } from '../infra/workbench';

Expand Down Expand Up @@ -255,7 +255,20 @@ export class DataExplorer {
await this.workbench.quickaccess.runCommand('workbench.action.positronDataExplorer.expandSummary');
}

async verifyTab(tabName: string): Promise<void> {
await expect(this.code.driver.page.getByRole('tab', { name: tabName })).toBeVisible();
async verifyTab(
tabName: string,
{ isVisible = true, isSelected = true }: { isVisible?: boolean; isSelected?: boolean }
): Promise<void> {
await test.step(`Verify tab: ${tabName} is ${isVisible ? '' : 'not'} visible, is ${isSelected ? '' : 'not'} selected`, async () => {
const tabLocator = this.code.driver.page.getByRole('tab', { name: tabName });

await (isVisible
? expect(tabLocator).toBeVisible()
: expect(tabLocator).not.toBeVisible());

await (isSelected
? expect(tabLocator).toHaveClass(/selected/)
: expect(tabLocator).not.toHaveClass(/selected/));
});
}
}
46 changes: 25 additions & 21 deletions test/e2e/pages/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { Code } from '../infra/code';
import * as os from 'os';
import { expect, Locator } from '@playwright/test';
import test, { expect, Locator } from '@playwright/test';

interface FlatVariables {
value: string;
Expand Down Expand Up @@ -58,14 +58,16 @@ export class Variables {
}

async waitForVariableRow(variableName: string): Promise<Locator> {
const desiredRow = this.code.driver.page.locator(`${VARIABLES_NAME_COLUMN} .name-value:text("${variableName}")`);
await desiredRow.waitFor({ state: 'attached' });
const desiredRow = this.code.driver.page.locator(VARIABLES_NAME_COLUMN).filter({ hasText: variableName });
await expect(desiredRow).toBeVisible();
return desiredRow;
}

async doubleClickVariableRow(variableName: string) {
const desiredRow = await this.waitForVariableRow(variableName);
await desiredRow.dblclick();
await test.step(`Double click variable: ${variableName}`, async () => {
const desiredRow = this.code.driver.page.locator(VARIABLES_NAME_COLUMN).filter({ hasText: variableName });
await desiredRow.dblclick();
});
}

async toggleVariablesView() {
Expand All @@ -76,24 +78,26 @@ export class Variables {
}

async toggleVariable({ variableName, action }: { variableName: string; action: 'expand' | 'collapse' }) {
await this.waitForVariableRow(variableName);
const variable = this.code.driver.page.locator(`${CURRENT_VARIABLES_GROUP} .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();
}
await test.step(`${action} variable: ${variableName}`, async () => {
await this.waitForVariableRow(variableName);
const variable = this.code.driver.page.locator(`${CURRENT_VARIABLES_GROUP} .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/;
const expectedClass = action === 'expand'
? /codicon-chevron-down/
: /codicon-chevron-right/;

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

async expandVariable(variableName: string) {
Expand Down
148 changes: 85 additions & 63 deletions test/e2e/tests/data-explorer/data-explorer-r.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,105 +3,127 @@
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import { Application } from '../../infra';
import { test, expect, tags } from '../_test.setup';

test.use({
suiteId: __filename
});

test.beforeEach(async function ({ app, runCommand }) {
await app.workbench.layouts.enterLayout('stacked');
await runCommand('workbench.panel.positronVariables.focus');
});

test.afterEach(async function ({ runCommand }) {
await runCommand('workbench.action.closeAllEditors');
});

test.describe('Data Explorer - R ', {
tag: [tags.WEB, tags.WIN, tags.DATA_EXPLORER]
}, () => {
test('R - Verifies basic data explorer functionality [C609620]', { tag: [tags.CRITICAL] }, async function ({ app, r, executeCode }) {
// snippet from https://www.w3schools.com/r/r_data_frames.asp
await executeCode('R', `Data_Frame <- data.frame (
Training = c("Strength", "Stamina", "Other"),
Pulse = c(100, NA, 120),
Duration = c(60, 30, 45),
Note = c(NA, NA, "Note")
)`);
test('R - Verifies basic data explorer functionality [C609620]', { tag: [tags.CRITICAL] }, async function ({ app, r, openFile, runCommand }) {
// Execute code to generate data frames
await openFile('workspaces/generate-data-frames-r/simple-data-frames.r');
await app.workbench.editor.playButton.click();

await app.workbench.variables.doubleClickVariableRow('Data_Frame');
await app.workbench.dataExplorer.verifyTab('Data: Data_Frame');
await app.workbench.dataExplorer.maximizeDataExplorer(true);

await expect(async () => {
const tableData = await app.workbench.dataExplorer.getDataExplorerTableData();
// Open Data Explorer
await app.workbench.variables.doubleClickVariableRow('df');
await app.workbench.dataExplorer.verifyTab('Data: df', { isVisible: true, isSelected: true });

expect(tableData[0]).toStrictEqual({ 'Training': 'Strength', 'Pulse': '100.00', 'Duration': '60.00', 'Note': 'NA' });
expect(tableData[1]).toStrictEqual({ 'Training': 'Stamina', 'Pulse': 'NA', 'Duration': '30.00', 'Note': 'NA' });
expect(tableData[2]).toStrictEqual({ 'Training': 'Other', 'Pulse': '120.00', 'Duration': '45.00', 'Note': 'Note' });
expect(tableData.length).toBe(3);
}).toPass({ timeout: 60000 });
// Verify the data in the table
await app.workbench.dataExplorer.maximizeDataExplorer(true);
await verifyTable(app);

});
test('R - Verifies basic data explorer column info functionality [C734265]', {
tag: [tags.CRITICAL]
}, async function ({ app, r, runCommand }) {
// Verify the summary column data
await app.workbench.dataExplorer.expandSummary();

expect(await app.workbench.dataExplorer.getColumnMissingPercent(1)).toBe('0%');
expect(await app.workbench.dataExplorer.getColumnMissingPercent(2)).toBe('33%');
expect(await app.workbench.dataExplorer.getColumnMissingPercent(3)).toBe('0%');
expect(await app.workbench.dataExplorer.getColumnMissingPercent(4)).toBe('66%');

await app.workbench.layouts.enterLayout('notebook');

const col1ProfileInfo = await app.workbench.dataExplorer.getColumnProfileInfo(1);
expect(col1ProfileInfo.profileData).toStrictEqual({ 'Missing': '0', 'Empty': '0', 'Unique': '3' });

const col2ProfileInfo = await app.workbench.dataExplorer.getColumnProfileInfo(2);
expect(col2ProfileInfo.profileData).toStrictEqual({ 'Missing': '1', 'Min': '100.00', 'Median': '110.00', 'Mean': '110.00', 'Max': '120.00', 'SD': '14.14' });

const col3ProfileInfo = await app.workbench.dataExplorer.getColumnProfileInfo(3);
expect(col3ProfileInfo.profileData).toStrictEqual({ 'Missing': '0', 'Min': '30.00', 'Median': '45.00', 'Mean': '45.00', 'Max': '60.00', 'SD': '15.00' });

const col4ProfileInfo = await app.workbench.dataExplorer.getColumnProfileInfo(4);
expect(col4ProfileInfo.profileData).toStrictEqual({ 'Missing': '2', 'Empty': '0', 'Unique': '2' });

await app.workbench.layouts.enterLayout('stacked');
await app.workbench.sideBar.closeSecondarySideBar();

await app.workbench.dataExplorer.closeDataExplorer();
await runCommand('workbench.panel.positronVariables.focus');

await verifyColumnData(app);
});

test('R - Open Data Explorer for the second time brings focus back [C701143]', {
annotation: [{ type: 'issue', description: 'https://github.com/posit-dev/positron/issues/5714' }]
annotation: [{
type: 'issue', description: 'https://github.com/posit-dev/positron/issues/5714'
}, {
type: 'regression', description: 'https://github.com/posit-dev/positron/issues/4197'
}]
}, async function ({ app, r, runCommand, executeCode }) {
// Regression test for https://github.com/posit-dev/positron/issues/4197
// and https://github.com/posit-dev/positron/issues/5714
// Execute code to generate data frames
await executeCode('R', `Data_Frame <- mtcars`);
await runCommand('workbench.panel.positronVariables.focus');

// Open Data Explorer
await app.workbench.variables.doubleClickVariableRow('Data_Frame');
await app.workbench.dataExplorer.verifyTab('Data: Data_Frame');
await app.workbench.dataExplorer.verifyTab('Data: Data_Frame', { isVisible: true, isSelected: true });

// Now move focus out of the the data explorer pane
await app.workbench.editors.newUntitledFile();
await runCommand('workbench.panel.positronVariables.focus');
await app.workbench.dataExplorer.verifyTab('Data: Data_Frame', { isVisible: true, isSelected: false });
await app.workbench.variables.doubleClickVariableRow('Data_Frame');
await app.workbench.dataExplorer.verifyTab('Data: Data_Frame');

await app.workbench.dataExplorer.closeDataExplorer();
await runCommand('workbench.panel.positronVariables.focus');
await app.workbench.dataExplorer.verifyTab('Data: Data_Frame', { isVisible: true, isSelected: true });
});

test('R - Check blank spaces in data explorer [C1078834]', async function ({ app, r, executeCode }) {
// Execute code to generate data frames
await executeCode('R', `df = data.frame(x = c("a ", "a", " ", ""))`);

// Open Data Explorer
await app.workbench.variables.doubleClickVariableRow('df');
await app.workbench.dataExplorer.verifyTab('Data: df');
await app.workbench.dataExplorer.verifyTab('Data: df', { isVisible: true, isSelected: true });

// Verify blank spaces in the table
await expect(async () => {
const tableData = await app.workbench.dataExplorer.getDataExplorerTableData();

expect(tableData[0]).toStrictEqual({ 'x': 'a·' });
expect(tableData[1]).toStrictEqual({ 'x': 'a' });
expect(tableData[2]).toStrictEqual({ 'x': '···' });
expect(tableData[3]).toStrictEqual({ 'x': '<empty>' });
expect(tableData.length).toBe(4);
const expectedData = [
{ 'x': 'a·' },
{ 'x': 'a' },
{ 'x': '···' },
{ 'x': '<empty>' },
];

expect(tableData).toStrictEqual(expectedData);
expect(tableData).toHaveLength(4);
}).toPass({ timeout: 60000 });
});
});

// Helpers

async function verifyTable(app: Application) {
await test.step('Verify table data', async () => {
await expect(async () => {
const tableData = await app.workbench.dataExplorer.getDataExplorerTableData();

const expectedData = [
{ 'Training': 'Strength', 'Pulse': '100.00', 'Duration': '60.00', 'Note': 'NA' },
{ 'Training': 'Stamina', 'Pulse': 'NA', 'Duration': '30.00', 'Note': 'NA' },
{ 'Training': 'Other', 'Pulse': '120.00', 'Duration': '45.00', 'Note': 'Note' },
];

expect(tableData).toStrictEqual(expectedData);
expect(tableData).toHaveLength(3);
}).toPass({ timeout: 60000 });
});
}

async function verifyColumnData(app: Application) {
await test.step('Verify column data', async () => {
expect(await app.workbench.dataExplorer.getColumnMissingPercent(1)).toBe('0%');
expect(await app.workbench.dataExplorer.getColumnMissingPercent(2)).toBe('33%');
expect(await app.workbench.dataExplorer.getColumnMissingPercent(3)).toBe('0%');
expect(await app.workbench.dataExplorer.getColumnMissingPercent(4)).toBe('66%');

const col1ProfileInfo = await app.workbench.dataExplorer.getColumnProfileInfo(1);
expect(col1ProfileInfo.profileData).toStrictEqual({ 'Missing': '0', 'Empty': '0', 'Unique': '3' });

const col2ProfileInfo = await app.workbench.dataExplorer.getColumnProfileInfo(2);
expect(col2ProfileInfo.profileData).toStrictEqual({ 'Missing': '1', 'Min': '100.00', 'Median': '110.00', 'Mean': '110.00', 'Max': '120.00', 'SD': '14.14' });

const col3ProfileInfo = await app.workbench.dataExplorer.getColumnProfileInfo(3);
expect(col3ProfileInfo.profileData).toStrictEqual({ 'Missing': '0', 'Min': '30.00', 'Median': '45.00', 'Mean': '45.00', 'Max': '60.00', 'SD': '15.00' });

const col4ProfileInfo = await app.workbench.dataExplorer.getColumnProfileInfo(4);
expect(col4ProfileInfo.profileData).toStrictEqual({ 'Missing': '2', 'Empty': '0', 'Unique': '2' });
});
}

0 comments on commit 6807337

Please sign in to comment.