diff --git a/test/e2e/pages/dataExplorer.ts b/test/e2e/pages/dataExplorer.ts index 372d47729af..e9047a4505d 100644 --- a/test/e2e/pages/dataExplorer.ts +++ b/test/e2e/pages/dataExplorer.ts @@ -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'; @@ -255,7 +255,20 @@ export class DataExplorer { await this.workbench.quickaccess.runCommand('workbench.action.positronDataExplorer.expandSummary'); } - async verifyTab(tabName: string): Promise { - await expect(this.code.driver.page.getByRole('tab', { name: tabName })).toBeVisible(); + async verifyTab( + tabName: string, + { isVisible = true, isSelected = true }: { isVisible?: boolean; isSelected?: boolean } + ): Promise { + 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/)); + }); } } diff --git a/test/e2e/pages/variables.ts b/test/e2e/pages/variables.ts index 593c4d6897c..5a78b9c02a4 100644 --- a/test/e2e/pages/variables.ts +++ b/test/e2e/pages/variables.ts @@ -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; @@ -58,14 +58,16 @@ export class Variables { } async waitForVariableRow(variableName: string): Promise { - 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() { @@ -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) { diff --git a/test/e2e/tests/data-explorer/data-explorer-r.test.ts b/test/e2e/tests/data-explorer/data-explorer-r.test.ts index 1d2cb3f03a0..9e8b6401231 100644 --- a/test/e2e/tests/data-explorer/data-explorer-r.test.ts +++ b/test/e2e/tests/data-explorer/data-explorer-r.test.ts @@ -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': '' }); - expect(tableData.length).toBe(4); + const expectedData = [ + { 'x': 'a·' }, + { 'x': 'a' }, + { 'x': '···' }, + { 'x': '' }, + ]; + + 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' }); + }); +}