Skip to content

Commit b89ba66

Browse files
authored
e2e test: data explorer test cleanup (#8346)
### Summary Preparing for upcoming work in the Data Explorer workstream by cleaning up some of the existing tests and POM. No functional changes were made. ### QA Notes @:data-explorer @:web @:win
1 parent 1d7480c commit b89ba66

23 files changed

+405
-477
lines changed

test/e2e/fixtures/keybindings.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,13 @@
5757
{
5858
"key": "cmd+j c",
5959
"command": "workbench.action.togglePanel"
60-
}, {
60+
},
61+
{
6162
"key": "cmd+j n",
6263
"command": "workbench.action.positronNotebookLayout" // View: Notebook Layout
64+
},
65+
{
66+
"key": "cmd+b c",
67+
"command": "workbench.action.closeSidebar" // View: Close Primary Side Bar
6368
}
6469
]

test/e2e/infra/workbench.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export class Workbench {
105105
this.sessions = new Sessions(code, this.quickaccess, this.quickInput, this.console);
106106
this.notebooks = new Notebooks(code, this.quickInput, this.quickaccess);
107107
this.welcome = new Welcome(code);
108-
this.clipboard = new Clipboard(code);
108+
this.clipboard = new Clipboard(code, this.hotKeys);
109109
this.terminal = new Terminal(code, this.quickaccess, this.clipboard, this.popups);
110110
this.viewer = new Viewer(code);
111111
this.editor = new Editor(code);

test/e2e/pages/clipboard.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,21 @@
33
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { expect } from '@playwright/test';
67
import { Code } from '../infra/code';
8+
import { HotKeys } from './hotKeys.js';
79

810
export class Clipboard {
911

10-
constructor(private code: Code) { }
12+
constructor(private code: Code, private hotKeys: HotKeys) { }
13+
14+
async copy(): Promise<void> {
15+
await this.hotKeys.copy();
16+
}
17+
18+
async paste(): Promise<void> {
19+
await this.hotKeys.paste();
20+
}
1121

1222
async getClipboardText(): Promise<string | null> {
1323
// Grant permissions to read from clipboard
@@ -25,6 +35,13 @@ export class Clipboard {
2535
return clipboardText;
2636
}
2737

38+
async expectClipboardTextToBe(expectedText: string): Promise<void> {
39+
await expect(async () => {
40+
const clipboardText = await this.getClipboardText();
41+
expect(clipboardText).toBe(expectedText);
42+
}).toPass({ timeout: 20000 });
43+
}
44+
2845
async setClipboardText(text: string): Promise<void> {
2946
// Grant permissions to write to clipboard
3047
await this.code.driver.context.grantPermissions(['clipboard-write']);

test/e2e/pages/dataExplorer.ts

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ const COLUMN_HEADERS = '.data-explorer-panel .right-column .data-grid-column-hea
1212
const HEADER_TITLES = '.data-grid-column-header .title-description .title';
1313
const DATA_GRID_ROWS = '.data-explorer-panel .right-column .data-grid-rows';
1414
const DATA_GRID_ROW = '.data-grid-row';
15-
const CLOSE_DATA_EXPLORER = '.tab .codicon-close';
1615
const IDLE_STATUS = '.status-bar-indicator .icon.idle';
1716
const SCROLLBAR_LOWER_RIGHT_CORNER = '.data-grid-scrollbar-corner';
1817
const DATA_GRID_TOP_LEFT = '.data-grid-corner-top-left';
@@ -99,10 +98,6 @@ export class DataExplorer {
9998
return tableData;
10099
}
101100

102-
async closeDataExplorer() {
103-
await this.code.driver.page.locator(CLOSE_DATA_EXPLORER).first().click();
104-
}
105-
106101
async clickLowerRightCorner() {
107102
await this.code.driver.page.locator(SCROLLBAR_LOWER_RIGHT_CORNER).click();
108103
}
@@ -132,18 +127,56 @@ export class DataExplorer {
132127
});
133128
}
134129

135-
async getDataExplorerStatusBarText(): Promise<String> {
136-
await expect(this.code.driver.page.locator(STATUS_BAR)).toHaveText(/Showing/, { timeout: 60000 });
137-
return (await this.code.driver.page.locator(STATUS_BAR).textContent()) ?? '';
130+
async expectLastCellContentToBe(columnName: string, expectedContent: string, rowAtIndex = -1): Promise<void> {
131+
await test.step(`Verify last cell content: ${expectedContent}`, async () => {
132+
await expect(async () => {
133+
const tableData = await this.getDataExplorerTableData();
134+
const lastRow = tableData.at(rowAtIndex);
135+
const lastHour = lastRow![columnName];
136+
expect(lastHour).toBe(expectedContent);
137+
}, 'Verify last hour cell content').toPass();
138+
});
139+
}
140+
141+
async expectStatusBarToHaveText(expectedText: string, timeout = 60000): Promise<void> {
142+
await test.step(`Expect status bar text: ${expectedText}`, async () => {
143+
await expect(this.code.driver.page.locator(STATUS_BAR)).toHaveText(expectedText, { timeout });
144+
});
138145
}
139146

147+
140148
async selectColumnMenuItem(columnIndex: number, menuItem: string) {
141149
await test.step(`Sort column ${columnIndex} by: ${menuItem}`, async () => {
142150
await this.code.driver.page.locator(`.data-grid-column-header:nth-child(${columnIndex}) .sort-button`).click();
143-
await this.code.driver.page.locator(`.positron-modal-overlay div.title:has-text("${menuItem}")`).click();
151+
await this.code.driver.page.locator(`.positron-modal-overlay div.title:has-text('${menuItem}')`).click();
144152
});
145153
}
146154

155+
async expectActionBarToHaveButton(buttonName: string, isVisible: boolean = true) {
156+
await test.step(`Expect action bar to have button: ${buttonName}`, async () => {
157+
const button = this.code.driver.page.getByRole('button', { name: buttonName });
158+
if (isVisible) {
159+
await expect(button).toBeVisible();
160+
} else {
161+
await expect(button).not.toBeVisible();
162+
}
163+
});
164+
}
165+
166+
async verifyCanOpenAsPlaintext(searchString: string | RegExp) {
167+
await this.workbench.editorActionBar.clickButton('Open as Plain Text File');
168+
169+
// Check if the 'Open Anyway' button is visible. This is needed on web only as it warns
170+
// that the file is large and may take a while to open. This is due to a vs code behavior and file size limit.
171+
const openAnyway = this.code.driver.page.getByText('Open Anyway');
172+
173+
if (await openAnyway.waitFor({ state: 'visible', timeout: 5000 }).then(() => true).catch(() => false)) {
174+
await openAnyway.click();
175+
}
176+
177+
await expect(this.code.driver.page.getByText(searchString, { exact: true })).toBeVisible();
178+
}
179+
147180
async home(): Promise<void> {
148181
await this.code.driver.page.keyboard.press('Home');
149182
}
@@ -180,10 +213,8 @@ export class DataExplorer {
180213
async getColumnProfileInfo(rowNumber: number): Promise<ColumnProfile> {
181214

182215
const expandCollapseLocator = this.code.driver.page.locator(EXPAND_COLLAPSE_PROFILE(rowNumber));
183-
184216
await expandCollapseLocator.scrollIntoViewIfNeeded();
185217
await expandCollapseLocator.click();
186-
187218
await expect(expandCollapseLocator.locator(EXPAND_COLLASPE_ICON)).toHaveClass(/codicon-chevron-down/);
188219

189220
const profileData: { [key: string]: string } = {};
@@ -204,7 +235,7 @@ export class DataExplorer {
204235
}
205236
}
206237

207-
// some rects have "count" class, some have "bin-count" class, some have "count other" class
238+
// some rects have 'count' class, some have 'bin-count' class, some have 'count other' class
208239
const rects = await this.code.driver.page.locator('.column-profile-sparkline').locator('[class*="count"]').all();
209240
const profileSparklineHeights: string[] = [];
210241
for (let i = 0; i < rects.length; i++) {
@@ -254,7 +285,7 @@ export class DataExplorer {
254285
});
255286
}
256287

257-
async verifyTableData(expectedData, timeout = 60000) {
288+
async verifyTableData(expectedData: Array<{ [key: string]: string }>, timeout = 60000) {
258289
await test.step('Verify data explorer data', async () => {
259290
await expect(async () => {
260291
const tableData = await this.getDataExplorerTableData();
@@ -302,8 +333,8 @@ export class DataExplorer {
302333
});
303334
}
304335

305-
async verifyProfileData(expectedValues: Array<{ column: number; expected: { [key: string]: string } }>) {
306-
await test.step('Verify profile data', async () => {
336+
async verifyColumnData(expectedValues: Array<{ column: number; expected: { [key: string]: string } }>) {
337+
await test.step('Verify column data', async () => {
307338
for (const { column, expected } of expectedValues) {
308339
const profileInfo = await this.getColumnProfileInfo(column);
309340
expect(profileInfo.profileData).toStrictEqual(expected);
@@ -324,6 +355,15 @@ export class DataExplorer {
324355
});
325356
}
326357

358+
async verifySparklineHeights(expectedHeights: Array<{ column: number; expected: string[] }>) {
359+
await test.step('Verify sparkline heights', async () => {
360+
for (const { column, expected } of expectedHeights) {
361+
const colProfileInfo = await this.getColumnProfileInfo(column);
362+
expect(colProfileInfo.profileSparklineHeights).toStrictEqual(expected);
363+
}
364+
});
365+
}
366+
327367
async verifyNullPercentHoverDialog(): Promise<void> {
328368
await test.step('Verify null percent hover dialog', async () => {
329369
const firstNullPercent = this.code.driver.page.locator('.column-null-percent').nth(0);
@@ -347,4 +387,11 @@ export class DataExplorer {
347387
expect(missing).toEqual([]); // Will throw if any are missing
348388
});
349389
}
390+
391+
async clickCell(rowIndex: number, columnIndex: number) {
392+
await test.step(`Click cell at row ${rowIndex}, column ${columnIndex}`, async () => {
393+
const cellLocator = this.code.driver.page.locator(`${DATA_GRID_ROWS} ${DATA_GRID_ROW}:nth-child(${rowIndex + 1}) > div:nth-child(${columnIndex + 1})`);
394+
await cellLocator.click();
395+
});
396+
}
350397
}

test/e2e/pages/hotKeys.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export class HotKeys {
118118
await this.pressHotKeys('Cmd+J B', 'Show secondary sidebar');
119119
}
120120

121-
public async hideSecondarySidebar() {
121+
public async closeSecondarySidebar() {
122122
await this.pressHotKeys('Cmd+J A', 'Hide secondary sidebar');
123123
}
124124

@@ -138,6 +138,10 @@ export class HotKeys {
138138
await this.pressHotKeys('Cmd+J N', 'Notebook layout');
139139
}
140140

141+
public async closePrimarySidebar() {
142+
await this.pressHotKeys('Cmd+B C', 'Close primary sidebar');
143+
}
144+
141145
// ----------------------
142146
// --- Workspace Actions ---
143147
// ----------------------

test/e2e/pages/popups.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66

7-
import { expect, Locator } from '@playwright/test';
7+
import test, { expect, Locator } from '@playwright/test';
88
import { Code } from '../infra/code';
99

1010
const POSITRON_MODAL_DIALOG_BOX = '.positron-modal-dialog-box';
@@ -159,6 +159,12 @@ export class Popups {
159159
await expect(this.code.driver.page.locator(POSITRON_MODAL_DIALOG_BOX)).toBeVisible({ timeout: 30000 });
160160
}
161161

162+
async verifyModalDialogBoxContainsText(text: string | RegExp) {
163+
await test.step(`Verify modal dialog box contains text: ${text}`, async () => {
164+
await expect(this.code.driver.page.locator('.dialog-box .message')).toHaveText(text, { timeout: 10000 });
165+
});
166+
}
167+
162168
async waitForModalDialogBoxToDisappear() {
163169
await expect(this.code.driver.page.locator(POSITRON_MODAL_DIALOG_BOX)).not.toBeVisible({ timeout: 30000 });
164170
}

test/e2e/tests/autocomplete/autocomplete.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ test.describe('Autocomplete', {
2222
const { editors, console } = app.workbench;
2323

2424
const [pySession1, pySession2, pyAltSession] = await sessions.start(['python', 'python', 'pythonAlt']);
25-
await hotKeys.hideSecondarySidebar();
25+
await hotKeys.closeSecondarySidebar();
2626

2727
// Session 1 - trigger and verify console autocomplete
2828
await sessions.select(pySession1.id);
@@ -59,7 +59,7 @@ test.describe('Autocomplete', {
5959
const { console } = app.workbench;
6060

6161
const [pySession, pyAltSession] = await sessions.start(['python', 'pythonAlt']);
62-
await hotKeys.hideSecondarySidebar();
62+
await hotKeys.closeSecondarySidebar();
6363

6464
// Session 1 - verify console autocomplete
6565
await sessions.select(pySession.id);
@@ -94,7 +94,7 @@ test.describe('Autocomplete', {
9494
const { editors, console } = app.workbench;
9595

9696
const [rSession1, rSession2, rSessionAlt] = await sessions.start(['r', 'r', 'rAlt']);
97-
await hotKeys.hideSecondarySidebar();
97+
await hotKeys.closeSecondarySidebar();
9898

9999
// Session 1 - verify console autocomplete
100100
await sessions.select(rSession1.id);
@@ -133,7 +133,7 @@ test.describe('Autocomplete', {
133133
const { console } = app.workbench;
134134

135135
const [rSession, rSessionAlt] = await sessions.start(['r', 'rAlt']);
136-
await hotKeys.hideSecondarySidebar();
136+
await hotKeys.closeSecondarySidebar();
137137

138138
// Session 1 - verify console autocomplete
139139
await sessions.select(rSession.id);

test/e2e/tests/connections/connections-postgres.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ test.describe('Postgres DB Connection', {
2727
tag: [tags.WEB, tags.CONNECTIONS]
2828
}, () => {
2929

30-
test('Python - Can establish a Postgres connection to a docker container', async function ({ app, python }) {
30+
test('Python - Can establish a Postgres connection to a docker container', async function ({ app, hotKeys, python }) {
3131

3232
await app.workbench.connections.openConnectionPane();
3333

@@ -65,7 +65,7 @@ test.describe('Postgres DB Connection', {
6565
}).toPass({ timeout: 60000 });
6666
});
6767

68-
await app.workbench.dataExplorer.closeDataExplorer();
68+
await hotKeys.closeAllEditors();
6969
await app.workbench.layouts.enterLayout('stacked');
7070

7171
await test.step('Remove connection', async () => {
@@ -83,7 +83,7 @@ test.describe('Postgres DB Connection', {
8383

8484
test('R - Can establish a Postgres connection to a docker container', {
8585
tag: [tags.ARK]
86-
}, async function ({ app, r }) {
86+
}, async function ({ app, hotKeys, r }) {
8787

8888
await app.workbench.connections.openConnectionPane();
8989

@@ -128,8 +128,7 @@ test.describe('Postgres DB Connection', {
128128
}).toPass({ timeout: 60000 });
129129
});
130130

131-
await app.workbench.dataExplorer.closeDataExplorer();
132-
await app.workbench.layouts.enterLayout('stacked');
131+
await hotKeys.closeAllEditors();
133132

134133
await test.step('Remove connection', async () => {
135134
await app.workbench.connections.openConnectionPane();

0 commit comments

Comments
 (0)